diff --git a/.github/workflows/callable-test-core-build-process.yml b/.github/workflows/callable-test-core-build-process.yml index bf566e7782241..136a4a048ae1b 100644 --- a/.github/workflows/callable-test-core-build-process.yml +++ b/.github/workflows/callable-test-core-build-process.yml @@ -46,7 +46,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' check-latest: true @@ -79,7 +79,7 @@ jobs: run: git diff --exit-code - name: Upload ZIP as a GitHub Actions artifact - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: ${{ inputs.directory == 'build' && 'ubuntu-latest' == inputs.os }} with: name: wordpress-build-${{ github.event_name == 'pull_request' && github.event.number || github.sha }} diff --git a/.github/workflows/callable-test-gutenberg-build-process.yml b/.github/workflows/callable-test-gutenberg-build-process.yml index ed45a4f2ac032..95236b78ed1d5 100644 --- a/.github/workflows/callable-test-gutenberg-build-process.yml +++ b/.github/workflows/callable-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@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' check-latest: true diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 393c7d1d7b013..a2c7ed894e7dc 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -75,7 +75,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: 'latest' coverage: none @@ -88,7 +88,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHPCS scan cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: | .cache/phpcs-src.json @@ -152,7 +152,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index 344201d2572d5..924708771d931 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -76,7 +76,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm @@ -127,7 +127,7 @@ jobs: run: npm run test:e2e - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 if: always() with: name: failures-artifacts${{ matrix.LOCAL_SCRIPT_DEBUG && '-SCRIPT_DEBUG' || '' }}-${{ github.run_id }} diff --git a/.github/workflows/install-testing.yml b/.github/workflows/install-testing.yml index 60e531a43ce30..ae841a75d8e67 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@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '${{ matrix.php }}' coverage: none diff --git a/.github/workflows/javascript-tests.yml b/.github/workflows/javascript-tests.yml index 1826b03041b9d..669ec6e1c123a 100644 --- a/.github/workflows/javascript-tests.yml +++ b/.github/workflows/javascript-tests.yml @@ -67,7 +67,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 900b77417b400..8e156a932aac5 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -110,7 +110,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm @@ -192,7 +192,7 @@ jobs: git reset --hard $TARGET_SHA - name: Set up Node.js - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm @@ -220,7 +220,7 @@ jobs: run: git reset --hard $GITHUB_SHA - name: Set up Node.js - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/workflows/php-compatibility.yml b/.github/workflows/php-compatibility.yml index 06463a72b7073..1606277d35495 100644 --- a/.github/workflows/php-compatibility.yml +++ b/.github/workflows/php-compatibility.yml @@ -70,7 +70,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '7.4' coverage: none @@ -87,7 +87,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHP compatibility scan cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: .cache/phpcompat.json key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-phpcompat-cache-${{ hashFiles('**/composer.json', 'phpcompat.xml.dist') }} diff --git a/.github/workflows/phpunit-tests-run.yml b/.github/workflows/phpunit-tests-run.yml index bebd1e3a29d23..cdedb4527379e 100644 --- a/.github/workflows/phpunit-tests-run.yml +++ b/.github/workflows/phpunit-tests-run.yml @@ -90,7 +90,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version-file: '.nvmrc' cache: npm @@ -103,7 +103,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '${{ inputs.php }}' coverage: none diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index 15cb91df50422..250e5594dca95 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -47,7 +47,7 @@ jobs: os: [ ubuntu-latest ] php: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] db-type: [ 'mysql' ] - db-version: [ '5.7', '8.0', '8.1', '8.2' ] + db-version: [ '5.7', '8.0', '8.1', '8.2', '8.3' ] multisite: [ false, true ] memcached: [ false ] diff --git a/.github/workflows/props-bot.yml b/.github/workflows/props-bot.yml new file mode 100644 index 0000000000000..f9d5668742372 --- /dev/null +++ b/.github/workflows/props-bot.yml @@ -0,0 +1,90 @@ +name: Props Bot + +on: + # This event runs anytime a PR is (re)opened, updated, marked ready for review, or labeled. + # GitHub does not allow filtering the `labeled` event by a specific label. + # However, the logic below will short-circuit the workflow when the `props-bot` label is not the one being added. + # Note: The pull_request_target event is used instead of pull_request because this workflow needs permission to comment + # on the pull request. Because this event grants extra permissions to `GITHUB_TOKEN`, any code changes within the PR + # should be considered untrusted. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/. + pull_request_target: + types: + - opened + - synchronize + - reopened + - labeled + - ready_for_review + # This event runs anytime a comment is added or deleted. + # You cannot filter this event for PR comments only. + # However, the logic below does short-circuit the workflow for issues. + issue_comment: + type: + - created + # This event will run everytime a new PR review is initially submitted. + pull_request_review: + types: + - submitted + # This event runs anytime a PR review comment is created or deleted. + pull_request_review_comment: + types: + - created + +# Cancels all previous workflow runs for pull requests that have not completed. +concurrency: + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ contains( fromJSON( '["pull_request_target", "pull_request_review", "pull_request_review_comment"]' ), github.event_name ) && github.head_ref || github.sha }} + cancel-in-progress: true + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + # Compiles a list of props for a pull request. + # + # Performs the following steps: + # - Collects a list of contributor props and leaves a comment. + # - Removes the props-bot label, if necessary. + props-bot: + name: Generate a list of props + runs-on: ubuntu-latest + permissions: + # The action needs permission `write` permission for PRs in order to add a comment. + pull-requests: write + contents: read + timeout-minutes: 20 + # The job will run when pull requests are open, ready for review and: + # + # - A comment is added to the pull request. + # - A review is created or commented on. + # - The pull request is opened, synchronized, marked ready for review, or reopened. + # - The `props-bot` label is added to the pull request. + if: | + ( + github.event_name == 'issue_comment' && github.event.issue.pull_request || + contains( fromJSON( '["pull_request_review", "pull_request_review_comment"]' ), github.event_name ) || + github.event_name == 'pull_request_target' && github.event.action != 'labeled' || + 'props-bot' == github.event.label.name + ) && + ( ! github.event.pull_request.draft && github.event.pull_request.state == 'open' || ! github.event.issue.draft && github.event.issue.state == 'open' ) + + steps: + - name: Gather a list of contributors + uses: WordPress/props-bot-action@trunk + with: + format: 'svn' + + - name: Remove the props-bot label + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + if: ${{ github.event.action == 'labeled' && 'props-bot' == github.event.label.name }} + with: + retries: 2 + retry-exempt-status-codes: 418 + script: | + github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: '${{ github.event.number }}', + name: 'props-bot' + }); diff --git a/.github/workflows/slack-notifications.yml b/.github/workflows/slack-notifications.yml index 566979a5698f8..aab3a85147bc0 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@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.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@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.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@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.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@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.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 771c4e21c9063..cea98ab2292b3 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@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 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@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: if-no-files-found: error name: ${{ matrix.theme }} diff --git a/.github/workflows/test-build-processes.yml b/.github/workflows/test-build-processes.yml index 660cfdee81543..a9276a6c58307 100644 --- a/.github/workflows/test-build-processes.yml +++ b/.github/workflows/test-build-processes.yml @@ -124,7 +124,7 @@ jobs: echo ${{ github.event.number }} > ./pr-number/NR - name: Upload PR number as artifact - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 with: name: pr-number path: pr-number/ diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 5d1e6b2f2af85..b99a4625bd905 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@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 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@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '7.4' coverage: none @@ -152,8 +152,9 @@ jobs: - name: Upload single site report to Codecov if: ${{ ! matrix.multisite && github.event_name != 'pull_request' }} - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 with: + token: ${{ secrets.CODECOV_TOKEN }} file: wp-code-coverage-single-clover-${{ github.sha }}.xml flags: single,php fail_ci_if_error: true @@ -167,8 +168,9 @@ jobs: - name: Upload multisite report to Codecov if: ${{ matrix.multisite && github.event_name != 'pull_request' }} - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 with: + token: ${{ secrets.CODECOV_TOKEN }} file: wp-code-coverage-multisite-clover-${{ github.sha }}.xml flags: multisite,php fail_ci_if_error: true diff --git a/.github/workflows/upgrade-testing-run.yml b/.github/workflows/upgrade-testing-run.yml index ce7452a047656..67e29983caa60 100644 --- a/.github/workflows/upgrade-testing-run.yml +++ b/.github/workflows/upgrade-testing-run.yml @@ -62,7 +62,7 @@ jobs: steps: - name: Set up PHP ${{ inputs.php }} - uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35 # v2.28.0 + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # v2.29.0 with: php-version: '${{ inputs.php }}' coverage: none diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000000000..51b8aeb41505a --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,3 @@ +// Import the default config file and expose it in the project root. +// Useful for editor integrations. +module.exports = require( '@wordpress/prettier-config' ); diff --git a/.version-support-mysql.json b/.version-support-mysql.json index e316908bc9aac..79858143a1ebd 100644 --- a/.version-support-mysql.json +++ b/.version-support-mysql.json @@ -1,6 +1,8 @@ { "6-5": [ + "8.3", "8.2", + "8.1", "8.0", "5.7", "5.6", diff --git a/Gruntfile.js b/Gruntfile.js index 92feec563818a..38904c6dac170 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1033,6 +1033,7 @@ module.exports = function(grunt) { cwd: SOURCE_DIR, src: [ 'wp-{admin,includes}/images/**/*.{png,jpg,gif,jpeg}', + 'wp-content/themes/**/*.{png,jpg,gif,jpeg}', 'wp-includes/js/tinymce/skins/wordpress/images/*.{png,jpg,gif,jpeg}' ], dest: SOURCE_DIR @@ -1558,14 +1559,18 @@ module.exports = function(grunt) { } ); /** - * Build assertions for the lack of source maps in JavaScript files. + * Compiled JavaScript files may link to sourcemaps. In some cases, + * the source map may not be available, which can cause 404 errors when + * browsers try to download the sourcemap from the referenced URLs. + * Ensure that sourcemap links are not included in JavaScript files. * * @ticket 24994 * @ticket 46218 + * @ticket 60348 */ grunt.registerTask( 'verify:source-maps', function() { const ignoredFiles = [ - 'build/wp-includes/js/dist/components.js' + 'build/wp-includes/js/dist/components.js', ]; const files = buildFiles.reduce( ( acc, path ) => { // Skip excluded paths and any path that isn't a file. @@ -1588,10 +1593,10 @@ module.exports = function(grunt) { encoding: 'utf8', } ); // `data:` URLs are allowed: - const match = contents.match( /sourceMappingURL=((?!data:).)/ ); + const doesNotHaveSourceMap = ! /^\/\/# sourceMappingURL=((?!data:).)/m.test(contents); assert( - match === null, + doesNotHaveSourceMap, `The ${ file } file must not contain a sourceMappingURL.` ); } ); diff --git a/composer.json b/composer.json index 750952457ff17..632ebd9f63136 100644 --- a/composer.json +++ b/composer.json @@ -10,13 +10,14 @@ "issues": "https://core.trac.wordpress.org/" }, "require": { + "ext-json": "*", "php": ">=7.0" }, "suggest": { "ext-dom": "*" }, "require-dev": { - "squizlabs/php_codesniffer": "3.7.2", + "squizlabs/php_codesniffer": "3.8.1", "wp-coding-standards/wpcs": "~3.0.1", "phpcompatibility/phpcompatibility-wp": "~2.1.3", "yoast/phpunit-polyfills": "^1.1.0" diff --git a/package-lock.json b/package-lock.json index 620787e67eea9..8be5038019fdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,74 +11,77 @@ "dependencies": { "@emotion/is-prop-valid": "0.8.8", "@emotion/memoize": "0.7.4", - "@wordpress/a11y": "3.42.13", - "@wordpress/annotations": "2.42.13", - "@wordpress/api-fetch": "6.39.13", - "@wordpress/autop": "3.42.13", - "@wordpress/blob": "3.42.13", - "@wordpress/block-directory": "4.19.16", - "@wordpress/block-editor": "12.10.14", - "@wordpress/block-library": "8.19.16", - "@wordpress/block-serialization-default-parser": "4.42.13", - "@wordpress/blocks": "12.19.13", - "@wordpress/commands": "0.13.14", - "@wordpress/components": "25.8.14", - "@wordpress/compose": "6.19.13", - "@wordpress/core-commands": "0.11.14", - "@wordpress/core-data": "6.19.14", - "@wordpress/customize-widgets": "4.19.16", - "@wordpress/data": "9.12.13", - "@wordpress/data-controls": "3.11.13", - "@wordpress/date": "4.42.13", - "@wordpress/deprecated": "3.42.13", - "@wordpress/dom": "3.42.13", - "@wordpress/dom-ready": "3.42.13", - "@wordpress/edit-post": "7.19.16", - "@wordpress/edit-site": "5.19.16", - "@wordpress/edit-widgets": "5.19.16", - "@wordpress/editor": "13.19.14", - "@wordpress/element": "5.19.13", - "@wordpress/escape-html": "2.42.13", - "@wordpress/format-library": "4.19.14", - "@wordpress/hooks": "3.42.13", - "@wordpress/html-entities": "3.42.13", - "@wordpress/i18n": "4.42.13", - "@wordpress/icons": "9.33.13", - "@wordpress/interactivity": "2.3.13", - "@wordpress/interface": "5.19.14", - "@wordpress/is-shallow-equal": "4.42.13", - "@wordpress/keyboard-shortcuts": "4.19.13", - "@wordpress/keycodes": "3.42.13", - "@wordpress/list-reusable-blocks": "4.19.14", - "@wordpress/media-utils": "4.33.13", - "@wordpress/notices": "4.10.13", - "@wordpress/nux": "8.4.14", - "@wordpress/patterns": "1.3.14", - "@wordpress/plugins": "6.10.14", - "@wordpress/preferences": "3.19.14", - "@wordpress/preferences-persistence": "1.34.13", - "@wordpress/primitives": "3.40.13", - "@wordpress/priority-queue": "2.42.13", - "@wordpress/private-apis": "0.24.13", - "@wordpress/redux-routine": "4.42.13", - "@wordpress/reusable-blocks": "4.19.14", - "@wordpress/rich-text": "6.19.13", - "@wordpress/router": "0.11.13", - "@wordpress/server-side-render": "4.19.14", - "@wordpress/shortcode": "3.42.13", - "@wordpress/style-engine": "1.25.13", - "@wordpress/sync": "0.4.13", - "@wordpress/token-list": "2.42.13", - "@wordpress/undo-manager": "0.2.13", - "@wordpress/url": "3.43.13", - "@wordpress/viewport": "5.19.13", - "@wordpress/warning": "2.42.13", - "@wordpress/widgets": "3.19.14", - "@wordpress/wordcount": "3.42.13", + "@wordpress/a11y": "3.50.0", + "@wordpress/annotations": "2.50.0", + "@wordpress/api-fetch": "6.47.0", + "@wordpress/autop": "3.50.0", + "@wordpress/blob": "3.50.0", + "@wordpress/block-directory": "4.27.2", + "@wordpress/block-editor": "12.18.2", + "@wordpress/block-library": "8.27.2", + "@wordpress/block-serialization-default-parser": "4.50.0", + "@wordpress/blocks": "12.27.1", + "@wordpress/commands": "0.21.0", + "@wordpress/components": "25.16.0", + "@wordpress/compose": "6.27.0", + "@wordpress/core-commands": "0.19.2", + "@wordpress/core-data": "6.27.2", + "@wordpress/customize-widgets": "4.27.2", + "@wordpress/data": "9.20.0", + "@wordpress/data-controls": "3.19.0", + "@wordpress/dataviews": "0.4.1", + "@wordpress/date": "4.50.0", + "@wordpress/deprecated": "3.50.0", + "@wordpress/dom": "3.50.0", + "@wordpress/dom-ready": "3.50.0", + "@wordpress/edit-post": "7.27.2", + "@wordpress/edit-site": "5.27.2", + "@wordpress/edit-widgets": "5.27.2", + "@wordpress/editor": "13.27.2", + "@wordpress/element": "5.27.0", + "@wordpress/escape-html": "2.50.0", + "@wordpress/format-library": "4.27.2", + "@wordpress/hooks": "3.50.0", + "@wordpress/html-entities": "3.50.0", + "@wordpress/i18n": "4.50.0", + "@wordpress/icons": "9.41.0", + "@wordpress/interactivity": "4.0.1", + "@wordpress/interactivity-router": "1.0.1", + "@wordpress/interface": "5.27.0", + "@wordpress/is-shallow-equal": "4.50.0", + "@wordpress/keyboard-shortcuts": "4.27.0", + "@wordpress/keycodes": "3.50.0", + "@wordpress/list-reusable-blocks": "4.27.0", + "@wordpress/media-utils": "4.41.0", + "@wordpress/notices": "4.18.0", + "@wordpress/nux": "8.12.0", + "@wordpress/patterns": "1.11.2", + "@wordpress/plugins": "6.18.0", + "@wordpress/preferences": "3.27.0", + "@wordpress/preferences-persistence": "1.42.0", + "@wordpress/primitives": "3.48.0", + "@wordpress/priority-queue": "2.50.0", + "@wordpress/private-apis": "0.32.0", + "@wordpress/redux-routine": "4.50.0", + "@wordpress/reusable-blocks": "4.27.2", + "@wordpress/rich-text": "6.27.0", + "@wordpress/router": "0.19.0", + "@wordpress/server-side-render": "4.27.1", + "@wordpress/shortcode": "3.50.0", + "@wordpress/style-engine": "1.33.1", + "@wordpress/sync": "0.12.0", + "@wordpress/token-list": "2.50.0", + "@wordpress/undo-manager": "0.10.0", + "@wordpress/url": "3.51.0", + "@wordpress/viewport": "5.27.0", + "@wordpress/warning": "2.50.0", + "@wordpress/widgets": "3.27.2", + "@wordpress/wordcount": "3.50.0", "backbone": "1.5.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", "framer-motion": "10.16.4", "hoverintent": "2.2.1", @@ -98,6 +101,7 @@ "polyfill-library": "4.8.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-is": "18.2.0", "regenerator-runtime": "0.14.0", "tslib": "2.6.2", "underscore": "1.13.6", @@ -108,11 +112,12 @@ "@lodder/grunt-postcss": "^3.1.1", "@playwright/test": "1.32.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.5", - "@wordpress/babel-preset-default": "7.26.13", - "@wordpress/dependency-extraction-webpack-plugin": "4.25.13", - "@wordpress/e2e-test-utils": "10.13.13", - "@wordpress/e2e-test-utils-playwright": "0.10.13", - "@wordpress/scripts": "26.13.13", + "@wordpress/babel-preset-default": "7.34.0", + "@wordpress/dependency-extraction-webpack-plugin": "5.1.0", + "@wordpress/e2e-test-utils": "10.21.0", + "@wordpress/e2e-test-utils-playwright": "0.18.0", + "@wordpress/prettier-config": "3.7.0", + "@wordpress/scripts": "27.1.0", "autoprefixer": "10.4.16", "chalk": "5.3.0", "check-node-version": "4.2.1", @@ -185,16 +190,16 @@ } }, "node_modules/@ariakit/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.2.7.tgz", - "integrity": "sha512-Hs0N1EMYq88WW4v9xnSIHNR38TvbQuoUX6FYFmeLCZSTIXQBiET7lr1DQXwOOmdEtRtlxQ5HsxbTkxeOkPv+eg==" + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.11.tgz", + "integrity": "sha512-+MnOeqnA4FLI/7vqsZLbZQHHN4ofd9kvkNjz44fNi0gqmD+ZbMWiDkFAvZII75dYnxYw5ZPpWjA4waK22VBWig==" }, "node_modules/@ariakit/react": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.2.12.tgz", - "integrity": "sha512-4rAgMyUURHW78EKgRCanhyRUtsiYCOxO65BBHF4mg3tZsDeOvu9kBG5IAXX8mUgakTcyr0EKXuOtGThaj7gobA==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.14.tgz", + "integrity": "sha512-h71BPMZ2eW+E2ESbdYxSAEMR1DozYzd5eHE5IOzGd9Egi5u7EZxqmuW4CXVXZ1Y6vbaDMV3SudgPh7iHS/ArFw==", "dependencies": { - "@ariakit/react-core": "0.2.12" + "@ariakit/react-core": "0.3.14" }, "funding": { "type": "opencollective", @@ -206,11 +211,11 @@ } }, "node_modules/@ariakit/react-core": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.2.12.tgz", - "integrity": "sha512-3KSKlX10nnhCvjsbPW0CAnqG+6grryfwnMkeJJ/h34FSV7hEfUMexmIjKBVZyfBG08Xj8NjSK8kkx9c3ChkXeA==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.14.tgz", + "integrity": "sha512-16Qj6kDPglpdWtU5roY9q+G66naOjauTY5HvUIaL2aLY0187ATaRrABIKoMMzTtJyhvsud4jFlzivz+/zCQ8yw==", "dependencies": { - "@ariakit/core": "0.2.7", + "@ariakit/core": "0.3.11", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.2.0" }, @@ -3645,19 +3650,11 @@ "node": ">= 8" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -3665,80 +3662,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@pkgr/utils/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/utils/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@playwright/test": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz", @@ -3888,9 +3811,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", "dev": true }, "node_modules/@popperjs/core": { @@ -3903,9 +3826,9 @@ } }, "node_modules/@preact/signals": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.2.1.tgz", - "integrity": "sha512-hRPvp1C2ooDzOHqfnhdpHgoIFDbYFAXLhoid3+jSItuPPD/J0r/UsiWKv/8ZO/oEhjRaP0M5niuRYsWqmY2GEA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.2.2.tgz", + "integrity": "sha512-ColCqdo4cRP18bAuIR4Oik5rDpiyFtPIJIygaYPMEAwTnl4buWkBOflGBSzhYyPyJfKpkwlekrvK+1pzQ2ldWw==", "dependencies": { "@preact/signals-core": "^1.4.0" }, @@ -3918,9 +3841,9 @@ } }, "node_modules/@preact/signals-core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.0.tgz", - "integrity": "sha512-U2diO1Z4i1n2IoFgMYmRdHWGObNrcuTRxyNEn7deSq2cru0vj0583HYQZHsAqcs7FE+hQyX3mjIV7LAfHCvy8w==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz", + "integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -4114,35 +4037,6 @@ "@babel/runtime": "^7.13.10" } }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz", - "integrity": "sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.2" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.2.tgz", - "integrity": "sha512-s8WdQQ6wNXpaxdZ308KSr8fEWGrg4un8i4r/w7fhiS4ElRNjk5rRcl0/C6TANG2LvLOGIxtzo/jAg6Qf73TEBw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-slot": "1.0.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", @@ -4297,53 +4191,6 @@ } } }, - "node_modules/@radix-ui/react-direction": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz", - "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz", - "integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-escape-keydown": "1.0.2" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.4.tgz", - "integrity": "sha512-y6AT9+MydyXcByivdK1+QpjWoKaC7MLjkS/cH1Q3keEyMvDkiY85m8o2Bi6+Z1PPUlCsMULopxagQOSfN0wahg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-menu": "2.0.4", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", @@ -4355,21 +4202,6 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz", - "integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@radix-ui/react-id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", @@ -4382,97 +4214,6 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, - "node_modules/@radix-ui/react-menu": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.4.tgz", - "integrity": "sha512-mzKR47tZ1t193trEqlQoJvzY4u9vYfVH16ryBrVrCAGZzkgyWnMQYEZdUkM7y8ak9mrkKtJiqB47TlEnubeOFQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-collection": "1.0.2", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-dismissable-layer": "1.0.3", - "@radix-ui/react-focus-guards": "1.0.0", - "@radix-ui/react-focus-scope": "1.0.2", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-popper": "1.1.1", - "@radix-ui/react-portal": "1.0.2", - "@radix-ui/react-presence": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-roving-focus": "1.0.3", - "@radix-ui/react-slot": "1.0.1", - "@radix-ui/react-use-callback-ref": "1.0.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.1.tgz", - "integrity": "sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "0.7.2", - "@radix-ui/react-arrow": "1.0.2", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-layout-effect": "1.0.0", - "@radix-ui/react-use-rect": "1.0.0", - "@radix-ui/react-use-size": "1.0.0", - "@radix-ui/rect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/core": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", - "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==" - }, - "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/dom": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz", - "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==", - "dependencies": { - "@floating-ui/core": "^0.7.3" - } - }, - "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/react-dom": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz", - "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==", - "dependencies": { - "@floating-ui/dom": "^0.5.3", - "use-isomorphic-layout-effect": "^1.1.1" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz", - "integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.2" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@radix-ui/react-presence": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", @@ -4487,52 +4228,6 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", - "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.3.tgz", - "integrity": "sha512-stjCkIoMe6h+1fWtXlA6cRfikdBzCLp3SnVk7c48cv/uy3DTGoXhN76YaOYUJuy3aEDvDIKwKR5KSmvrtPvQPQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-collection": "1.0.2", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-controllable-state": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", @@ -4556,18 +4251,6 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz", - "integrity": "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", @@ -4579,38 +4262,6 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz", - "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/rect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz", - "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz", - "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, "node_modules/@react-spring/animated": { "version": "9.7.1", "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.1.tgz", @@ -5522,9 +5173,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -5532,27 +5183,27 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -5586,9 +5237,9 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -5598,14 +5249,15 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" } }, "node_modules/@types/glob": { @@ -5627,10 +5279,26 @@ "@types/node": "*" } }, + "node_modules/@types/gradient-parser": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.3.tgz", + "integrity": "sha512-XDbrTSBlQV9nxE1GiDL3FaOPy4G/KaJkhDutBX48Kg8CYZMBARyyDFGCWfWJn4pobmInmwud1xxH7VJMAr0CKQ==" + }, + "node_modules/@types/highlight-words-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", + "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/http-proxy": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", - "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dev": true, "dependencies": { "@types/node": "*" @@ -5706,9 +5374,9 @@ "dev": true }, "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/minimatch": { @@ -5731,8 +5399,16 @@ "node_modules/@types/node": { "version": "14.14.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", - "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", - "dev": true + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -5758,15 +5434,15 @@ "optional": true }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/react": { @@ -5799,34 +5475,53 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@types/semver": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", - "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", "dev": true, "dependencies": { + "@types/http-errors": "*", "@types/mime": "*", "@types/node": "*" } }, + "node_modules/@types/simple-peer": { + "version": "9.11.8", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.8.tgz", + "integrity": "sha512-rvqefdp2rvIA6wiomMgKWd2UZNPe6LM2EV5AuY3CPQJF+8TbdrL5TjYdMf0VAjGczzlkH4l1NjDkihwbj3Xodw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "dependencies": { "@types/node": "*" @@ -5918,9 +5613,9 @@ } }, "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "dependencies": { "@types/node": "*" @@ -5952,16 +5647,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz", - "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", + "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/type-utils": "6.11.0", - "@typescript-eslint/utils": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/type-utils": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -5987,15 +5682,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz", - "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", + "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4" }, "engines": { @@ -6015,13 +5710,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", - "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", + "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0" + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6032,13 +5727,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz", - "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", + "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/utils": "6.11.0", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/utils": "6.19.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -6059,9 +5754,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz", - "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", + "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6072,16 +5767,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz", - "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", + "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -6098,18 +5794,42 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz", - "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", + "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", "semver": "^7.5.4" }, "engines": { @@ -6124,12 +5844,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", - "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", + "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/types": "6.19.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -6315,34 +6035,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -6351,28 +6079,28 @@ } }, "node_modules/@wordpress/a11y": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.42.13.tgz", - "integrity": "sha512-57KH89dbt8ipimoBGezKQHLvwSsJHW/W4HpvzZFqnPHvnlNNYoVC9UuqiBavxdB2WkzMPmNYFKsM7kOInEdyTA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.50.0.tgz", + "integrity": "sha512-eQiPGnxqiL1LgnHztFG0RGSFZ5phwR8B8Fr4lbJsFalsc9R/tOcjewvf2KN0yi2UlRA5ssAeiTP+tYmeAqtOHQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/dom-ready": "^3.42.13", - "@wordpress/i18n": "^4.42.13" + "@wordpress/dom-ready": "^3.50.0", + "@wordpress/i18n": "^4.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/annotations": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-2.42.13.tgz", - "integrity": "sha512-S/bCU8AOYzVMKBSMMtmoB9Dw1kiOS3KNfFzdCLpiyzNhfqXPWp7ciM5WJTHJRdzobeTOh1CIJ6x5WHhBqO/t8w==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-2.50.0.tgz", + "integrity": "sha512-E9cu8xuGvIRw3LVtuS+XSzAXVBF41sgvxpVJAz/5FEibzxUHPy8flu5tTKf+mi4WGZxC4AJGNP1bhZRj7cynZQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^9.12.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/rich-text": "^6.19.13", + "@wordpress/data": "^9.20.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/rich-text": "^6.27.0", "rememo": "^4.0.2", "uuid": "^9.0.1" }, @@ -6384,22 +6112,22 @@ } }, "node_modules/@wordpress/api-fetch": { - "version": "6.39.13", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.39.13.tgz", - "integrity": "sha512-DFaiNq5bEOVqYDpcqXqdxjyBDboeElma6e7FNSX2APVZZt/8xxeb4eI9X0877i6B15G5blyHsjSit5rq88iqtg==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.47.0.tgz", + "integrity": "sha512-NA/jWDXoVtJmiVBYhlxts2UrgKJpJM+zTGzLCfRQCZUzpJYm3LonB8x+uCQ78nEyxCY397Esod3jnbquYjOr0Q==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.42.13", - "@wordpress/url": "^3.43.13" + "@wordpress/i18n": "^4.50.0", + "@wordpress/url": "^3.51.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/autop": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.42.13.tgz", - "integrity": "sha512-vQjEvJaYZ5OyMJ6IdbVL/RUqOy/VTe6E1BCkdRW8amJTm8koTkbBbOFWSsVoXh6jFoq2mDAqC7Tt2vL0yMA4rQ==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.50.0.tgz", + "integrity": "sha512-4E0vq2MvSOVDKXs4OulIbTdKU6S5O9QjT4qc63rAd0uiKGBYV12ViPzmwbJ6k38zOO0PKdcwlVCj55Gq4aoPDw==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -6408,9 +6136,9 @@ } }, "node_modules/@wordpress/babel-plugin-import-jsx-pragma": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.26.0.tgz", - "integrity": "sha512-XZCTBqEmOlM87/6wkgtHhnHaj8cJPOY5avyjKtMDwoBbcXAmHUknbphZG7KEWIiVIilyxKyHnsTxjTplkqTtCQ==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.33.0.tgz", + "integrity": "sha512-CjzruFKWgzU/mO/nnQJ2l9UlzZQpqS60UC6l2vNdJ9oD2nKHR5Oou6kNic3QhWDVJrBf2JUiJJ0TC280bykXmA==", "dev": true, "engines": { "node": ">=14" @@ -6420,9 +6148,9 @@ } }, "node_modules/@wordpress/babel-preset-default": { - "version": "7.26.13", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.26.13.tgz", - "integrity": "sha512-kW9sg3lwbrhYzVR24n7cUEC2Sx1Pj4UNnITbXqVmxnVok0CK7IkvstMlbtLDbULh9o2f92OPNMwdAStErEjT7g==", + "version": "7.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.34.0.tgz", + "integrity": "sha512-yjFOllyTktFHtcIEgU3ghXBn8lItzr5mPLf0xdSpe0cHceFYL1hT1oprhgRL+olZweaO96Yfm0qUCCKQfJBWsA==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", @@ -6431,27 +6159,27 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/babel-plugin-import-jsx-pragma": "^4.25.13", - "@wordpress/browserslist-config": "^5.25.13", - "@wordpress/element": "^5.19.13", - "@wordpress/warning": "^2.42.13", - "browserslist": "^4.21.9", - "core-js": "^3.31.0" + "@wordpress/babel-plugin-import-jsx-pragma": "^4.33.0", + "@wordpress/browserslist-config": "^5.33.0", + "@wordpress/warning": "^2.50.0", + "browserslist": "^4.21.10", + "core-js": "^3.31.0", + "react": "^18.2.0" }, "engines": { "node": ">=14" } }, "node_modules/@wordpress/base-styles": { - "version": "4.34.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.34.0.tgz", - "integrity": "sha512-LYiNFWl+6yJDVQ7hSNJu2kVuM1p3C3aTB769lXnMSxi3gubzxqjZqz9i9XQ3UjO9EFiDSvgbOXa8YhvTUfNnkQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.41.0.tgz", + "integrity": "sha512-MjPAZeAqvyskDXDp2wGZ0DjtYOQLOydI1WqVIZS4wnIdhsQWQD//VMeXgLrcmCzNyQg+iKTx3o+BzmXVTOD0+w==", "dev": true }, "node_modules/@wordpress/blob": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.42.13.tgz", - "integrity": "sha512-W5TaJK9Vl8LInjdxRRq5hE08r34JKybVjm7UuSIPOppNErLu9g6edcGHsv3b/7f5so3TcSnPsLfDkPgwSFTjXA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.50.0.tgz", + "integrity": "sha512-QvBhsW9WPdsOJhJ0BxzZ83i+cH/gAdjJ1iHY4Rkb02qbZEz4jhdvucGQf2oVnWwvAsFiFPKWk7CwAM5XjoahCA==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -6460,29 +6188,30 @@ } }, "node_modules/@wordpress/block-directory": { - "version": "4.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-4.19.16.tgz", - "integrity": "sha512-7YOqeZt8ExyMidbblzht7x5jnfpZVD6N69VuDrvdlB/8eB7gl62tKZdNXHwWoZccSWJb+xUTZL01k2HpJulcPQ==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-4.27.2.tgz", + "integrity": "sha512-EblzP8BbkqAeFomH3/L9wdmbz1iw0n2siBMdZNZKHifwWv0iLFQfZlMZo4ImgWwC4YE3is7zSGpkWJ1kHMbj7w==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/edit-post": "^7.19.16", - "@wordpress/editor": "^13.19.14", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/plugins": "^6.10.14", - "@wordpress/url": "^3.43.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/edit-post": "^7.27.2", + "@wordpress/editor": "^13.27.2", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/plugins": "^6.18.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2" }, "engines": { @@ -6494,44 +6223,43 @@ } }, "node_modules/@wordpress/block-editor": { - "version": "12.10.14", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.10.14.tgz", - "integrity": "sha512-x56FPZZfJPk/Vd1aKIdpBIllrUuAVgwom+mYH0OohCmUzCBp1Eg8Urg5nshZpiLXpHt2dXycQCLu2Mpb+YpOJw==", + "version": "12.18.2", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.18.2.tgz", + "integrity": "sha512-LDZIcp5Bl2FCyfkf07XgfM0kzY+AYhyTS4kt2U4GRSeUey79AM+GIYXb8TM2Y68B09HP/rpntBW4e/cBqjHfjw==", "dependencies": { "@babel/runtime": "^7.16.0", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/blocks": "^12.19.13", - "@wordpress/commands": "^0.13.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/shortcode": "^3.42.13", - "@wordpress/style-engine": "^1.25.13", - "@wordpress/token-list": "^2.42.13", - "@wordpress/url": "^3.43.13", - "@wordpress/warning": "^2.42.13", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/style-engine": "^1.33.1", + "@wordpress/token-list": "^2.50.0", + "@wordpress/url": "^3.51.0", + "@wordpress/warning": "^2.50.0", + "@wordpress/wordcount": "^3.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -6539,12 +6267,14 @@ "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", - "inherits": "^2.0.3", + "memize": "^2.1.0", + "postcss": "^8.4.21", + "postcss-prefixwrap": "^1.41.0", + "postcss-urlrebase": "^1.0.0", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", "rememo": "^4.0.2", - "remove-accents": "^0.5.0", - "traverse": "^0.6.6" + "remove-accents": "^0.5.0" }, "engines": { "node": ">=12" @@ -6555,41 +6285,43 @@ } }, "node_modules/@wordpress/block-library": { - "version": "8.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.19.16.tgz", - "integrity": "sha512-6NqTHjEYk3X+jzw6JS3pOgVYl2HPlr0iAI3Ch9sdOxozAm1+VrE5DKeM//rf9QpR7wWJ6je4F/eNjZ2WJIYTfw==", + "version": "8.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.27.2.tgz", + "integrity": "sha512-Wabc1nmCMuTr/BgS63iHaQYtvfVO9Z30SwLaMVLHwGe7Hrvtb19pSOwKb/PIuoiWrlqJ/sZEZPXFENAJB5FVYA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/autop": "^3.42.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interactivity": "^2.3.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/primitives": "^3.40.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/server-side-render": "^4.19.14", - "@wordpress/url": "^3.43.13", - "@wordpress/viewport": "^5.19.13", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/autop": "^3.50.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interactivity": "^4.0.1", + "@wordpress/interactivity-router": "^1.0.1", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/server-side-render": "^4.27.1", + "@wordpress/url": "^3.51.0", + "@wordpress/viewport": "^5.27.0", + "@wordpress/wordcount": "^3.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -6609,9 +6341,9 @@ } }, "node_modules/@wordpress/block-serialization-default-parser": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.42.13.tgz", - "integrity": "sha512-+ggjHxrjbpIwknsfKy18HXOVGWHeFykxlElE9dYVspJvr734mMMTQuIeL5WM+vZUy5NWv0oHF0VykX0MHyy60w==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.50.0.tgz", + "integrity": "sha512-ihf2vr+w2zHBOvYTPQZXDiR2IMvso8yJJtzKIHA2ZEgVQ+VVLb4X86n34hfWXtPA3i2KDW+t1WCtq56aNq3Zag==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -6620,32 +6352,33 @@ } }, "node_modules/@wordpress/blocks": { - "version": "12.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.19.13.tgz", - "integrity": "sha512-KdNcYb5Cr4sgzOkJM+KpPZeLLFr8e06CkRDp0EQk7VGSsoScXpqIcMEtMcKNQp1XPuJ6npMr/BacC5qNjyHA1A==", + "version": "12.27.1", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.27.1.tgz", + "integrity": "sha512-9uZtuTG6+fiFV2bLn8b1gzv4BgMpBu4SDQGnvzc5f9U5GL5oYns3PP8vXDOwM2cK1DEmqPsohQWhRnz8QYZDtw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/autop": "^3.42.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/block-serialization-default-parser": "^4.42.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/shortcode": "^3.42.13", + "@wordpress/autop": "^3.50.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-serialization-default-parser": "^4.50.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/shortcode": "^3.50.0", "change-case": "^4.1.2", "colord": "^2.7.0", - "deepmerge": "^4.3.0", "fast-deep-equal": "^3.1.3", "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", + "react-is": "^18.2.0", "rememo": "^4.0.2", "remove-accents": "^0.5.0", "showdown": "^1.9.1", @@ -6660,27 +6393,27 @@ } }, "node_modules/@wordpress/browserslist-config": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.26.0.tgz", - "integrity": "sha512-rpkxAnPOc4HuxKZBwZ1iV1oC0Rd21azzBDyS8OoVUW6V8DAv4eYfHNFGkyds7Z+nI6dI15Rl7xJYJhHJKVaJvg==", + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.33.0.tgz", + "integrity": "sha512-dv1ZlpqGk8gaSBJPP/Z/1uOuxjtP0EBsHVKInLRu6FWLTJkK8rnCeC3xJT3/2TtJ0rasLC79RoytfhXTOODVwg==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@wordpress/commands": { - "version": "0.13.14", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.13.14.tgz", - "integrity": "sha512-aSOuRbsr+YYFvRbkXaubHdlAtf/xpG1mUWXEw9VMWCag77hiK6vk04Xb3N8ad8eo8am0N/iRgn8V8IS4LyBTyA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.21.0.tgz", + "integrity": "sha512-MzMUGCT9cQXto1jrA5lHAtnieTyAhcuNIxfyxlcE+316KNQfbyD8bc7KOzSV2sxXD/rfHuCxvHjfomFyyP+4kA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.8.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/private-apis": "^0.24.13", + "@wordpress/components": "^25.16.0", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/private-apis": "^0.32.0", "classnames": "^2.3.1", "cmdk": "^0.2.0", "rememo": "^4.0.2" @@ -6694,11 +6427,11 @@ } }, "node_modules/@wordpress/components": { - "version": "25.8.14", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.8.14.tgz", - "integrity": "sha512-wRQSRlLXsL4bEd1JhCQPSdIb0bO4WDAloQufeyIbXUIK9CDgN/jmkv+vrgKrpP3Nqu1sBAFzW1qd9WEXfSBgXw==", + "version": "25.16.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.16.0.tgz", + "integrity": "sha512-voQuMsO5JbH+JW33TnWurwwvpSb8IQ4XU5wyVMubX4TUwadt+/2ToNJbZIDXoaJPei7vbM81Ft+pH+zGlN8CyA==", "dependencies": { - "@ariakit/react": "^0.2.12", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -6707,25 +6440,26 @@ "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", "@floating-ui/react-dom": "^2.0.1", - "@radix-ui/react-dropdown-menu": "2.0.4", + "@types/gradient-parser": "0.1.3", + "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.2.24", - "@wordpress/a11y": "^3.42.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/primitives": "^3.40.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/warning": "^2.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/warning": "^2.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -6757,21 +6491,21 @@ } }, "node_modules/@wordpress/compose": { - "version": "6.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.19.13.tgz", - "integrity": "sha512-3HDdccND+EoEr7tHQ75eCDh07e5TdFh0KFIdWGweq9gU5Z/tssRW8QEyU9J+xEz+DTL/hvFilQ681f58eUZi1g==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.27.0.tgz", + "integrity": "sha512-jbEQQ2znRyJTwUNR4m5BKaDyIsuK9TMZx0SKqP+FTfGqT3y7scOnQrHpK0kZdPji++/1cBbn3gSPBLCEmtmHRw==", "dependencies": { "@babel/runtime": "^7.16.0", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/priority-queue": "^2.42.13", - "@wordpress/undo-manager": "^0.2.13", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/priority-queue": "^2.50.0", + "@wordpress/undo-manager": "^0.10.0", "change-case": "^4.1.2", - "clipboard": "^2.0.8", + "clipboard": "^2.0.11", "mousetrap": "^1.6.5", "use-memo-one": "^1.1.1" }, @@ -6783,21 +6517,21 @@ } }, "node_modules/@wordpress/core-commands": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.11.14.tgz", - "integrity": "sha512-f2DA9lUji96OC5UD85Gbv2vz14R0TR+FSXzXAa68F/EBPFkiaxs2huhruhRvZKbasxugk/vjTBbQuwZ8rinROA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.19.2.tgz", + "integrity": "sha512-9ewP1fxB8MB5u15zMZBfShgGN2qJl+fBXCWR9MXB3gi8gA/Kd600W5I/jh2nLJuCRou09SsRzI6s+ihnir/V4A==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/commands": "^0.13.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/router": "^0.11.13", - "@wordpress/url": "^3.43.13" + "@wordpress/block-editor": "^12.18.2", + "@wordpress/commands": "^0.21.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/router": "^0.19.0", + "@wordpress/url": "^3.51.0" }, "engines": { "node": ">=12" @@ -6808,25 +6542,26 @@ } }, "node_modules/@wordpress/core-data": { - "version": "6.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.19.14.tgz", - "integrity": "sha512-wdstu/qMBKwXnFRX4wMeTkxvHsOgbXm7ZJ0Lgtj+jE86O086Ook7suxacOdMcCaAKNCfMqoGBHtjsNQk3SWE1Q==", + "version": "6.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.27.2.tgz", + "integrity": "sha512-Jsy+vW/izrd/T36D/4b266ScobCezNYX2Me/clCmHGB4eRW3drXZPbMnWZLNEDagYr87sQcM1Namasb69dnDhA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/sync": "^0.4.13", - "@wordpress/undo-manager": "^0.2.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/sync": "^0.12.0", + "@wordpress/undo-manager": "^0.10.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -6843,31 +6578,31 @@ } }, "node_modules/@wordpress/customize-widgets": { - "version": "4.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-4.19.16.tgz", - "integrity": "sha512-UK4RrEBFwdn8WcY7qXXbRcncuWXLMpB9gjiBVhwPmM5m1//A0wsOQu2kAkZeACuhYoEJ/N6g4yZh2ZnldJVR3w==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-4.27.2.tgz", + "integrity": "sha512-zq/PacEqW8eMX6LKeMHn39JNU2ZJ3GiCH3+oOeI3eewN8/aGrtJJh1btSL0liLTDXo6dqnQ8AXHjGu9/J/XDSg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/widgets": "^3.19.14", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/widgets": "^3.27.2", "classnames": "^2.3.1", "fast-deep-equal": "^3.1.3" }, @@ -6880,25 +6615,24 @@ } }, "node_modules/@wordpress/data": { - "version": "9.12.13", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.12.13.tgz", - "integrity": "sha512-8SIsPFrnQ1LIZRWseOF+9uQ9thy8oB7NSOq+bkRCo+qldagooBTZUFp8Y++evFbPOotmTy6XGSPYf7HV9qBHVw==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.20.0.tgz", + "integrity": "sha512-3cm2te6NUj/X1zzmRO+WhueCanjocniX6sJFVzkg5mGXme6wFI8iSOnGPKlMkGcZGd0fVei1ydBKaIUMjrPBTQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.19.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/priority-queue": "^2.42.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/redux-routine": "^4.42.13", + "@wordpress/compose": "^6.27.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/priority-queue": "^2.50.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/redux-routine": "^4.50.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", "redux": "^4.1.2", "rememo": "^4.0.2", - "turbo-combine-reducers": "^1.0.2", "use-memo-one": "^1.1.1" }, "engines": { @@ -6909,14 +6643,39 @@ } }, "node_modules/@wordpress/data-controls": { - "version": "3.11.13", - "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-3.11.13.tgz", - "integrity": "sha512-BW7yBPePnS5SVMVTTWeHG1U4RwV4X46NVOvX4/Vvq8CBjLmvqbiXZZxLMYI4xBi1y6+XRDjORHXP3WMJzwTdEg==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-3.19.0.tgz", + "integrity": "sha512-ceUK8kB8r8s8XFYlYWGVLuaoDJx5IAXND6q7B6MX1gKndqnSNi1766Q9iAEwOT9eVMai0lDLNq7mdK2ktVh4bw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13" + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/dataviews": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-0.4.1.tgz", + "integrity": "sha512-9ZTP5l9lyLMK95uEuAbOkILPIa2XvYxm2qa5Yo6SEUJbKnOVGCGH1fcNX1GuzHHrJwclYA3TeGgMaYoXpudjjw==", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/a11y": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "classnames": "^2.3.1", + "remove-accents": "^0.5.0" }, "engines": { "node": ">=12" @@ -6926,12 +6685,12 @@ } }, "node_modules/@wordpress/date": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.42.13.tgz", - "integrity": "sha512-SrJL7WbnQwSmogyNiFA+ZKNuECPvneCZOVzC/76DIV7seVDbpdJky/3UAkQLMgvYzym5PK3A8vkENPgAykrh3g==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.50.0.tgz", + "integrity": "sha512-FhfaG6YRXWmni66RjwhCB7rQNlLJ05+qTa/jXrj2UNWDNv/sfZ6Ky+b/rKrrUnLaIs9pGiW1195cSxsAS4EY3w==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.42.13", + "@wordpress/deprecated": "^3.50.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -6940,49 +6699,48 @@ } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "4.25.13", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-4.25.13.tgz", - "integrity": "sha512-ke3CkU9wWgMpAsf5E1zG7aN/pr9P3qdDaIOgU2kXbjSLxrbhgBeK4mCgT/uxCJu0uqaieYkZWRcNmxXKMbF9hw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.1.0.tgz", + "integrity": "sha512-W2W+9JNAaGirAtGDSf83pjEKb63DLhgpJGgvMOpEPoRPtucgO6CCm3uMoNkJTpKoxJQ2tSZEymAhF/YdLm+ScQ==", "dev": true, "dependencies": { - "json2php": "^0.0.7", - "webpack-sources": "^3.2.2" + "json2php": "^0.0.7" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "webpack": "^4.8.3 || ^5.0.0" + "webpack": "^5.0.0" } }, "node_modules/@wordpress/deprecated": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.42.13.tgz", - "integrity": "sha512-Jxivx5eTKhjVNW1/rqShM1dzDKm/9wKp9jPlF58uAXpQSIaH8Q09D6Pgzi72DsDyefL8SV/QllLQbo0bVenydg==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.50.0.tgz", + "integrity": "sha512-DL01l0Wlo3df9OcSGHP11Ot/nq0HytbdmD+iPkiCCRI6Xctepbs/DzRR2CO3qLrJkWn6RReFwZWZZjzI7lZUqg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.42.13" + "@wordpress/hooks": "^3.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/dom": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.42.13.tgz", - "integrity": "sha512-E7TnWuSOrxY5sn57+6Bf5v7JAL9PmNrOljf8Jj7FDsRdH6tCXf8BDqyIBz53cmzv/bsWOklQKIOeU/BQoEItHw==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.50.0.tgz", + "integrity": "sha512-rMnV1ysGOHbKnmjLQYwGkT1co1iEkC3YsKrEObP8mklw1R7rbCy7fc2brIz7kqcHU1DRyg/+7wOCMkg8a/EV/Q==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.42.13" + "@wordpress/deprecated": "^3.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/dom-ready": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.42.13.tgz", - "integrity": "sha512-mtqstqT1YFfIGl8rQipG9d8UwvGIZUP4Y8E1Tq3V9CAMV6ChJEYCZIGs/asHjqJSebNnXEWUEzQKAbPnIhnW3Q==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.50.0.tgz", + "integrity": "sha512-97tJpat1emXnwfGlJMiG6p37CpHJXDLmM/SIbsGJ0Oj8P4/TXbTuE9DNT1H8B1wKe5zD7kICjp48y91ugmgSrQ==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -6991,15 +6749,15 @@ } }, "node_modules/@wordpress/e2e-test-utils": { - "version": "10.13.13", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.13.13.tgz", - "integrity": "sha512-QibCpLfRW6Stm5BDd1zxc0eqX3uOE1yINPs8K7esUIHL8AqnCPEJUPa86NnOIaA2t8E52f+bhlTxzM7ZsaffoQ==", + "version": "10.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.21.0.tgz", + "integrity": "sha512-Oh62GkqAKBIyD0IO3/Oa0l42yL/jbpTRDyh8H+t6gZbHWYTDvEGEr/LOqI9bk5Lwk7Jt5jpN6136FDwyMzHSXw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "^2.6.0" @@ -7013,19 +6771,20 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "0.10.13", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.10.13.tgz", - "integrity": "sha512-5zqIsG6Nn6N0DBlK9GyvYKxUrK7dEBHFInRnIqqfimWAQmz07iBCJU34njs9lQi+/GzKfXS+2XgBI7dDQnbfwQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.18.0.tgz", + "integrity": "sha512-Z8uH1dUzy/STQjOU6eb9nquVK4RC1rUx0gXY/GN1IVNDJvGN/yJxT/gNKmfiL7KpmHvNp2Q5M4bnUT9uiNcM+Q==", "dev": true, "dependencies": { - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", "lighthouse": "^10.4.0", - "mime": "^3.0.0" + "mime": "^3.0.0", + "web-vitals": "^3.5.0" }, "engines": { "node": ">=12" @@ -7034,79 +6793,6 @@ "@playwright/test": ">=1" } }, - "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/api-fetch": { - "version": "6.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.40.0.tgz", - "integrity": "sha512-sNk6vZW02ldci1EpNIjmm61323x/0n2Ra/cDHuehZf8avOH/OV0zF0dXxttT8M9Fncz+XZDSIHopm76dU3Phug==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.43.0", - "@wordpress/url": "^3.44.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/hooks": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.43.0.tgz", - "integrity": "sha512-SHSiyFUEsggihl0pDvY1l72q+fHMDyFHtIR3GCt0uV2ifctvoa/PIYdVwrxpGQaGdNEV25XCZ4kNldqJmfTddw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/i18n": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.43.0.tgz", - "integrity": "sha512-XHU/vGgI+pgjJU9WzWDHke1u948z8i3OPpKUNdxc/gMcTkKaKM4D8DW1+VMSQHyU6pneP8+ph7EF+1RIehP3lQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.43.0", - "gettext-parser": "^1.3.1", - "memize": "^2.1.0", - "sprintf-js": "^1.1.1", - "tannin": "^1.2.0" - }, - "bin": { - "pot-to-php": "tools/pot-to-php.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/keycodes": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.43.0.tgz", - "integrity": "sha512-B6rYPiKFdQTlnJfm93R+usQnjEODUX/K4+hMvY5ZZOinvxe7KyU/xyFGz7gRrS8WmIEYcJowqSmAlGgVs4XwKQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.43.0", - "change-case": "^4.1.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/url": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.44.0.tgz", - "integrity": "sha512-QNtTPFg/cGHTJLOvOtQCvCgn5quFQgJml8A88I05o4dyUH/tc92rb8LNXi0qcVz/z4JPrx2g3+Ki8heYellP4A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "remove-accents": "^0.5.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -7148,41 +6834,41 @@ } }, "node_modules/@wordpress/edit-post": { - "version": "7.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.19.16.tgz", - "integrity": "sha512-PK0XVHLrn6Bg47O8sq7UIBykJOJGF2xsbkOjhRVniD+6EYdYifpGYHTC9nHogEfw691xcz+vAqS87D01x3SfEQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.27.2.tgz", + "integrity": "sha512-GEWPr2TkzOH2OZx+WVtn+DGrkE+H5GOq1w+vAtoCEq1lLIdkGJe+YAieJKkSz/rqah25YzmRcyBgfYSL2iaULg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/commands": "^0.13.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-commands": "^0.11.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/editor": "^13.19.14", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13", - "@wordpress/viewport": "^5.19.13", - "@wordpress/warning": "^2.42.13", - "@wordpress/widgets": "^3.19.14", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-commands": "^0.19.2", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/editor": "^13.27.2", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", + "@wordpress/viewport": "^5.27.0", + "@wordpress/warning": "^2.50.0", + "@wordpress/widgets": "^3.27.2", "classnames": "^2.3.1", "memize": "^2.1.0", "rememo": "^4.0.2" @@ -7196,54 +6882,55 @@ } }, "node_modules/@wordpress/edit-site": { - "version": "5.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-5.19.16.tgz", - "integrity": "sha512-shraoCd4LCNngtBn9E7U6Na/l+zrU0nTXztgZSuVsqSGktAgHBi7pXMUTsCGqO/vp9fnmW9LU3tQ9XgLEogjkg==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-5.27.2.tgz", + "integrity": "sha512-/lZhqadnX/A7owFre4ZxcKjlj7pisdxVAQJgtB9OYSdpreG2x8sGNKvLhv686BTKzSffS1TzvmKbNl7e+pQZDA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/commands": "^0.13.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-commands": "^0.11.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/editor": "^13.19.14", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/patterns": "^1.3.14", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/primitives": "^3.40.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/router": "^0.11.13", - "@wordpress/style-engine": "^1.25.13", - "@wordpress/url": "^3.43.13", - "@wordpress/viewport": "^5.19.13", - "@wordpress/widgets": "^3.19.14", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-commands": "^0.19.2", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/dataviews": "^0.4.1", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/editor": "^13.27.2", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/router": "^0.19.0", + "@wordpress/style-engine": "^1.33.1", + "@wordpress/url": "^3.51.0", + "@wordpress/viewport": "^5.27.0", + "@wordpress/widgets": "^3.27.2", + "@wordpress/wordcount": "^3.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", - "downloadjs": "^1.4.7", "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", @@ -7260,38 +6947,39 @@ } }, "node_modules/@wordpress/edit-widgets": { - "version": "5.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-5.19.16.tgz", - "integrity": "sha512-1yTkLHQjf/LEmxlw2y0bqgkZcqO2Gs0H8QK1JHEJdHrAK+R5nBd55Jq4Wb2IU+QsUAaGvQzuF+FfHAA4YkLUwQ==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-5.27.2.tgz", + "integrity": "sha512-AE5qgDCd5u16C3/EZQAP3STcxfpTZg2Ed6iHmN+PBg1RCEP11rv31aMaXy2+7Z+80bGsXwicmZAlqHxzm2vc2g==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/patterns": "^1.3.14", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/url": "^3.43.13", - "@wordpress/widgets": "^3.19.14", - "classnames": "^2.3.1" + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/url": "^3.51.0", + "@wordpress/widgets": "^3.27.2", + "classnames": "^2.3.1", + "rememo": "^4.0.2" }, "engines": { "node": ">=12" @@ -7302,40 +6990,41 @@ } }, "node_modules/@wordpress/editor": { - "version": "13.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.19.14.tgz", - "integrity": "sha512-t1RFJl0Bf+qJpBHtiUl0qoxJjpNNGcpSZLejnhR97+i32l/4ewg8+z69zwFtW4ChNQjLnAFnpQZ5pT/CqkkKpQ==", + "version": "13.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.27.2.tgz", + "integrity": "sha512-Wk1dwG5bkmDD74zip36yC1NO3EleXe/t35Z9GHfLaiZkUYlhZV2gv66QrrGN7Y59Zl68j+b4lRGLkUxEMWkleA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/patterns": "^1.3.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/server-side-render": "^4.19.14", - "@wordpress/url": "^3.43.13", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/server-side-render": "^4.27.1", + "@wordpress/url": "^3.51.0", + "@wordpress/wordcount": "^3.50.0", "classnames": "^2.3.1", "date-fns": "^2.28.0", "memize": "^2.1.0", @@ -7352,14 +7041,14 @@ } }, "node_modules/@wordpress/element": { - "version": "5.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.19.13.tgz", - "integrity": "sha512-8VSGNrJkSf0coC2xciFBFodVa6eQOLPKMThVAz1eIDtQwbAcFo9001tjkMXgyhcn/FMoxdhaGGOxg4VeUvgJSw==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.27.0.tgz", + "integrity": "sha512-IA5LTAfx5bDNXULPmctcNb/04i4JcnIReG0RAuPgrZ8lbMZWUxGFymh10PEQjs7ZJ++qGsI6E+6JISpjkRaDQQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.42.13", + "@wordpress/escape-html": "^2.50.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -7370,9 +7059,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.42.13.tgz", - "integrity": "sha512-0I7loSc8M1vjqg6vXb6lCumaGzbbAeoI26NEpATcEq24MLgd8+UiidyHII4UNgdloRoq1Jj3e83AjDhFpAVfAg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.50.0.tgz", + "integrity": "sha512-hBvoMCEZocziZDGCmBanSO+uupnd054mxd7FQ6toQ4UnsZ4JwXSmEC72W2Ed+cRGB1DeJDD0dY9iC0b4xkumsQ==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7381,16 +7070,16 @@ } }, "node_modules/@wordpress/eslint-plugin": { - "version": "16.0.13", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-16.0.13.tgz", - "integrity": "sha512-Qk5Y7ifT0lfOOx5RQrEGa/DSw01CP+D2bCKr20SXLt3KDstViBlqjBiI1Yxv7EeS+AvaNbQO5M8Mm4B5mUB3kQ==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.7.0.tgz", + "integrity": "sha512-JSFaCogE0WlZpl0SV4q8DK8G6jwDjEzXRzOsgesWilea4OuVp1KxCamkddTorRNM3QAbjrGuPJ4NYaGrNG9QsA==", "dev": true, "dependencies": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^7.26.13", - "@wordpress/prettier-config": "^2.25.13", + "@wordpress/babel-preset-default": "^7.34.0", + "@wordpress/prettier-config": "^3.7.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -7411,7 +7100,7 @@ "peerDependencies": { "@babel/core": ">=7", "eslint": ">=8", - "prettier": ">=2", + "prettier": ">=3", "typescript": ">=4" }, "peerDependenciesMeta": { @@ -7424,9 +7113,9 @@ } }, "node_modules/@wordpress/eslint-plugin/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -7439,22 +7128,23 @@ } }, "node_modules/@wordpress/format-library": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-4.19.14.tgz", - "integrity": "sha512-NyJ1nmb6PODE5hXM9oOEBlYA48k6c2DlGcUTXkSzDcdLPRVinTeWDfPL4kpze30JcQPv9m6Y5/EfWp48bDnByA==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-4.27.2.tgz", + "integrity": "sha512-pgLWc+8QuRyWc3GtEL1X18u4FNmWI3Y821TbKW1MjnfMDYNhN7Vpypqk4AFuxq2PY0NxzmM0PGdcoqUXRGdldQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/url": "^3.43.13" + "@wordpress/a11y": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/url": "^3.51.0" }, "engines": { "node": ">=12" @@ -7465,9 +7155,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.42.13.tgz", - "integrity": "sha512-KITkyj2DhbbBevqLzGx4GCtq8XX/GjkMWe0NP7SkcX9d4rkEdON96eKwwoMUD6keL03Tijg87kIYZAU5Xsr8bA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.50.0.tgz", + "integrity": "sha512-YIhwT1y0ss7Byfz46NBx08EUmXzWMu+g5DCY7FMuDNhwxSEoZMB8edKMiwNmFk4mFKBCnXM1d5FeONUPIUkJwg==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7476,9 +7166,9 @@ } }, "node_modules/@wordpress/html-entities": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.42.13.tgz", - "integrity": "sha512-015rUF0FOSGXbUBq+sc++vo3UTGZZkl23z7tGxrTTXZG10AjcTVd3oMnpvffJeiBjrtEAJz/gq3QKpFXihvmww==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.50.0.tgz", + "integrity": "sha512-DBRgShv6FLtDpapoTgmEx//6uHeq+mk5zKhAWAAqu6+/6LqOm/TCoUTxb0E2xtHh4oRBgU5nYC92pObRaczFdQ==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7487,12 +7177,12 @@ } }, "node_modules/@wordpress/i18n": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.42.13.tgz", - "integrity": "sha512-4zYz5BbueJ3c19DYhO7cXf9GF2K5Fysd+c2r0rcE0lr2RqMqmyDdL49930L7XJw+mT4ql8g/8p+i3FOzPCsg9A==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.50.0.tgz", + "integrity": "sha512-FkA2se6HMQm4eFC+/kTWvWQqs51VxpZuvY2MlWUp/L1r1d/dMBHXu049x86+/+6yk3ZNqiK5h6j6Z76dvPHZ4w==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.42.13", + "@wordpress/hooks": "^3.50.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -7506,48 +7196,60 @@ } }, "node_modules/@wordpress/icons": { - "version": "9.33.13", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.33.13.tgz", - "integrity": "sha512-4M34sMRIlyL7a3CDRI7rAfysZQm2VW1ptB4aGDf5tVMXd//hCRkj/OGE++AYkTYQNckli9uqhTkv2xoOOw1F6Q==", + "version": "9.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.41.0.tgz", + "integrity": "sha512-L4fp9ZdxGBpMk3o2YqABgiPHNoHyu9Enid7JNkCdWP8iUgk7dEiDvo/XoiWPTAeNbF6W8Nqu54635mq01es0NQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.19.13", - "@wordpress/primitives": "^3.40.13" + "@wordpress/element": "^5.27.0", + "@wordpress/primitives": "^3.48.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/interactivity": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-2.3.13.tgz", - "integrity": "sha512-WNmw/r+G1XllTZwKwpRDFJoGPm8cRztbU+MJhAogKzUOcrCu4Bp8xArroPSzlKr3aUuEquT/3WsWsFmHsSHYjg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-4.0.1.tgz", + "integrity": "sha512-sw9Cqoj+MNF9FAU5nJC3nAqoH7kgUvh6HwaEMaLdSlK0qEcp05ba5x7geDSNi5cUWY4QSk1r9DH2jKUg9zfpNg==", + "dependencies": { + "@preact/signals": "^1.2.2", + "deepsignal": "^1.4.0", + "preact": "^10.19.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wordpress/interactivity-router": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-1.0.1.tgz", + "integrity": "sha512-XShZV0+Sqs+1C26nVyns6nT8kjAGRBJNArVPceZlkkpsX7DIRZcEZ2larWxOuQFWk67lzIRiXd5V51L71b8XrQ==", "dependencies": { - "@preact/signals": "^1.1.3", - "deepsignal": "^1.3.6", - "preact": "^10.13.2" + "@wordpress/interactivity": "^4.0.1" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/interface": { - "version": "5.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.19.14.tgz", - "integrity": "sha512-WsIsSKJuhAcXD3YbmUoncL1JZ6hKAJXs7Lb/bjrOJxCts/YOy5yMF3/I05r8f1Tfw/pS8wlHMRjIXH/gvnvWVA==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.27.0.tgz", + "integrity": "sha512-ZybF4tuuuFOgGsB0n9u5ajrWKf/PYaS8d2yu2T+6ukliLnXI6AMMCXvM534H0VZa7DMLjMYKRXtfs7QqR/p95Q==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/viewport": "^5.19.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/viewport": "^5.27.0", "classnames": "^2.3.1" }, "engines": { @@ -7559,9 +7261,9 @@ } }, "node_modules/@wordpress/is-shallow-equal": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.42.13.tgz", - "integrity": "sha512-C3Pdan4alanyaQJ4Ucg7GZvkgDv7mXQZXe0xIYmKUNCnohS3wcFXmaLE6VGvf3I2OhRz8WLh5uxno/suJ8cyRw==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.50.0.tgz", + "integrity": "sha512-lX0fMa1f/TwWYYF+Oj0MG2Eze4Bb+vsnhXX6X1l+Ri3PG34wWGonjq729qHbJRDwm8o1y9GeswCgESIpuAm9wg==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7570,9 +7272,9 @@ } }, "node_modules/@wordpress/jest-console": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.14.0.tgz", - "integrity": "sha512-o7EZZ+StfLg/qgTRn47O0WY2V1I+xNJCiN13a/fHZtXdRgPJ9qajf7tkDYz+MKPf8MhdMfHhgIr9sQrWhLCzDA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.21.0.tgz", + "integrity": "sha512-o2vZRlwwJ6WoxRwnFFT5iZzfdc2d9MZvrtwB093RWPNcyK5qVtApji4VN/ieHijB4bjEHGalm0UKfKpt0EDlUQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -7586,12 +7288,12 @@ } }, "node_modules/@wordpress/jest-preset-default": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.14.0.tgz", - "integrity": "sha512-eGenm5xUpPcsgWMSFXYWg+RQlcAZa6zo7sT9bBK8HVIGqORTr3TTtWeHVGFL48UooL5PibUc+GxQdlW97YOwlQ==", + "version": "11.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.21.0.tgz", + "integrity": "sha512-XAztKOROu02iBsz+Qosv/RYuPWB1XwwlU+FiA5Y68tRztrqFy4b/il+DFg4Jue/zXF7UECWUvosd5ow/GmKa6Q==", "dev": true, "dependencies": { - "@wordpress/jest-console": "^7.14.0", + "@wordpress/jest-console": "^7.21.0", "babel-jest": "^29.6.2" }, "engines": { @@ -7603,14 +7305,14 @@ } }, "node_modules/@wordpress/keyboard-shortcuts": { - "version": "4.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.19.13.tgz", - "integrity": "sha512-5u/pMERHn1b17d3HqDWWulJp08MLlNG1idsuJiLzbQBrYW3wLPd23fPG1QObUSH/texVDvi/W4/9N4hsbZlXEg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.27.0.tgz", + "integrity": "sha512-mpYhaSAMHXbRMp9hP08LejX/u1nLQaZONhwGSytqIhN1DQwpBbNbmV8ZNm1dnevUsYqEfPVVov6HFyPxYQ6m4w==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/keycodes": "^3.42.13", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/keycodes": "^3.50.0", "rememo": "^4.0.2" }, "engines": { @@ -7621,29 +7323,29 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.42.13.tgz", - "integrity": "sha512-3lGlnYj+ky5OOnFjTW6NSxFFeNk/ESUF2Gbhz888HV+QF55SPvRfb+G7kjAzxRomIpdwACYsn80PdqabxLVqgw==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.50.0.tgz", + "integrity": "sha512-ykWpyCbgwcaT8i5kSfotYtd2oOHyMDpWEYR73InYrzEhl7pnS3wD7hi/KfeKLvMfYhbysUXlCVr6q/oH+qK/DQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.42.13", - "change-case": "^4.1.2" + "@wordpress/i18n": "^4.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/list-reusable-blocks": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-4.19.14.tgz", - "integrity": "sha512-GuorU374D0Ft7RtIZWWc7ltIkV3ThjU/u+LwbNzh5y7iaVs4l64qvqopqoj/IoRVdahpnLEO3MNxj9InlUiNeg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-4.27.0.tgz", + "integrity": "sha512-szDQnIdU34yIvNel+Kk1oBOugiqwXNm4jF77T90kaWB/SIQFW80CFYoIjIYQc63r9v3wi0D483KpXoci1AUSeQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", "change-case": "^4.1.2" }, "engines": { @@ -7655,28 +7357,28 @@ } }, "node_modules/@wordpress/media-utils": { - "version": "4.33.13", - "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.33.13.tgz", - "integrity": "sha512-+QJvDbBEtjMC6V2kJ04dEZkmElDneueW6HxGcx9lD786N0pcHwHZCnY9mLN+Tg/2f6Y8/9u0emvbFFuX0FLE8w==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.41.0.tgz", + "integrity": "sha512-wCxk8DAhmZ/3/a+oPRrieGurMOKDrYoDnnA0jhTm2D45kvn9y+NfnNBvLo2q1Is1ZiVTtNq54IRUXcdOjZgR9A==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13" + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/notices": { - "version": "4.10.13", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.10.13.tgz", - "integrity": "sha512-6U0im51yJFXLLMzL6zZ+eyeJIeY2cyiUCDdziJSI1ZrsfV2ml9o4nB3EYYOxZBaVvJg66vY3wIQ/osMFwTW6xg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.18.0.tgz", + "integrity": "sha512-Y2XpY6niJ7NuqPBtGYvDYSPCfw/y4yxv60ahu1kYd8r5BamKSchTYwKSnV0yrx/IUfNO04VAsNq9NCUQG12pRA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/data": "^9.12.13" + "@wordpress/a11y": "^3.50.0", + "@wordpress/data": "^9.20.0" }, "engines": { "node": ">=12" @@ -7686,9 +7388,9 @@ } }, "node_modules/@wordpress/npm-package-json-lint-config": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.28.0.tgz", - "integrity": "sha512-lxrs1F4scwDuF8AJLK+SHtLWuhRVjzvl8EW/++ZQWRt7op99m41QQUqUwwCQC09cDcYlGddXeAczRijx5eLREg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.35.0.tgz", + "integrity": "sha512-QmkhYM4/s+2r3RuolVRRmoUa5o3lFgcHA6I3A9akaSVGZr//4p2p+iXOGmNub9njgGlj7j8SAPN8GUsCO/VqZQ==", "dev": true, "engines": { "node": ">=14" @@ -7698,18 +7400,18 @@ } }, "node_modules/@wordpress/nux": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-8.4.14.tgz", - "integrity": "sha512-JcxUtWOzl7lTuv39BWRwzwPDvVEhFECGzK819i3kExbTjmsVHCHtsdB7khPrdAYZOm2GXzR1le+/UFfkGuHS2Q==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-8.12.0.tgz", + "integrity": "sha512-fMnm9f+lmaCV5YoRHjqQNVU0P+FxthY8Lt84ZW1owlPjpJqdYZX/bKtp+bfWFgR3/Th26/uO4WxZqQzj8V1Pjg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", "rememo": "^4.0.2" }, "engines": { @@ -7721,25 +7423,26 @@ } }, "node_modules/@wordpress/patterns": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.3.14.tgz", - "integrity": "sha512-eaZWZlaF/MlxqDY7KYzL8cApY4b4f89wuqHVSmjv52UfvaqxW0vd09ddX+jwkcXysDHFzwM63takIIVZwYn9Lg==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.11.2.tgz", + "integrity": "sha512-cN7xjw5pfKq73mVF0q0ebZh4DmAab5SlQ9CvM7PtB03JOl3GMwVIDV5Tnbbhfi1KIsFwep2/CGft3xwuJlS3FQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13" + "@wordpress/a11y": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", + "nanoid": "^3.3.4" }, "engines": { "node": ">=16.0.0" @@ -7750,17 +7453,17 @@ } }, "node_modules/@wordpress/plugins": { - "version": "6.10.14", - "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.10.14.tgz", - "integrity": "sha512-Duxh0OxpSuUFTMHa500iitrD21/JeTklc8/Hf3ApCpn4SdDzFR4IrwUdoJk0jGDY79cTwBVeWts5GhObbJByng==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.18.0.tgz", + "integrity": "sha512-m2BRJ5BApIMwT2Ck5E5yD8pS3RiIoOvWhzsYWrRqRfwjRhc6K46BreCbkiHgduBaFgzDIWpujlUHkYtdl27RoQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/is-shallow-equal": "^4.42.13", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/is-shallow-equal": "^4.50.0", "memize": "^2.0.1" }, "engines": { @@ -7772,12 +7475,12 @@ } }, "node_modules/@wordpress/postcss-plugins-preset": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.27.0.tgz", - "integrity": "sha512-4hk8UWfJvv21u/Et0NypfR1r22LVWGXMit3QM0MD7d6XQ4dNNbzqW2c9TfM36SdcR9KY5PZ8d5V1IrkheNUb/w==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.34.0.tgz", + "integrity": "sha512-OLQBSLE2q11Ik+WdcO2QfGr/O4X/zJYOGXNsychx/EaMamLzJInFcRL6kGbPX41zPINhadq5x2vFIZI2EC+Uyg==", "dev": true, "dependencies": { - "@wordpress/base-styles": "^4.34.0", + "@wordpress/base-styles": "^4.41.0", "autoprefixer": "^10.2.5" }, "engines": { @@ -7788,17 +7491,20 @@ } }, "node_modules/@wordpress/preferences": { - "version": "3.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.19.14.tgz", - "integrity": "sha512-xLu+G22Vlm4KajE/Eimq8qLzBoxMZ7BJLp8WobFC3yyzdU9R785dug9t9et4r45NxWJr8aVWkFzhEBzAadHjnA==", + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.27.0.tgz", + "integrity": "sha512-LMhOHX5FI4CJHv2YhtpiEtHfLqL/pjKAMja/v7skkHPlrh64Sgzi/gep016/My5SjcR64JUD1Na2U2j/BnrBNQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/components": "^25.8.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/private-apis": "^0.32.0", "classnames": "^2.3.1" }, "engines": { @@ -7810,36 +7516,36 @@ } }, "node_modules/@wordpress/preferences-persistence": { - "version": "1.34.13", - "resolved": "https://registry.npmjs.org/@wordpress/preferences-persistence/-/preferences-persistence-1.34.13.tgz", - "integrity": "sha512-23bUN1WdJ9mtfU51uoPBrSwbYHaW2zG+HDlH+leZURdPe48jbWSA8LRPwni5z3Kc9zh8D0vXkvt0hg1/RcNgUQ==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences-persistence/-/preferences-persistence-1.42.0.tgz", + "integrity": "sha512-n/VBhZHUEXWoBGsvHUf5uq6b872Lzn+cenfB2ex/etcWLXiVUkEl3rlzocyS50g2YoNQg/zQOn1hoSh+AgCm8Q==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13" + "@wordpress/api-fetch": "^6.47.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/prettier-config": { - "version": "2.25.13", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-2.25.13.tgz", - "integrity": "sha512-iz58o0X91E24j0VFtzwn5qG84w+s4VlRCuZWa/lPL6pfGtOSw30c60wCrYKCA1IWIIAWdpRAYfEh7errPyKiPQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.7.0.tgz", + "integrity": "sha512-JRTc5p7UxtcPkqdSDXSFJoJnVuS510uiRVz8anXEl5nuOx5p+SJAzi9QPrxTgOE8bN3wRABH4eIhfOcta4CFdg==", "dev": true, "engines": { "node": ">=14" }, "peerDependencies": { - "prettier": ">=2" + "prettier": ">=3" } }, "node_modules/@wordpress/primitives": { - "version": "3.40.13", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.40.13.tgz", - "integrity": "sha512-dYYrPceV8w78AHJfPe5wkxnT7P0tG/4yDcr9/HvznFHkzQFnW8kG8Nci20RV/+ENxfNiuWqfWyICI2y7myIoGw==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.48.0.tgz", + "integrity": "sha512-uBoMxpl+FiZF6aRXH/+Hwol4EAL6QqlNSaGF1IzEwklFzdRF1m5wTM4vh21w8Bq7lgxiuAqyueY7X5u32v+zPw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.19.13", + "@wordpress/element": "^5.27.0", "classnames": "^2.3.1" }, "engines": { @@ -7847,9 +7553,9 @@ } }, "node_modules/@wordpress/priority-queue": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.42.13.tgz", - "integrity": "sha512-vrkjBcJnuzhpfWLFF4LfdNVrM3s73KW3KOZBTuN6oizJVYKyQaaPSLmDdORuXFc017MMasO5N/fYk/qJyll5bg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.50.0.tgz", + "integrity": "sha512-21E842EVFYUd1ZrNTLAW57IyloDCUZr6h1Te6BgqKoeKOEteoTQwA9BemMzZJUiThUSZymW94ot0Omb+C8VX2g==", "dependencies": { "@babel/runtime": "^7.16.0", "requestidlecallback": "^0.3.0" @@ -7859,9 +7565,9 @@ } }, "node_modules/@wordpress/private-apis": { - "version": "0.24.13", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.24.13.tgz", - "integrity": "sha512-RgvGB6VQpPnEGU8Y61tzpgPFYDRAW28+2gcdOXYiqSVdZfGBL6+hBs5bMbLSJYRU9G5pl5q4Eb0lHlkMgHW5FA==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.32.0.tgz", + "integrity": "sha512-P7nxI/bGMDQhtlTfSe1Y2SDfrd20K5UMnTHbq+hmIkzBGRpNPbdGeNu2bZaZtIvmXk1OCR0Fkef+e6QqkOfYPg==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7870,9 +7576,9 @@ } }, "node_modules/@wordpress/redux-routine": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.42.13.tgz", - "integrity": "sha512-R+8W8CcjhHXPRlfPCdtElO2lsZzObR6DWvO49BjfJcKs0QPvKaO3ofjsadRgv+gg1+nXiE7rH6LmHbZ4eLanGw==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.50.0.tgz", + "integrity": "sha512-giHjQYhmFDCpeNEnsZKP0JNPBnpuQwsoxLmHAUUSNFWAmd4rtnNnG6M8HuqOLmgYTvEa8Hlx3Bl+reTGvrtI2g==", "dependencies": { "@babel/runtime": "^7.16.0", "is-plain-object": "^5.0.0", @@ -7887,22 +7593,22 @@ } }, "node_modules/@wordpress/reusable-blocks": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.19.14.tgz", - "integrity": "sha512-WhQNDtq2ohGlGlodNyEbvMux631D+7jRABwodvoC42dVJyHR3lH1O8uhnQeKyPl91YWLxJ6+mHmrPInEo2fAcQ==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.27.2.tgz", + "integrity": "sha512-kkhZyYFj4rbf7lPOqDMfaNO3fqLEyHYKjWITWzRMUPtLeIHin/DHepVz6Z6NERANHpbP0mD4BDoBEGYJ9/brbA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13" + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0" }, "engines": { "node": ">=12" @@ -7913,19 +7619,19 @@ } }, "node_modules/@wordpress/rich-text": { - "version": "6.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.19.13.tgz", - "integrity": "sha512-7kCbTLiy+dIOToBktkrftCfVLsqCN0dY9uE6rz/TRsKS6+pnF6fUhqHLBV5OFf0tttKjHykSj5ixFDejqWCvrQ==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.27.0.tgz", + "integrity": "sha512-B7t++SldcI4nb+lO2m9oEdyD8y2FbH5DKY5F2G3xpcEnw4EKSt4SsTzeclMQ/2zzlEHPRKU/IR29SeOIJ1H8JQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/keycodes": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/keycodes": "^3.50.0", "memize": "^2.1.0", "rememo": "^4.0.2" }, @@ -7937,14 +7643,14 @@ } }, "node_modules/@wordpress/router": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.11.13.tgz", - "integrity": "sha512-OZyuFOuX6nW5fQ1kq250EqCCA1Ad6KSH0wlaC68kCF06VFft2JNiATba7rC9Uq3ozM9HjPCtkbJ1dAW4PQdS1g==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.19.0.tgz", + "integrity": "sha512-S2z4WrgrfMNAl6amIjekGV1V6XGnjolYmRgUH/VTN45CQUV/o5ABo04xI/L3uvUnaRpH022n/yQX5H1p1kKhdA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.19.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13", + "@wordpress/element": "^5.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", "history": "^5.1.0" }, "engines": { @@ -7955,28 +7661,28 @@ } }, "node_modules/@wordpress/scripts": { - "version": "26.13.13", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-26.13.13.tgz", - "integrity": "sha512-G2K56PmjRPI0ddgmrnopp3AVMLACqfrFvz+NyGbYCPWQoYL3xnphrS+w3uPwuxcuBtgR34yr+xCvrMnJsY3Wag==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.1.0.tgz", + "integrity": "sha512-jewyOxqaNrsct5R1NXv2lT8CA70vzrvpdZHYERCcH9LzKuvrcc32Telm9Jqso6ay1ZgHeIbjHSCd2+r2sBG7hw==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.2", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^7.26.13", - "@wordpress/browserslist-config": "^5.25.13", - "@wordpress/dependency-extraction-webpack-plugin": "^4.25.13", - "@wordpress/e2e-test-utils-playwright": "^0.10.13", - "@wordpress/eslint-plugin": "^16.0.13", - "@wordpress/jest-preset-default": "^11.13.13", - "@wordpress/npm-package-json-lint-config": "^4.27.13", - "@wordpress/postcss-plugins-preset": "^4.26.13", - "@wordpress/prettier-config": "^2.25.13", - "@wordpress/stylelint-config": "^21.25.13", + "@wordpress/babel-preset-default": "^7.34.0", + "@wordpress/browserslist-config": "^5.33.0", + "@wordpress/dependency-extraction-webpack-plugin": "^5.1.0", + "@wordpress/e2e-test-utils-playwright": "^0.18.0", + "@wordpress/eslint-plugin": "^17.7.0", + "@wordpress/jest-preset-default": "^11.21.0", + "@wordpress/npm-package-json-lint-config": "^4.35.0", + "@wordpress/postcss-plugins-preset": "^4.34.0", + "@wordpress/prettier-config": "^3.7.0", + "@wordpress/stylelint-config": "^21.33.0", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", - "browserslist": "^4.21.9", + "browserslist": "^4.21.10", "chalk": "^4.0.0", "check-node-version": "^4.1.0", "clean-webpack-plugin": "^3.0.0", @@ -7991,7 +7697,7 @@ "fast-glob": "^3.2.7", "filenamify": "^4.2.0", "jest": "^29.6.2", - "jest-dev-server": "^6.0.2", + "jest-dev-server": "^9.0.1", "jest-environment-jsdom": "^29.6.2", "jest-environment-node": "^29.6.2", "markdownlint-cli": "^0.31.1", @@ -8000,12 +7706,12 @@ "minimist": "^1.2.0", "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", - "playwright-core": "1.32.0", + "playwright-core": "1.39.0", "postcss": "^8.4.5", "postcss-loader": "^6.2.1", - "prettier": "npm:wp-prettier@3.0.3-beta-3", + "prettier": "npm:wp-prettier@3.0.3", "puppeteer-core": "^13.2.0", - "react-refresh": "^0.10.0", + "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", "sass": "^1.35.2", @@ -8014,24 +7720,184 @@ "stylelint": "^14.2.0", "terser-webpack-plugin": "^5.3.9", "url-loader": "^4.1.1", - "webpack": "^5.47.1", - "webpack-bundle-analyzer": "^4.4.2", - "webpack-cli": "^4.9.1", - "webpack-dev-server": "^4.4.0" + "webpack": "^5.88.2", + "webpack-bundle-analyzer": "^4.9.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1" }, "bin": { "wp-scripts": "bin/wp-scripts.js" }, "engines": { - "node": ">=14", + "node": ">=18", "npm": ">=6.14.4" }, "peerDependencies": { - "@playwright/test": "^1.32.0", + "@playwright/test": "^1.39.0", "react": "^18.0.0", "react-dom": "^18.0.0" } }, + "node_modules/@wordpress/scripts/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==", + "dev": true, + "dependencies": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "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", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "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-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@wordpress/scripts/node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@wordpress/scripts/node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@wordpress/scripts/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==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/scripts/node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@wordpress/scripts/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==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/scripts/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/@wordpress/scripts/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==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/scripts/node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/@wordpress/scripts/node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -8255,11 +8121,23 @@ "node": ">=8" } }, + "node_modules/@wordpress/scripts/node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@wordpress/scripts/node_modules/prettier": { "name": "wp-prettier", - "version": "3.0.3-beta-3", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3-beta-3.tgz", - "integrity": "sha512-R3+TD7j0rnqEpMgylrUrHdi1W6ypwh4QGeFOZQ9YjP9WvNnZzBAS71yry1h7xIcG/bVaNKBCoWNqbqJY6vkOKQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz", + "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -8271,15 +8149,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@wordpress/scripts/node_modules/react-refresh": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz", - "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@wordpress/scripts/node_modules/read-pkg-up": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", @@ -8328,6 +8197,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wordpress/scripts/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/@wordpress/scripts/node_modules/source-map-loader": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", @@ -8371,20 +8249,20 @@ } }, "node_modules/@wordpress/server-side-render": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.19.14.tgz", - "integrity": "sha512-As3Xc3TDM0R0siAFaldobRdZnPfQQMXvlQxalFJgs/kSoYOmcdc46mR5Wgmfn7r0Kc/Z5uOHLbvm4mWekE0a2A==", + "version": "4.27.1", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.27.1.tgz", + "integrity": "sha512-hovofyT0z75NSK/CSkkSbbTdkq9Afc1MKbEVGXTGpqq5sKOa7IAcxWjzmh8byTgT8x7GEaAyHZUr31p4l0CGnQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/url": "^3.51.0", "fast-deep-equal": "^3.1.3" }, "engines": { @@ -8396,9 +8274,9 @@ } }, "node_modules/@wordpress/shortcode": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.42.13.tgz", - "integrity": "sha512-pq+xdRdND7vEuqskPoZx+VAOHsmatqHcox3dElFU5lxlx/3fvKC7NIrFCn+glxFGGxO5hY5JfUOC70x8tm7uMA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.50.0.tgz", + "integrity": "sha512-RnlqS2OsNUaI6VOLwyUiaL3trAJcWjtoiW21BjIXODbTkEreRJgBJnch7wdFpGimJmKIWBwRD8jQ4hdTND8xVw==", "dependencies": { "@babel/runtime": "^7.16.0", "memize": "^2.0.1" @@ -8408,9 +8286,9 @@ } }, "node_modules/@wordpress/style-engine": { - "version": "1.25.13", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.25.13.tgz", - "integrity": "sha512-4ixhGNVNrtt6zppLWnPCKSl4O4X+TO48PbLEbLDvN2NvUK1Yp1wChiX+NFIBa1dJp1zDlrxaTjttCqC1bs3MUA==", + "version": "1.33.1", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.33.1.tgz", + "integrity": "sha512-mkur1jw3Trz76iwxU6DalTFsJyF5P/NTdU9xniMT8bo1H9HspgKrzqXAaxkTL9F9BXkyiYs+ctVekJYRUKlgcw==", "dependencies": { "@babel/runtime": "^7.16.0", "change-case": "^4.1.2" @@ -8420,9 +8298,9 @@ } }, "node_modules/@wordpress/stylelint-config": { - "version": "21.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.26.0.tgz", - "integrity": "sha512-xTnvoNk9aCdRl1ntBxnmhdmghwzRNurp5Y9LjUCwrYutxnj8t/CCKhPyjgIgHxz+RwKgnpGKupKLVvuHxu1CzQ==", + "version": "21.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.33.0.tgz", + "integrity": "sha512-DwjXrjRBva0tkYILvDV7rjl3VaKXxvchlxnFfFs6l2DWL/Qo31CJ+f2rVw4XSWuuWxY1EsyIn9tOBS9URloWTQ==", "dev": true, "dependencies": { "stylelint-config-recommended": "^6.0.0", @@ -8436,12 +8314,18 @@ } }, "node_modules/@wordpress/sync": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.4.13.tgz", - "integrity": "sha512-3Lq7MENUpCaSvR6WOLOovNmRMXGmFcdnbMjSZlHh0sx3ycWbKpXlGyfQWJ20MZRiO/qTOOrj4VW4GejqqJSEZw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.12.0.tgz", + "integrity": "sha512-45gU1Gu/ys3zqYO4dDQf6eG5gGgJK9nXa62IUtUWFXIH4FN29XlvGppMVK/zzhJwejF/XnDuT7mQuVEFCZGswA==", "dependencies": { "@babel/runtime": "^7.16.0", + "@types/simple-peer": "^9.11.5", + "@wordpress/url": "^3.51.0", + "import-locals": "^2.0.0", + "lib0": "^0.2.42", + "simple-peer": "^9.11.0", "y-indexeddb": "~9.0.11", + "y-protocols": "^1.0.5", "y-webrtc": "~10.2.5", "yjs": "~13.6.6" }, @@ -8450,9 +8334,9 @@ } }, "node_modules/@wordpress/token-list": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.42.13.tgz", - "integrity": "sha512-eAKU/5U7c/Acqcqnurpp79lrwCAm+Tb8PfSBTmtGs1fJsR1xtJh4d6IZw5MLDFiqLuVRT65ec3T4Sjqb6N4CMQ==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.50.0.tgz", + "integrity": "sha512-LTjXkoljQpJIHqs0isTUzIc1fMu68y0N9HcDIdsCMGkmKptWUCETtb+DItnraxDDLuyWNuTYf840S83a3XAVRA==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -8461,21 +8345,21 @@ } }, "node_modules/@wordpress/undo-manager": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.2.13.tgz", - "integrity": "sha512-SFIYRs65GEjr0eeh7BZcETaH32qQVm78aFMZXnYTHzBmTXxoJ98XRgEGWXRJU92RXBcjom+1gARKChJoV5dlNw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.10.0.tgz", + "integrity": "sha512-ODDqAL6BSvD+J7FV+sQTAaVHiPChh/4KBnKg8pb2ogg+Weq6VynthxDxGpQnN8FcMKB9ZoyS3SNIl8pVXLKIwA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/is-shallow-equal": "^4.42.13" + "@wordpress/is-shallow-equal": "^4.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/url": { - "version": "3.43.13", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.43.13.tgz", - "integrity": "sha512-GrIkGZoCgd+87CyAjgGzShoI6m/Kvknmc6syqrN34J1LdrEE+vPNMjM+NvUVvyPdvgG7/iFzRM8D/ZEUvaTm9A==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.51.0.tgz", + "integrity": "sha512-OjucjlP1763gfKbe8lv/k3RCisyX8AfNBrhASk7JqxAj6rFhb1ZZO7YmAgB2m+WoGB5v7fkOli0FZyDqISdYyg==", "dependencies": { "@babel/runtime": "^7.16.0", "remove-accents": "^0.5.0" @@ -8485,14 +8369,14 @@ } }, "node_modules/@wordpress/viewport": { - "version": "5.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.19.13.tgz", - "integrity": "sha512-xYWTcaQLhZrDZA0lpl9TivbU4RPw+CUfuRc3NEBiQY0GDDfuLe8n1Pb9AkmAP5PLNyxZhHjKLBGojfchOGhzdg==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.27.0.tgz", + "integrity": "sha512-ET8X3Ln0K6wrBba+u0AjBD/mP02SuvwhK/EVaI3uAhNlGnkx+J3PdtShbu63lHmp0SG+J27CDjEqfcZ6CdAnfA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13" + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0" }, "engines": { "node": ">=12" @@ -8502,30 +8386,30 @@ } }, "node_modules/@wordpress/warning": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.42.13.tgz", - "integrity": "sha512-SYi37xiR7Wq4Vde4JBkCYJIyfUQzyuABrwh7aon1XwcUhWP072tv4/LKP6F+zWYC5M8pPdRqjznxgwZ2mNzcyw==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.50.0.tgz", + "integrity": "sha512-y7Zf48roDfiPgbRAWGXDwN3C8sfbEdneGq+HvXCW6rIeGYnDLdEkpX9i7RfultkFFPVeSP3FpMKVMkto2nbqzA==", "engines": { "node": ">=12" } }, "node_modules/@wordpress/widgets": { - "version": "3.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.19.14.tgz", - "integrity": "sha512-nFyXrCBVp24joFa96sAdNwkWnnf23t960ebnoW+Wk+lMT0PsGfGjiMIRmtks2cfqbQuQYFdO/8go+DSE54ekAg==", + "version": "3.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.27.2.tgz", + "integrity": "sha512-z/OsrXbBY8PanemOHdtup1OlfdBmbc6dMfXqZ3pelH75z4n73JtPhVEqM/FJFdwP737fV1gU1nvMB17VtnyXKw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", "classnames": "^2.3.1" }, "peerDependencies": { @@ -8534,9 +8418,9 @@ } }, "node_modules/@wordpress/wordcount": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.42.13.tgz", - "integrity": "sha512-yapganGNO/9JjfWTcMNECjIOKlnLOJR2VTh4UFBL/lSi2GM1AE7bjnXsV2pD0H/3mwdhAomRCUV6BA3nG5UUfA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.50.0.tgz", + "integrity": "sha512-lRfIX3B9ha//bqsUihym2BnOiAsdDQr22vdy0wZIpm5G2tFvTddCKHy0YClf52IJK0z61WqbNuF9hrvzWWxL+g==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -8581,27 +8465,6 @@ "node": ">= 0.6" } }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -8973,9 +8836,9 @@ } }, "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, "node_modules/array-includes": { @@ -9302,12 +9165,28 @@ } }, "node_modules/axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { - "follow-redirects": "^1.14.7" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, "node_modules/axobject-query": { @@ -9719,15 +9598,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -10211,13 +10081,11 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", - "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dev": true, "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } @@ -10228,18 +10096,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -10409,21 +10265,6 @@ "semver": "^7.0.0" } }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -10623,9 +10464,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001549", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", - "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==", + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", "dev": true, "funding": [ { @@ -11254,9 +11095,9 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "node_modules/colors": { @@ -11452,25 +11293,17 @@ } }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, - "optional": true, "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -11660,10 +11493,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", - "deprecated": "core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.1.tgz", + "integrity": "sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -12251,6 +12083,12 @@ "node": "*" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -12581,13 +12419,13 @@ } }, "node_modules/deepsignal": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.6.tgz", - "integrity": "sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.4.0.tgz", + "integrity": "sha512-x0XUMT48s+xQRLc2fPFfxnYLCJ46vffw47OQ5NcHFzacOjfW5eA0NrEmI0bhQHL6MgUHkBVT4TIiWTVwzTEwpg==", "peerDependencies": { "@preact/signals": "^1.1.4", - "@preact/signals-core": "^1.3.1", - "@preact/signals-react": "^1.3.3", + "@preact/signals-core": "^1.5.1", + "@preact/signals-react": "^1.3.8 || ^2.0.0", "preact": "^10.16.0" }, "peerDependenciesMeta": { @@ -12605,206 +12443,6 @@ } } }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/default-browser/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-browser/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/default-browser/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -13188,16 +12826,10 @@ "node": ">=8" } }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, "node_modules/dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -13418,11 +13050,6 @@ "node": ">=4" } }, - "node_modules/downloadjs": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", - "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" - }, "node_modules/downshift": { "version": "6.1.12", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz", @@ -13595,9 +13222,9 @@ } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -13723,6 +13350,11 @@ "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-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -13955,9 +13587,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -13976,7 +13608,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -14016,9 +13648,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "27.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz", - "integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==", + "version": "27.6.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", + "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^5.10.0" @@ -14153,9 +13785,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "46.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz", - "integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==", + "version": "46.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.10.1.tgz", + "integrity": "sha512-x8wxIpv00Y50NyweDUpa+58ffgSAI5sqe+zcZh33xphD0AVh+1kqr1ombaTRb7Fhpove1zfUuujlX9DWWBP5ag==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.41.0", @@ -14166,13 +13798,13 @@ "esquery": "^1.5.0", "is-builtin-module": "^3.2.1", "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1" + "spdx-expression-parse": "^4.0.0" }, "engines": { "node": ">=16" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { @@ -14187,6 +13819,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", @@ -14239,23 +13881,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", - "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", + "eslint-config-prettier": "*", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -15103,24 +14746,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -16068,6 +15693,15 @@ "node": ">= 0.10" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -16103,9 +15737,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true, "funding": [ { @@ -16308,9 +15942,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "node_modules/fs.realpath": { @@ -18155,6 +17789,11 @@ "react-is": "^16.7.0" } }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -18798,6 +18437,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-locals": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-locals/-/import-locals-2.0.0.tgz", + "integrity": "sha512-1/bPE89IZhyf7dr5Pkz7b4UyVXy5pEt7PTEfye15UEn3AK8+2zwcDCfKk9Pwun4ltfhOSszOrReSsFcDKw/yoA==" + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -18968,12 +18612,12 @@ } }, "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/intl-messageformat": { @@ -19021,9 +18665,9 @@ "dev": true }, "node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "dev": true, "engines": { "node": ">= 10" @@ -19346,39 +18990,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container/node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-jpg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-2.0.0.tgz", @@ -20606,18 +20217,21 @@ } }, "node_modules/jest-dev-server": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-6.2.0.tgz", - "integrity": "sha512-ZWh8CuvxwjhYfvw4tGeftziqIvw/26R6AG3OTgNTQeXul8aZz48RQjDpnlDwnWX53jxJJl9fcigqIdSU5lYZuw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-9.0.2.tgz", + "integrity": "sha512-Zc/JB0IlNNrpXkhBw+h86cGrde/Mey52KvF+FER2eyrtYJTHObOwW7Iarxm3rPyTKby5+3Y2QZtl8pRz/5GCxg==", "dev": true, "dependencies": { "chalk": "^4.1.2", "cwd": "^0.10.0", "find-process": "^1.4.7", "prompts": "^2.4.2", - "spawnd": "^6.2.0", + "spawnd": "^9.0.2", "tree-kill": "^1.2.2", - "wait-on": "^6.0.1" + "wait-on": "^7.2.0" + }, + "engines": { + "node": ">=16" } }, "node_modules/jest-dev-server/node_modules/ansi-styles": { @@ -20679,9 +20293,9 @@ } }, "node_modules/jest-dev-server/node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -20700,22 +20314,22 @@ } }, "node_modules/jest-dev-server/node_modules/wait-on": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", - "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", "dev": true, "dependencies": { - "axios": "^0.25.0", - "joi": "^17.6.0", + "axios": "^1.6.1", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^7.5.4" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" } }, "node_modules/jest-diff": { @@ -22130,15 +21744,15 @@ } }, "node_modules/joi": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", - "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.0.tgz", + "integrity": "sha512-HSLsmSmXz+PV9PYoi3p7cgIbj06WnEBNT28n+bbBNcPZXZFqCzzvGqpTBPujx/Z0nh1+KNQPDrNgdmQ8dq0qYw==", "dev": true, "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.4", + "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, @@ -22744,6 +22358,16 @@ "node": ">=0.10" } }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -22776,9 +22400,9 @@ } }, "node_modules/lib0": { - "version": "0.2.87", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.87.tgz", - "integrity": "sha512-TbB63XJixvNToW2IHWAFsCJj9tVnajmwjE14p69i51Rx8byOQd2IP4ourE8v4d7vhyO++nVm1sQk3ePslfbucg==", + "version": "0.2.88", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz", + "integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -24331,12 +23955,12 @@ } }, "node_modules/memfs": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", - "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", "dev": true, "dependencies": { - "fs-monkey": "^1.0.3" + "fs-monkey": "^1.0.4" }, "engines": { "node": ">= 4.0.0" @@ -24464,21 +24088,21 @@ } }, "node_modules/mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "dependencies": { - "mime-db": "1.45.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -24603,10 +24227,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minimist-options": { "version": "4.1.0", @@ -24747,9 +24374,9 @@ "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" }, "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "dev": true, "engines": { "node": ">=10" @@ -24784,7 +24411,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, "funding": [ { "type": "github", @@ -26407,8 +26033,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -26600,7 +26225,6 @@ "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -27056,6 +26680,14 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-prefixwrap": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.44.0.tgz", + "integrity": "sha512-h9MJGaIvT5hnzFc7Vuo+2ulBw6ecmmfcd8SKKH2TziUzcIA04gUoXIbptuM+tR+htmsQIKNEluiQlmCQ2p5a2g==", + "peerDependencies": { + "postcss": "*" + } + }, "node_modules/postcss-reduce-initial": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", @@ -27356,16 +26988,26 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-urlrebase": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.3.0.tgz", + "integrity": "sha512-LOFN43n1IewKriXiypMNNinXeptttSyGGRLPbBMdQzuTvvCEo5mz/gG06y/HqrkN7p3ayHQf2R2bTBv639FOaQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.3.0" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/preact": { - "version": "10.19.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.1.tgz", - "integrity": "sha512-ZSsUr6EFlwWH0btdXMj6+X+hJAZ1v+aUzKlfwBGokKB1ZO6Shz+D16LxQhM8f+E/UgkKbVe2tsWXtGTUMCkGpQ==", + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -27447,12 +27089,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -27495,6 +27131,11 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -27980,9 +27621,9 @@ "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-refresh": { "version": "0.14.0", @@ -27993,30 +27634,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-remove-scroll-bar": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", @@ -28805,124 +28422,6 @@ "node": ">=0.10.0" } }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -29345,11 +28844,12 @@ "dev": true }, "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, "dependencies": { + "@types/node-forge": "^1.3.0", "node-forge": "^1" }, "engines": { @@ -29675,6 +29175,15 @@ "node": ">=0.10.0" } }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/showdown": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz", @@ -29833,14 +29342,14 @@ } }, "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dev": true, "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" }, "engines": { "node": ">= 10" @@ -30198,7 +29707,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -30265,14 +29773,28 @@ "dev": true }, "node_modules/spawnd": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-6.2.0.tgz", - "integrity": "sha512-qX/I4lQy4KgVEcNle0kuc4FxFWHISzBhZW1YemPfwmrmQjyZmfTK/OhBKkhrD2ooAaFZEm1maEBLE6/6enwt+g==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-9.0.2.tgz", + "integrity": "sha512-nl8DVHEDQ57IcKakzpjanspVChkMpGLuVwMR/eOn9cXE55Qr6luD2Kn06sA0ootRMdgrU4tInN6lA6ohTNvysw==", "dev": true, "dependencies": { - "exit": "^0.1.2", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "tree-kill": "^1.2.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/spawnd/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/spdx-correct": { @@ -30338,9 +29860,9 @@ } }, "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -31461,13 +30983,13 @@ "dev": true }, "node_modules/synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -31821,18 +31343,6 @@ "ms": "^2.1.1" } }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -31921,9 +31431,9 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "engines": { "node": ">=6" @@ -31950,14 +31460,6 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, - "node_modules/traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -32002,9 +31504,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -32072,11 +31574,6 @@ "node": "*" } }, - "node_modules/turbo-combine-reducers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/turbo-combine-reducers/-/turbo-combine-reducers-1.0.2.tgz", - "integrity": "sha512-gHbdMZlA6Ym6Ur5pSH/UWrNQMIM9IqTH6SoL1DbHpqEdQ8i+cFunSmSlFykPt0eGQwZ4d/XTHOl74H0/kFBVWw==" - }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -32413,15 +31910,6 @@ "node": ">=0.10.0" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -32582,19 +32070,6 @@ } } }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/use-lilius": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/use-lilius/-/use-lilius-2.0.3.tgz", @@ -32858,15 +32333,6 @@ "node": ">= 6" } }, - "node_modules/wait-on/node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wait-on/node_modules/rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -32921,6 +32387,12 @@ "node": ">= 8" } }, + "node_modules/web-vitals": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.1.tgz", + "integrity": "sha512-xQ9lvIpfLxUj0eSmT79ZjRoU5wIRfIr7pNukL7ZE4EcWZSmfZQqOlhuAGfkVa3EFmzPHZhWhXfm2i5ys+THVPg==", + "dev": true + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -32975,20 +32447,23 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", - "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", + "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "html-escaper": "^2.0.2", + "is-plain-object": "^5.0.0", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { @@ -32998,55 +32473,6 @@ "node": ">= 10.13.0" } }, - "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/webpack-bundle-analyzer/node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -33056,66 +32482,55 @@ "node": ">= 10" } }, - "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=10" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^10.0.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -33125,12 +32540,12 @@ } }, "node_modules/webpack-cli/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==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { - "node": ">= 10" + "node": ">=14" } }, "node_modules/webpack-cli/node_modules/cross-spawn": { @@ -33156,6 +32571,18 @@ "node": ">=8" } }, + "node_modules/webpack-cli/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/webpack-cli/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -33234,37 +32661,16 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "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": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -33275,9 +32681,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -33286,7 +32692,7 @@ "@types/serve-index": "^1.9.1", "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", + "@types/ws": "^8.5.5", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -33299,6 +32705,7 @@ "html-entities": "^2.3.2", "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", "open": "^8.0.9", "p-retry": "^4.5.0", "rimraf": "^3.0.2", @@ -33308,7 +32715,7 @@ "sockjs": "^0.3.24", "spdy": "^4.0.2", "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" + "ws": "^8.13.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" @@ -33324,6 +32731,9 @@ "webpack": "^4.37.0 || ^5.0.0" }, "peerDependenciesMeta": { + "webpack": { + "optional": true + }, "webpack-cli": { "optional": true } @@ -33379,15 +32789,15 @@ } }, "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "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": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 12.13.0" @@ -33398,9 +32808,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", - "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -33437,12 +32847,13 @@ } }, "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -33678,9 +33089,9 @@ "integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang==" }, "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, "node_modules/wrap-ansi": { @@ -33719,7 +33130,7 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -33832,13 +33243,13 @@ } }, "node_modules/y-webrtc": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.5.tgz", - "integrity": "sha512-ZyBNvTI5L28sQ2PQI0T/JvyWgvuTq05L21vGkIlcvNLNSJqAaLCBJRe3FHEqXoaogqWmRcEAKGfII4ErNXMnNw==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.6.tgz", + "integrity": "sha512-1kZ4YYwksFZi8+l8mTebVX9vW6Q5MnqxMkvNU700X5dBE38usurt/JgeXSIQRpK3NwUYYb9y63Jn9FMpMH6/vA==", "dependencies": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", - "y-protocols": "^1.0.5" + "y-protocols": "^1.0.6" }, "bin": { "y-webrtc-signaling": "bin/server.js" @@ -33851,7 +33262,31 @@ "url": "https://github.com/sponsors/dmonad" }, "optionalDependencies": { - "ws": "^7.2.0" + "ws": "^8.14.2" + }, + "peerDependencies": { + "yjs": "^13.6.8" + } + }, + "node_modules/y-webrtc/node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/y18n": { @@ -33910,11 +33345,11 @@ } }, "node_modules/yjs": { - "version": "13.6.8", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.8.tgz", - "integrity": "sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==", + "version": "13.6.11", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.11.tgz", + "integrity": "sha512-FvRRJKX9u270dOLkllGF/UDCWwmIv2Z+ucM4v1QO1TuxdmoiMnSUXH1HAcOKOrkBEhQtPTkxep7tD2DrQB+l0g==", "dependencies": { - "lib0": "^0.2.74" + "lib0": "^0.2.86" }, "engines": { "node": ">=16.0.0", @@ -33956,24 +33391,24 @@ } }, "@ariakit/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.2.7.tgz", - "integrity": "sha512-Hs0N1EMYq88WW4v9xnSIHNR38TvbQuoUX6FYFmeLCZSTIXQBiET7lr1DQXwOOmdEtRtlxQ5HsxbTkxeOkPv+eg==" + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.11.tgz", + "integrity": "sha512-+MnOeqnA4FLI/7vqsZLbZQHHN4ofd9kvkNjz44fNi0gqmD+ZbMWiDkFAvZII75dYnxYw5ZPpWjA4waK22VBWig==" }, "@ariakit/react": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.2.12.tgz", - "integrity": "sha512-4rAgMyUURHW78EKgRCanhyRUtsiYCOxO65BBHF4mg3tZsDeOvu9kBG5IAXX8mUgakTcyr0EKXuOtGThaj7gobA==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.14.tgz", + "integrity": "sha512-h71BPMZ2eW+E2ESbdYxSAEMR1DozYzd5eHE5IOzGd9Egi5u7EZxqmuW4CXVXZ1Y6vbaDMV3SudgPh7iHS/ArFw==", "requires": { - "@ariakit/react-core": "0.2.12" + "@ariakit/react-core": "0.3.14" } }, "@ariakit/react-core": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.2.12.tgz", - "integrity": "sha512-3KSKlX10nnhCvjsbPW0CAnqG+6grryfwnMkeJJ/h34FSV7hEfUMexmIjKBVZyfBG08Xj8NjSK8kkx9c3ChkXeA==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.14.tgz", + "integrity": "sha512-16Qj6kDPglpdWtU5roY9q+G66naOjauTY5HvUIaL2aLY0187ATaRrABIKoMMzTtJyhvsud4jFlzivz+/zCQ8yw==", "requires": { - "@ariakit/core": "0.2.7", + "@ariakit/core": "0.3.11", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.2.0" } @@ -36495,71 +35930,11 @@ "fastq": "^1.6.0" } }, - "@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true - }, - "open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "requires": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - } - } + "@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true }, "@playwright/test": { "version": "1.32.0", @@ -36641,9 +36016,9 @@ } }, "@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", "dev": true }, "@popperjs/core": { @@ -36652,17 +36027,17 @@ "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, "@preact/signals": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.2.1.tgz", - "integrity": "sha512-hRPvp1C2ooDzOHqfnhdpHgoIFDbYFAXLhoid3+jSItuPPD/J0r/UsiWKv/8ZO/oEhjRaP0M5niuRYsWqmY2GEA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.2.2.tgz", + "integrity": "sha512-ColCqdo4cRP18bAuIR4Oik5rDpiyFtPIJIygaYPMEAwTnl4buWkBOflGBSzhYyPyJfKpkwlekrvK+1pzQ2ldWw==", "requires": { "@preact/signals-core": "^1.4.0" } }, "@preact/signals-core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.0.tgz", - "integrity": "sha512-U2diO1Z4i1n2IoFgMYmRdHWGObNrcuTRxyNEn7deSq2cru0vj0583HYQZHsAqcs7FE+hQyX3mjIV7LAfHCvy8w==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz", + "integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==" }, "@puppeteer/browsers": { "version": "0.5.0", @@ -36801,27 +36176,6 @@ "@babel/runtime": "^7.13.10" } }, - "@radix-ui/react-arrow": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz", - "integrity": "sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.2" - } - }, - "@radix-ui/react-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.2.tgz", - "integrity": "sha512-s8WdQQ6wNXpaxdZ308KSr8fEWGrg4un8i4r/w7fhiS4ElRNjk5rRcl0/C6TANG2LvLOGIxtzo/jAg6Qf73TEBw==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-slot": "1.0.1" - } - }, "@radix-ui/react-compose-refs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", @@ -36934,42 +36288,6 @@ } } }, - "@radix-ui/react-direction": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz", - "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==", - "requires": { - "@babel/runtime": "^7.13.10" - } - }, - "@radix-ui/react-dismissable-layer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz", - "integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-escape-keydown": "1.0.2" - } - }, - "@radix-ui/react-dropdown-menu": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.4.tgz", - "integrity": "sha512-y6AT9+MydyXcByivdK1+QpjWoKaC7MLjkS/cH1Q3keEyMvDkiY85m8o2Bi6+Z1PPUlCsMULopxagQOSfN0wahg==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-menu": "2.0.4", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.0" - } - }, "@radix-ui/react-focus-guards": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", @@ -36978,17 +36296,6 @@ "@babel/runtime": "^7.13.10" } }, - "@radix-ui/react-focus-scope": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz", - "integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0" - } - }, "@radix-ui/react-id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", @@ -36998,83 +36305,6 @@ "@radix-ui/react-use-layout-effect": "1.0.0" } }, - "@radix-ui/react-menu": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.4.tgz", - "integrity": "sha512-mzKR47tZ1t193trEqlQoJvzY4u9vYfVH16ryBrVrCAGZzkgyWnMQYEZdUkM7y8ak9mrkKtJiqB47TlEnubeOFQ==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-collection": "1.0.2", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-dismissable-layer": "1.0.3", - "@radix-ui/react-focus-guards": "1.0.0", - "@radix-ui/react-focus-scope": "1.0.2", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-popper": "1.1.1", - "@radix-ui/react-portal": "1.0.2", - "@radix-ui/react-presence": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-roving-focus": "1.0.3", - "@radix-ui/react-slot": "1.0.1", - "@radix-ui/react-use-callback-ref": "1.0.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - } - }, - "@radix-ui/react-popper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.1.tgz", - "integrity": "sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==", - "requires": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "0.7.2", - "@radix-ui/react-arrow": "1.0.2", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-layout-effect": "1.0.0", - "@radix-ui/react-use-rect": "1.0.0", - "@radix-ui/react-use-size": "1.0.0", - "@radix-ui/rect": "1.0.0" - }, - "dependencies": { - "@floating-ui/core": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", - "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==" - }, - "@floating-ui/dom": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz", - "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==", - "requires": { - "@floating-ui/core": "^0.7.3" - } - }, - "@floating-ui/react-dom": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz", - "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==", - "requires": { - "@floating-ui/dom": "^0.5.3", - "use-isomorphic-layout-effect": "^1.1.1" - } - } - } - }, - "@radix-ui/react-portal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz", - "integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.2" - } - }, "@radix-ui/react-presence": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", @@ -37085,41 +36315,6 @@ "@radix-ui/react-use-layout-effect": "1.0.0" } }, - "@radix-ui/react-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", - "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.1" - } - }, - "@radix-ui/react-roving-focus": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.3.tgz", - "integrity": "sha512-stjCkIoMe6h+1fWtXlA6cRfikdBzCLp3SnVk7c48cv/uy3DTGoXhN76YaOYUJuy3aEDvDIKwKR5KSmvrtPvQPQ==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-collection": "1.0.2", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-id": "1.0.0", - "@radix-ui/react-primitive": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-controllable-state": "1.0.0" - } - }, - "@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" - } - }, "@radix-ui/react-use-callback-ref": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", @@ -37137,15 +36332,6 @@ "@radix-ui/react-use-callback-ref": "1.0.0" } }, - "@radix-ui/react-use-escape-keydown": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz", - "integrity": "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.0" - } - }, "@radix-ui/react-use-layout-effect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", @@ -37154,32 +36340,6 @@ "@babel/runtime": "^7.13.10" } }, - "@radix-ui/react-use-rect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz", - "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/rect": "1.0.0" - } - }, - "@radix-ui/react-use-size": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz", - "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==", - "requires": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.0" - } - }, - "@radix-ui/rect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz", - "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==", - "requires": { - "@babel/runtime": "^7.13.10" - } - }, "@react-spring/animated": { "version": "9.7.1", "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.1.tgz", @@ -37833,9 +36993,9 @@ } }, "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "requires": { "@types/connect": "*", @@ -37843,27 +37003,27 @@ } }, "@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "requires": { "@types/node": "*" } }, "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "requires": { "@types/node": "*" } }, "@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "requires": { "@types/express-serve-static-core": "*", @@ -37897,9 +37057,9 @@ "dev": true }, "@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "requires": { "@types/body-parser": "*", @@ -37909,14 +37069,15 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.33", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", - "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", - "@types/range-parser": "*" + "@types/range-parser": "*", + "@types/send": "*" } }, "@types/glob": { @@ -37938,10 +37099,26 @@ "@types/node": "*" } }, + "@types/gradient-parser": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.3.tgz", + "integrity": "sha512-XDbrTSBlQV9nxE1GiDL3FaOPy4G/KaJkhDutBX48Kg8CYZMBARyyDFGCWfWJn4pobmInmwud1xxH7VJMAr0CKQ==" + }, + "@types/highlight-words-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", + "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==" + }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "@types/http-proxy": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", - "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", "dev": true, "requires": { "@types/node": "*" @@ -38017,9 +37194,9 @@ "dev": true }, "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "@types/minimatch": { @@ -38042,8 +37219,16 @@ "@types/node": { "version": "14.14.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", - "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", - "dev": true + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" + }, + "@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/normalize-package-data": { "version": "2.4.1", @@ -38069,15 +37254,15 @@ "optional": true }, "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", "dev": true }, "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "@types/react": { @@ -38110,34 +37295,53 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "@types/semver": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", - "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "requires": { "@types/express": "*" } }, "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", "dev": true, "requires": { + "@types/http-errors": "*", "@types/mime": "*", "@types/node": "*" } }, + "@types/simple-peer": { + "version": "9.11.8", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.8.tgz", + "integrity": "sha512-rvqefdp2rvIA6wiomMgKWd2UZNPe6LM2EV5AuY3CPQJF+8TbdrL5TjYdMf0VAjGczzlkH4l1NjDkihwbj3Xodw==", + "requires": { + "@types/node": "*" + } + }, "@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "requires": { "@types/node": "*" @@ -38226,9 +37430,9 @@ } }, "@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "requires": { "@types/node": "*" @@ -38260,16 +37464,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz", - "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", + "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/type-utils": "6.11.0", - "@typescript-eslint/utils": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/type-utils": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -38279,83 +37483,104 @@ } }, "@typescript-eslint/parser": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz", - "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", + "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz", - "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", + "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", "dev": true, "requires": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0" + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1" } }, "@typescript-eslint/type-utils": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz", - "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", + "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.11.0", - "@typescript-eslint/utils": "6.11.0", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/utils": "6.19.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz", - "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", + "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz", - "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", + "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", "dev": true, "requires": { - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/visitor-keys": "6.11.0", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@typescript-eslint/utils": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz", - "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", + "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.11.0", - "@typescript-eslint/types": "6.11.0", - "@typescript-eslint/typescript-estree": "6.11.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz", - "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", + "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", "dev": true, "requires": { - "@typescript-eslint/types": "6.11.0", + "@typescript-eslint/types": "6.19.1", "eslint-visitor-keys": "^3.4.1" }, "dependencies": { @@ -38527,78 +37752,75 @@ } }, "@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true }, "@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true }, "@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true }, "@wordpress/a11y": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.42.13.tgz", - "integrity": "sha512-57KH89dbt8ipimoBGezKQHLvwSsJHW/W4HpvzZFqnPHvnlNNYoVC9UuqiBavxdB2WkzMPmNYFKsM7kOInEdyTA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.50.0.tgz", + "integrity": "sha512-eQiPGnxqiL1LgnHztFG0RGSFZ5phwR8B8Fr4lbJsFalsc9R/tOcjewvf2KN0yi2UlRA5ssAeiTP+tYmeAqtOHQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/dom-ready": "^3.42.13", - "@wordpress/i18n": "^4.42.13" + "@wordpress/dom-ready": "^3.50.0", + "@wordpress/i18n": "^4.50.0" } }, "@wordpress/annotations": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-2.42.13.tgz", - "integrity": "sha512-S/bCU8AOYzVMKBSMMtmoB9Dw1kiOS3KNfFzdCLpiyzNhfqXPWp7ciM5WJTHJRdzobeTOh1CIJ6x5WHhBqO/t8w==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-2.50.0.tgz", + "integrity": "sha512-E9cu8xuGvIRw3LVtuS+XSzAXVBF41sgvxpVJAz/5FEibzxUHPy8flu5tTKf+mi4WGZxC4AJGNP1bhZRj7cynZQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^9.12.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/rich-text": "^6.19.13", + "@wordpress/data": "^9.20.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/rich-text": "^6.27.0", "rememo": "^4.0.2", "uuid": "^9.0.1" } }, "@wordpress/api-fetch": { - "version": "6.39.13", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.39.13.tgz", - "integrity": "sha512-DFaiNq5bEOVqYDpcqXqdxjyBDboeElma6e7FNSX2APVZZt/8xxeb4eI9X0877i6B15G5blyHsjSit5rq88iqtg==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.47.0.tgz", + "integrity": "sha512-NA/jWDXoVtJmiVBYhlxts2UrgKJpJM+zTGzLCfRQCZUzpJYm3LonB8x+uCQ78nEyxCY397Esod3jnbquYjOr0Q==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.42.13", - "@wordpress/url": "^3.43.13" + "@wordpress/i18n": "^4.50.0", + "@wordpress/url": "^3.51.0" } }, "@wordpress/autop": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.42.13.tgz", - "integrity": "sha512-vQjEvJaYZ5OyMJ6IdbVL/RUqOy/VTe6E1BCkdRW8amJTm8koTkbBbOFWSsVoXh6jFoq2mDAqC7Tt2vL0yMA4rQ==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.50.0.tgz", + "integrity": "sha512-4E0vq2MvSOVDKXs4OulIbTdKU6S5O9QjT4qc63rAd0uiKGBYV12ViPzmwbJ6k38zOO0PKdcwlVCj55Gq4aoPDw==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/babel-plugin-import-jsx-pragma": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.26.0.tgz", - "integrity": "sha512-XZCTBqEmOlM87/6wkgtHhnHaj8cJPOY5avyjKtMDwoBbcXAmHUknbphZG7KEWIiVIilyxKyHnsTxjTplkqTtCQ==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.33.0.tgz", + "integrity": "sha512-CjzruFKWgzU/mO/nnQJ2l9UlzZQpqS60UC6l2vNdJ9oD2nKHR5Oou6kNic3QhWDVJrBf2JUiJJ0TC280bykXmA==", "dev": true }, "@wordpress/babel-preset-default": { - "version": "7.26.13", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.26.13.tgz", - "integrity": "sha512-kW9sg3lwbrhYzVR24n7cUEC2Sx1Pj4UNnITbXqVmxnVok0CK7IkvstMlbtLDbULh9o2f92OPNMwdAStErEjT7g==", + "version": "7.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.34.0.tgz", + "integrity": "sha512-yjFOllyTktFHtcIEgU3ghXBn8lItzr5mPLf0xdSpe0cHceFYL1hT1oprhgRL+olZweaO96Yfm0qUCCKQfJBWsA==", "dev": true, "requires": { "@babel/core": "^7.16.0", @@ -38607,94 +37829,94 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/babel-plugin-import-jsx-pragma": "^4.25.13", - "@wordpress/browserslist-config": "^5.25.13", - "@wordpress/element": "^5.19.13", - "@wordpress/warning": "^2.42.13", - "browserslist": "^4.21.9", - "core-js": "^3.31.0" + "@wordpress/babel-plugin-import-jsx-pragma": "^4.33.0", + "@wordpress/browserslist-config": "^5.33.0", + "@wordpress/warning": "^2.50.0", + "browserslist": "^4.21.10", + "core-js": "^3.31.0", + "react": "^18.2.0" } }, "@wordpress/base-styles": { - "version": "4.34.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.34.0.tgz", - "integrity": "sha512-LYiNFWl+6yJDVQ7hSNJu2kVuM1p3C3aTB769lXnMSxi3gubzxqjZqz9i9XQ3UjO9EFiDSvgbOXa8YhvTUfNnkQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.41.0.tgz", + "integrity": "sha512-MjPAZeAqvyskDXDp2wGZ0DjtYOQLOydI1WqVIZS4wnIdhsQWQD//VMeXgLrcmCzNyQg+iKTx3o+BzmXVTOD0+w==", "dev": true }, "@wordpress/blob": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.42.13.tgz", - "integrity": "sha512-W5TaJK9Vl8LInjdxRRq5hE08r34JKybVjm7UuSIPOppNErLu9g6edcGHsv3b/7f5so3TcSnPsLfDkPgwSFTjXA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.50.0.tgz", + "integrity": "sha512-QvBhsW9WPdsOJhJ0BxzZ83i+cH/gAdjJ1iHY4Rkb02qbZEz4jhdvucGQf2oVnWwvAsFiFPKWk7CwAM5XjoahCA==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/block-directory": { - "version": "4.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-4.19.16.tgz", - "integrity": "sha512-7YOqeZt8ExyMidbblzht7x5jnfpZVD6N69VuDrvdlB/8eB7gl62tKZdNXHwWoZccSWJb+xUTZL01k2HpJulcPQ==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-4.27.2.tgz", + "integrity": "sha512-EblzP8BbkqAeFomH3/L9wdmbz1iw0n2siBMdZNZKHifwWv0iLFQfZlMZo4ImgWwC4YE3is7zSGpkWJ1kHMbj7w==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/edit-post": "^7.19.16", - "@wordpress/editor": "^13.19.14", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/plugins": "^6.10.14", - "@wordpress/url": "^3.43.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/edit-post": "^7.27.2", + "@wordpress/editor": "^13.27.2", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/plugins": "^6.18.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2" } }, "@wordpress/block-editor": { - "version": "12.10.14", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.10.14.tgz", - "integrity": "sha512-x56FPZZfJPk/Vd1aKIdpBIllrUuAVgwom+mYH0OohCmUzCBp1Eg8Urg5nshZpiLXpHt2dXycQCLu2Mpb+YpOJw==", + "version": "12.18.2", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.18.2.tgz", + "integrity": "sha512-LDZIcp5Bl2FCyfkf07XgfM0kzY+AYhyTS4kt2U4GRSeUey79AM+GIYXb8TM2Y68B09HP/rpntBW4e/cBqjHfjw==", "requires": { "@babel/runtime": "^7.16.0", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/blocks": "^12.19.13", - "@wordpress/commands": "^0.13.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/shortcode": "^3.42.13", - "@wordpress/style-engine": "^1.25.13", - "@wordpress/token-list": "^2.42.13", - "@wordpress/url": "^3.43.13", - "@wordpress/warning": "^2.42.13", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/style-engine": "^1.33.1", + "@wordpress/token-list": "^2.50.0", + "@wordpress/url": "^3.51.0", + "@wordpress/warning": "^2.50.0", + "@wordpress/wordcount": "^3.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -38702,50 +37924,54 @@ "diff": "^4.0.2", "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", - "inherits": "^2.0.3", + "memize": "^2.1.0", + "postcss": "^8.4.21", + "postcss-prefixwrap": "^1.41.0", + "postcss-urlrebase": "^1.0.0", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", "rememo": "^4.0.2", - "remove-accents": "^0.5.0", - "traverse": "^0.6.6" + "remove-accents": "^0.5.0" } }, "@wordpress/block-library": { - "version": "8.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.19.16.tgz", - "integrity": "sha512-6NqTHjEYk3X+jzw6JS3pOgVYl2HPlr0iAI3Ch9sdOxozAm1+VrE5DKeM//rf9QpR7wWJ6je4F/eNjZ2WJIYTfw==", + "version": "8.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.27.2.tgz", + "integrity": "sha512-Wabc1nmCMuTr/BgS63iHaQYtvfVO9Z30SwLaMVLHwGe7Hrvtb19pSOwKb/PIuoiWrlqJ/sZEZPXFENAJB5FVYA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/autop": "^3.42.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interactivity": "^2.3.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/primitives": "^3.40.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/server-side-render": "^4.19.14", - "@wordpress/url": "^3.43.13", - "@wordpress/viewport": "^5.19.13", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/autop": "^3.50.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interactivity": "^4.0.1", + "@wordpress/interactivity-router": "^1.0.1", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/server-side-render": "^4.27.1", + "@wordpress/url": "^3.51.0", + "@wordpress/viewport": "^5.27.0", + "@wordpress/wordcount": "^3.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -38758,40 +37984,41 @@ } }, "@wordpress/block-serialization-default-parser": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.42.13.tgz", - "integrity": "sha512-+ggjHxrjbpIwknsfKy18HXOVGWHeFykxlElE9dYVspJvr734mMMTQuIeL5WM+vZUy5NWv0oHF0VykX0MHyy60w==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.50.0.tgz", + "integrity": "sha512-ihf2vr+w2zHBOvYTPQZXDiR2IMvso8yJJtzKIHA2ZEgVQ+VVLb4X86n34hfWXtPA3i2KDW+t1WCtq56aNq3Zag==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/blocks": { - "version": "12.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.19.13.tgz", - "integrity": "sha512-KdNcYb5Cr4sgzOkJM+KpPZeLLFr8e06CkRDp0EQk7VGSsoScXpqIcMEtMcKNQp1XPuJ6npMr/BacC5qNjyHA1A==", + "version": "12.27.1", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.27.1.tgz", + "integrity": "sha512-9uZtuTG6+fiFV2bLn8b1gzv4BgMpBu4SDQGnvzc5f9U5GL5oYns3PP8vXDOwM2cK1DEmqPsohQWhRnz8QYZDtw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/autop": "^3.42.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/block-serialization-default-parser": "^4.42.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/shortcode": "^3.42.13", + "@wordpress/autop": "^3.50.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-serialization-default-parser": "^4.50.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/shortcode": "^3.50.0", "change-case": "^4.1.2", "colord": "^2.7.0", - "deepmerge": "^4.3.0", "fast-deep-equal": "^3.1.3", "hpq": "^1.3.0", "is-plain-object": "^5.0.0", "memize": "^2.1.0", + "react-is": "^18.2.0", "rememo": "^4.0.2", "remove-accents": "^0.5.0", "showdown": "^1.9.1", @@ -38800,35 +38027,35 @@ } }, "@wordpress/browserslist-config": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.26.0.tgz", - "integrity": "sha512-rpkxAnPOc4HuxKZBwZ1iV1oC0Rd21azzBDyS8OoVUW6V8DAv4eYfHNFGkyds7Z+nI6dI15Rl7xJYJhHJKVaJvg==", + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.33.0.tgz", + "integrity": "sha512-dv1ZlpqGk8gaSBJPP/Z/1uOuxjtP0EBsHVKInLRu6FWLTJkK8rnCeC3xJT3/2TtJ0rasLC79RoytfhXTOODVwg==", "dev": true }, "@wordpress/commands": { - "version": "0.13.14", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.13.14.tgz", - "integrity": "sha512-aSOuRbsr+YYFvRbkXaubHdlAtf/xpG1mUWXEw9VMWCag77hiK6vk04Xb3N8ad8eo8am0N/iRgn8V8IS4LyBTyA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.21.0.tgz", + "integrity": "sha512-MzMUGCT9cQXto1jrA5lHAtnieTyAhcuNIxfyxlcE+316KNQfbyD8bc7KOzSV2sxXD/rfHuCxvHjfomFyyP+4kA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.8.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/private-apis": "^0.24.13", + "@wordpress/components": "^25.16.0", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/private-apis": "^0.32.0", "classnames": "^2.3.1", "cmdk": "^0.2.0", "rememo": "^4.0.2" } }, "@wordpress/components": { - "version": "25.8.14", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.8.14.tgz", - "integrity": "sha512-wRQSRlLXsL4bEd1JhCQPSdIb0bO4WDAloQufeyIbXUIK9CDgN/jmkv+vrgKrpP3Nqu1sBAFzW1qd9WEXfSBgXw==", + "version": "25.16.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.16.0.tgz", + "integrity": "sha512-voQuMsO5JbH+JW33TnWurwwvpSb8IQ4XU5wyVMubX4TUwadt+/2ToNJbZIDXoaJPei7vbM81Ft+pH+zGlN8CyA==", "requires": { - "@ariakit/react": "^0.2.12", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -38837,25 +38064,26 @@ "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", "@floating-ui/react-dom": "^2.0.1", - "@radix-ui/react-dropdown-menu": "2.0.4", + "@types/gradient-parser": "0.1.3", + "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.2.24", - "@wordpress/a11y": "^3.42.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/primitives": "^3.40.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/warning": "^2.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/warning": "^2.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -38880,63 +38108,64 @@ } }, "@wordpress/compose": { - "version": "6.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.19.13.tgz", - "integrity": "sha512-3HDdccND+EoEr7tHQ75eCDh07e5TdFh0KFIdWGweq9gU5Z/tssRW8QEyU9J+xEz+DTL/hvFilQ681f58eUZi1g==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.27.0.tgz", + "integrity": "sha512-jbEQQ2znRyJTwUNR4m5BKaDyIsuK9TMZx0SKqP+FTfGqT3y7scOnQrHpK0kZdPji++/1cBbn3gSPBLCEmtmHRw==", "requires": { "@babel/runtime": "^7.16.0", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/priority-queue": "^2.42.13", - "@wordpress/undo-manager": "^0.2.13", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/priority-queue": "^2.50.0", + "@wordpress/undo-manager": "^0.10.0", "change-case": "^4.1.2", - "clipboard": "^2.0.8", + "clipboard": "^2.0.11", "mousetrap": "^1.6.5", "use-memo-one": "^1.1.1" } }, "@wordpress/core-commands": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.11.14.tgz", - "integrity": "sha512-f2DA9lUji96OC5UD85Gbv2vz14R0TR+FSXzXAa68F/EBPFkiaxs2huhruhRvZKbasxugk/vjTBbQuwZ8rinROA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.19.2.tgz", + "integrity": "sha512-9ewP1fxB8MB5u15zMZBfShgGN2qJl+fBXCWR9MXB3gi8gA/Kd600W5I/jh2nLJuCRou09SsRzI6s+ihnir/V4A==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/commands": "^0.13.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/router": "^0.11.13", - "@wordpress/url": "^3.43.13" + "@wordpress/block-editor": "^12.18.2", + "@wordpress/commands": "^0.21.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/router": "^0.19.0", + "@wordpress/url": "^3.51.0" } }, "@wordpress/core-data": { - "version": "6.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.19.14.tgz", - "integrity": "sha512-wdstu/qMBKwXnFRX4wMeTkxvHsOgbXm7ZJ0Lgtj+jE86O086Ook7suxacOdMcCaAKNCfMqoGBHtjsNQk3SWE1Q==", + "version": "6.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.27.2.tgz", + "integrity": "sha512-Jsy+vW/izrd/T36D/4b266ScobCezNYX2Me/clCmHGB4eRW3drXZPbMnWZLNEDagYr87sQcM1Namasb69dnDhA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/sync": "^0.4.13", - "@wordpress/undo-manager": "^0.2.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/sync": "^0.12.0", + "@wordpress/undo-manager": "^0.10.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -38946,126 +38175,143 @@ } }, "@wordpress/customize-widgets": { - "version": "4.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-4.19.16.tgz", - "integrity": "sha512-UK4RrEBFwdn8WcY7qXXbRcncuWXLMpB9gjiBVhwPmM5m1//A0wsOQu2kAkZeACuhYoEJ/N6g4yZh2ZnldJVR3w==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-4.27.2.tgz", + "integrity": "sha512-zq/PacEqW8eMX6LKeMHn39JNU2ZJ3GiCH3+oOeI3eewN8/aGrtJJh1btSL0liLTDXo6dqnQ8AXHjGu9/J/XDSg==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/widgets": "^3.19.14", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/widgets": "^3.27.2", "classnames": "^2.3.1", "fast-deep-equal": "^3.1.3" } }, "@wordpress/data": { - "version": "9.12.13", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.12.13.tgz", - "integrity": "sha512-8SIsPFrnQ1LIZRWseOF+9uQ9thy8oB7NSOq+bkRCo+qldagooBTZUFp8Y++evFbPOotmTy6XGSPYf7HV9qBHVw==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.20.0.tgz", + "integrity": "sha512-3cm2te6NUj/X1zzmRO+WhueCanjocniX6sJFVzkg5mGXme6wFI8iSOnGPKlMkGcZGd0fVei1ydBKaIUMjrPBTQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.19.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/is-shallow-equal": "^4.42.13", - "@wordpress/priority-queue": "^2.42.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/redux-routine": "^4.42.13", + "@wordpress/compose": "^6.27.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/priority-queue": "^2.50.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/redux-routine": "^4.50.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", "redux": "^4.1.2", "rememo": "^4.0.2", - "turbo-combine-reducers": "^1.0.2", "use-memo-one": "^1.1.1" } }, "@wordpress/data-controls": { - "version": "3.11.13", - "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-3.11.13.tgz", - "integrity": "sha512-BW7yBPePnS5SVMVTTWeHG1U4RwV4X46NVOvX4/Vvq8CBjLmvqbiXZZxLMYI4xBi1y6+XRDjORHXP3WMJzwTdEg==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-3.19.0.tgz", + "integrity": "sha512-ceUK8kB8r8s8XFYlYWGVLuaoDJx5IAXND6q7B6MX1gKndqnSNi1766Q9iAEwOT9eVMai0lDLNq7mdK2ktVh4bw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13" + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0" + } + }, + "@wordpress/dataviews": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-0.4.1.tgz", + "integrity": "sha512-9ZTP5l9lyLMK95uEuAbOkILPIa2XvYxm2qa5Yo6SEUJbKnOVGCGH1fcNX1GuzHHrJwclYA3TeGgMaYoXpudjjw==", + "requires": { + "@babel/runtime": "^7.16.0", + "@wordpress/a11y": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "classnames": "^2.3.1", + "remove-accents": "^0.5.0" } }, "@wordpress/date": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.42.13.tgz", - "integrity": "sha512-SrJL7WbnQwSmogyNiFA+ZKNuECPvneCZOVzC/76DIV7seVDbpdJky/3UAkQLMgvYzym5PK3A8vkENPgAykrh3g==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.50.0.tgz", + "integrity": "sha512-FhfaG6YRXWmni66RjwhCB7rQNlLJ05+qTa/jXrj2UNWDNv/sfZ6Ky+b/rKrrUnLaIs9pGiW1195cSxsAS4EY3w==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.42.13", + "@wordpress/deprecated": "^3.50.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" } }, "@wordpress/dependency-extraction-webpack-plugin": { - "version": "4.25.13", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-4.25.13.tgz", - "integrity": "sha512-ke3CkU9wWgMpAsf5E1zG7aN/pr9P3qdDaIOgU2kXbjSLxrbhgBeK4mCgT/uxCJu0uqaieYkZWRcNmxXKMbF9hw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.1.0.tgz", + "integrity": "sha512-W2W+9JNAaGirAtGDSf83pjEKb63DLhgpJGgvMOpEPoRPtucgO6CCm3uMoNkJTpKoxJQ2tSZEymAhF/YdLm+ScQ==", "dev": true, "requires": { - "json2php": "^0.0.7", - "webpack-sources": "^3.2.2" + "json2php": "^0.0.7" } }, "@wordpress/deprecated": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.42.13.tgz", - "integrity": "sha512-Jxivx5eTKhjVNW1/rqShM1dzDKm/9wKp9jPlF58uAXpQSIaH8Q09D6Pgzi72DsDyefL8SV/QllLQbo0bVenydg==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.50.0.tgz", + "integrity": "sha512-DL01l0Wlo3df9OcSGHP11Ot/nq0HytbdmD+iPkiCCRI6Xctepbs/DzRR2CO3qLrJkWn6RReFwZWZZjzI7lZUqg==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.42.13" + "@wordpress/hooks": "^3.50.0" } }, "@wordpress/dom": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.42.13.tgz", - "integrity": "sha512-E7TnWuSOrxY5sn57+6Bf5v7JAL9PmNrOljf8Jj7FDsRdH6tCXf8BDqyIBz53cmzv/bsWOklQKIOeU/BQoEItHw==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.50.0.tgz", + "integrity": "sha512-rMnV1ysGOHbKnmjLQYwGkT1co1iEkC3YsKrEObP8mklw1R7rbCy7fc2brIz7kqcHU1DRyg/+7wOCMkg8a/EV/Q==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.42.13" + "@wordpress/deprecated": "^3.50.0" } }, "@wordpress/dom-ready": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.42.13.tgz", - "integrity": "sha512-mtqstqT1YFfIGl8rQipG9d8UwvGIZUP4Y8E1Tq3V9CAMV6ChJEYCZIGs/asHjqJSebNnXEWUEzQKAbPnIhnW3Q==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.50.0.tgz", + "integrity": "sha512-97tJpat1emXnwfGlJMiG6p37CpHJXDLmM/SIbsGJ0Oj8P4/TXbTuE9DNT1H8B1wKe5zD7kICjp48y91ugmgSrQ==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/e2e-test-utils": { - "version": "10.13.13", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.13.13.tgz", - "integrity": "sha512-QibCpLfRW6Stm5BDd1zxc0eqX3uOE1yINPs8K7esUIHL8AqnCPEJUPa86NnOIaA2t8E52f+bhlTxzM7ZsaffoQ==", + "version": "10.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.21.0.tgz", + "integrity": "sha512-Oh62GkqAKBIyD0IO3/Oa0l42yL/jbpTRDyh8H+t6gZbHWYTDvEGEr/LOqI9bk5Lwk7Jt5jpN6136FDwyMzHSXw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "^2.6.0" @@ -39085,76 +38331,22 @@ } }, "@wordpress/e2e-test-utils-playwright": { - "version": "0.10.13", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.10.13.tgz", - "integrity": "sha512-5zqIsG6Nn6N0DBlK9GyvYKxUrK7dEBHFInRnIqqfimWAQmz07iBCJU34njs9lQi+/GzKfXS+2XgBI7dDQnbfwQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.18.0.tgz", + "integrity": "sha512-Z8uH1dUzy/STQjOU6eb9nquVK4RC1rUx0gXY/GN1IVNDJvGN/yJxT/gNKmfiL7KpmHvNp2Q5M4bnUT9uiNcM+Q==", "dev": true, "requires": { - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", "lighthouse": "^10.4.0", - "mime": "^3.0.0" + "mime": "^3.0.0", + "web-vitals": "^3.5.0" }, "dependencies": { - "@wordpress/api-fetch": { - "version": "6.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.40.0.tgz", - "integrity": "sha512-sNk6vZW02ldci1EpNIjmm61323x/0n2Ra/cDHuehZf8avOH/OV0zF0dXxttT8M9Fncz+XZDSIHopm76dU3Phug==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.43.0", - "@wordpress/url": "^3.44.0" - } - }, - "@wordpress/hooks": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.43.0.tgz", - "integrity": "sha512-SHSiyFUEsggihl0pDvY1l72q+fHMDyFHtIR3GCt0uV2ifctvoa/PIYdVwrxpGQaGdNEV25XCZ4kNldqJmfTddw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - }, - "@wordpress/i18n": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.43.0.tgz", - "integrity": "sha512-XHU/vGgI+pgjJU9WzWDHke1u948z8i3OPpKUNdxc/gMcTkKaKM4D8DW1+VMSQHyU6pneP8+ph7EF+1RIehP3lQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.43.0", - "gettext-parser": "^1.3.1", - "memize": "^2.1.0", - "sprintf-js": "^1.1.1", - "tannin": "^1.2.0" - } - }, - "@wordpress/keycodes": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.43.0.tgz", - "integrity": "sha512-B6rYPiKFdQTlnJfm93R+usQnjEODUX/K4+hMvY5ZZOinvxe7KyU/xyFGz7gRrS8WmIEYcJowqSmAlGgVs4XwKQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.43.0", - "change-case": "^4.1.2" - } - }, - "@wordpress/url": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.44.0.tgz", - "integrity": "sha512-QNtTPFg/cGHTJLOvOtQCvCgn5quFQgJml8A88I05o4dyUH/tc92rb8LNXi0qcVz/z4JPrx2g3+Ki8heYellP4A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "remove-accents": "^0.5.0" - } - }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -39175,95 +38367,96 @@ } }, "@wordpress/edit-post": { - "version": "7.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.19.16.tgz", - "integrity": "sha512-PK0XVHLrn6Bg47O8sq7UIBykJOJGF2xsbkOjhRVniD+6EYdYifpGYHTC9nHogEfw691xcz+vAqS87D01x3SfEQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.27.2.tgz", + "integrity": "sha512-GEWPr2TkzOH2OZx+WVtn+DGrkE+H5GOq1w+vAtoCEq1lLIdkGJe+YAieJKkSz/rqah25YzmRcyBgfYSL2iaULg==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/commands": "^0.13.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-commands": "^0.11.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/editor": "^13.19.14", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13", - "@wordpress/viewport": "^5.19.13", - "@wordpress/warning": "^2.42.13", - "@wordpress/widgets": "^3.19.14", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-commands": "^0.19.2", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/editor": "^13.27.2", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", + "@wordpress/viewport": "^5.27.0", + "@wordpress/warning": "^2.50.0", + "@wordpress/widgets": "^3.27.2", "classnames": "^2.3.1", "memize": "^2.1.0", "rememo": "^4.0.2" } }, "@wordpress/edit-site": { - "version": "5.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-5.19.16.tgz", - "integrity": "sha512-shraoCd4LCNngtBn9E7U6Na/l+zrU0nTXztgZSuVsqSGktAgHBi7pXMUTsCGqO/vp9fnmW9LU3tQ9XgLEogjkg==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-5.27.2.tgz", + "integrity": "sha512-/lZhqadnX/A7owFre4ZxcKjlj7pisdxVAQJgtB9OYSdpreG2x8sGNKvLhv686BTKzSffS1TzvmKbNl7e+pQZDA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/commands": "^0.13.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-commands": "^0.11.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/editor": "^13.19.14", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/patterns": "^1.3.14", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/primitives": "^3.40.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/router": "^0.11.13", - "@wordpress/style-engine": "^1.25.13", - "@wordpress/url": "^3.43.13", - "@wordpress/viewport": "^5.19.13", - "@wordpress/widgets": "^3.19.14", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-commands": "^0.19.2", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/dataviews": "^0.4.1", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/editor": "^13.27.2", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/primitives": "^3.48.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/router": "^0.19.0", + "@wordpress/style-engine": "^1.33.1", + "@wordpress/url": "^3.51.0", + "@wordpress/viewport": "^5.27.0", + "@wordpress/widgets": "^3.27.2", + "@wordpress/wordcount": "^3.50.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.9.2", "deepmerge": "^4.3.0", - "downloadjs": "^1.4.7", "fast-deep-equal": "^3.1.3", "is-plain-object": "^5.0.0", "memize": "^2.1.0", @@ -39273,75 +38466,77 @@ } }, "@wordpress/edit-widgets": { - "version": "5.19.16", - "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-5.19.16.tgz", - "integrity": "sha512-1yTkLHQjf/LEmxlw2y0bqgkZcqO2Gs0H8QK1JHEJdHrAK+R5nBd55Jq4Wb2IU+QsUAaGvQzuF+FfHAA4YkLUwQ==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-5.27.2.tgz", + "integrity": "sha512-AE5qgDCd5u16C3/EZQAP3STcxfpTZg2Ed6iHmN+PBg1RCEP11rv31aMaXy2+7Z+80bGsXwicmZAlqHxzm2vc2g==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/block-library": "^8.19.16", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/interface": "^5.19.14", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/patterns": "^1.3.14", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/url": "^3.43.13", - "@wordpress/widgets": "^3.19.14", - "classnames": "^2.3.1" + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/block-library": "^8.27.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/interface": "^5.27.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/url": "^3.51.0", + "@wordpress/widgets": "^3.27.2", + "classnames": "^2.3.1", + "rememo": "^4.0.2" } }, "@wordpress/editor": { - "version": "13.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.19.14.tgz", - "integrity": "sha512-t1RFJl0Bf+qJpBHtiUl0qoxJjpNNGcpSZLejnhR97+i32l/4ewg8+z69zwFtW4ChNQjLnAFnpQZ5pT/CqkkKpQ==", + "version": "13.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.27.2.tgz", + "integrity": "sha512-Wk1dwG5bkmDD74zip36yC1NO3EleXe/t35Z9GHfLaiZkUYlhZV2gv66QrrGN7Y59Zl68j+b4lRGLkUxEMWkleA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/date": "^4.42.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/dom": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/keyboard-shortcuts": "^4.19.13", - "@wordpress/keycodes": "^3.42.13", - "@wordpress/media-utils": "^4.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/patterns": "^1.3.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/reusable-blocks": "^4.19.14", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/server-side-render": "^4.19.14", - "@wordpress/url": "^3.43.13", - "@wordpress/wordcount": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/commands": "^0.21.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/date": "^4.50.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/dom": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/keyboard-shortcuts": "^4.27.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/media-utils": "^4.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/patterns": "^1.11.2", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/reusable-blocks": "^4.27.2", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/server-side-render": "^4.27.1", + "@wordpress/url": "^3.51.0", + "@wordpress/wordcount": "^3.50.0", "classnames": "^2.3.1", "date-fns": "^2.28.0", "memize": "^2.1.0", @@ -39351,14 +38546,14 @@ } }, "@wordpress/element": { - "version": "5.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.19.13.tgz", - "integrity": "sha512-8VSGNrJkSf0coC2xciFBFodVa6eQOLPKMThVAz1eIDtQwbAcFo9001tjkMXgyhcn/FMoxdhaGGOxg4VeUvgJSw==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.27.0.tgz", + "integrity": "sha512-IA5LTAfx5bDNXULPmctcNb/04i4JcnIReG0RAuPgrZ8lbMZWUxGFymh10PEQjs7ZJ++qGsI6E+6JISpjkRaDQQ==", "requires": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.42.13", + "@wordpress/escape-html": "^2.50.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -39366,24 +38561,24 @@ } }, "@wordpress/escape-html": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.42.13.tgz", - "integrity": "sha512-0I7loSc8M1vjqg6vXb6lCumaGzbbAeoI26NEpATcEq24MLgd8+UiidyHII4UNgdloRoq1Jj3e83AjDhFpAVfAg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.50.0.tgz", + "integrity": "sha512-hBvoMCEZocziZDGCmBanSO+uupnd054mxd7FQ6toQ4UnsZ4JwXSmEC72W2Ed+cRGB1DeJDD0dY9iC0b4xkumsQ==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/eslint-plugin": { - "version": "16.0.13", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-16.0.13.tgz", - "integrity": "sha512-Qk5Y7ifT0lfOOx5RQrEGa/DSw01CP+D2bCKr20SXLt3KDstViBlqjBiI1Yxv7EeS+AvaNbQO5M8Mm4B5mUB3kQ==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.7.0.tgz", + "integrity": "sha512-JSFaCogE0WlZpl0SV4q8DK8G6jwDjEzXRzOsgesWilea4OuVp1KxCamkddTorRNM3QAbjrGuPJ4NYaGrNG9QsA==", "dev": true, "requires": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^7.26.13", - "@wordpress/prettier-config": "^2.25.13", + "@wordpress/babel-preset-default": "^7.34.0", + "@wordpress/prettier-config": "^3.7.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -39399,9 +38594,9 @@ }, "dependencies": { "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -39410,47 +38605,48 @@ } }, "@wordpress/format-library": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-4.19.14.tgz", - "integrity": "sha512-NyJ1nmb6PODE5hXM9oOEBlYA48k6c2DlGcUTXkSzDcdLPRVinTeWDfPL4kpze30JcQPv9m6Y5/EfWp48bDnByA==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-4.27.2.tgz", + "integrity": "sha512-pgLWc+8QuRyWc3GtEL1X18u4FNmWI3Y821TbKW1MjnfMDYNhN7Vpypqk4AFuxq2PY0NxzmM0PGdcoqUXRGdldQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/rich-text": "^6.19.13", - "@wordpress/url": "^3.43.13" + "@wordpress/a11y": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/rich-text": "^6.27.0", + "@wordpress/url": "^3.51.0" } }, "@wordpress/hooks": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.42.13.tgz", - "integrity": "sha512-KITkyj2DhbbBevqLzGx4GCtq8XX/GjkMWe0NP7SkcX9d4rkEdON96eKwwoMUD6keL03Tijg87kIYZAU5Xsr8bA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.50.0.tgz", + "integrity": "sha512-YIhwT1y0ss7Byfz46NBx08EUmXzWMu+g5DCY7FMuDNhwxSEoZMB8edKMiwNmFk4mFKBCnXM1d5FeONUPIUkJwg==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/html-entities": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.42.13.tgz", - "integrity": "sha512-015rUF0FOSGXbUBq+sc++vo3UTGZZkl23z7tGxrTTXZG10AjcTVd3oMnpvffJeiBjrtEAJz/gq3QKpFXihvmww==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.50.0.tgz", + "integrity": "sha512-DBRgShv6FLtDpapoTgmEx//6uHeq+mk5zKhAWAAqu6+/6LqOm/TCoUTxb0E2xtHh4oRBgU5nYC92pObRaczFdQ==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/i18n": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.42.13.tgz", - "integrity": "sha512-4zYz5BbueJ3c19DYhO7cXf9GF2K5Fysd+c2r0rcE0lr2RqMqmyDdL49930L7XJw+mT4ql8g/8p+i3FOzPCsg9A==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.50.0.tgz", + "integrity": "sha512-FkA2se6HMQm4eFC+/kTWvWQqs51VxpZuvY2MlWUp/L1r1d/dMBHXu049x86+/+6yk3ZNqiK5h6j6Z76dvPHZ4w==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.42.13", + "@wordpress/hooks": "^3.50.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -39458,57 +38654,66 @@ } }, "@wordpress/icons": { - "version": "9.33.13", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.33.13.tgz", - "integrity": "sha512-4M34sMRIlyL7a3CDRI7rAfysZQm2VW1ptB4aGDf5tVMXd//hCRkj/OGE++AYkTYQNckli9uqhTkv2xoOOw1F6Q==", + "version": "9.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.41.0.tgz", + "integrity": "sha512-L4fp9ZdxGBpMk3o2YqABgiPHNoHyu9Enid7JNkCdWP8iUgk7dEiDvo/XoiWPTAeNbF6W8Nqu54635mq01es0NQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.19.13", - "@wordpress/primitives": "^3.40.13" + "@wordpress/element": "^5.27.0", + "@wordpress/primitives": "^3.48.0" } }, "@wordpress/interactivity": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-2.3.13.tgz", - "integrity": "sha512-WNmw/r+G1XllTZwKwpRDFJoGPm8cRztbU+MJhAogKzUOcrCu4Bp8xArroPSzlKr3aUuEquT/3WsWsFmHsSHYjg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-4.0.1.tgz", + "integrity": "sha512-sw9Cqoj+MNF9FAU5nJC3nAqoH7kgUvh6HwaEMaLdSlK0qEcp05ba5x7geDSNi5cUWY4QSk1r9DH2jKUg9zfpNg==", + "requires": { + "@preact/signals": "^1.2.2", + "deepsignal": "^1.4.0", + "preact": "^10.19.3" + } + }, + "@wordpress/interactivity-router": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-1.0.1.tgz", + "integrity": "sha512-XShZV0+Sqs+1C26nVyns6nT8kjAGRBJNArVPceZlkkpsX7DIRZcEZ2larWxOuQFWk67lzIRiXd5V51L71b8XrQ==", "requires": { - "@preact/signals": "^1.1.3", - "deepsignal": "^1.3.6", - "preact": "^10.13.2" + "@wordpress/interactivity": "^4.0.1" } }, "@wordpress/interface": { - "version": "5.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.19.14.tgz", - "integrity": "sha512-WsIsSKJuhAcXD3YbmUoncL1JZ6hKAJXs7Lb/bjrOJxCts/YOy5yMF3/I05r8f1Tfw/pS8wlHMRjIXH/gvnvWVA==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.27.0.tgz", + "integrity": "sha512-ZybF4tuuuFOgGsB0n9u5ajrWKf/PYaS8d2yu2T+6ukliLnXI6AMMCXvM534H0VZa7DMLjMYKRXtfs7QqR/p95Q==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/plugins": "^6.10.14", - "@wordpress/preferences": "^3.19.14", - "@wordpress/viewport": "^5.19.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/plugins": "^6.18.0", + "@wordpress/preferences": "^3.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/viewport": "^5.27.0", "classnames": "^2.3.1" } }, "@wordpress/is-shallow-equal": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.42.13.tgz", - "integrity": "sha512-C3Pdan4alanyaQJ4Ucg7GZvkgDv7mXQZXe0xIYmKUNCnohS3wcFXmaLE6VGvf3I2OhRz8WLh5uxno/suJ8cyRw==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.50.0.tgz", + "integrity": "sha512-lX0fMa1f/TwWYYF+Oj0MG2Eze4Bb+vsnhXX6X1l+Ri3PG34wWGonjq729qHbJRDwm8o1y9GeswCgESIpuAm9wg==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/jest-console": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.14.0.tgz", - "integrity": "sha512-o7EZZ+StfLg/qgTRn47O0WY2V1I+xNJCiN13a/fHZtXdRgPJ9qajf7tkDYz+MKPf8MhdMfHhgIr9sQrWhLCzDA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.21.0.tgz", + "integrity": "sha512-o2vZRlwwJ6WoxRwnFFT5iZzfdc2d9MZvrtwB093RWPNcyK5qVtApji4VN/ieHijB4bjEHGalm0UKfKpt0EDlUQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -39516,203 +38721,207 @@ } }, "@wordpress/jest-preset-default": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.14.0.tgz", - "integrity": "sha512-eGenm5xUpPcsgWMSFXYWg+RQlcAZa6zo7sT9bBK8HVIGqORTr3TTtWeHVGFL48UooL5PibUc+GxQdlW97YOwlQ==", + "version": "11.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.21.0.tgz", + "integrity": "sha512-XAztKOROu02iBsz+Qosv/RYuPWB1XwwlU+FiA5Y68tRztrqFy4b/il+DFg4Jue/zXF7UECWUvosd5ow/GmKa6Q==", "dev": true, "requires": { - "@wordpress/jest-console": "^7.14.0", + "@wordpress/jest-console": "^7.21.0", "babel-jest": "^29.6.2" } }, "@wordpress/keyboard-shortcuts": { - "version": "4.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.19.13.tgz", - "integrity": "sha512-5u/pMERHn1b17d3HqDWWulJp08MLlNG1idsuJiLzbQBrYW3wLPd23fPG1QObUSH/texVDvi/W4/9N4hsbZlXEg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.27.0.tgz", + "integrity": "sha512-mpYhaSAMHXbRMp9hP08LejX/u1nLQaZONhwGSytqIhN1DQwpBbNbmV8ZNm1dnevUsYqEfPVVov6HFyPxYQ6m4w==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/keycodes": "^3.42.13", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/keycodes": "^3.50.0", "rememo": "^4.0.2" } }, "@wordpress/keycodes": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.42.13.tgz", - "integrity": "sha512-3lGlnYj+ky5OOnFjTW6NSxFFeNk/ESUF2Gbhz888HV+QF55SPvRfb+G7kjAzxRomIpdwACYsn80PdqabxLVqgw==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.50.0.tgz", + "integrity": "sha512-ykWpyCbgwcaT8i5kSfotYtd2oOHyMDpWEYR73InYrzEhl7pnS3wD7hi/KfeKLvMfYhbysUXlCVr6q/oH+qK/DQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.42.13", - "change-case": "^4.1.2" + "@wordpress/i18n": "^4.50.0" } }, "@wordpress/list-reusable-blocks": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-4.19.14.tgz", - "integrity": "sha512-GuorU374D0Ft7RtIZWWc7ltIkV3ThjU/u+LwbNzh5y7iaVs4l64qvqopqoj/IoRVdahpnLEO3MNxj9InlUiNeg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-4.27.0.tgz", + "integrity": "sha512-szDQnIdU34yIvNel+Kk1oBOugiqwXNm4jF77T90kaWB/SIQFW80CFYoIjIYQc63r9v3wi0D483KpXoci1AUSeQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", "change-case": "^4.1.2" } }, "@wordpress/media-utils": { - "version": "4.33.13", - "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.33.13.tgz", - "integrity": "sha512-+QJvDbBEtjMC6V2kJ04dEZkmElDneueW6HxGcx9lD786N0pcHwHZCnY9mLN+Tg/2f6Y8/9u0emvbFFuX0FLE8w==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.41.0.tgz", + "integrity": "sha512-wCxk8DAhmZ/3/a+oPRrieGurMOKDrYoDnnA0jhTm2D45kvn9y+NfnNBvLo2q1Is1ZiVTtNq54IRUXcdOjZgR9A==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blob": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13" + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blob": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0" } }, "@wordpress/notices": { - "version": "4.10.13", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.10.13.tgz", - "integrity": "sha512-6U0im51yJFXLLMzL6zZ+eyeJIeY2cyiUCDdziJSI1ZrsfV2ml9o4nB3EYYOxZBaVvJg66vY3wIQ/osMFwTW6xg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.18.0.tgz", + "integrity": "sha512-Y2XpY6niJ7NuqPBtGYvDYSPCfw/y4yxv60ahu1kYd8r5BamKSchTYwKSnV0yrx/IUfNO04VAsNq9NCUQG12pRA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/data": "^9.12.13" + "@wordpress/a11y": "^3.50.0", + "@wordpress/data": "^9.20.0" } }, "@wordpress/npm-package-json-lint-config": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.28.0.tgz", - "integrity": "sha512-lxrs1F4scwDuF8AJLK+SHtLWuhRVjzvl8EW/++ZQWRt7op99m41QQUqUwwCQC09cDcYlGddXeAczRijx5eLREg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.35.0.tgz", + "integrity": "sha512-QmkhYM4/s+2r3RuolVRRmoUa5o3lFgcHA6I3A9akaSVGZr//4p2p+iXOGmNub9njgGlj7j8SAPN8GUsCO/VqZQ==", "dev": true }, "@wordpress/nux": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-8.4.14.tgz", - "integrity": "sha512-JcxUtWOzl7lTuv39BWRwzwPDvVEhFECGzK819i3kExbTjmsVHCHtsdB7khPrdAYZOm2GXzR1le+/UFfkGuHS2Q==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-8.12.0.tgz", + "integrity": "sha512-fMnm9f+lmaCV5YoRHjqQNVU0P+FxthY8Lt84ZW1owlPjpJqdYZX/bKtp+bfWFgR3/Th26/uO4WxZqQzj8V1Pjg==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", "rememo": "^4.0.2" } }, "@wordpress/patterns": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.3.14.tgz", - "integrity": "sha512-eaZWZlaF/MlxqDY7KYzL8cApY4b4f89wuqHVSmjv52UfvaqxW0vd09ddX+jwkcXysDHFzwM63takIIVZwYn9Lg==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.11.2.tgz", + "integrity": "sha512-cN7xjw5pfKq73mVF0q0ebZh4DmAab5SlQ9CvM7PtB03JOl3GMwVIDV5Tnbbhfi1KIsFwep2/CGft3xwuJlS3FQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/html-entities": "^3.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13" + "@wordpress/a11y": "^3.50.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/html-entities": "^3.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", + "nanoid": "^3.3.4" } }, "@wordpress/plugins": { - "version": "6.10.14", - "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.10.14.tgz", - "integrity": "sha512-Duxh0OxpSuUFTMHa500iitrD21/JeTklc8/Hf3ApCpn4SdDzFR4IrwUdoJk0jGDY79cTwBVeWts5GhObbJByng==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.18.0.tgz", + "integrity": "sha512-m2BRJ5BApIMwT2Ck5E5yD8pS3RiIoOvWhzsYWrRqRfwjRhc6K46BreCbkiHgduBaFgzDIWpujlUHkYtdl27RoQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/element": "^5.19.13", - "@wordpress/hooks": "^3.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/is-shallow-equal": "^4.42.13", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/element": "^5.27.0", + "@wordpress/hooks": "^3.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/is-shallow-equal": "^4.50.0", "memize": "^2.0.1" } }, "@wordpress/postcss-plugins-preset": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.27.0.tgz", - "integrity": "sha512-4hk8UWfJvv21u/Et0NypfR1r22LVWGXMit3QM0MD7d6XQ4dNNbzqW2c9TfM36SdcR9KY5PZ8d5V1IrkheNUb/w==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.34.0.tgz", + "integrity": "sha512-OLQBSLE2q11Ik+WdcO2QfGr/O4X/zJYOGXNsychx/EaMamLzJInFcRL6kGbPX41zPINhadq5x2vFIZI2EC+Uyg==", "dev": true, "requires": { - "@wordpress/base-styles": "^4.34.0", + "@wordpress/base-styles": "^4.41.0", "autoprefixer": "^10.2.5" } }, "@wordpress/preferences": { - "version": "3.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.19.14.tgz", - "integrity": "sha512-xLu+G22Vlm4KajE/Eimq8qLzBoxMZ7BJLp8WobFC3yyzdU9R785dug9t9et4r45NxWJr8aVWkFzhEBzAadHjnA==", + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.27.0.tgz", + "integrity": "sha512-LMhOHX5FI4CJHv2YhtpiEtHfLqL/pjKAMja/v7skkHPlrh64Sgzi/gep016/My5SjcR64JUD1Na2U2j/BnrBNQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/components": "^25.8.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/private-apis": "^0.32.0", "classnames": "^2.3.1" } }, "@wordpress/preferences-persistence": { - "version": "1.34.13", - "resolved": "https://registry.npmjs.org/@wordpress/preferences-persistence/-/preferences-persistence-1.34.13.tgz", - "integrity": "sha512-23bUN1WdJ9mtfU51uoPBrSwbYHaW2zG+HDlH+leZURdPe48jbWSA8LRPwni5z3Kc9zh8D0vXkvt0hg1/RcNgUQ==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences-persistence/-/preferences-persistence-1.42.0.tgz", + "integrity": "sha512-n/VBhZHUEXWoBGsvHUf5uq6b872Lzn+cenfB2ex/etcWLXiVUkEl3rlzocyS50g2YoNQg/zQOn1hoSh+AgCm8Q==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13" + "@wordpress/api-fetch": "^6.47.0" } }, "@wordpress/prettier-config": { - "version": "2.25.13", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-2.25.13.tgz", - "integrity": "sha512-iz58o0X91E24j0VFtzwn5qG84w+s4VlRCuZWa/lPL6pfGtOSw30c60wCrYKCA1IWIIAWdpRAYfEh7errPyKiPQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.7.0.tgz", + "integrity": "sha512-JRTc5p7UxtcPkqdSDXSFJoJnVuS510uiRVz8anXEl5nuOx5p+SJAzi9QPrxTgOE8bN3wRABH4eIhfOcta4CFdg==", "dev": true }, "@wordpress/primitives": { - "version": "3.40.13", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.40.13.tgz", - "integrity": "sha512-dYYrPceV8w78AHJfPe5wkxnT7P0tG/4yDcr9/HvznFHkzQFnW8kG8Nci20RV/+ENxfNiuWqfWyICI2y7myIoGw==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.48.0.tgz", + "integrity": "sha512-uBoMxpl+FiZF6aRXH/+Hwol4EAL6QqlNSaGF1IzEwklFzdRF1m5wTM4vh21w8Bq7lgxiuAqyueY7X5u32v+zPw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.19.13", + "@wordpress/element": "^5.27.0", "classnames": "^2.3.1" } }, "@wordpress/priority-queue": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.42.13.tgz", - "integrity": "sha512-vrkjBcJnuzhpfWLFF4LfdNVrM3s73KW3KOZBTuN6oizJVYKyQaaPSLmDdORuXFc017MMasO5N/fYk/qJyll5bg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.50.0.tgz", + "integrity": "sha512-21E842EVFYUd1ZrNTLAW57IyloDCUZr6h1Te6BgqKoeKOEteoTQwA9BemMzZJUiThUSZymW94ot0Omb+C8VX2g==", "requires": { "@babel/runtime": "^7.16.0", "requestidlecallback": "^0.3.0" } }, "@wordpress/private-apis": { - "version": "0.24.13", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.24.13.tgz", - "integrity": "sha512-RgvGB6VQpPnEGU8Y61tzpgPFYDRAW28+2gcdOXYiqSVdZfGBL6+hBs5bMbLSJYRU9G5pl5q4Eb0lHlkMgHW5FA==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.32.0.tgz", + "integrity": "sha512-P7nxI/bGMDQhtlTfSe1Y2SDfrd20K5UMnTHbq+hmIkzBGRpNPbdGeNu2bZaZtIvmXk1OCR0Fkef+e6QqkOfYPg==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/redux-routine": { - "version": "4.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.42.13.tgz", - "integrity": "sha512-R+8W8CcjhHXPRlfPCdtElO2lsZzObR6DWvO49BjfJcKs0QPvKaO3ofjsadRgv+gg1+nXiE7rH6LmHbZ4eLanGw==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.50.0.tgz", + "integrity": "sha512-giHjQYhmFDCpeNEnsZKP0JNPBnpuQwsoxLmHAUUSNFWAmd4rtnNnG6M8HuqOLmgYTvEa8Hlx3Bl+reTGvrtI2g==", "requires": { "@babel/runtime": "^7.16.0", "is-plain-object": "^5.0.0", @@ -39721,77 +38930,77 @@ } }, "@wordpress/reusable-blocks": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.19.14.tgz", - "integrity": "sha512-WhQNDtq2ohGlGlodNyEbvMux631D+7jRABwodvoC42dVJyHR3lH1O8uhnQeKyPl91YWLxJ6+mHmrPInEo2fAcQ==", + "version": "4.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.27.2.tgz", + "integrity": "sha512-kkhZyYFj4rbf7lPOqDMfaNO3fqLEyHYKjWITWzRMUPtLeIHin/DHepVz6Z6NERANHpbP0mD4BDoBEGYJ9/brbA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13" + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0" } }, "@wordpress/rich-text": { - "version": "6.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.19.13.tgz", - "integrity": "sha512-7kCbTLiy+dIOToBktkrftCfVLsqCN0dY9uE6rz/TRsKS6+pnF6fUhqHLBV5OFf0tttKjHykSj5ixFDejqWCvrQ==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.27.0.tgz", + "integrity": "sha512-B7t++SldcI4nb+lO2m9oEdyD8y2FbH5DKY5F2G3xpcEnw4EKSt4SsTzeclMQ/2zzlEHPRKU/IR29SeOIJ1H8JQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.42.13", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/escape-html": "^2.42.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/keycodes": "^3.42.13", + "@wordpress/a11y": "^3.50.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/escape-html": "^2.50.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/keycodes": "^3.50.0", "memize": "^2.1.0", "rememo": "^4.0.2" } }, "@wordpress/router": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.11.13.tgz", - "integrity": "sha512-OZyuFOuX6nW5fQ1kq250EqCCA1Ad6KSH0wlaC68kCF06VFft2JNiATba7rC9Uq3ozM9HjPCtkbJ1dAW4PQdS1g==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.19.0.tgz", + "integrity": "sha512-S2z4WrgrfMNAl6amIjekGV1V6XGnjolYmRgUH/VTN45CQUV/o5ABo04xI/L3uvUnaRpH022n/yQX5H1p1kKhdA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.19.13", - "@wordpress/private-apis": "^0.24.13", - "@wordpress/url": "^3.43.13", + "@wordpress/element": "^5.27.0", + "@wordpress/private-apis": "^0.32.0", + "@wordpress/url": "^3.51.0", "history": "^5.1.0" } }, "@wordpress/scripts": { - "version": "26.13.13", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-26.13.13.tgz", - "integrity": "sha512-G2K56PmjRPI0ddgmrnopp3AVMLACqfrFvz+NyGbYCPWQoYL3xnphrS+w3uPwuxcuBtgR34yr+xCvrMnJsY3Wag==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.1.0.tgz", + "integrity": "sha512-jewyOxqaNrsct5R1NXv2lT8CA70vzrvpdZHYERCcH9LzKuvrcc32Telm9Jqso6ay1ZgHeIbjHSCd2+r2sBG7hw==", "dev": true, "requires": { "@babel/core": "^7.16.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.2", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^7.26.13", - "@wordpress/browserslist-config": "^5.25.13", - "@wordpress/dependency-extraction-webpack-plugin": "^4.25.13", - "@wordpress/e2e-test-utils-playwright": "^0.10.13", - "@wordpress/eslint-plugin": "^16.0.13", - "@wordpress/jest-preset-default": "^11.13.13", - "@wordpress/npm-package-json-lint-config": "^4.27.13", - "@wordpress/postcss-plugins-preset": "^4.26.13", - "@wordpress/prettier-config": "^2.25.13", - "@wordpress/stylelint-config": "^21.25.13", + "@wordpress/babel-preset-default": "^7.34.0", + "@wordpress/browserslist-config": "^5.33.0", + "@wordpress/dependency-extraction-webpack-plugin": "^5.1.0", + "@wordpress/e2e-test-utils-playwright": "^0.18.0", + "@wordpress/eslint-plugin": "^17.7.0", + "@wordpress/jest-preset-default": "^11.21.0", + "@wordpress/npm-package-json-lint-config": "^4.35.0", + "@wordpress/postcss-plugins-preset": "^4.34.0", + "@wordpress/prettier-config": "^3.7.0", + "@wordpress/stylelint-config": "^21.33.0", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", - "browserslist": "^4.21.9", + "browserslist": "^4.21.10", "chalk": "^4.0.0", "check-node-version": "^4.1.0", "clean-webpack-plugin": "^3.0.0", @@ -39806,7 +39015,7 @@ "fast-glob": "^3.2.7", "filenamify": "^4.2.0", "jest": "^29.6.2", - "jest-dev-server": "^6.0.2", + "jest-dev-server": "^9.0.1", "jest-environment-jsdom": "^29.6.2", "jest-environment-node": "^29.6.2", "markdownlint-cli": "^0.31.1", @@ -39815,12 +39024,12 @@ "minimist": "^1.2.0", "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", - "playwright-core": "1.32.0", + "playwright-core": "1.39.0", "postcss": "^8.4.5", "postcss-loader": "^6.2.1", - "prettier": "npm:wp-prettier@3.0.3-beta-3", + "prettier": "npm:wp-prettier@3.0.3", "puppeteer-core": "^13.2.0", - "react-refresh": "^0.10.0", + "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", "sass": "^1.35.2", @@ -39829,12 +39038,103 @@ "stylelint": "^14.2.0", "terser-webpack-plugin": "^5.3.9", "url-loader": "^4.1.1", - "webpack": "^5.47.1", - "webpack-bundle-analyzer": "^4.4.2", - "webpack-cli": "^4.9.1", - "webpack-dev-server": "^4.4.0" - }, - "dependencies": { + "webpack": "^5.88.2", + "webpack-bundle-analyzer": "^4.9.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1" + }, + "dependencies": { + "@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==", + "dev": true, + "requires": { + "ansi-html-community": "^0.0.8", + "common-path-prefix": "^3.0.0", + "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", + "source-map": "^0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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" + } + }, + "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==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, "ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -39990,16 +39290,16 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "prettier": { - "version": "npm:wp-prettier@3.0.3-beta-3", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3-beta-3.tgz", - "integrity": "sha512-R3+TD7j0rnqEpMgylrUrHdi1W6ypwh4QGeFOZQ9YjP9WvNnZzBAS71yry1h7xIcG/bVaNKBCoWNqbqJY6vkOKQ==", + "playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", "dev": true }, - "react-refresh": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz", - "integrity": "sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==", + "prettier": { + "version": "npm:wp-prettier@3.0.3", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz", + "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==", "dev": true }, "read-pkg-up": { @@ -40031,6 +39331,12 @@ "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, "source-map-loader": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", @@ -40060,45 +39366,45 @@ } }, "@wordpress/server-side-render": { - "version": "4.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.19.14.tgz", - "integrity": "sha512-As3Xc3TDM0R0siAFaldobRdZnPfQQMXvlQxalFJgs/kSoYOmcdc46mR5Wgmfn7r0Kc/Z5uOHLbvm4mWekE0a2A==", + "version": "4.27.1", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.27.1.tgz", + "integrity": "sha512-hovofyT0z75NSK/CSkkSbbTdkq9Afc1MKbEVGXTGpqq5sKOa7IAcxWjzmh8byTgT8x7GEaAyHZUr31p4l0CGnQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/deprecated": "^3.42.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/url": "^3.43.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/deprecated": "^3.50.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/url": "^3.51.0", "fast-deep-equal": "^3.1.3" } }, "@wordpress/shortcode": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.42.13.tgz", - "integrity": "sha512-pq+xdRdND7vEuqskPoZx+VAOHsmatqHcox3dElFU5lxlx/3fvKC7NIrFCn+glxFGGxO5hY5JfUOC70x8tm7uMA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.50.0.tgz", + "integrity": "sha512-RnlqS2OsNUaI6VOLwyUiaL3trAJcWjtoiW21BjIXODbTkEreRJgBJnch7wdFpGimJmKIWBwRD8jQ4hdTND8xVw==", "requires": { "@babel/runtime": "^7.16.0", "memize": "^2.0.1" } }, "@wordpress/style-engine": { - "version": "1.25.13", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.25.13.tgz", - "integrity": "sha512-4ixhGNVNrtt6zppLWnPCKSl4O4X+TO48PbLEbLDvN2NvUK1Yp1wChiX+NFIBa1dJp1zDlrxaTjttCqC1bs3MUA==", + "version": "1.33.1", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.33.1.tgz", + "integrity": "sha512-mkur1jw3Trz76iwxU6DalTFsJyF5P/NTdU9xniMT8bo1H9HspgKrzqXAaxkTL9F9BXkyiYs+ctVekJYRUKlgcw==", "requires": { "@babel/runtime": "^7.16.0", "change-case": "^4.1.2" } }, "@wordpress/stylelint-config": { - "version": "21.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.26.0.tgz", - "integrity": "sha512-xTnvoNk9aCdRl1ntBxnmhdmghwzRNurp5Y9LjUCwrYutxnj8t/CCKhPyjgIgHxz+RwKgnpGKupKLVvuHxu1CzQ==", + "version": "21.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.33.0.tgz", + "integrity": "sha512-DwjXrjRBva0tkYILvDV7rjl3VaKXxvchlxnFfFs6l2DWL/Qo31CJ+f2rVw4XSWuuWxY1EsyIn9tOBS9URloWTQ==", "dev": true, "requires": { "stylelint-config-recommended": "^6.0.0", @@ -40106,82 +39412,88 @@ } }, "@wordpress/sync": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.4.13.tgz", - "integrity": "sha512-3Lq7MENUpCaSvR6WOLOovNmRMXGmFcdnbMjSZlHh0sx3ycWbKpXlGyfQWJ20MZRiO/qTOOrj4VW4GejqqJSEZw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.12.0.tgz", + "integrity": "sha512-45gU1Gu/ys3zqYO4dDQf6eG5gGgJK9nXa62IUtUWFXIH4FN29XlvGppMVK/zzhJwejF/XnDuT7mQuVEFCZGswA==", "requires": { "@babel/runtime": "^7.16.0", + "@types/simple-peer": "^9.11.5", + "@wordpress/url": "^3.51.0", + "import-locals": "^2.0.0", + "lib0": "^0.2.42", + "simple-peer": "^9.11.0", "y-indexeddb": "~9.0.11", + "y-protocols": "^1.0.5", "y-webrtc": "~10.2.5", "yjs": "~13.6.6" } }, "@wordpress/token-list": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.42.13.tgz", - "integrity": "sha512-eAKU/5U7c/Acqcqnurpp79lrwCAm+Tb8PfSBTmtGs1fJsR1xtJh4d6IZw5MLDFiqLuVRT65ec3T4Sjqb6N4CMQ==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.50.0.tgz", + "integrity": "sha512-LTjXkoljQpJIHqs0isTUzIc1fMu68y0N9HcDIdsCMGkmKptWUCETtb+DItnraxDDLuyWNuTYf840S83a3XAVRA==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/undo-manager": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.2.13.tgz", - "integrity": "sha512-SFIYRs65GEjr0eeh7BZcETaH32qQVm78aFMZXnYTHzBmTXxoJ98XRgEGWXRJU92RXBcjom+1gARKChJoV5dlNw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.10.0.tgz", + "integrity": "sha512-ODDqAL6BSvD+J7FV+sQTAaVHiPChh/4KBnKg8pb2ogg+Weq6VynthxDxGpQnN8FcMKB9ZoyS3SNIl8pVXLKIwA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/is-shallow-equal": "^4.42.13" + "@wordpress/is-shallow-equal": "^4.50.0" } }, "@wordpress/url": { - "version": "3.43.13", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.43.13.tgz", - "integrity": "sha512-GrIkGZoCgd+87CyAjgGzShoI6m/Kvknmc6syqrN34J1LdrEE+vPNMjM+NvUVvyPdvgG7/iFzRM8D/ZEUvaTm9A==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.51.0.tgz", + "integrity": "sha512-OjucjlP1763gfKbe8lv/k3RCisyX8AfNBrhASk7JqxAj6rFhb1ZZO7YmAgB2m+WoGB5v7fkOli0FZyDqISdYyg==", "requires": { "@babel/runtime": "^7.16.0", "remove-accents": "^0.5.0" } }, "@wordpress/viewport": { - "version": "5.19.13", - "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.19.13.tgz", - "integrity": "sha512-xYWTcaQLhZrDZA0lpl9TivbU4RPw+CUfuRc3NEBiQY0GDDfuLe8n1Pb9AkmAP5PLNyxZhHjKLBGojfchOGhzdg==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.27.0.tgz", + "integrity": "sha512-ET8X3Ln0K6wrBba+u0AjBD/mP02SuvwhK/EVaI3uAhNlGnkx+J3PdtShbu63lHmp0SG+J27CDjEqfcZ6CdAnfA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.19.13", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13" + "@wordpress/compose": "^6.27.0", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0" } }, "@wordpress/warning": { - "version": "2.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.42.13.tgz", - "integrity": "sha512-SYi37xiR7Wq4Vde4JBkCYJIyfUQzyuABrwh7aon1XwcUhWP072tv4/LKP6F+zWYC5M8pPdRqjznxgwZ2mNzcyw==" + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.50.0.tgz", + "integrity": "sha512-y7Zf48roDfiPgbRAWGXDwN3C8sfbEdneGq+HvXCW6rIeGYnDLdEkpX9i7RfultkFFPVeSP3FpMKVMkto2nbqzA==" }, "@wordpress/widgets": { - "version": "3.19.14", - "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.19.14.tgz", - "integrity": "sha512-nFyXrCBVp24joFa96sAdNwkWnnf23t960ebnoW+Wk+lMT0PsGfGjiMIRmtks2cfqbQuQYFdO/8go+DSE54ekAg==", + "version": "3.27.2", + "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.27.2.tgz", + "integrity": "sha512-z/OsrXbBY8PanemOHdtup1OlfdBmbc6dMfXqZ3pelH75z4n73JtPhVEqM/FJFdwP737fV1gU1nvMB17VtnyXKw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.39.13", - "@wordpress/block-editor": "^12.10.14", - "@wordpress/blocks": "^12.19.13", - "@wordpress/components": "^25.8.14", - "@wordpress/compose": "^6.19.13", - "@wordpress/core-data": "^6.19.14", - "@wordpress/data": "^9.12.13", - "@wordpress/element": "^5.19.13", - "@wordpress/i18n": "^4.42.13", - "@wordpress/icons": "^9.33.13", - "@wordpress/notices": "^4.10.13", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/block-editor": "^12.18.2", + "@wordpress/blocks": "^12.27.1", + "@wordpress/components": "^25.16.0", + "@wordpress/compose": "^6.27.0", + "@wordpress/core-data": "^6.27.2", + "@wordpress/data": "^9.20.0", + "@wordpress/element": "^5.27.0", + "@wordpress/i18n": "^4.50.0", + "@wordpress/icons": "^9.41.0", + "@wordpress/notices": "^4.18.0", "classnames": "^2.3.1" } }, "@wordpress/wordcount": { - "version": "3.42.13", - "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.42.13.tgz", - "integrity": "sha512-yapganGNO/9JjfWTcMNECjIOKlnLOJR2VTh4UFBL/lSi2GM1AE7bjnXsV2pD0H/3mwdhAomRCUV6BA3nG5UUfA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.50.0.tgz", + "integrity": "sha512-lRfIX3B9ha//bqsUihym2BnOiAsdDQr22vdy0wZIpm5G2tFvTddCKHy0YClf52IJK0z61WqbNuF9hrvzWWxL+g==", "requires": { "@babel/runtime": "^7.16.0" } @@ -40218,23 +39530,6 @@ "requires": { "mime-types": "~2.1.34", "negotiator": "0.6.3" - }, - "dependencies": { - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - } } }, "acorn": { @@ -40499,9 +39794,9 @@ "optional": true }, "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, "array-includes": { @@ -40730,12 +40025,27 @@ "dev": true }, "axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dev": true, "requires": { - "follow-redirects": "^1.14.7" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } } }, "axobject-query": { @@ -41048,12 +40358,6 @@ "tweetnacl": "^0.14.3" } }, - "big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -41463,13 +40767,11 @@ "integrity": "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==" }, "bonjour-service": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", - "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dev": true, "requires": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } @@ -41480,15 +40782,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "requires": { - "big-integer": "^1.6.44" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -41611,15 +40904,6 @@ "semver": "^7.0.0" } }, - "bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "requires": { - "run-applescript": "^5.0.0" - } - }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -41784,9 +41068,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001549", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", - "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==", + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", "dev": true }, "capital-case": { @@ -42265,9 +41549,9 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "colors": { @@ -42446,22 +41730,12 @@ } }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, - "optional": true, "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true - } + "safe-buffer": "5.2.1" } }, "content-type": { @@ -42598,9 +41872,9 @@ } }, "core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.1.tgz", + "integrity": "sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==", "dev": true }, "core-js-url-browser": { @@ -43054,6 +42328,12 @@ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -43316,138 +42596,9 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "deepsignal": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.6.tgz", - "integrity": "sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==" - }, - "default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "requires": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true - }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - }, - "dependencies": { - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - } - } - }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - } - } - }, - "default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "requires": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - } + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.4.0.tgz", + "integrity": "sha512-x0XUMT48s+xQRLc2fPFfxnYLCJ46vffw47OQ5NcHFzacOjfW5eA0NrEmI0bhQHL6MgUHkBVT4TIiWTVwzTEwpg==" }, "default-gateway": { "version": "6.0.3", @@ -43737,16 +42888,10 @@ "path-type": "^4.0.0" } }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, "dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, "requires": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -43918,11 +43063,6 @@ } } }, - "downloadjs": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", - "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" - }, "downshift": { "version": "6.1.12", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz", @@ -44066,9 +43206,9 @@ "dev": true }, "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", "dev": true }, "equivalent-key-map": { @@ -44182,6 +43322,11 @@ "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-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -44569,9 +43714,9 @@ } }, "eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "requires": { "array-includes": "^3.1.7", @@ -44590,7 +43735,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { @@ -44620,9 +43765,9 @@ } }, "eslint-plugin-jest": { - "version": "27.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz", - "integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==", + "version": "27.6.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", + "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", "dev": true, "requires": { "@typescript-eslint/utils": "^5.10.0" @@ -44694,9 +43839,9 @@ } }, "eslint-plugin-jsdoc": { - "version": "46.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz", - "integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==", + "version": "46.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.10.1.tgz", + "integrity": "sha512-x8wxIpv00Y50NyweDUpa+58ffgSAI5sqe+zcZh33xphD0AVh+1kqr1ombaTRb7Fhpove1zfUuujlX9DWWBP5ag==", "dev": true, "requires": { "@es-joy/jsdoccomment": "~0.41.0", @@ -44707,7 +43852,7 @@ "esquery": "^1.5.0", "is-builtin-module": "^3.2.1", "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1" + "spdx-expression-parse": "^4.0.0" }, "dependencies": { "escape-string-regexp": { @@ -44715,6 +43860,16 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true + }, + "spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } } } }, @@ -44757,13 +43912,13 @@ "dev": true }, "eslint-plugin-prettier": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", - "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" } }, "eslint-plugin-react": { @@ -45183,21 +44338,6 @@ "vary": "~1.1.2" }, "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "requires": { - "safe-buffer": "5.2.1" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -45941,6 +45081,12 @@ "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", "dev": true }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -45969,9 +45115,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true }, "for-each": { @@ -46108,9 +45254,9 @@ } }, "fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "fs.realpath": { @@ -47500,6 +46646,13 @@ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "homedir-polyfill": { @@ -47992,6 +47145,11 @@ "resolve-cwd": "^3.0.0" } }, + "import-locals": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-locals/-/import-locals-2.0.0.tgz", + "integrity": "sha512-1/bPE89IZhyf7dr5Pkz7b4UyVXy5pEt7PTEfye15UEn3AK8+2zwcDCfKk9Pwun4ltfhOSszOrReSsFcDKw/yoA==" + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -48131,9 +47289,9 @@ } }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, "intl-messageformat": { @@ -48177,9 +47335,9 @@ "dev": true }, "ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "dev": true }, "irregular-plurals": { @@ -48399,23 +47557,6 @@ "is-extglob": "^2.1.1" } }, - "is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "requires": { - "is-docker": "^3.0.0" - }, - "dependencies": { - "is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true - } - } - }, "is-jpg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-2.0.0.tgz", @@ -49298,18 +48439,18 @@ } }, "jest-dev-server": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-6.2.0.tgz", - "integrity": "sha512-ZWh8CuvxwjhYfvw4tGeftziqIvw/26R6AG3OTgNTQeXul8aZz48RQjDpnlDwnWX53jxJJl9fcigqIdSU5lYZuw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-9.0.2.tgz", + "integrity": "sha512-Zc/JB0IlNNrpXkhBw+h86cGrde/Mey52KvF+FER2eyrtYJTHObOwW7Iarxm3rPyTKby5+3Y2QZtl8pRz/5GCxg==", "dev": true, "requires": { "chalk": "^4.1.2", "cwd": "^0.10.0", "find-process": "^1.4.7", "prompts": "^2.4.2", - "spawnd": "^6.2.0", + "spawnd": "^9.0.2", "tree-kill": "^1.2.2", - "wait-on": "^6.0.1" + "wait-on": "^7.2.0" }, "dependencies": { "ansi-styles": { @@ -49353,9 +48494,9 @@ "dev": true }, "rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "requires": { "tslib": "^2.1.0" @@ -49371,16 +48512,16 @@ } }, "wait-on": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", - "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", "dev": true, "requires": { - "axios": "^0.25.0", - "joi": "^17.6.0", + "axios": "^1.6.1", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^7.5.4" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" } } } @@ -50438,15 +49579,15 @@ } }, "joi": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", - "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.0.tgz", + "integrity": "sha512-HSLsmSmXz+PV9PYoi3p7cgIbj06WnEBNT28n+bbBNcPZXZFqCzzvGqpTBPujx/Z0nh1+KNQPDrNgdmQ8dq0qYw==", "dev": true, "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.4", + "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, @@ -50933,6 +50074,16 @@ "language-subtag-registry": "^0.3.20" } }, + "launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -50956,9 +50107,9 @@ } }, "lib0": { - "version": "0.2.87", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.87.tgz", - "integrity": "sha512-TbB63XJixvNToW2IHWAFsCJj9tVnajmwjE14p69i51Rx8byOQd2IP4ourE8v4d7vhyO++nVm1sQk3ePslfbucg==", + "version": "0.2.88", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz", + "integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", "requires": { "isomorphic.js": "^0.2.4" } @@ -52158,12 +51309,12 @@ "dev": true }, "memfs": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", - "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", "dev": true, "requires": { - "fs-monkey": "^1.0.3" + "fs-monkey": "^1.0.4" } }, "memize": { @@ -52266,18 +51417,18 @@ "dev": true }, "mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true }, "mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { - "mime-db": "1.45.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -52365,9 +51516,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "minimist-options": { @@ -52485,9 +51636,9 @@ "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" }, "mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "dev": true }, "ms": { @@ -52515,8 +51666,7 @@ "nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "nanomatch": { "version": "1.2.13", @@ -53739,8 +52889,7 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { "version": "2.3.1", @@ -53882,7 +53031,6 @@ "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -54148,6 +53296,11 @@ "postcss-value-parser": "^4.2.0" } }, + "postcss-prefixwrap": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.44.0.tgz", + "integrity": "sha512-h9MJGaIvT5hnzFc7Vuo+2ulBw6ecmmfcd8SKKH2TziUzcIA04gUoXIbptuM+tR+htmsQIKNEluiQlmCQ2p5a2g==" + }, "postcss-reduce-initial": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", @@ -54341,16 +53494,23 @@ "postcss-selector-parser": "^6.0.5" } }, + "postcss-urlrebase": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.3.0.tgz", + "integrity": "sha512-LOFN43n1IewKriXiypMNNinXeptttSyGGRLPbBMdQzuTvvCEo5mz/gG06y/HqrkN7p3ayHQf2R2bTBv639FOaQ==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, "postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "preact": { - "version": "10.19.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.1.tgz", - "integrity": "sha512-ZSsUr6EFlwWH0btdXMj6+X+hJAZ1v+aUzKlfwBGokKB1ZO6Shz+D16LxQhM8f+E/UgkKbVe2tsWXtGTUMCkGpQ==" + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==" }, "prelude-ls": { "version": "1.2.1", @@ -54402,12 +53562,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true } } }, @@ -54445,6 +53599,13 @@ "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "proto-list": { @@ -54789,9 +53950,9 @@ } }, "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "react-refresh": { "version": "0.14.0", @@ -54799,18 +53960,6 @@ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", "dev": true }, - "react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "requires": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - } - }, "react-remove-scroll-bar": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", @@ -55415,87 +54564,6 @@ } } }, - "run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "requires": { - "execa": "^5.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - } - } - }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -55790,11 +54858,12 @@ "dev": true }, "selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, "requires": { + "@types/node-forge": "^1.3.0", "node-forge": "^1" } }, @@ -56069,6 +55138,12 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true + }, "showdown": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz", @@ -56178,14 +55253,14 @@ "dev": true }, "sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dev": true, "requires": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" } }, "sisteransi": { @@ -56471,8 +55546,7 @@ "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==", - "dev": true + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, "source-map-loader": { "version": "4.0.1", @@ -56523,14 +55597,21 @@ "dev": true }, "spawnd": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-6.2.0.tgz", - "integrity": "sha512-qX/I4lQy4KgVEcNle0kuc4FxFWHISzBhZW1YemPfwmrmQjyZmfTK/OhBKkhrD2ooAaFZEm1maEBLE6/6enwt+g==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-9.0.2.tgz", + "integrity": "sha512-nl8DVHEDQ57IcKakzpjanspVChkMpGLuVwMR/eOn9cXE55Qr6luD2Kn06sA0ootRMdgrU4tInN6lA6ohTNvysw==", "dev": true, "requires": { - "exit": "^0.1.2", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "tree-kill": "^1.2.2" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } } }, "spdx-correct": { @@ -56593,9 +55674,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -57460,13 +56541,13 @@ "dev": true }, "synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "requires": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" } }, "table": { @@ -57750,12 +56831,6 @@ } } }, - "titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -57826,9 +56901,9 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, "totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true }, "tough-cookie": { @@ -57849,11 +56924,6 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, - "traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==" - }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -57883,9 +56953,9 @@ "dev": true }, "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { "@types/json5": "^0.0.29", @@ -57942,11 +57012,6 @@ "safe-buffer": "^5.0.1" } }, - "turbo-combine-reducers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/turbo-combine-reducers/-/turbo-combine-reducers-1.0.2.tgz", - "integrity": "sha512-gHbdMZlA6Ym6Ur5pSH/UWrNQMIM9IqTH6SoL1DbHpqEdQ8i+cFunSmSlFykPt0eGQwZ4d/XTHOl74H0/kFBVWw==" - }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -58203,12 +57268,6 @@ } } }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, "update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -58308,11 +57367,6 @@ "tslib": "^2.0.0" } }, - "use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" - }, "use-lilius": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/use-lilius/-/use-lilius-2.0.3.tgz", @@ -58483,12 +57537,6 @@ "mime-types": "^2.1.12" } }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - }, "rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -58541,6 +57589,12 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" }, + "web-vitals": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.1.tgz", + "integrity": "sha512-xQ9lvIpfLxUj0eSmT79ZjRoU5wIRfIr7pNukL7ZE4EcWZSmfZQqOlhuAGfkVa3EFmzPHZhWhXfm2i5ys+THVPg==", + "dev": true + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -58588,104 +57642,65 @@ } }, "webpack-bundle-analyzer": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", - "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", + "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "html-escaper": "^2.0.2", + "is-plain-object": "^5.0.0", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "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 }, - "has-flag": { + "escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, "webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", - "commander": "^7.0.0", + "commander": "^10.0.1", "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true }, "cross-spawn": { @@ -58705,6 +57720,15 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -58762,39 +57786,24 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "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": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" } } } }, "webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dev": true, "requires": { "@types/bonjour": "^3.5.9", @@ -58803,7 +57812,7 @@ "@types/serve-index": "^1.9.1", "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", + "@types/ws": "^8.5.5", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -58816,6 +57825,7 @@ "html-entities": "^2.3.2", "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", "open": "^8.0.9", "p-retry": "^4.5.0", "rimraf": "^3.0.2", @@ -58825,7 +57835,7 @@ "sockjs": "^0.3.24", "spdy": "^4.0.2", "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" + "ws": "^8.13.0" }, "dependencies": { "ajv": { @@ -58865,21 +57875,21 @@ } }, "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "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": { "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", + "ajv": "^8.9.0", "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ajv-keywords": "^5.1.0" } }, "ws": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", - "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true } } @@ -58897,12 +57907,13 @@ } }, "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, "requires": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "dependencies": { @@ -59082,9 +58093,9 @@ "integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang==" }, "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, "wrap-ansi": { @@ -59117,7 +58128,7 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "devOptional": true + "dev": true }, "xdg-basedir": { "version": "4.0.0", @@ -59177,14 +58188,22 @@ } }, "y-webrtc": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.5.tgz", - "integrity": "sha512-ZyBNvTI5L28sQ2PQI0T/JvyWgvuTq05L21vGkIlcvNLNSJqAaLCBJRe3FHEqXoaogqWmRcEAKGfII4ErNXMnNw==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.6.tgz", + "integrity": "sha512-1kZ4YYwksFZi8+l8mTebVX9vW6Q5MnqxMkvNU700X5dBE38usurt/JgeXSIQRpK3NwUYYb9y63Jn9FMpMH6/vA==", "requires": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", - "ws": "^7.2.0", - "y-protocols": "^1.0.5" + "ws": "^8.14.2", + "y-protocols": "^1.0.6" + }, + "dependencies": { + "ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "optional": true + } } }, "y18n": { @@ -59240,11 +58259,11 @@ } }, "yjs": { - "version": "13.6.8", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.8.tgz", - "integrity": "sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==", + "version": "13.6.11", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.11.tgz", + "integrity": "sha512-FvRRJKX9u270dOLkllGF/UDCWwmIv2Z+ucM4v1QO1TuxdmoiMnSUXH1HAcOKOrkBEhQtPTkxep7tD2DrQB+l0g==", "requires": { - "lib0": "^0.2.74" + "lib0": "^0.2.86" } }, "yocto-queue": { diff --git a/package.json b/package.json index 6a9db6624057d..de72ea186a656 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,12 @@ "@lodder/grunt-postcss": "^3.1.1", "@playwright/test": "1.32.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.5", - "@wordpress/babel-preset-default": "7.26.13", - "@wordpress/dependency-extraction-webpack-plugin": "4.25.13", - "@wordpress/e2e-test-utils": "10.13.13", - "@wordpress/e2e-test-utils-playwright": "0.10.13", - "@wordpress/scripts": "26.13.13", + "@wordpress/babel-preset-default": "7.34.0", + "@wordpress/dependency-extraction-webpack-plugin": "5.1.0", + "@wordpress/e2e-test-utils": "10.21.0", + "@wordpress/e2e-test-utils-playwright": "0.18.0", + "@wordpress/prettier-config": "3.7.0", + "@wordpress/scripts": "27.1.0", "autoprefixer": "10.4.16", "chalk": "5.3.0", "check-node-version": "4.2.1", @@ -79,74 +80,77 @@ "dependencies": { "@emotion/is-prop-valid": "0.8.8", "@emotion/memoize": "0.7.4", - "@wordpress/a11y": "3.42.13", - "@wordpress/annotations": "2.42.13", - "@wordpress/api-fetch": "6.39.13", - "@wordpress/autop": "3.42.13", - "@wordpress/blob": "3.42.13", - "@wordpress/block-directory": "4.19.16", - "@wordpress/block-editor": "12.10.14", - "@wordpress/block-library": "8.19.16", - "@wordpress/block-serialization-default-parser": "4.42.13", - "@wordpress/blocks": "12.19.13", - "@wordpress/commands": "0.13.14", - "@wordpress/components": "25.8.14", - "@wordpress/compose": "6.19.13", - "@wordpress/core-commands": "0.11.14", - "@wordpress/core-data": "6.19.14", - "@wordpress/customize-widgets": "4.19.16", - "@wordpress/data": "9.12.13", - "@wordpress/data-controls": "3.11.13", - "@wordpress/date": "4.42.13", - "@wordpress/deprecated": "3.42.13", - "@wordpress/dom": "3.42.13", - "@wordpress/dom-ready": "3.42.13", - "@wordpress/edit-post": "7.19.16", - "@wordpress/edit-site": "5.19.16", - "@wordpress/edit-widgets": "5.19.16", - "@wordpress/editor": "13.19.14", - "@wordpress/element": "5.19.13", - "@wordpress/escape-html": "2.42.13", - "@wordpress/format-library": "4.19.14", - "@wordpress/hooks": "3.42.13", - "@wordpress/html-entities": "3.42.13", - "@wordpress/i18n": "4.42.13", - "@wordpress/icons": "9.33.13", - "@wordpress/interactivity": "2.3.13", - "@wordpress/interface": "5.19.14", - "@wordpress/is-shallow-equal": "4.42.13", - "@wordpress/keyboard-shortcuts": "4.19.13", - "@wordpress/keycodes": "3.42.13", - "@wordpress/list-reusable-blocks": "4.19.14", - "@wordpress/media-utils": "4.33.13", - "@wordpress/notices": "4.10.13", - "@wordpress/nux": "8.4.14", - "@wordpress/patterns": "1.3.14", - "@wordpress/plugins": "6.10.14", - "@wordpress/preferences": "3.19.14", - "@wordpress/preferences-persistence": "1.34.13", - "@wordpress/primitives": "3.40.13", - "@wordpress/priority-queue": "2.42.13", - "@wordpress/private-apis": "0.24.13", - "@wordpress/redux-routine": "4.42.13", - "@wordpress/reusable-blocks": "4.19.14", - "@wordpress/rich-text": "6.19.13", - "@wordpress/router": "0.11.13", - "@wordpress/server-side-render": "4.19.14", - "@wordpress/shortcode": "3.42.13", - "@wordpress/style-engine": "1.25.13", - "@wordpress/sync": "0.4.13", - "@wordpress/token-list": "2.42.13", - "@wordpress/undo-manager": "0.2.13", - "@wordpress/url": "3.43.13", - "@wordpress/viewport": "5.19.13", - "@wordpress/warning": "2.42.13", - "@wordpress/widgets": "3.19.14", - "@wordpress/wordcount": "3.42.13", + "@wordpress/a11y": "3.50.0", + "@wordpress/annotations": "2.50.0", + "@wordpress/api-fetch": "6.47.0", + "@wordpress/autop": "3.50.0", + "@wordpress/blob": "3.50.0", + "@wordpress/block-directory": "4.27.2", + "@wordpress/block-editor": "12.18.2", + "@wordpress/block-library": "8.27.2", + "@wordpress/block-serialization-default-parser": "4.50.0", + "@wordpress/blocks": "12.27.1", + "@wordpress/commands": "0.21.0", + "@wordpress/components": "25.16.0", + "@wordpress/compose": "6.27.0", + "@wordpress/core-commands": "0.19.2", + "@wordpress/core-data": "6.27.2", + "@wordpress/customize-widgets": "4.27.2", + "@wordpress/data": "9.20.0", + "@wordpress/data-controls": "3.19.0", + "@wordpress/dataviews": "0.4.1", + "@wordpress/date": "4.50.0", + "@wordpress/deprecated": "3.50.0", + "@wordpress/dom": "3.50.0", + "@wordpress/dom-ready": "3.50.0", + "@wordpress/edit-post": "7.27.2", + "@wordpress/edit-site": "5.27.2", + "@wordpress/edit-widgets": "5.27.2", + "@wordpress/editor": "13.27.2", + "@wordpress/element": "5.27.0", + "@wordpress/escape-html": "2.50.0", + "@wordpress/format-library": "4.27.2", + "@wordpress/hooks": "3.50.0", + "@wordpress/html-entities": "3.50.0", + "@wordpress/i18n": "4.50.0", + "@wordpress/icons": "9.41.0", + "@wordpress/interactivity": "4.0.1", + "@wordpress/interactivity-router": "1.0.1", + "@wordpress/interface": "5.27.0", + "@wordpress/is-shallow-equal": "4.50.0", + "@wordpress/keyboard-shortcuts": "4.27.0", + "@wordpress/keycodes": "3.50.0", + "@wordpress/list-reusable-blocks": "4.27.0", + "@wordpress/media-utils": "4.41.0", + "@wordpress/notices": "4.18.0", + "@wordpress/nux": "8.12.0", + "@wordpress/patterns": "1.11.2", + "@wordpress/plugins": "6.18.0", + "@wordpress/preferences": "3.27.0", + "@wordpress/preferences-persistence": "1.42.0", + "@wordpress/primitives": "3.48.0", + "@wordpress/priority-queue": "2.50.0", + "@wordpress/private-apis": "0.32.0", + "@wordpress/redux-routine": "4.50.0", + "@wordpress/reusable-blocks": "4.27.2", + "@wordpress/rich-text": "6.27.0", + "@wordpress/router": "0.19.0", + "@wordpress/server-side-render": "4.27.1", + "@wordpress/shortcode": "3.50.0", + "@wordpress/style-engine": "1.33.1", + "@wordpress/sync": "0.12.0", + "@wordpress/token-list": "2.50.0", + "@wordpress/undo-manager": "0.10.0", + "@wordpress/url": "3.51.0", + "@wordpress/viewport": "5.27.0", + "@wordpress/warning": "2.50.0", + "@wordpress/widgets": "3.27.2", + "@wordpress/wordcount": "3.50.0", "backbone": "1.5.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", "framer-motion": "10.16.4", "hoverintent": "2.2.1", @@ -166,6 +170,7 @@ "polyfill-library": "4.8.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-is": "18.2.0", "regenerator-runtime": "0.14.0", "tslib": "2.6.2", "underscore": "1.13.6", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index ccb04303218ae..840bdad8fe977 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -59,6 +59,7 @@ /src/wp-includes/class-requests\.php /src/wp-includes/class-simplepie\.php /src/wp-includes/class-snoopy\.php + /src/wp-includes/class-avif-info\.php /src/wp-includes/deprecated\.php /src/wp-includes/ms-deprecated\.php /src/wp-includes/pluggable-deprecated\.php diff --git a/src/js/_enqueues/vendor/plupload/handlers.js b/src/js/_enqueues/vendor/plupload/handlers.js index b82a6e88479ad..71e248fb5ed9f 100644 --- a/src/js/_enqueues/vendor/plupload/handlers.js +++ b/src/js/_enqueues/vendor/plupload/handlers.js @@ -608,6 +608,11 @@ jQuery( document ).ready( function( $ ) { wpQueueError( pluploadL10n.noneditable_image ); up.removeFile( file ); return; + } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { + // Disallow uploading of AVIF images if the server cannot edit them. + wpQueueError( pluploadL10n.noneditable_image ); + up.removeFile( file ); + return; } fileQueued( file ); diff --git a/src/js/_enqueues/vendor/plupload/wp-plupload.js b/src/js/_enqueues/vendor/plupload/wp-plupload.js index 0fdebf77d1858..c0eb570657bf4 100644 --- a/src/js/_enqueues/vendor/plupload/wp-plupload.js +++ b/src/js/_enqueues/vendor/plupload/wp-plupload.js @@ -363,6 +363,11 @@ window.wp = window.wp || {}; error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); up.removeFile( file ); return; + } else if ( file.type === 'image/avif' && up.settings.avif_upload_error ) { + // Disallow uploading of AVIF images if the server cannot edit them. + error( pluploadL10n.noneditable_image, {}, file, 'no-retry' ); + up.removeFile( file ); + return; } // Generate attributes for a new `Attachment` model. diff --git a/src/js/_enqueues/vendor/thickbox/thickbox.js b/src/js/_enqueues/vendor/thickbox/thickbox.js index 5470467a1e0a8..e8b95677c1ad4 100644 --- a/src/js/_enqueues/vendor/thickbox/thickbox.js +++ b/src/js/_enqueues/vendor/thickbox/thickbox.js @@ -76,7 +76,7 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic baseURL = url; } - var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$|\.webp$/; + var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$|\.webp$|\.avif$/; var urlType = baseURL.toLowerCase().match(urlString); if(urlType == '.jpg' || @@ -84,7 +84,8 @@ function tb_show(caption, url, imageGroup) {//function called when the user clic urlType == '.png' || urlType == '.gif' || urlType == '.bmp' || - urlType == '.webp' + urlType == '.webp' || + urlType == '.avif' ){//code to show images TB_PrevCaption = ""; diff --git a/src/js/_enqueues/wp/updates.js b/src/js/_enqueues/wp/updates.js index a994fda694ae9..a11f47ab54871 100644 --- a/src/js/_enqueues/wp/updates.js +++ b/src/js/_enqueues/wp/updates.js @@ -413,6 +413,31 @@ } }; + /** + * Sends a message from a modal to the main screen to update buttons in plugin cards. + * + * @since 6.5.0 + * + * @param {Object} data An object of data to use for the button. + * @param {string} data.slug The plugin's slug. + * @param {string} data.text The text to use for the button. + * @param {string} data.ariaLabel The value for the button's aria-label attribute. An empty string removes the attribute. + * @param {string=} data.status Optional. An identifier for the status. + * @param {string=} data.removeClasses Optional. A space-separated list of classes to remove from the button. + * @param {string=} data.addClasses Optional. A space-separated list of classes to add to the button. + * @param {string=} data.href Optional. The button's URL. + * @param {string=} data.pluginName Optional. The plugin's name. + * @param {string=} data.plugin Optional. The plugin file, relative to the plugins directory. + */ + wp.updates.setCardButtonStatus = function( data ) { + var target = window.parent === window ? null : window.parent; + + $.support.postMessage = !! window.postMessage; + if ( false !== $.support.postMessage && null !== target && -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) { + target.postMessage( JSON.stringify( data ), window.location.origin ); + } + }; + /** * Decrements the update counts throughout the various menus. * @@ -452,7 +477,8 @@ */ wp.updates.updatePlugin = function( args ) { var $updateRow, $card, $message, message, - $adminBarUpdates = $( '#wp-admin-bar-updates' ); + $adminBarUpdates = $( '#wp-admin-bar-updates' ), + buttonText = __( 'Updating...' ); args = _.extend( { success: wp.updates.updatePluginSuccess, @@ -468,7 +494,7 @@ $updateRow.find( '.plugin-title strong' ).text() ); } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { - $card = $( '.plugin-card-' + args.slug ); + $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ); $message = $card.find( '.update-now' ).addClass( 'updating-message' ); message = sprintf( /* translators: %s: Plugin name and version. */ @@ -488,10 +514,22 @@ $message .attr( 'aria-label', message ) - .text( __( 'Updating...' ) ); + .text( buttonText ); $document.trigger( 'wp-plugin-updating', args ); + if ( 'plugin-information-footer' === $card.attr('id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'updating-plugin', + slug: args.slug, + addClasses: 'updating-message', + text: buttonText, + ariaLabel: message + } + ); + } + return wp.updates.ajax( 'update-plugin', args ); }; @@ -511,7 +549,13 @@ */ wp.updates.updatePluginSuccess = function( response ) { var $pluginRow, $updateMessage, newText, - $adminBarUpdates = $( '#wp-admin-bar-updates' ); + $adminBarUpdates = $( '#wp-admin-bar-updates' ), + buttonText = _x( 'Updated!', 'plugin' ), + ariaLabel = sprintf( + /* translators: %s: Plugin name and version. */ + _x( '%s updated!', 'plugin' ), + response.pluginName + ); if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) { $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' ) @@ -528,7 +572,7 @@ // Clear the "time to next auto-update" text. $pluginRow.find( '.auto-update-time' ).empty(); } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { - $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' ) + $updateMessage = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.update-now' ) .removeClass( 'updating-message' ) .addClass( 'button-disabled updated-message' ); } @@ -536,19 +580,25 @@ $adminBarUpdates.removeClass( 'spin' ); $updateMessage - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( '%s updated!', 'plugin' ), - response.pluginName - ) - ) - .text( _x( 'Updated!', 'plugin' ) ); + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); wp.a11y.speak( __( 'Update completed successfully.' ) ); - wp.updates.decrementCount( 'plugin' ); + if ( 'plugin_install_from_iframe' !== $updateMessage.attr( 'id' ) ) { + wp.updates.decrementCount( 'plugin' ); + } else { + wp.updates.setCardButtonStatus( + { + status: 'updated-plugin', + slug: response.slug, + removeClasses: 'updating-message', + addClasses: 'button-disabled updated-message', + text: buttonText, + ariaLabel: ariaLabel + } + ); + } $document.trigger( 'wp-plugin-update-success', response ); }; @@ -567,7 +617,7 @@ * @param {string} response.errorMessage The error that occurred. */ wp.updates.updatePluginError = function( response ) { - var $pluginRow, $card, $message, errorMessage, + var $pluginRow, $card, $message, errorMessage, buttonText, ariaLabel, $adminBarUpdates = $( '#wp-admin-bar-updates' ); if ( ! wp.updates.isValidResponse( response, 'update' ) ) { @@ -608,28 +658,32 @@ $message.find( 'p' ).removeAttr( 'aria-label' ); } } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) { - $card = $( '.plugin-card-' + response.slug ) - .addClass( 'plugin-card-update-failed' ) + buttonText = __( 'Update failed.' ); + + $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ) .append( wp.updates.adminNotice( { className: 'update-message notice-error notice-alt is-dismissible', message: errorMessage } ) ); + if ( $card.hasClass( 'plugin-card-' + response.slug ) ) { + $card.addClass( 'plugin-card-update-failed' ); + } + $card.find( '.update-now' ) - .text( __( 'Update failed.' ) ) + .text( buttonText ) .removeClass( 'updating-message' ); if ( response.pluginName ) { - $card.find( '.update-now' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( '%s update failed.', 'plugin' ), - response.pluginName - ) - ); + ariaLabel = sprintf( + /* translators: %s: Plugin name and version. */ + _x( '%s update failed.', 'plugin' ), + response.pluginName + ); + + $card.find( '.update-now' ).attr( 'aria-label', ariaLabel ); } else { + ariaLabel = ''; $card.find( '.update-now' ).removeAttr( 'aria-label' ); } @@ -652,6 +706,18 @@ wp.a11y.speak( errorMessage, 'assertive' ); + if ( 'plugin-information-footer' === $card.attr('id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'plugin-update-failed', + slug: response.slug, + removeClasses: 'updating-message', + text: buttonText, + ariaLabel: ariaLabel + } + ); + } + $document.trigger( 'wp-plugin-update-error', response ); }; @@ -668,8 +734,10 @@ * decorated with an abort() method. */ wp.updates.installPlugin = function( args ) { - var $card = $( '.plugin-card-' + args.slug ), - $message = $card.find( '.install-now' ); + var $card = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ), + $message = $card.find( '.install-now' ), + buttonText = __( 'Installing...' ), + ariaLabel; args = _.extend( { success: wp.updates.installPluginSuccess, @@ -684,17 +752,16 @@ $message.data( 'originaltext', $message.html() ); } + ariaLabel = sprintf( + /* translators: %s: Plugin name and version. */ + _x( 'Installing %s...', 'plugin' ), + $message.data( 'name' ) + ); + $message .addClass( 'updating-message' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( 'Installing %s...', 'plugin' ), - $message.data( 'name' ) - ) - ) - .text( __( 'Installing...' ) ); + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); wp.a11y.speak( __( 'Installing... please wait.' ) ); @@ -703,6 +770,18 @@ $document.trigger( 'wp-plugin-installing', args ); + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'installing-plugin', + slug: args.slug, + addClasses: 'updating-message', + text: buttonText, + ariaLabel: ariaLabel + } + ); + } + return wp.updates.ajax( 'install-plugin', args ); }; @@ -717,20 +796,19 @@ * @param {string} response.activateUrl URL to activate the just installed plugin. */ wp.updates.installPluginSuccess = function( response ) { - var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' ); + var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ), + buttonText = _x( 'Installed!', 'plugin' ), + ariaLabel = sprintf( + /* translators: %s: Plugin name and version. */ + _x( '%s installed!', 'plugin' ), + response.pluginName + ); $message .removeClass( 'updating-message' ) .addClass( 'updated-message installed button-disabled' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( '%s installed!', 'plugin' ), - response.pluginName - ) - ) - .text( _x( 'Installed!', 'plugin' ) ); + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); wp.a11y.speak( __( 'Installation completed successfully.' ) ); @@ -738,36 +816,23 @@ if ( response.activateUrl ) { setTimeout( function() { + wp.updates.checkPluginDependencies( { + slug: response.slug + } ); + }, 1000 ); + } - // Transform the 'Install' button into an 'Activate' button. - $message.removeClass( 'install-now installed button-disabled updated-message' ) - .addClass( 'activate-now button-primary' ) - .attr( 'href', response.activateUrl ); - - if ( 'plugins-network' === pagenow ) { - $message - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name. */ - _x( 'Network Activate %s', 'plugin' ), - response.pluginName - ) - ) - .text( __( 'Network Activate' ) ); - } else { - $message - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name. */ - _x( 'Activate %s', 'plugin' ), - response.pluginName - ) - ) - .text( __( 'Activate' ) ); + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'installed-plugin', + slug: response.slug, + removeClasses: 'updating-message', + addClasses: 'updated-message installed button-disabled', + text: buttonText, + ariaLabel: ariaLabel } - }, 1000 ); + ); } }; @@ -783,8 +848,14 @@ * @param {string} response.errorMessage The error that occurred. */ wp.updates.installPluginError = function( response ) { - var $card = $( '.plugin-card-' + response.slug ), + var $card = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ), $button = $card.find( '.install-now' ), + buttonText = __( 'Installation failed.' ), + ariaLabel = sprintf( + /* translators: %s: Plugin name and version. */ + _x( '%s installation failed', 'plugin' ), + $button.data( 'name' ) + ), errorMessage; if ( ! wp.updates.isValidResponse( response, 'install' ) ) { @@ -817,21 +888,334 @@ $button .removeClass( 'updating-message' ).addClass( 'button-disabled' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( '%s installation failed', 'plugin' ), - $button.data( 'name' ) - ) - ) - .text( __( 'Installation failed.' ) ); + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); wp.a11y.speak( errorMessage, 'assertive' ); + wp.updates.setCardButtonStatus( + { + status: 'plugin-install-failed', + slug: response.slug, + removeClasses: 'updating-message', + addClasses: 'button-disabled', + text: buttonText, + ariaLabel: ariaLabel + } + ); + $document.trigger( 'wp-plugin-install-error', response ); }; + /** + * Sends an Ajax request to the server to check a plugin's dependencies. + * + * @since 6.5.0 + * + * @param {Object} args Arguments. + * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository. + * @param {checkPluginDependenciesSuccess=} args.success Optional. Success callback. Default: wp.updates.checkPluginDependenciesSuccess + * @param {checkPluginDependenciesError=} args.error Optional. Error callback. Default: wp.updates.checkPluginDependenciesError + * @return {$.promise} A jQuery promise that represents the request, + * decorated with an abort() method. + */ + wp.updates.checkPluginDependencies = function( args ) { + args = _.extend( { + success: wp.updates.checkPluginDependenciesSuccess, + error: wp.updates.checkPluginDependenciesError + }, args ); + + wp.a11y.speak( __( 'Checking plugin dependencies... please wait.' ) ); + $document.trigger( 'wp-checking-plugin-dependencies', args ); + + return wp.updates.ajax( 'check_plugin_dependencies', args ); + }; + + /** + * Updates the UI appropriately after a successful plugin dependencies check. + * + * @since 6.5.0 + * + * @param {Object} response Response from the server. + * @param {string} response.slug Slug of the checked plugin. + * @param {string} response.pluginName Name of the checked plugin. + * @param {string} response.plugin The plugin file, relative to the plugins directory. + * @param {string} response.activateUrl URL to activate the just checked plugin. + */ + wp.updates.checkPluginDependenciesSuccess = function( response ) { + var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ), + buttonText, ariaLabel; + + // Transform the 'Install' button into an 'Activate' button. + $message + .removeClass( 'install-now installed button-disabled updated-message' ) + .addClass( 'activate-now button-primary' ) + .attr( 'href', response.activateUrl ); + + wp.a11y.speak( __( 'Plugin dependencies check completed successfully.' ) ); + $document.trigger( 'wp-check-plugin-dependencies-success', response ); + + if ( 'plugins-network' === pagenow ) { + buttonText = _x( 'Network Activate' ); + ariaLabel = sprintf( + /* translators: %s: Plugin name. */ + _x( 'Network Activate %s', 'plugin' ), + response.pluginName + ); + + $message + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); + } else { + buttonText = _x( 'Activate', 'plugin' ); + ariaLabel = sprintf( + /* translators: %s: Plugin name. */ + _x( 'Activate %s', 'plugin' ), + response.pluginName + ); + + $message + .attr( 'aria-label', ariaLabel ) + .attr( 'data-name', response.pluginName ) + .attr( 'data-slug', response.slug ) + .attr( 'data-plugin', response.plugin ) + .text( buttonText ); + } + + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'dependencies-check-success', + slug: response.slug, + removeClasses: 'install-now installed button-disabled updated-message', + addClasses: 'activate-now button-primary', + text: buttonText, + ariaLabel: ariaLabel, + pluginName: response.pluginName, + plugin: response.plugin, + href: response.activateUrl + } + ); + } + }; + + /** + * Updates the UI appropriately after a failed plugin dependencies check. + * + * @since 6.5.0 + * + * @param {Object} response Response from the server. + * @param {string} response.slug Slug of the plugin to be checked. + * @param {string=} response.pluginName Optional. Name of the plugin to be checked. + * @param {string} response.errorCode Error code for the error that occurred. + * @param {string} response.errorMessage The error that occurred. + */ + wp.updates.checkPluginDependenciesError = function( response ) { + var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.install-now' ), + buttonText = __( 'Activate' ), + ariaLabel = sprintf( + /* translators: 1: Plugin name, 2. The reason the plugin cannot be activated. */ + _x( 'Cannot activate %1$s. %2$s', 'plugin' ), + response.pluginName, + response.errorMessage + ), + errorMessage; + + if ( ! wp.updates.isValidResponse( response, 'check-dependencies' ) ) { + return; + } + + errorMessage = sprintf( + /* translators: %s: Error string for a failed activation. */ + __( 'Activation failed: %s' ), + response.errorMessage + ); + + wp.a11y.speak( errorMessage, 'assertive' ); + $document.trigger( 'wp-check-plugin-dependencies-error', response ); + + $message + .removeClass( 'install-now installed updated-message' ) + .addClass( 'activate-now button-primary' ) + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); + + if ( 'plugin-information-footer' === $message.parent().attr('id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'dependencies-check-failed', + slug: response.slug, + removeClasses: 'install-now installed updated-message', + addClasses: 'activate-now button-primary', + text: buttonText, + ariaLabel: ariaLabel + } + ); + } + }; + + /** + * Sends an Ajax request to the server to activate a plugin. + * + * @since 6.5.0 + * + * @param {Object} args Arguments. + * @param {string} args.name The name of the plugin. + * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository. + * @param {string} args.plugin The plugin file, relative to the plugins directory. + * @param {activatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.activatePluginSuccess + * @param {activatePluginError=} args.error Optional. Error callback. Default: wp.updates.activatePluginError + * @return {$.promise} A jQuery promise that represents the request, + * decorated with an abort() method. + */ + wp.updates.activatePlugin = function( args ) { + var $message = $( '.plugin-card-' + args.slug + ', #plugin-information-footer' ).find( '.activate-now, .activating-message' ); + + args = _.extend( { + success: wp.updates.activatePluginSuccess, + error: wp.updates.activatePluginError + }, args ); + + wp.a11y.speak( __( 'Activating... please wait.' ) ); + $document.trigger( 'wp-activating-plugin', args ); + + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'activating-plugin', + slug: args.slug, + removeClasses: 'installed updated-message button-primary', + addClasses: 'activating-message', + text: _x( 'Activating...', 'plugin' ), + ariaLabel: sprintf( + /* translators: %s: Plugin name. */ + _x( 'Activating %s', 'plugin' ), + args.name + ) + } + ); + } + + return wp.updates.ajax( 'activate-plugin', args ); + }; + + /** + * Updates the UI appropriately after a successful plugin activation. + * + * @since 6.5.0 + * + * @param {Object} response Response from the server. + * @param {string} response.slug Slug of the activated plugin. + * @param {string} response.pluginName Name of the activated plugin. + * @param {string} response.plugin The plugin file, relative to the plugins directory. + */ + wp.updates.activatePluginSuccess = function( response ) { + var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ), + buttonText = _x( 'Activated!', 'plugin' ), + ariaLabel = sprintf( + /* translators: %s: The plugin name. */ + '%s activated successfully.', + response.pluginName + ); + + wp.a11y.speak( __( 'Activation completed successfully.' ) ); + $document.trigger( 'wp-plugin-activate-success', response ); + + $message + .removeClass( 'activating-message' ) + .addClass( 'activated-message button-disabled' ) + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); + + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'activated-plugin', + slug: response.slug, + removeClasses: 'activating-message', + addClasses: 'activated-message button-disabled', + text: buttonText, + ariaLabel: ariaLabel + } + ); + } + + setTimeout( function() { + $message.removeClass( 'activated-message' ) + .text( _x( 'Active', 'plugin' ) ); + + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'plugin-active', + slug: response.slug, + removeClasses: 'activated-message', + text: _x( 'Active', 'plugin' ), + ariaLabel: sprintf( + /* translators: %s: The plugin name. */ + '%s is active.', + response.pluginName + ) + } + ); + } + }, 1000 ); + }; + + /** + * Updates the UI appropriately after a failed plugin activation. + * + * @since 6.5.0 + * + * @param {Object} response Response from the server. + * @param {string} response.slug Slug of the plugin to be activated. + * @param {string=} response.pluginName Optional. Name of the plugin to be activated. + * @param {string} response.errorCode Error code for the error that occurred. + * @param {string} response.errorMessage The error that occurred. + */ + wp.updates.activatePluginError = function( response ) { + var $message = $( '.plugin-card-' + response.slug + ', #plugin-information-footer' ).find( '.activating-message' ), + buttonText = __( 'Activation failed.' ), + ariaLabel = sprintf( + /* translators: %s: Plugin name. */ + _x( '%s activation failed', 'plugin' ), + response.pluginName + ), + errorMessage; + + if ( ! wp.updates.isValidResponse( response, 'activate' ) ) { + return; + } + + errorMessage = sprintf( + /* translators: %s: Error string for a failed activation. */ + __( 'Activation failed: %s' ), + response.errorMessage + ); + + wp.a11y.speak( errorMessage, 'assertive' ); + $document.trigger( 'wp-plugin-activate-error', response ); + + $message + .removeClass( 'install-now installed activating-message' ) + .addClass( 'button-disabled' ) + .attr( 'aria-label', ariaLabel ) + .text( buttonText ); + + if ( 'plugin-information-footer' === $message.parent().attr( 'id' ) ) { + wp.updates.setCardButtonStatus( + { + status: 'plugin-activation-failed', + slug: response.slug, + removeClasses: 'install-now installed activating-message', + addClasses: 'button-disabled', + text: buttonText, + ariaLabel: ariaLabel + } + ); + } + }; + /** * Updates the UI appropriately after a successful importer install. * @@ -1970,6 +2354,16 @@ errorMessage = __( 'Installation failed: %s' ); break; + case 'check-dependencies': + /* translators: %s: Error string for a failed dependencies check. */ + errorMessage = __( 'Dependencies check failed: %s' ); + break; + + case 'activate': + /* translators: %s: Error string for a failed activation. */ + errorMessage = __( 'Activation failed: %s' ); + break; + case 'delete': /* translators: %s: Error string for a failed deletion. */ errorMessage = __( 'Deletion failed: %s' ); @@ -2025,7 +2419,7 @@ }; $( function() { - var $pluginFilter = $( '#plugin-filter' ), + var $pluginFilter = $( '#plugin-filter, #plugin-information-footer' ), $bulkActionForm = $( '#bulk-action-form' ), $filesystemForm = $( '#request-filesystem-credentials-form' ), $filesystemModal = $( '#request-filesystem-credentials-dialog' ), @@ -2241,6 +2635,44 @@ } ); } ); + /** + * Click handler for plugin activations in plugin activation view. + * + * @since 6.5.0 + * + * @param {Event} event Event interface. + */ + $pluginFilter.on( 'click', '.activate-now', function( event ) { + var $activateButton = $( event.target ); + + event.preventDefault(); + + if ( $activateButton.hasClass( 'activating-message' ) || $activateButton.hasClass( 'button-disabled' ) ) { + return; + } + + $activateButton + .removeClass( 'activate-now button-primary' ) + .addClass( 'activating-message' ) + .attr( + 'aria-label', + sprintf( + /* translators: %s: Plugin name. */ + _x( 'Activating %s', 'plugin' ), + $activateButton.data( 'name' ) + ) + ) + .text( _x( 'Activating...', 'plugin' ) ); + + wp.updates.activatePlugin( + { + name: $activateButton.data( 'name' ), + slug: $activateButton.data( 'slug' ), + plugin: $activateButton.data( 'plugin' ) + } + ); + }); + /** * Click handler for importer plugins installs in the Import screen. * @@ -2766,35 +3198,6 @@ target.postMessage( JSON.stringify( update ), window.location.origin ); } ); - /** - * Click handler for installing a plugin from the details modal on `plugin-install.php`. - * - * @since 4.6.0 - * - * @param {Event} event Event interface. - */ - $( '#plugin_install_from_iframe' ).on( 'click', function( event ) { - var target = window.parent === window ? null : window.parent, - install; - - $.support.postMessage = !! window.postMessage; - - if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) { - return; - } - - event.preventDefault(); - - install = { - action: 'install-plugin', - data: { - slug: $( this ).data( 'slug' ) - } - }; - - target.postMessage( JSON.stringify( install ), window.location.origin ); - } ); - /** * Handles postMessage events. * @@ -2818,7 +3221,45 @@ return; } - if ( ! message || 'undefined' === typeof message.action ) { + if ( ! message ) { + return; + } + + if ( + 'undefined' !== typeof message.status && + 'undefined' !== typeof message.slug && + 'undefined' !== typeof message.text && + 'undefined' !== typeof message.ariaLabel + ) { + var $card = $( '.plugin-card-' + message.slug ), + $message = $card.find( '[data-slug="' + message.slug + '"]' ); + + if ( 'undefined' !== typeof message.removeClasses ) { + $message.removeClass( message.removeClasses ); + } + + if ( 'undefined' !== typeof message.addClasses ) { + $message.addClass( message.addClasses ); + } + + if ( '' === message.ariaLabel ) { + $message.removeAttr( 'aria-label' ); + } else { + $message.attr( 'aria-label', message.ariaLabel ); + } + + if ( 'dependencies-check-success' === message.status ) { + $message + .attr( 'data-name', message.pluginName ) + .attr( 'data-slug', message.slug ) + .attr( 'data-plugin', message.plugin ) + .attr( 'href', message.href ); + } + + $message.text( message.text ); + } + + if ( 'undefined' === typeof message.action || 'undefined' === typeof message.data.slug ) { return; } @@ -2832,10 +3273,6 @@ case 'install-plugin': case 'update-plugin': - /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ - window.tb_remove(); - /* jscs:enable */ - message.data = wp.updates._addCallbacks( message.data, message.action ); wp.updates.queue.push( message ); diff --git a/src/js/media/controllers/library.js b/src/js/media/controllers/library.js index 2acc89a58692e..126ce8d7837fb 100644 --- a/src/js/media/controllers/library.js +++ b/src/js/media/controllers/library.js @@ -196,7 +196,7 @@ Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Librar isImageAttachment: function( attachment ) { // If uploading, we know the filename but not the mime type. if ( attachment.get('uploading') ) { - return /\.(jpe?g|png|gif|webp)$/i.test( attachment.get('filename') ); + return /\.(jpe?g|png|gif|webp|avif)$/i.test( attachment.get('filename') ); } return attachment.get('type') === 'image'; diff --git a/src/js/media/views/attachment.js b/src/js/media/views/attachment.js index a85b381c7017a..6f9f7f5001640 100644 --- a/src/js/media/views/attachment.js +++ b/src/js/media/views/attachment.js @@ -120,7 +120,7 @@ Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{ options.can.save = !! options.nonces.update; } - if ( this.controller.state().get('allowLocalEdits') ) { + if ( this.controller.state().get('allowLocalEdits') && ! options.uploading ) { options.allowLocalEdits = true; } diff --git a/src/wp-admin/admin-ajax.php b/src/wp-admin/admin-ajax.php index fb191100299ce..b6645fd5ca0e8 100644 --- a/src/wp-admin/admin-ajax.php +++ b/src/wp-admin/admin-ajax.php @@ -117,6 +117,7 @@ 'parse-media-shortcode', 'destroy-sessions', 'install-plugin', + 'activate-plugin', 'update-plugin', 'crop-image', 'generate-password', @@ -169,6 +170,9 @@ add_action( 'wp_ajax_nopriv_heartbeat', 'wp_ajax_nopriv_heartbeat', 1 ); +// Register Plugin Dependencies Ajax calls. +add_action( 'wp_ajax_check_plugin_dependencies', array( 'WP_Plugin_Dependencies', 'check_plugin_dependencies_during_ajax' ) ); + $action = $_REQUEST['action']; if ( is_user_logged_in() ) { diff --git a/src/wp-admin/css/common.css b/src/wp-admin/css/common.css index 2e6e605ce209e..98555d1c6c9a1 100644 --- a/src/wp-admin/css/common.css +++ b/src/wp-admin/css/common.css @@ -279,11 +279,9 @@ a:focus .media-icon img, a:focus .plugin-icon, .wp-person a:focus .gravatar { color: #043959; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); - /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } #adminmenu a:focus { @@ -844,9 +842,9 @@ img.emoji { } .tagchecklist .ntdelbutton:focus .remove-tag-icon:before { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .key-labels label { @@ -1460,10 +1458,9 @@ div.error p, } .notice-dismiss:focus { - outline: none; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .notice-success, @@ -1500,6 +1497,22 @@ div.error { background-color: #f0f6fc; } +#plugin-information-footer .update-now:not(.button-disabled):before { + color: #d63638; + content: "\f463"; + display: inline-block; + font: normal 20px/1 dashicons; + margin: -3px 5px 0 -2px; + speak: never; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + vertical-align: middle; +} + +#plugin-information-footer .notice { + margin-top: -5px; +} + .update-message p:before, .updating-message p:before, .updated-message p:before, @@ -1507,7 +1520,9 @@ div.error { .button.updating-message:before, .button.updated-message:before, .button.installed:before, -.button.installing:before { +.button.installing:before, +.button.activating-message:before, +.button.activated-message:before { display: inline-block; font: normal 20px/1 'dashicons'; -webkit-font-smoothing: antialiased; @@ -1544,7 +1559,8 @@ div.error { .updating-message p:before, .import-php .updating-message:before, .button.updating-message:before, -.button.installing:before { +.button.installing:before, +.button.activating-message:before { color: #d63638; content: "\f463"; } @@ -1554,6 +1570,7 @@ div.error { .import-php .updating-message:before, .button.updating-message:before, .button.installing:before, +.button.activating-message:before, .plugins .column-auto-updates .dashicons-update.spin, .theme-overlay .theme-autoupdate .dashicons-update.spin { animation: rotation 2s infinite linear; @@ -1564,6 +1581,7 @@ div.error { .import-php .updating-message:before, .button.updating-message:before, .button.installing:before, + .button.activating-message:before, .plugins .column-auto-updates .dashicons-update.spin, .theme-overlay .theme-autoupdate .dashicons-update.spin { animation: none; @@ -1577,7 +1595,8 @@ div.error { /* Updated icon (check mark). */ .updated-message p:before, .installed p:before, -.button.updated-message:before { +.button.updated-message:before, +.button.activated-message:before { color: #68de7c; content: "\f147"; } @@ -1662,19 +1681,37 @@ p.auto-update-status { .button.updating-message:before, .button.updated-message:before, .button.installed:before, -.button.installing:before { +.button.installing:before, +.button.activated-message:before, +.button.activating-message:before { margin: 3px 5px 0 -2px; } -.button-primary.updating-message:before { +#plugin-information-footer .button.installed:before, +#plugin-information-footer .button.installing:before, +#plugin-information-footer .button.updating-message:before, +#plugin-information-footer .button.updated-message:before, +#plugin-information-footer .button.activated-message:before, +#plugin-information-footer .button.activating-message:before { + margin: 9px 5px 0 -2px; +} + +#plugin-information-footer .button.update-now.updating-message:before { + margin: -3px 5px 0 -2px; +} + +.button-primary.updating-message:before, +.button-primary.activating-message:before { color: #fff; } -.button-primary.updated-message:before { +.button-primary.updated-message:before, +.button-primary.activated-message:before { color: #9ec2e6; } -.button.updated-message { +.button.updated-message, +.button.activated-message { transition-property: border, background, color; transition-duration: .05s; transition-timing-function: ease-in-out; @@ -1764,8 +1801,10 @@ p.auto-update-status { } #screen-meta-links .show-settings:focus { - border-color: #4f94d4; - box-shadow: 0 0 3px rgba(34, 113, 177, 0.8); + border-color: #2271b1; + box-shadow: 0 0 0 1px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } #screen-meta-links .show-settings:active { @@ -2103,8 +2142,8 @@ html.wp-toolbar { .postbox .handle-order-higher, .postbox .handle-order-lower, .postbox .handlediv { - width: 36px; - height: 36px; + width: 1.62rem; + height: 1.62rem; margin: 0; padding: 0; border: 0; @@ -3021,7 +3060,6 @@ div.action-links { } @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { #TB_window.plugin-details-modal.thickbox-loading:before { @@ -3147,11 +3185,10 @@ img { .postbox .handle-order-higher:focus, .postbox .handle-order-lower:focus, .postbox .handlediv:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: inset 0 0 0 2px #2271b1; + border-radius: 50%; /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .postbox .handle-order-higher:focus .order-higher-indicator::before, @@ -3176,7 +3213,6 @@ img { font-family: Consolas, Monaco, monospace; font-size: 13px; background: #f6f7f7; - -o-tab-size: 4; tab-size: 4; } @@ -3251,12 +3287,17 @@ img { [role="treeitem"] { outline: 0; } + +[role="treeitem"] a:focus, [role="treeitem"] .folder-label.focus { color: #043959; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + /* Reset default focus style. */ + box-shadow: none; + /* Use an inset outline instead, so it's visible also over the current file item. */ + outline: 2px solid #2271b1; + outline-offset: -2px; } + [role="treeitem"].hover, [role="treeitem"] .folder-label.hover { background-color: #f0f0f1; @@ -3398,6 +3439,10 @@ img { text-decoration: none; } +#templateside li.current-file > a { + padding-bottom: 0; +} + #templateside li:not(.howto) > a:first-of-type { padding-top: 0; } @@ -3483,13 +3528,13 @@ img { .accordion-section-title:hover:after { color: #1d2327; /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .widget-top .widget-action:focus .toggle-indicator:before { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .control-section .accordion-section-title:after, @@ -3765,7 +3810,6 @@ img { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { /* Back-compat for pre-3.8 */ div.star-holder, diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index 9422d0cc8c65e..ca21fe2889a33 100644 --- a/src/wp-admin/css/customize-controls.css +++ b/src/wp-admin/css/customize-controls.css @@ -1566,10 +1566,9 @@ p.customize-section-description { } .customize-control-header .choice:focus { - outline: none; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 3px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .customize-control-header .uploaded div:last-child > .choice { @@ -1604,7 +1603,6 @@ p.customize-section-description { font-family: Consolas, Monaco, monospace; font-size: 12px; padding: 6px 8px; - -o-tab-size: 2; tab-size: 2; } .customize-control-code_editor textarea, @@ -2680,9 +2678,9 @@ body.adding-widget .add-new-widget:before, #available-widgets-filter .clear-results:focus, #available-menu-items-search .clear-results:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } #available-menu-items-search .search-icon:after, diff --git a/src/wp-admin/css/customize-nav-menus.css b/src/wp-admin/css/customize-nav-menus.css index 46ac3066efd1d..c645bb5f11d00 100644 --- a/src/wp-admin/css/customize-nav-menus.css +++ b/src/wp-admin/css/customize-nav-menus.css @@ -271,7 +271,7 @@ .customize-screen-options-toggle:focus, #customize-controls .customize-info .customize-help-toggle:focus { /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .customize-screen-options-toggle:before { @@ -864,9 +864,9 @@ li.assigned-to-menu-location .add-new-menu-item { .menu-delete:focus, .menu-item-bar .item-delete:focus:before, #available-menu-items .item-add:focus:before { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } diff --git a/src/wp-admin/css/deprecated-media.css b/src/wp-admin/css/deprecated-media.css index 359fc59e3c3b5..36fafeb65f76b 100644 --- a/src/wp-admin/css/deprecated-media.css +++ b/src/wp-admin/css/deprecated-media.css @@ -404,7 +404,6 @@ table.not-image tr.image-only { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .image-align-none-label { diff --git a/src/wp-admin/css/edit.css b/src/wp-admin/css/edit.css index 7973154666a42..757d676b727f5 100644 --- a/src/wp-admin/css/edit.css +++ b/src/wp-admin/css/edit.css @@ -1293,8 +1293,9 @@ div.tabs-panel-inactive { } div.tabs-panel-active:focus { - box-shadow: inset 0 0 0 1px #4f94d4, inset 0 0 2px 1px rgba(79, 148, 212, 0.8); - outline: 0 none; + box-shadow: inset 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } #front-page-warning, @@ -1688,7 +1689,6 @@ table.links-table { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { #content-resize-handle, #post-body .wp_themeSkin .mceStatusbar a.mceResize { diff --git a/src/wp-admin/css/forms.css b/src/wp-admin/css/forms.css index 7274b0bcd7fee..16f7cb899fcde 100644 --- a/src/wp-admin/css/forms.css +++ b/src/wp-admin/css/forms.css @@ -22,10 +22,6 @@ textarea { resize: vertical; } -label { - cursor: pointer; -} - input, select { margin: 0 1px; @@ -287,8 +283,10 @@ textarea.disabled { input[type="file"]:disabled, input[type="file"].disabled, +input[type="file"][aria-disabled="true"], input[type="range"]:disabled, -input[type="range"].disabled { +input[type="range"].disabled, +input[type="range"][aria-disabled="true"] { background: none; box-shadow: none; cursor: default; @@ -296,13 +294,16 @@ input[type="range"].disabled { input[type="checkbox"]:disabled, input[type="checkbox"].disabled, +input[type="checkbox"][aria-disabled="true"], input[type="radio"]:disabled, input[type="radio"].disabled, +input[type="radio"][aria-disabled="true"], input[type="checkbox"]:disabled:checked:before, input[type="checkbox"].disabled:checked:before, input[type="radio"]:disabled:checked:before, input[type="radio"].disabled:checked:before { opacity: 0.7; + cursor: default; } /*------------------------------------------------------------------------------ @@ -356,6 +357,10 @@ input[type="radio"].disabled:checked:before { transform: none; } +.wp-core-ui select[aria-disabled="true"] { + cursor: default; +} + /* Reset Firefox inner outline that appears on :focus. */ /* This ruleset overrides the color change on :focus thus needs to be after select:focus. */ .wp-core-ui select:-moz-focusring { @@ -674,6 +679,13 @@ fieldset label, border-color: #68de7c; } +#pass1:focus, +#pass1-text:focus { + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; +} + .pw-weak { display: none; } diff --git a/src/wp-admin/css/install.css b/src/wp-admin/css/install.css index 144f99c97879c..29e85715f68a0 100644 --- a/src/wp-admin/css/install.css +++ b/src/wp-admin/css/install.css @@ -26,9 +26,9 @@ a:active { a:focus { color: #043959; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } h1, h2 { @@ -72,10 +72,6 @@ fieldset { margin: 0; } -label { - cursor: pointer; -} - #logo { margin: -130px auto 25px; padding: 0 0 25px; @@ -391,7 +387,6 @@ body.language-chooser { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .spinner { diff --git a/src/wp-admin/css/list-tables.css b/src/wp-admin/css/list-tables.css index 07cbc6229d07a..098c31a6d1148 100644 --- a/src/wp-admin/css/list-tables.css +++ b/src/wp-admin/css/list-tables.css @@ -261,8 +261,10 @@ } th .comment-grey-bubble { - height: 16px; width: 16px; + /* Make sure the link clickable area fills the entire table header. */ + position: relative; + top: 2px; } th .comment-grey-bubble:before { @@ -343,7 +345,6 @@ table.fixed { .fixed .column-comments { width: 5.5em; - padding: 8px 0; text-align: left; } @@ -537,9 +538,9 @@ th.sorted.desc:hover .sorting-indicator.asc:before { } .wp-list-table .toggle-row:focus:before { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .wp-list-table .toggle-row:active { @@ -585,8 +586,7 @@ th.sorted.desc:hover .sorting-indicator.asc:before { z-index: 1; } -.check-column input:where(:not(:disabled)):hover, -.check-column:hover input:where(:not(:disabled)) { +.check-column .label-covers-full-cell:hover + input:not(:disabled) { box-shadow: 0 0 0 1px #2271b1; } @@ -650,9 +650,11 @@ th.sorted a { padding: 8px; } -.fixed .column-comments.sortable a, -.fixed .column-comments.sorted a { - padding: 8px 0; +th.sortable a:focus, +th.sorted a:focus { + box-shadow: inset 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } th.sortable a span, @@ -1548,10 +1550,96 @@ div.action-links, line-height: 1.3; } -.plugin-card .name, .plugin-card .desc { - margin-left: 148px; /* icon + margin */ - margin-right: 128px; /* action links + margin */ + margin-inline: 0; +} + +.plugin-card .name, .plugin-card .desc > p { + margin-left: 148px; +} + +@media (min-width: 1101px) { + .plugin-card .name, .plugin-card .desc > p { + margin-right: 128px; + } +} + +@media (min-width: 481px) and (max-width: 781px) { + .plugin-card .name, .plugin-card .desc > p { + margin-right: 128px; + } +} + +.plugin-card .column-description { + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.plugin-card .column-description > p { + margin-top: 0; +} + +.plugin-card .column-description .authors { + order: 1; +} + +.plugin-card .column-description .plugin-dependencies { + order: 2; +} + +.plugin-card .column-description p:empty { + display: none; +} + +.plugin-card .plugin-dependencies { + background-color: #e5f5fa; + border-left: 3px solid #72aee6; + margin-bottom: .5em; + padding: 15px; +} + +.plugin-card .plugin-dependencies-explainer-text { + margin-block: 0; +} + +.plugin-card .plugin-dependency { + align-items: center; + display: flex; + flex-wrap: wrap; + margin-top: .5em; + column-gap: 1%; + row-gap: .5em; +} + +.plugin-card .plugin-dependency:nth-child(2), +.plugin-card .plugin-dependency:last-child { + margin-top: 1em; +} + +.plugin-card .plugin-dependency-name { + flex-basis: 74%; +} + +.plugin-card .plugin-dependency .more-details-link { + margin-left: auto; +} + +.rtl .plugin-card .plugin-dependency .more-details-link { + margin-right: auto; +} + +@media (max-width: 939px) { + .plugin-card .plugin-dependency-name { + flex-basis: 69%; + } + .plugin-card .plugin-dependency .more-details-link { + } +} + +.plugins #the-list .required-by, +.plugins #the-list .requires { + margin-top: 1em; } .plugin-card .action-links { @@ -2143,6 +2231,11 @@ div.action-links, padding: 10px 9px; /* reset from other list tables that have a label at this width */ } + #wpbody-content .wp-list-table.plugins .plugin-deleted-tr td, + #wpbody-content .wp-list-table.plugins .no-items td { + display: table-cell; + } + /* Plugin description hidden via Screen Options */ #wpbody-content .wp-list-table.plugins .desc.hidden { display: none; diff --git a/src/wp-admin/css/login.css b/src/wp-admin/css/login.css index fe790f0b42d1f..b9f488bd6c480 100644 --- a/src/wp-admin/css/login.css +++ b/src/wp-admin/css/login.css @@ -32,9 +32,9 @@ a:active { a:focus { color: #043959; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } p { diff --git a/src/wp-admin/css/media.css b/src/wp-admin/css/media.css index 623f808887e03..4463ae373d32a 100644 --- a/src/wp-admin/css/media.css +++ b/src/wp-admin/css/media.css @@ -356,9 +356,7 @@ } #find-posts-close:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; outline-offset: -2px; @@ -533,9 +531,7 @@ border color while dragging a file over the uploader drop area */ .media-frame.mode-grid .attachment:focus, .media-frame.mode-grid .selected.attachment:focus, .media-frame.mode-grid .attachment.details:focus { - box-shadow: - inset 0 0 2px 3px #f0f0f1, - inset 0 0 0 7px #4f94d4; + box-shadow: inset 0 0 0 2px #2271b1; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; outline-offset: -6px; @@ -1172,8 +1168,8 @@ border color while dragging a file over the uploader drop area */ .image-editor .imgedit-settings .imgedit-help-toggle:focus { color: #2271b1; - border-color: #4f94d4; - box-shadow: 0 0 3px rgba(34, 113, 177, 0.8); + border-color: #2271b1; + box-shadow: 0 0 0 1px #2271b1; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; } @@ -1272,7 +1268,6 @@ audio, video { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .imgedit-wait:before { background-image: url(../images/spinner-2x.gif); diff --git a/src/wp-admin/css/nav-menus.css b/src/wp-admin/css/nav-menus.css index 4fd178dbb0177..ff586a4ee7c98 100644 --- a/src/wp-admin/css/nav-menus.css +++ b/src/wp-admin/css/nav-menus.css @@ -103,7 +103,7 @@ ul.add-menu-item-tabs li { #nav-menu-bulk-actions-bottom { margin: 1em 0; - margin: calc( 1em + 9px ) 0 ; + margin: calc( 1em + 9px ) 0; } .bulk-actions input.button { @@ -732,9 +732,9 @@ body.menu-max-depth-11 { min-width: 1280px !important; } } .nav-menus-php .item-edit:focus:before { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } /* Menu editing */ @@ -991,7 +991,7 @@ body.menu-max-depth-11 { min-width: 1280px !important; } @media only screen and (min-width: 783px) { @supports (position: sticky) and (scroll-margin-bottom: 130px) { - + #nav-menu-footer { position: sticky; bottom: 0; diff --git a/src/wp-admin/css/revisions.css b/src/wp-admin/css/revisions.css index e523ee431ce93..46cf263b5e67c 100644 --- a/src/wp-admin/css/revisions.css +++ b/src/wp-admin/css/revisions.css @@ -558,7 +558,6 @@ div.revisions-controls > .wp-slider > .ui-slider-handle { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .revision-tick.completed-false { background-image: url(../images/spinner-2x.gif); diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css index 07f33565014e0..a68abbca04f94 100644 --- a/src/wp-admin/css/themes.css +++ b/src/wp-admin/css/themes.css @@ -176,12 +176,14 @@ body.js .theme-browser.search-loading { } .theme-browser .theme .more-details:focus { - box-shadow: 0 0 0 1px #fff, 0 0 0 3px #2271b1; + box-shadow: 0 0 0 2px #2271b1; } .theme-browser .theme.focus { - border-color: #4f94d4; - box-shadow: 0 0 2px rgba(79, 148, 212, 0.8); + border-color: #2271b1; + box-shadow: 0 0 0 1px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .theme-browser .theme.focus .more-details { @@ -1586,9 +1588,9 @@ body.full-overlay-active { .wp-full-overlay .collapse-sidebar:hover .collapse-sidebar-arrow, .wp-full-overlay .collapse-sidebar:focus .collapse-sidebar-arrow { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .wp-full-overlay .collapse-sidebar-label { @@ -1938,7 +1940,6 @@ body.full-overlay-active { * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .wp-full-overlay .collapse-sidebar-arrow { background-image: url(../images/arrows-2x.png); diff --git a/src/wp-admin/css/widgets.css b/src/wp-admin/css/widgets.css index 8faca8f9ac42d..e24140926eebc 100644 --- a/src/wp-admin/css/widgets.css +++ b/src/wp-admin/css/widgets.css @@ -336,9 +336,9 @@ } .sidebar-name .handlediv:focus .toggle-indicator:before { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .sidebar-name h2, diff --git a/src/wp-admin/images/bubble_bg-2x.gif b/src/wp-admin/images/bubble_bg-2x.gif index 8e34e01dcd4ea..21302a34dc133 100644 Binary files a/src/wp-admin/images/bubble_bg-2x.gif and b/src/wp-admin/images/bubble_bg-2x.gif differ diff --git a/src/wp-admin/images/loading.gif b/src/wp-admin/images/loading.gif index fdc589f809793..79d140e838ac3 100644 Binary files a/src/wp-admin/images/loading.gif and b/src/wp-admin/images/loading.gif differ diff --git a/src/wp-admin/images/media-button-music.gif b/src/wp-admin/images/media-button-music.gif index 3bcda105c6f50..daa9101833a4f 100644 Binary files a/src/wp-admin/images/media-button-music.gif and b/src/wp-admin/images/media-button-music.gif differ diff --git a/src/wp-admin/images/media-button-other.gif b/src/wp-admin/images/media-button-other.gif index cfe16a83dfd79..0a8920066c7af 100644 Binary files a/src/wp-admin/images/media-button-other.gif and b/src/wp-admin/images/media-button-other.gif differ diff --git a/src/wp-admin/images/wpspin_light-2x.gif b/src/wp-admin/images/wpspin_light-2x.gif index 08e47e8211c4d..978f585b983b2 100644 Binary files a/src/wp-admin/images/wpspin_light-2x.gif and b/src/wp-admin/images/wpspin_light-2x.gif differ diff --git a/src/wp-admin/images/wpspin_light.gif b/src/wp-admin/images/wpspin_light.gif index fbf9be46c1094..b9b7ae4875168 100644 Binary files a/src/wp-admin/images/wpspin_light.gif and b/src/wp-admin/images/wpspin_light.gif differ diff --git a/src/wp-admin/images/xit.gif b/src/wp-admin/images/xit.gif index b11c5d43e9bce..9e62856adb47e 100644 Binary files a/src/wp-admin/images/xit.gif and b/src/wp-admin/images/xit.gif differ diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index b8e8246eb7c52..7541b28d51fdc 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -4578,6 +4578,56 @@ function wp_ajax_install_plugin() { wp_send_json_success( $status ); } +/** + * Handles activating a plugin via AJAX. + * + * @since 6.5.0 + */ +function wp_ajax_activate_plugin() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) { + wp_send_json_error( + array( + 'slug' => '', + 'pluginName' => '', + 'plugin' => '', + 'errorCode' => 'no_plugin_specified', + 'errorMessage' => __( 'No plugin specified.' ), + ) + ); + } + + $status = array( + 'activate' => 'plugin', + 'slug' => wp_unslash( $_POST['slug'] ), + 'pluginName' => wp_unslash( $_POST['name'] ), + 'plugin' => wp_unslash( $_POST['plugin'] ), + ); + + if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' ); + wp_send_json_error( $status ); + } + + if ( is_plugin_active( $status['plugin'] ) ) { + $status['errorMessage'] = sprintf( + /* translators: %s: Plugin name. */ + __( '%s is already active.' ), + $status['pluginName'] + ); + } + + $activated = activate_plugin( $status['plugin'] ); + + if ( is_wp_error( $activated ) ) { + $status['errorMessage'] = $activated->get_error_message(); + wp_send_json_error( $status ); + } + + wp_send_json_success( $status ); +} + /** * Handles updating a plugin via AJAX. * diff --git a/src/wp-admin/includes/class-custom-image-header.php b/src/wp-admin/includes/class-custom-image-header.php index a4b04bf3fc250..5c3271478b62f 100644 --- a/src/wp-admin/includes/class-custom-image-header.php +++ b/src/wp-admin/includes/class-custom-image-header.php @@ -934,7 +934,7 @@ public function step_2() {

- +
diff --git a/src/wp-admin/includes/class-file-upload-upgrader.php b/src/wp-admin/includes/class-file-upload-upgrader.php index 2b76e41ea61d1..1201c6d188920 100644 --- a/src/wp-admin/includes/class-file-upload-upgrader.php +++ b/src/wp-admin/includes/class-file-upload-upgrader.php @@ -69,6 +69,13 @@ public function __construct( $form, $urlholder ) { wp_die( $file['error'] ); } + if ( 'pluginzip' === $form || 'themezip' === $form ) { + if ( ! wp_zip_file_is_valid( $file['file'] ) ) { + wp_delete_file( $file['file'] ); + wp_die( __( 'Incompatible Archive.' ) ); + } + } + $this->filename = $_FILES[ $form ]['name']; $this->package = $file['file']; diff --git a/src/wp-admin/includes/class-language-pack-upgrader.php b/src/wp-admin/includes/class-language-pack-upgrader.php index 3c3d42a56a9c8..855dbe642ad25 100644 --- a/src/wp-admin/includes/class-language-pack-upgrader.php +++ b/src/wp-admin/includes/class-language-pack-upgrader.php @@ -409,12 +409,16 @@ public function clear_destination( $remote_destination ) { $files = array( $remote_destination . $language_update->language . '.po', $remote_destination . $language_update->language . '.mo', + $remote_destination . $language_update->language . '.l10n.php', $remote_destination . 'admin-' . $language_update->language . '.po', $remote_destination . 'admin-' . $language_update->language . '.mo', + $remote_destination . 'admin-' . $language_update->language . '.l10n.php', $remote_destination . 'admin-network-' . $language_update->language . '.po', $remote_destination . 'admin-network-' . $language_update->language . '.mo', + $remote_destination . 'admin-network-' . $language_update->language . '.l10n.php', $remote_destination . 'continents-cities-' . $language_update->language . '.po', $remote_destination . 'continents-cities-' . $language_update->language . '.mo', + $remote_destination . 'continents-cities-' . $language_update->language . '.l10n.php', ); $json_translation_files = glob( $language_directory . $language_update->language . '-*.json' ); @@ -427,6 +431,7 @@ public function clear_destination( $remote_destination ) { $files = array( $remote_destination . $language_update->slug . '-' . $language_update->language . '.po', $remote_destination . $language_update->slug . '-' . $language_update->language . '.mo', + $remote_destination . $language_update->slug . '-' . $language_update->language . '.l10n.php', ); $language_directory = $language_directory . $language_update->type . 's/'; diff --git a/src/wp-admin/includes/class-plugin-upgrader.php b/src/wp-admin/includes/class-plugin-upgrader.php index 091cfebc188ff..97343fcf9ded8 100644 --- a/src/wp-admin/includes/class-plugin-upgrader.php +++ b/src/wp-admin/includes/class-plugin-upgrader.php @@ -155,6 +155,12 @@ public function install( $package, $args = array() ) { // Force refresh of plugin update information. wp_clean_plugins_cache( $parsed_args['clear_update_cache'] ); + $all_plugin_data = get_option( 'plugin_data', array() ); + $plugin_file = $this->new_plugin_data['file']; + unset( $this->new_plugin_data['file'] ); + $all_plugin_data[ $plugin_file ] = $this->new_plugin_data; + update_option( 'plugin_data', $all_plugin_data ); + if ( $parsed_args['overwrite_package'] ) { /** * Fires when the upgrader has successfully overwritten a currently installed @@ -482,7 +488,16 @@ public function check_package( $source ) { foreach ( $files as $file ) { $info = get_plugin_data( $file, false, false ); if ( ! empty( $info['Name'] ) ) { - $this->new_plugin_data = $info; + $basename = basename( $file ); + $dirname = basename( dirname( $file ) ); + + if ( '.' === $dirname ) { + $plugin_file = $basename; + } else { + $plugin_file = "$dirname/$basename"; + } + $this->new_plugin_data = ( $info ); + $this->new_plugin_data['file'] = $plugin_file; break; } } diff --git a/src/wp-admin/includes/class-wp-plugin-install-list-table.php b/src/wp-admin/includes/class-wp-plugin-install-list-table.php index 7823f00b70996..132af29452ec2 100644 --- a/src/wp-admin/includes/class-wp-plugin-install-list-table.php +++ b/src/wp-admin/includes/class-wp-plugin-install-list-table.php @@ -525,6 +525,8 @@ public function display_rows() { // Remove any HTML from the description. $description = strip_tags( $plugin['short_description'] ); + $description .= $this->get_dependencies_notice( $plugin ); + /** * Filters the plugin card description on the Add Plugins screen. * @@ -555,102 +557,7 @@ public function display_rows() { $action_links = array(); - if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) { - $status = install_plugin_install_status( $plugin ); - - switch ( $status['status'] ) { - case 'install': - if ( $status['url'] ) { - if ( $compatible_php && $compatible_wp ) { - $action_links[] = sprintf( - '%s', - esc_attr( $plugin['slug'] ), - esc_url( $status['url'] ), - /* translators: %s: Plugin name and version. */ - esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ), - esc_attr( $name ), - __( 'Install Now' ) - ); - } else { - $action_links[] = sprintf( - '', - _x( 'Cannot Install', 'plugin' ) - ); - } - } - break; - - case 'update_available': - if ( $status['url'] ) { - if ( $compatible_php && $compatible_wp ) { - $action_links[] = sprintf( - '%s', - esc_attr( $status['file'] ), - esc_attr( $plugin['slug'] ), - esc_url( $status['url'] ), - /* translators: %s: Plugin name and version. */ - esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ), - esc_attr( $name ), - __( 'Update Now' ) - ); - } else { - $action_links[] = sprintf( - '', - _x( 'Cannot Update', 'plugin' ) - ); - } - } - break; - - case 'latest_installed': - case 'newer_installed': - if ( is_plugin_active( $status['file'] ) ) { - $action_links[] = sprintf( - '', - _x( 'Active', 'plugin' ) - ); - } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) { - if ( $compatible_php && $compatible_wp ) { - $button_text = __( 'Activate' ); - /* translators: %s: Plugin name. */ - $button_label = _x( 'Activate %s', 'plugin' ); - $activate_url = add_query_arg( - array( - '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ), - 'action' => 'activate', - 'plugin' => $status['file'], - ), - network_admin_url( 'plugins.php' ) - ); - - if ( is_network_admin() ) { - $button_text = __( 'Network Activate' ); - /* translators: %s: Plugin name. */ - $button_label = _x( 'Network Activate %s', 'plugin' ); - $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url ); - } - - $action_links[] = sprintf( - '%3$s', - esc_url( $activate_url ), - esc_attr( sprintf( $button_label, $plugin['name'] ) ), - $button_text - ); - } else { - $action_links[] = sprintf( - '', - _x( 'Cannot Activate', 'plugin' ) - ); - } - } else { - $action_links[] = sprintf( - '', - _x( 'Installed', 'plugin' ) - ); - } - break; - } - } + $action_links[] = wp_get_plugin_action_button( $name, $plugin, $compatible_php, $compatible_wp ); $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin['slug'] . @@ -828,4 +735,90 @@ public function display_rows() { echo ''; } } + + /** + * Returns a notice containing a list of dependencies required by the plugin. + * + * @since 6.5.0 + * + * @param array $plugin_data An array of plugin data. See {@see plugins_api()} + * for the list of possible values. + * @return string A notice containing a list of dependencies required by the plugin, + * or an empty string if none is required. + */ + protected function get_dependencies_notice( $plugin_data ) { + if ( empty( $plugin_data['requires_plugins'] ) ) { + return ''; + } + + $no_name_markup = '
%s
'; + $has_name_markup = '
%s %s
'; + + $dependencies_list = ''; + foreach ( $plugin_data['requires_plugins'] as $dependency ) { + $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $dependency ); + + if ( + false !== $dependency_data && + ! empty( $dependency_data['name'] ) && + ! empty( $dependency_data['slug'] ) && + ! empty( $dependency_data['version'] ) + ) { + $more_details_link = $this->get_more_details_link( $dependency_data['name'], $dependency_data['slug'] ); + $dependencies_list .= sprintf( $has_name_markup, esc_html( $dependency_data['name'] ), $more_details_link ); + continue; + } + + $result = plugins_api( 'plugin_information', array( 'slug' => $dependency ) ); + + if ( ! empty( $result->name ) ) { + $more_details_link = $this->get_more_details_link( $result->name, $result->slug ); + $dependencies_list .= sprintf( $has_name_markup, esc_html( $result->name ), $more_details_link ); + continue; + } + + $dependencies_list .= sprintf( $no_name_markup, esc_html( $dependency ) ); + } + + $dependencies_notice = sprintf( + '

%s

%s
', + '' . __( 'Additional plugins are required' ) . '', + $dependencies_list + ); + + return $dependencies_notice; + } + + /** + * Creates a 'More details' link for the plugin. + * + * @since 6.5.0 + * + * @param string $name The plugin's name. + * @param string $slug The plugin's slug. + * @return string The 'More details' link for the plugin. + */ + protected function get_more_details_link( $name, $slug ) { + $url = add_query_arg( + array( + 'tab' => 'plugin-information', + 'plugin' => $slug, + 'TB_iframe' => 'true', + 'width' => '600', + 'height' => '550', + ), + network_admin_url( 'plugin-install.php' ) + ); + + $more_details_link = sprintf( + '%4$s', + esc_url( $url ), + /* translators: %s: Plugin name. */ + sprintf( __( 'More information about %s' ), esc_html( $name ) ), + esc_attr( $name ), + __( 'More Details' ) + ); + + return $more_details_link; + } } diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php index 5c92fba7aad1c..ad0e6d7eb864a 100644 --- a/src/wp-admin/includes/class-wp-plugins-list-table.php +++ b/src/wp-admin/includes/class-wp-plugins-list-table.php @@ -754,6 +754,11 @@ public function single_row( $item ) { $compatible_php = is_php_version_compatible( $requires_php ); $compatible_wp = is_wp_version_compatible( $requires_wp ); + $has_dependents = WP_Plugin_Dependencies::has_dependents( $plugin_file ); + $has_active_dependents = WP_Plugin_Dependencies::has_active_dependents( $plugin_file ); + $has_unmet_dependencies = WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file ); + $has_circular_dependency = WP_Plugin_Dependencies::has_circular_dependency( $plugin_file ); + if ( 'mustuse' === $context ) { $is_active = true; } elseif ( 'dropins' === $context ) { @@ -796,26 +801,53 @@ public function single_row( $item ) { if ( $screen->in_admin( 'network' ) ) { if ( $is_active ) { if ( current_user_can( 'manage_network_plugins' ) ) { - $actions['deactivate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Network Deactivate' ) - ); + if ( $has_active_dependents ) { + $actions['deactivate'] = __( 'Deactivate' ) . + '' . + __( 'You cannot deactivate this plugin as other plugins require it.' ) . + ''; + + } else { + $deactivate_url = 'plugins.php?action=deactivate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['deactivate'] = sprintf( + '%s', + wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Network Deactivate' ) + ); + } } } else { if ( current_user_can( 'manage_network_plugins' ) ) { if ( $compatible_php && $compatible_wp ) { - $actions['activate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Network Activate' ) - ); + if ( $has_unmet_dependencies ) { + $actions['activate'] = __( 'Network Activate' ) . + '' . + __( 'You cannot activate this plugin as it has unmet requirements.' ) . + ''; + } else { + $activate_url = 'plugins.php?action=activate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['activate'] = sprintf( + '%s', + wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Network Activate' ) + ); + } } else { $actions['activate'] = sprintf( '%s', @@ -825,14 +857,27 @@ public function single_row( $item ) { } if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) { - $actions['delete'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=delete-selected&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'bulk-plugins' ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Delete' ) - ); + if ( $has_dependents && ! $has_circular_dependency ) { + $actions['delete'] = __( 'Delete' ) . + '' . + __( 'You cannot delete this plugin as other plugins require it.' ) . + ''; + } else { + $delete_url = 'plugins.php?action=delete-selected' . + '&checked[]=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['delete'] = sprintf( + '%s', + wp_nonce_url( $delete_url, 'bulk-plugins' ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Delete' ) + ); + } } } } else { @@ -846,20 +891,39 @@ public function single_row( $item ) { ); } elseif ( $is_active ) { if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) { - $actions['deactivate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Deactivate' ) - ); + if ( $has_active_dependents ) { + $actions['deactivate'] = __( 'Deactivate' ) . + '' . + __( 'You cannot deactivate this plugin as other plugins depend on it.' ) . + ''; + } else { + $deactivate_url = 'plugins.php?action=deactivate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['deactivate'] = sprintf( + '%s', + wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Deactivate' ) + ); + } } if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) { + $resume_url = 'plugins.php?action=resume' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + $actions['resume'] = sprintf( '%s', - wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ), + wp_nonce_url( $resume_url, 'resume-plugin_' . $plugin_file ), esc_attr( $plugin_id_attr ), /* translators: %s: Plugin name. */ esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ), @@ -869,14 +933,27 @@ public function single_row( $item ) { } else { if ( current_user_can( 'activate_plugin', $plugin_file ) ) { if ( $compatible_php && $compatible_wp ) { - $actions['activate'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Activate' ) - ); + if ( $has_unmet_dependencies ) { + $actions['activate'] = __( 'Activate' ) . + '' . + __( 'You cannot activate this plugin as it has unmet requirements.' ) . + ''; + } else { + $activate_url = 'plugins.php?action=activate' . + '&plugin=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['activate'] = sprintf( + '%s', + wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Activate' ) + ); + } } else { $actions['activate'] = sprintf( '%s', @@ -886,14 +963,27 @@ public function single_row( $item ) { } if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) { - $actions['delete'] = sprintf( - '%s', - wp_nonce_url( 'plugins.php?action=delete-selected&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'bulk-plugins' ), - esc_attr( $plugin_id_attr ), - /* translators: %s: Plugin name. */ - esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), - __( 'Delete' ) - ); + if ( $has_dependents && ! $has_circular_dependency ) { + $actions['delete'] = __( 'Delete' ) . + '' . + __( 'You cannot delete this plugin as other plugins require it.' ) . + ''; + } else { + $delete_url = 'plugins.php?action=delete-selected' . + '&checked[]=' . urlencode( $plugin_file ) . + '&plugin_status=' . $context . + '&paged=' . $page . + '&s=' . $s; + + $actions['delete'] = sprintf( + '%s', + wp_nonce_url( $delete_url, 'bulk-plugins' ), + esc_attr( $plugin_id_attr ), + /* translators: %s: Plugin name. */ + esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), + __( 'Delete' ) + ); + } } } // End if $is_active. } // End if $screen->in_admin( 'network' ). @@ -988,17 +1078,28 @@ public function single_row( $item ) { $class = $is_active ? 'active' : 'inactive'; $checkbox_id = 'checkbox_' . md5( $plugin_file ); + $disabled = ''; - if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ), true ) || ! $compatible_php ) { + if ( $has_active_dependents || $has_unmet_dependencies ) { + $disabled = 'disabled'; + } + + if ( + $restrict_network_active || + $restrict_network_only || + in_array( $status, array( 'mustuse', 'dropins' ), true ) || + ! $compatible_php + ) { $checkbox = ''; } else { $checkbox = sprintf( - '' . - '', - esc_attr( $plugin_file ), + '' . + '', $checkbox_id, /* translators: Hidden accessibility text. %s: Plugin name. */ - sprintf( __( 'Select %s' ), $plugin_data['Name'] ) + sprintf( __( 'Select %s' ), $plugin_data['Name'] ), + esc_attr( $plugin_file ) ); } @@ -1007,8 +1108,11 @@ public function single_row( $item ) { $plugin_name = $plugin_data['Name']; } - if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) - || ! $compatible_php || ! $compatible_wp + if ( + ! empty( $totals['upgrade'] ) && + ! empty( $plugin_data['update'] ) || + ! $compatible_php || + ! $compatible_wp ) { $class .= ' update'; } @@ -1057,15 +1161,19 @@ public function single_row( $item ) {
"; $plugin_meta = array(); + if ( ! empty( $plugin_data['Version'] ) ) { /* translators: %s: Plugin version number. */ $plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); } + if ( ! empty( $plugin_data['Author'] ) ) { $author = $plugin_data['Author']; + if ( ! empty( $plugin_data['AuthorURI'] ) ) { $author = '' . $plugin_data['Author'] . ''; } + /* translators: %s: Plugin author name. */ $plugin_meta[] = sprintf( __( 'By %s' ), $author ); } @@ -1149,6 +1257,24 @@ public function single_row( $item ) { echo '
'; + if ( $has_dependents ) { + $this->add_dependents_to_dependency_plugin_row( $plugin_file ); + } + + if ( WP_Plugin_Dependencies::has_dependencies( $plugin_file ) ) { + $this->add_dependencies_to_dependent_plugin_row( $plugin_file ); + } + + /** + * Fires after plugin row meta. + * + * @since 6.5.0 + * + * @param string $plugin_file Refer to {@see 'plugin_row_meta'} filter. + * @param array $plugin_data Refer to {@see 'plugin_row_meta'} filter. + */ + do_action( 'after_plugin_row_meta', $plugin_file, $plugin_data ); + if ( $paused ) { $notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' ); @@ -1391,4 +1517,111 @@ public function single_row( $item ) { protected function get_primary_column_name() { return 'name'; } + + /** + * Prints a list of other plugins that depend on the plugin. + * + * @since 6.5.0 + * + * @param string $dependency The dependency's filepath, relative to the plugins directory. + */ + protected function add_dependents_to_dependency_plugin_row( $dependency ) { + $dependent_names = WP_Plugin_Dependencies::get_dependent_names( $dependency ); + + if ( empty( $dependent_names ) ) { + return; + } + + $dependency_note = __( 'Note: this plugin cannot be deactivated or deleted until the plugins that require it are deactivated or deleted.' ); + printf( + '

%1$s %2$s

%3$s

', + __( 'Required by:' ), + esc_html( implode( ' | ', $dependent_names ) ), + $dependency_note + ); + } + + /** + * Prints a list of other plugins that the plugin depends on. + * + * @since 6.5.0 + * + * @param string $dependent The dependent plugin's filepath, relative to the plugins directory. + */ + protected function add_dependencies_to_dependent_plugin_row( $dependent ) { + $dependency_names = WP_Plugin_Dependencies::get_dependency_names( $dependent ); + + if ( array() === $dependency_names ) { + return; + } + + $links = array(); + foreach ( $dependency_names as $slug => $name ) { + $links[] = $this->get_dependency_view_details_link( $name, $slug ); + } + + $dependency_note = __( 'Note: this plugin cannot be activated until the plugins that are required by it are activated.' ); + + printf( + '

%1$s %2$s

%3$s

', + __( 'Requires:' ), + implode( ' | ', $links ), + $dependency_note + ); + } + + /** + * Returns a 'View details' like link for a dependency. + * + * @since 6.5.0 + * + * @param string $name The dependency's name. + * @param string $slug The dependency's slug. + * @return string A 'View details' link for the dependency. + */ + protected function get_dependency_view_details_link( $name, $slug ) { + $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $slug ); + + if ( false === $dependency_data + || $name === $slug + || $name !== $dependency_data['name'] + || empty( $dependency_data['version'] ) + ) { + return $name; + } + + return $this->get_view_details_link( $name, $slug ); + } + + /** + * Returns a 'View details' link for the plugin. + * + * @since 6.5.0 + * + * @param string $name The plugin's name. + * @param string $slug The plugin's slug. + * @return string A 'View details' link for the plugin. + */ + protected function get_view_details_link( $name, $slug ) { + $url = add_query_arg( + array( + 'tab' => 'plugin-information', + 'plugin' => $slug, + 'TB_iframe' => 'true', + 'width' => '600', + 'height' => '550', + ), + network_admin_url( 'plugin-install.php' ) + ); + + $name_attr = esc_attr( $name ); + return sprintf( + "%s", + esc_url( $url ), + /* translators: %s: Plugin name. */ + sprintf( __( 'More information about %s' ), $name_attr ), + $name_attr, + esc_html( $name ) + ); + } } diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php index c3863ba2ea5ba..583256955e250 100644 --- a/src/wp-admin/includes/file.php +++ b/src/wp-admin/includes/file.php @@ -1563,6 +1563,37 @@ function wp_trusted_keys() { return apply_filters( 'wp_trusted_keys', $trusted_keys ); } +/** + * Determines whether the given file is a valid ZIP file. + * + * This function does not test to ensure that a file exists. Non-existent files + * are not valid ZIPs, so those will also return false. + * + * @since 6.4.4 + * + * @param string $file Full path to the ZIP file. + * @return bool Whether the file is a valid ZIP file. + */ +function wp_zip_file_is_valid( $file ) { + /** This filter is documented in wp-admin/includes/file.php */ + if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) { + $archive = new ZipArchive(); + $archive_is_valid = $archive->open( $file, ZipArchive::CHECKCONS ); + if ( true === $archive_is_valid ) { + $archive->close(); + return true; + } + } + + // Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file. + require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; + + $archive = new PclZip( $file ); + $archive_is_valid = is_array( $archive->properties() ); + + return $archive_is_valid; +} + /** * Unzips a specified ZIP file to a location on the filesystem via the WordPress * Filesystem Abstraction. diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php index 739b09f9a1fca..2d150e691c293 100644 --- a/src/wp-admin/includes/image-edit.php +++ b/src/wp-admin/includes/image-edit.php @@ -390,6 +390,12 @@ function wp_stream_image( $image, $mime_type, $attachment_id ) { return imagewebp( $image, null, 90 ); } return false; + case 'image/avif': + if ( function_exists( 'imageavif' ) ) { + header( 'Content-Type: image/avif' ); + return imageavif( $image, null, 90 ); + } + return false; default: return false; } @@ -494,6 +500,11 @@ function wp_save_image_file( $filename, $image, $mime_type, $post_id ) { return imagewebp( $image, $filename ); } return false; + case 'image/avif': + if ( function_exists( 'imageavif' ) ) { + return imageavif( $image, $filename ); + } + return false; default: return false; } diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index d60ec8508baf5..0f4ba818e6535 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -1006,7 +1006,7 @@ function file_is_valid_image( $path ) { * @return bool True if suitable, false if not suitable. */ function file_is_displayable_image( $path ) { - $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP ); + $displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP, IMAGETYPE_AVIF ); $info = wp_getimagesize( $path ); if ( empty( $info ) ) { diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index 57df2bcbff0c3..193e67f7dd641 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -2198,6 +2198,11 @@ function media_upload_form( $errors = null ) { $plupload_init['webp_upload_error'] = true; } + // Check if AVIF images can be edited. + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { + $plupload_init['avif_upload_error'] = true; + } + /** * Filters the default Plupload settings. * diff --git a/src/wp-admin/includes/plugin-install.php b/src/wp-admin/includes/plugin-install.php index 7662076581c3e..2de2aabbc3a01 100644 --- a/src/wp-admin/includes/plugin-install.php +++ b/src/wp-admin/includes/plugin-install.php @@ -884,43 +884,176 @@ function install_plugin_information() { echo "\n"; // #plugin-information-scrollable echo "\n"; + + wp_print_request_filesystem_credentials_modal(); + wp_print_admin_notice_templates(); + + iframe_footer(); + exit; +} + +/** + * Gets the markup for the plugin install action button. + * + * @since 6.5.0 + * + * @param string $name Plugin name. + * @param array|object $data { + * An array or object of plugin data. Can be retrieved from the API. + * + * @type string $slug The plugin slug. + * @type string[] $requires_plugins An array of plugin dependency slugs. + * @type string $version The plugin's version string. Used when getting the install status. + * } + * @param bool $compatible_php The result of a PHP compatibility check. + * @param bool $compatible_wp The result of a WP compatibility check. + * @return string $button The markup for the dependency row button. + */ +function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible_wp ) { + $button = ''; + $data = (object) $data; + $status = install_plugin_install_status( $data ); + $requires_plugins = $data->requires_plugins ?? array(); + + // Determine the status of plugin dependencies. + $installed_plugins = get_plugins(); + $active_plugins = get_option( 'active_plugins' ); + $plugin_dependencies_count = count( $requires_plugins ); + $installed_plugin_dependencies_count = 0; + $active_plugin_dependencies_count = 0; + foreach ( $requires_plugins as $dependency ) { + foreach ( array_keys( $installed_plugins ) as $installed_plugin_file ) { + if ( str_contains( $installed_plugin_file, '/' ) && explode( '/', $installed_plugin_file )[0] === $dependency ) { + ++$installed_plugin_dependencies_count; + } + } + + foreach ( $active_plugins as $active_plugin_file ) { + if ( str_contains( $active_plugin_file, '/' ) && explode( '/', $active_plugin_file )[0] === $dependency ) { + ++$active_plugin_dependencies_count; + } + } + } + $all_plugin_dependencies_installed = $installed_plugin_dependencies_count === $plugin_dependencies_count; + $all_plugin_dependencies_active = $active_plugin_dependencies_count === $plugin_dependencies_count; + + sprintf( + '%s', + esc_attr( $data->slug ), + esc_url( $status['url'] ), + /* translators: %s: Plugin name and version. */ + esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ), + esc_attr( $name ), + __( 'Install Now' ) + ); + + if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) { switch ( $status['status'] ) { case 'install': if ( $status['url'] ) { - if ( $compatible_php && $compatible_wp ) { - echo '' . __( 'Install Now' ) . ''; + if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_installed && ! empty( $data->download_link ) ) { + $button = sprintf( + '%s', + esc_attr( $data->slug ), + esc_url( $status['url'] ), + /* translators: %s: Plugin name and version. */ + esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ), + esc_attr( $name ), + __( 'Install Now' ) + ); } else { - printf( - '', - _x( 'Cannot Install', 'plugin' ) + $button = sprintf( + '', + _x( 'Install Now', 'plugin' ) ); } } break; + case 'update_available': if ( $status['url'] ) { - if ( $compatible_php ) { - echo '' . __( 'Install Update Now' ) . ''; + if ( $compatible_php && $compatible_wp ) { + $button = sprintf( + '%s', + esc_attr( $status['file'] ), + esc_attr( $data->slug ), + esc_url( $status['url'] ), + /* translators: %s: Plugin name and version. */ + esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $name ) ), + esc_attr( $name ), + __( 'Update Now' ) + ); } else { - printf( - '', - _x( 'Cannot Update', 'plugin' ) + $button = sprintf( + '', + _x( 'Update Now', 'plugin' ) ); } } break; - case 'newer_installed': - /* translators: %s: Plugin version. */ - echo '' . sprintf( __( 'Newer Version (%s) Installed' ), esc_html( $status['version'] ) ) . ''; - break; + case 'latest_installed': - echo '' . __( 'Latest Version Installed' ) . ''; + case 'newer_installed': + if ( is_plugin_active( $status['file'] ) ) { + $button = sprintf( + '', + _x( 'Active', 'plugin' ) + ); + } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) { + if ( $compatible_php && $compatible_wp && $all_plugin_dependencies_active ) { + $button_text = __( 'Activate' ); + /* translators: %s: Plugin name. */ + $button_label = _x( 'Activate %s', 'plugin' ); + $activate_url = add_query_arg( + array( + '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ), + 'action' => 'activate', + 'plugin' => $status['file'], + ), + network_admin_url( 'plugins.php' ) + ); + + if ( is_network_admin() ) { + $button_text = __( 'Network Activate' ); + /* translators: %s: Plugin name. */ + $button_label = _x( 'Network Activate %s', 'plugin' ); + $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url ); + } + + $button = sprintf( + '%6$s', + esc_url( $activate_url ), + esc_attr( $name ), + esc_attr( $data->slug ), + esc_attr( $status['file'] ), + esc_attr( sprintf( $button_label, $name ) ), + $button_text + ); + } else { + $button = sprintf( + '', + is_network_admin() ? _x( 'Network Activate %s', 'plugin' ) : _x( 'Activate', 'plugin' ) + ); + } + } else { + $button = sprintf( + '', + _x( 'Installed', 'plugin' ) + ); + } break; } - } - echo "\n"; - iframe_footer(); - exit; + return $button; + } } diff --git a/src/wp-admin/includes/plugin.php b/src/wp-admin/includes/plugin.php index f55bbd80eb5df..54a5b95f346b7 100644 --- a/src/wp-admin/includes/plugin.php +++ b/src/wp-admin/includes/plugin.php @@ -45,6 +45,7 @@ * @since 1.5.0 * @since 5.3.0 Added support for `Requires at least` and `Requires PHP` headers. * @since 5.8.0 Added support for `Update URI` header. + * @since 6.5.0 Added support for `Requires Plugins` header. * * @param string $plugin_file Absolute path to the main plugin file. * @param bool $markup Optional. If the returned data should have HTML markup applied. @@ -53,39 +54,41 @@ * @return array { * Plugin data. Values will be empty if not supplied by the plugin. * - * @type string $Name Name of the plugin. Should be unique. - * @type string $PluginURI Plugin URI. - * @type string $Version Plugin version. - * @type string $Description Plugin description. - * @type string $Author Plugin author's name. - * @type string $AuthorURI Plugin author's website address (if set). - * @type string $TextDomain Plugin textdomain. - * @type string $DomainPath Plugin's relative directory path to .mo files. - * @type bool $Network Whether the plugin can only be activated network-wide. - * @type string $RequiresWP Minimum required version of WordPress. - * @type string $RequiresPHP Minimum required version of PHP. - * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. - * @type string $Title Title of the plugin and link to the plugin's site (if set). - * @type string $AuthorName Plugin author's name. + * @type string $Name Name of the plugin. Should be unique. + * @type string $PluginURI Plugin URI. + * @type string $Version Plugin version. + * @type string $Description Plugin description. + * @type string $Author Plugin author's name. + * @type string $AuthorURI Plugin author's website address (if set). + * @type string $TextDomain Plugin textdomain. + * @type string $DomainPath Plugin's relative directory path to .mo files. + * @type bool $Network Whether the plugin can only be activated network-wide. + * @type string $RequiresWP Minimum required version of WordPress. + * @type string $RequiresPHP Minimum required version of PHP. + * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. + * @type string $RequiresPlugins Comma separated list of dot org plugin slugs. + * @type string $Title Title of the plugin and link to the plugin's site (if set). + * @type string $AuthorName Plugin author's name. * } */ function get_plugin_data( $plugin_file, $markup = true, $translate = true ) { $default_headers = array( - 'Name' => 'Plugin Name', - 'PluginURI' => 'Plugin URI', - 'Version' => 'Version', - 'Description' => 'Description', - 'Author' => 'Author', - 'AuthorURI' => 'Author URI', - 'TextDomain' => 'Text Domain', - 'DomainPath' => 'Domain Path', - 'Network' => 'Network', - 'RequiresWP' => 'Requires at least', - 'RequiresPHP' => 'Requires PHP', - 'UpdateURI' => 'Update URI', + 'Name' => 'Plugin Name', + 'PluginURI' => 'Plugin URI', + 'Version' => 'Version', + 'Description' => 'Description', + 'Author' => 'Author', + 'AuthorURI' => 'Author URI', + 'TextDomain' => 'Text Domain', + 'DomainPath' => 'Domain Path', + 'Network' => 'Network', + 'RequiresWP' => 'Requires at least', + 'RequiresPHP' => 'Requires PHP', + 'UpdateURI' => 'Update URI', + 'RequiresPlugins' => 'Requires Plugins', // Site Wide Only is deprecated in favor of Network. - '_sitewide' => 'Site Wide Only', + '_sitewide' => 'Site Wide Only', ); $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' ); @@ -330,6 +333,7 @@ function get_plugins( $plugin_folder = '' ) { return $wp_plugins; } + $new_plugin_data = array(); foreach ( $plugin_files as $plugin_file ) { if ( ! is_readable( "$plugin_root/$plugin_file" ) ) { continue; @@ -342,6 +346,13 @@ function get_plugins( $plugin_folder = '' ) { continue; } + $new_plugin_file = str_replace( + trailingslashit( WP_PLUGIN_DIR ), + '', + "$plugin_root/$plugin_file" + ); + + $new_plugin_data[ $new_plugin_file ] = $plugin_data; $wp_plugins[ plugin_basename( $plugin_file ) ] = $plugin_data; } @@ -349,6 +360,7 @@ function get_plugins( $plugin_folder = '' ) { $cache_plugins[ $plugin_folder ] = $wp_plugins; wp_cache_set( 'plugins', $cache_plugins, 'plugins' ); + update_option( 'plugin_data', $new_plugin_data ); return $wp_plugins; } @@ -951,6 +963,7 @@ function delete_plugins( $plugins, $deprecated = '' ) { $plugins_dir = trailingslashit( $plugins_dir ); $plugin_translations = wp_get_installed_translations( 'plugins' ); + $all_plugin_data = get_option( 'plugin_data', array() ); $errors = array(); @@ -995,6 +1008,7 @@ function delete_plugins( $plugins, $deprecated = '' ) { $errors[] = $plugin_file; continue; } + unset( $all_plugin_data[ $plugin_file ] ); $plugin_slug = dirname( $plugin_file ); @@ -1009,6 +1023,7 @@ function delete_plugins( $plugins, $deprecated = '' ) { foreach ( $translations as $translation => $data ) { $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' ); $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' ); + $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.l10n.php' ); $json_translation_files = glob( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '-*.json' ); if ( $json_translation_files ) { @@ -1042,6 +1057,7 @@ function delete_plugins( $plugins, $deprecated = '' ) { return new WP_Error( 'could_not_remove_plugin', sprintf( $message, implode( ', ', $errors ) ) ); } + update_option( 'plugin_data', $all_plugin_data ); return true; } @@ -1113,13 +1129,14 @@ function validate_plugin( $plugin ) { /** * Validates the plugin requirements for WordPress version and PHP version. * - * Uses the information from `Requires at least` and `Requires PHP` headers + * Uses the information from `Requires at least`, `Requires PHP` and `Requires Plugins` headers * defined in the plugin's main PHP file. * * @since 5.2.0 * @since 5.3.0 Added support for reading the headers from the plugin's * main PHP file, with `readme.txt` as a fallback. * @since 5.8.0 Removed support for using `readme.txt` as a fallback. + * @since 6.5.0 Added support for the 'Requires Plugins' header. * * @param string $plugin Path to the plugin file relative to the plugins directory. * @return true|WP_Error True if requirements are met, WP_Error on failure. @@ -1128,8 +1145,9 @@ function validate_plugin_requirements( $plugin ) { $plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); $requirements = array( - 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '', - 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '', + 'requires' => ! empty( $plugin_headers['RequiresWP'] ) ? $plugin_headers['RequiresWP'] : '', + 'requires_php' => ! empty( $plugin_headers['RequiresPHP'] ) ? $plugin_headers['RequiresPHP'] : '', + 'requires_plugins' => ! empty( $plugin_headers['RequiresPlugins'] ) ? $plugin_headers['RequiresPlugins'] : '', ); $compatible_wp = is_wp_version_compatible( $requirements['requires'] ); @@ -1184,6 +1202,31 @@ function validate_plugin_requirements( $plugin ) { ); } + if ( WP_Plugin_Dependencies::has_unmet_dependencies( $plugin ) ) { + $dependencies = WP_Plugin_Dependencies::get_dependencies( $plugin ); + $unmet_dependencies = array(); + + foreach ( $dependencies as $dependency ) { + $dependency_file = WP_Plugin_Dependencies::get_dependency_filepath( $dependency ); + + if ( false === $dependency_file ) { + $unmet_dependencies['not_installed'][] = $dependency; + } elseif ( is_plugin_inactive( $dependency_file ) ) { + $unmet_dependencies['inactive'][] = $dependency; + } + } + + return new WP_Error( + 'plugin_missing_dependencies', + '

' . sprintf( + /* translators: %s: Plugin name. */ + _x( 'Error: %s requires plugins that are not installed or activated.', 'plugin' ), + $plugin_headers['Name'] + ) . '

', + $unmet_dependencies + ); + } + return true; } diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index c93ac71bec525..90aaf2228e371 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -2303,6 +2303,7 @@ function get_block_editor_server_block_settings() { 'keywords' => 'keywords', 'example' => 'example', 'variations' => 'variations', + 'allowed_blocks' => 'allowedBlocks', ); foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { diff --git a/src/wp-admin/includes/schema.php b/src/wp-admin/includes/schema.php index d339af3b17324..63655ccf174ca 100644 --- a/src/wp-admin/includes/schema.php +++ b/src/wp-admin/includes/schema.php @@ -599,14 +599,12 @@ function populate_options( array $options = array() ) { $autoload = 'yes'; } - if ( is_array( $value ) ) { - $value = serialize( $value ); - } - if ( ! empty( $insert ) ) { $insert .= ', '; } + $value = maybe_serialize( sanitize_option( $option, $value ) ); + $insert .= $wpdb->prepare( '(%s, %s, %s)', $option, $value, $autoload ); } @@ -1252,6 +1250,7 @@ function populate_network_meta( $network_id, array $meta = array() ) { 'png', 'gif', 'webp', + 'avif', // Video. 'mov', 'avi', diff --git a/src/wp-admin/includes/theme.php b/src/wp-admin/includes/theme.php index 91e92995ece81..70abab68ad799 100644 --- a/src/wp-admin/includes/theme.php +++ b/src/wp-admin/includes/theme.php @@ -114,6 +114,7 @@ function delete_theme( $stylesheet, $redirect = '' ) { foreach ( $translations as $translation => $data ) { $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '.po' ); $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '.mo' ); + $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '.l10n.php' ); $json_translation_files = glob( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '-*.json' ); if ( $json_translation_files ) { diff --git a/src/wp-admin/includes/update-core.php b/src/wp-admin/includes/update-core.php index 0c66ad3ee9db0..6d996adb52e38 100644 --- a/src/wp-admin/includes/update-core.php +++ b/src/wp-admin/includes/update-core.php @@ -1848,13 +1848,14 @@ function _upgrade_440_force_deactivate_incompatible_plugins() { * @since 5.9.0 The minimum compatible version of Gutenberg is 11.9. * @since 6.1.1 The minimum compatible version of Gutenberg is 14.1. * @since 6.4.0 The minimum compatible version of Gutenberg is 16.5. + * @since 6.5.0 The minimum compatible version of Gutenberg is 17.6. */ function _upgrade_core_deactivate_incompatible_plugins() { - if ( defined( 'GUTENBERG_VERSION' ) && version_compare( GUTENBERG_VERSION, '16.5', '<' ) ) { + if ( defined( 'GUTENBERG_VERSION' ) && version_compare( GUTENBERG_VERSION, '17.6', '<' ) ) { $deactivated_gutenberg['gutenberg'] = array( 'plugin_name' => 'Gutenberg', 'version_deactivated' => GUTENBERG_VERSION, - 'version_compatible' => '16.5', + 'version_compatible' => '17.6', ); if ( is_plugin_active_for_network( 'gutenberg/gutenberg.php' ) ) { $deactivated_plugins = get_site_option( 'wp_force_deactivated_plugins', array() ); diff --git a/src/wp-admin/menu.php b/src/wp-admin/menu.php index 71fd94b2e3842..a756fa40f3624 100644 --- a/src/wp-admin/menu.php +++ b/src/wp-admin/menu.php @@ -205,10 +205,12 @@ if ( wp_is_block_theme() ) { $submenu['themes.php'][6] = array( _x( 'Editor', 'site editor menu item' ), 'edit_theme_options', 'site-editor.php' ); +} else { + $submenu['themes.php'][6] = array( __( 'Patterns', 'site editor menu item' ), 'edit_theme_options', 'edit.php?post_type=wp_block' ); } if ( ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ) ) { - $submenu['themes.php'][6] = array( + $submenu['themes.php'][7] = array( __( 'Template Parts' ), 'edit_theme_options', 'site-editor.php?path=/wp_template_part/all', @@ -220,7 +222,7 @@ // Hide Customize link on block themes unless a plugin or theme // is using 'customize_register' to add a setting. if ( ! wp_is_block_theme() || has_action( 'customize_register' ) ) { - $position = ( wp_is_block_theme() || current_theme_supports( 'block-template-parts' ) ) ? 7 : 6; + $position = ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ) ? 8 : 7; $submenu['themes.php'][ $position ] = array( __( 'Customize' ), 'customize', esc_url( $customize_url ), '', 'hide-if-no-customize' ); } diff --git a/src/wp-admin/plugin-editor.php b/src/wp-admin/plugin-editor.php index 345f0c09e05c2..da57d95e74fff 100644 --- a/src/wp-admin/plugin-editor.php +++ b/src/wp-admin/plugin-editor.php @@ -295,7 +295,7 @@
- +
diff --git a/src/wp-admin/plugin-install.php b/src/wp-admin/plugin-install.php index 571b9a9875ab1..a8beb8249bbc7 100644 --- a/src/wp-admin/plugin-install.php +++ b/src/wp-admin/plugin-install.php @@ -134,6 +134,10 @@ * WordPress Administration Template Header. */ require_once ABSPATH . 'wp-admin/admin-header.php'; + +WP_Plugin_Dependencies::display_admin_notice_for_unmet_dependencies(); +WP_Plugin_Dependencies::display_admin_notice_for_deactivated_dependents(); +WP_Plugin_Dependencies::display_admin_notice_for_circular_dependencies(); ?>
">

diff --git a/src/wp-admin/plugins.php b/src/wp-admin/plugins.php index 93f9c454abe14..f7514ab473b1b 100644 --- a/src/wp-admin/plugins.php +++ b/src/wp-admin/plugins.php @@ -739,6 +739,9 @@ } ?> + + +

- +

diff --git a/src/wp-admin/update-core.php b/src/wp-admin/update-core.php index c34ac06f242e6..80fbd530b24b6 100644 --- a/src/wp-admin/update-core.php +++ b/src/wp-admin/update-core.php @@ -42,7 +42,7 @@ function list_core_update( $update ) { if ( 'en_US' === $update->locale && 'en_US' === get_locale() ) { $version_string = $update->current; - } elseif ( 'en_US' === $update->locale && $update->packages->partial && $wp_version == $update->partial_version ) { + } elseif ( 'en_US' === $update->locale && $update->packages->partial && $wp_version === $update->partial_version ) { $updates = get_core_updates(); if ( $updates && 1 === count( $updates ) ) { // If the only available update is a partial builds, it doesn't need a language-specific version string. @@ -179,9 +179,9 @@ function list_core_update( $update ) { } echo '

'; - if ( 'en_US' !== $update->locale && ( ! isset( $wp_local_package ) || $wp_local_package != $update->locale ) ) { + if ( 'en_US' !== $update->locale && ( ! isset( $wp_local_package ) || $wp_local_package !== $update->locale ) ) { echo '

' . __( 'This localized version contains both the translation and various other localization fixes.' ) . '

'; - } elseif ( 'en_US' === $update->locale && 'en_US' !== get_locale() && ( ! $update->packages->partial && $wp_version == $update->partial_version ) ) { + } elseif ( 'en_US' === $update->locale && 'en_US' !== get_locale() && ( ! $update->packages->partial && $wp_version === $update->partial_version ) ) { // Partial builds don't need language-specific warnings. echo '

' . sprintf( /* translators: %s: WordPress version. */ diff --git a/src/wp-admin/update.php b/src/wp-admin/update.php index 29480c2840a57..090c37cfc4dfe 100644 --- a/src/wp-admin/update.php +++ b/src/wp-admin/update.php @@ -154,6 +154,10 @@ check_admin_referer( 'plugin-upload' ); + if ( isset( $_FILES['pluginzip']['name'] ) && ! str_ends_with( strtolower( $_FILES['pluginzip']['name'] ), '.zip' ) ) { + wp_die( __( 'Only .zip archives may be uploaded.' ) ); + } + $file_upload = new File_Upload_Upgrader( 'pluginzip', 'package' ); // Used in the HTML title tag. @@ -302,6 +306,10 @@ check_admin_referer( 'theme-upload' ); + if ( isset( $_FILES['themezip']['name'] ) && ! str_ends_with( strtolower( $_FILES['themezip']['name'] ), '.zip' ) ) { + wp_die( __( 'Only .zip archives may be uploaded.' ) ); + } + $file_upload = new File_Upload_Upgrader( 'themezip', 'package' ); // Used in the HTML title tag. diff --git a/src/wp-content/themes/twentyeleven/functions.php b/src/wp-content/themes/twentyeleven/functions.php index 3ca5da5882f27..3c7fa3c3a8167 100644 --- a/src/wp-content/themes/twentyeleven/functions.php +++ b/src/wp-content/themes/twentyeleven/functions.php @@ -562,7 +562,7 @@ function twentyeleven_page_menu_args( $args ) { /** * Register sidebars and widgetized areas. * - * Also register the default Epherma widget. + * Also register the default Ephemera widget. * * @since Twenty Eleven 1.0 */ diff --git a/src/wp-content/themes/twentyeleven/images/headers/chessboard-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/chessboard-thumbnail.jpg index e8c84d3f4d9c6..cb6977e7b6697 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/chessboard-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/chessboard-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/chessboard.jpg b/src/wp-content/themes/twentyeleven/images/headers/chessboard.jpg index 831961fff53ef..aebf01b2d228e 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/chessboard.jpg and b/src/wp-content/themes/twentyeleven/images/headers/chessboard.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/hanoi-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/hanoi-thumbnail.jpg index 9fc963f59bcec..699b5780e47b0 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/hanoi-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/hanoi-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/hanoi.jpg b/src/wp-content/themes/twentyeleven/images/headers/hanoi.jpg index 5b0fa3fb87575..83d7695c9028c 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/hanoi.jpg and b/src/wp-content/themes/twentyeleven/images/headers/hanoi.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/lanterns-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/lanterns-thumbnail.jpg index 3790f96caaf13..6a6bf9a4d42c2 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/lanterns-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/lanterns-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/lanterns.jpg b/src/wp-content/themes/twentyeleven/images/headers/lanterns.jpg index f71ce8f8cdf2d..96bea4c4ada42 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/lanterns.jpg and b/src/wp-content/themes/twentyeleven/images/headers/lanterns.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/pine-cone-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/pine-cone-thumbnail.jpg index 248fe00a14d04..c0b0e88ea3be4 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/pine-cone-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/pine-cone-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/pine-cone.jpg b/src/wp-content/themes/twentyeleven/images/headers/pine-cone.jpg index e55ce97f1d63b..a908fac799d08 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/pine-cone.jpg and b/src/wp-content/themes/twentyeleven/images/headers/pine-cone.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/shore-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/shore-thumbnail.jpg index 5e5e740e9b422..07e91f1a4eaa0 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/shore-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/shore-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/shore.jpg b/src/wp-content/themes/twentyeleven/images/headers/shore.jpg index 71a8c54fed14b..972cb6251140a 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/shore.jpg and b/src/wp-content/themes/twentyeleven/images/headers/shore.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/trolley-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/trolley-thumbnail.jpg index d2ee200088f8c..ea70fe13b17ba 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/trolley-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/trolley-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/trolley.jpg b/src/wp-content/themes/twentyeleven/images/headers/trolley.jpg index 110a764e5b643..67d63396de4d1 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/trolley.jpg and b/src/wp-content/themes/twentyeleven/images/headers/trolley.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/wheel-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/wheel-thumbnail.jpg index d7fa9713588b4..2cf102fc980e4 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/wheel-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/wheel-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/wheel.jpg b/src/wp-content/themes/twentyeleven/images/headers/wheel.jpg index c5b98789a81b1..31e5e17d79e40 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/wheel.jpg and b/src/wp-content/themes/twentyeleven/images/headers/wheel.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/willow-thumbnail.jpg b/src/wp-content/themes/twentyeleven/images/headers/willow-thumbnail.jpg index 240fff8a16aa5..70f7fb2f021bb 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/willow-thumbnail.jpg and b/src/wp-content/themes/twentyeleven/images/headers/willow-thumbnail.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/headers/willow.jpg b/src/wp-content/themes/twentyeleven/images/headers/willow.jpg index e867cc43df474..d1829f6762f01 100644 Binary files a/src/wp-content/themes/twentyeleven/images/headers/willow.jpg and b/src/wp-content/themes/twentyeleven/images/headers/willow.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/patterns/pattern-flower.jpg b/src/wp-content/themes/twentyeleven/images/patterns/pattern-flower.jpg index 54bdcc593d215..1dd4865a37a1d 100644 Binary files a/src/wp-content/themes/twentyeleven/images/patterns/pattern-flower.jpg and b/src/wp-content/themes/twentyeleven/images/patterns/pattern-flower.jpg differ diff --git a/src/wp-content/themes/twentyeleven/images/patterns/pattern-woman.jpg b/src/wp-content/themes/twentyeleven/images/patterns/pattern-woman.jpg index 8af40f871a11b..4bf164c0313d9 100644 Binary files a/src/wp-content/themes/twentyeleven/images/patterns/pattern-woman.jpg and b/src/wp-content/themes/twentyeleven/images/patterns/pattern-woman.jpg differ diff --git a/src/wp-content/themes/twentyfifteen/assets/pier-seagull.jpg b/src/wp-content/themes/twentyfifteen/assets/pier-seagull.jpg index fad56465bb8b2..b71d9d380729c 100644 Binary files a/src/wp-content/themes/twentyfifteen/assets/pier-seagull.jpg and b/src/wp-content/themes/twentyfifteen/assets/pier-seagull.jpg differ diff --git a/src/wp-content/themes/twentyfifteen/assets/pier-seagulls.jpg b/src/wp-content/themes/twentyfifteen/assets/pier-seagulls.jpg index 01d52f1dc899c..6f318856fa6fc 100644 Binary files a/src/wp-content/themes/twentyfifteen/assets/pier-seagulls.jpg and b/src/wp-content/themes/twentyfifteen/assets/pier-seagulls.jpg differ diff --git a/src/wp-content/themes/twentyfifteen/assets/pier-sunset.jpg b/src/wp-content/themes/twentyfifteen/assets/pier-sunset.jpg index 75430630a0e79..f69dc562d2260 100644 Binary files a/src/wp-content/themes/twentyfifteen/assets/pier-sunset.jpg and b/src/wp-content/themes/twentyfifteen/assets/pier-sunset.jpg differ diff --git a/src/wp-content/themes/twentyfifteen/css/blocks.css b/src/wp-content/themes/twentyfifteen/css/blocks.css index 5967ea52e9f30..3487cc7b98ef4 100644 --- a/src/wp-content/themes/twentyfifteen/css/blocks.css +++ b/src/wp-content/themes/twentyfifteen/css/blocks.css @@ -470,7 +470,7 @@ p.has-drop-cap:not(:focus)::first-letter { } } -/* Seperators */ +/* Separators */ .wp-block-separator { border: 0; diff --git a/src/wp-content/themes/twentyfourteen/images/bridge.jpg b/src/wp-content/themes/twentyfourteen/images/bridge.jpg index d172bb2818995..a0bf15159f6ac 100644 Binary files a/src/wp-content/themes/twentyfourteen/images/bridge.jpg and b/src/wp-content/themes/twentyfourteen/images/bridge.jpg differ diff --git a/src/wp-content/themes/twentyfourteen/images/clouds.jpg b/src/wp-content/themes/twentyfourteen/images/clouds.jpg index f753465d6fb89..1e550a14a1bce 100644 Binary files a/src/wp-content/themes/twentyfourteen/images/clouds.jpg and b/src/wp-content/themes/twentyfourteen/images/clouds.jpg differ diff --git a/src/wp-content/themes/twentyfourteen/images/person.jpg b/src/wp-content/themes/twentyfourteen/images/person.jpg index 8dbad50819938..83febdaad03c7 100644 Binary files a/src/wp-content/themes/twentyfourteen/images/person.jpg and b/src/wp-content/themes/twentyfourteen/images/person.jpg differ diff --git a/src/wp-content/themes/twentyfourteen/images/street.jpg b/src/wp-content/themes/twentyfourteen/images/street.jpg index 71735a04b6f60..2111f1a750a9e 100644 Binary files a/src/wp-content/themes/twentyfourteen/images/street.jpg and b/src/wp-content/themes/twentyfourteen/images/street.jpg differ diff --git a/src/wp-content/themes/twentyfourteen/images/sunset.jpg b/src/wp-content/themes/twentyfourteen/images/sunset.jpg index 8c1b379fbaed3..892b195586df9 100644 Binary files a/src/wp-content/themes/twentyfourteen/images/sunset.jpg and b/src/wp-content/themes/twentyfourteen/images/sunset.jpg differ diff --git a/src/wp-content/themes/twentynineteen/image.php b/src/wp-content/themes/twentynineteen/image.php index a3580b4e668f3..defab756b252f 100644 --- a/src/wp-content/themes/twentynineteen/image.php +++ b/src/wp-content/themes/twentynineteen/image.php @@ -32,7 +32,7 @@ /** * Filters the default twentynineteen image attachment size. * - * @since Twenty Sixteen 1.0 + * @since Twenty Nineteen 1.0 * * @param string $image_size Image size. Default 'large'. */ diff --git a/src/wp-content/themes/twentynineteen/images/pattern_01.jpg b/src/wp-content/themes/twentynineteen/images/pattern_01.jpg index b6bc3b477892a..e01b19df0ed10 100644 Binary files a/src/wp-content/themes/twentynineteen/images/pattern_01.jpg and b/src/wp-content/themes/twentynineteen/images/pattern_01.jpg differ diff --git a/src/wp-content/themes/twentynineteen/images/pattern_02.jpg b/src/wp-content/themes/twentynineteen/images/pattern_02.jpg index 88d66f2f3a3b1..56c7f70943244 100644 Binary files a/src/wp-content/themes/twentynineteen/images/pattern_02.jpg and b/src/wp-content/themes/twentynineteen/images/pattern_02.jpg differ diff --git a/src/wp-content/themes/twentynineteen/images/pattern_03.jpg b/src/wp-content/themes/twentynineteen/images/pattern_03.jpg index 0b323a8a02869..bd69d37a29112 100644 Binary files a/src/wp-content/themes/twentynineteen/images/pattern_03.jpg and b/src/wp-content/themes/twentynineteen/images/pattern_03.jpg differ diff --git a/src/wp-content/themes/twentynineteen/images/pattern_04.jpg b/src/wp-content/themes/twentynineteen/images/pattern_04.jpg index 8e5a4936bd7ca..b7419fe4f4be2 100644 Binary files a/src/wp-content/themes/twentynineteen/images/pattern_04.jpg and b/src/wp-content/themes/twentynineteen/images/pattern_04.jpg differ diff --git a/src/wp-content/themes/twentynineteen/screenshot.png b/src/wp-content/themes/twentynineteen/screenshot.png index 7dc53c5f1dd49..fa27e95463571 100644 Binary files a/src/wp-content/themes/twentynineteen/screenshot.png and b/src/wp-content/themes/twentynineteen/screenshot.png differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/coffee.jpg b/src/wp-content/themes/twentyseventeen/assets/images/coffee.jpg index 13847cde7c16b..145d1f7116484 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/coffee.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/coffee.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/direct-light.jpg b/src/wp-content/themes/twentyseventeen/assets/images/direct-light.jpg index a3255791e79a5..8f3d814c57b19 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/direct-light.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/direct-light.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/espresso.jpg b/src/wp-content/themes/twentyseventeen/assets/images/espresso.jpg index 7514c96bdfaf9..a3bd2c6dc0049 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/espresso.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/espresso.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/header.jpg b/src/wp-content/themes/twentyseventeen/assets/images/header.jpg index a3fd3e7122cbc..ef2c07aff98eb 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/header.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/header.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/sandwich.jpg b/src/wp-content/themes/twentyseventeen/assets/images/sandwich.jpg index 6baddbf053749..cf81066b2eebc 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/sandwich.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/sandwich.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/stripes.jpg b/src/wp-content/themes/twentyseventeen/assets/images/stripes.jpg index 26e3b6d10ce6c..2e0920ff7e257 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/stripes.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/stripes.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/assets/images/white-border.jpg b/src/wp-content/themes/twentyseventeen/assets/images/white-border.jpg index a1b11d77cd3f5..40773b14bd028 100644 Binary files a/src/wp-content/themes/twentyseventeen/assets/images/white-border.jpg and b/src/wp-content/themes/twentyseventeen/assets/images/white-border.jpg differ diff --git a/src/wp-content/themes/twentyseventeen/inc/block-patterns.php b/src/wp-content/themes/twentyseventeen/inc/block-patterns.php index 66e6fa0c87300..f013aa9a545cc 100644 --- a/src/wp-content/themes/twentyseventeen/inc/block-patterns.php +++ b/src/wp-content/themes/twentyseventeen/inc/block-patterns.php @@ -1,6 +1,6 @@ get( 'Version' ), 'all' ); + // Add output of Customizer settings as inline style. $customizer_css = twentytwenty_get_customizer_css( 'front-end' ); if ( $customizer_css ) { @@ -417,6 +421,7 @@ function twentytwenty_sidebar_registration() { * * @since Twenty Twenty 1.0 * @since Twenty Twenty 2.4 Removed a script related to the obsolete Squared style of Button blocks. + * @since Twenty Twenty 2.6 Enqueue the CSS file for the variable font. */ function twentytwenty_block_editor_styles() { @@ -430,6 +435,9 @@ function twentytwenty_block_editor_styles() { wp_add_inline_style( 'twentytwenty-block-editor-styles', $customizer_css ); } + // Enqueue the CSS file for the variable font, Inter. + wp_enqueue_style( 'twentytwenty-fonts', get_theme_file_uri( '/assets/css/font-inter.css' ), array(), wp_get_theme()->get( 'Version' ), 'all' ); + // Add inline style for non-latin fonts. $custom_css = TwentyTwenty_Non_Latin_Languages::get_non_latin_css( 'block-editor' ); if ( $custom_css ) { @@ -447,11 +455,13 @@ function twentytwenty_block_editor_styles() { * Enqueue classic editor styles. * * @since Twenty Twenty 1.0 + * @since Twenty Twenty 2.6 Enqueue the CSS file for the variable font. */ function twentytwenty_classic_editor_styles() { $classic_editor_styles = array( '/assets/css/editor-style-classic.css', + '/assets/css/font-inter.css', ); add_editor_style( $classic_editor_styles ); diff --git a/src/wp-content/themes/twentytwentyfour/functions.php b/src/wp-content/themes/twentytwentyfour/functions.php index baee062f6f14c..8536cb8423163 100644 --- a/src/wp-content/themes/twentytwentyfour/functions.php +++ b/src/wp-content/themes/twentytwentyfour/functions.php @@ -194,7 +194,7 @@ function twentytwentyfour_block_stylesheets() { function twentytwentyfour_pattern_categories() { register_block_pattern_category( - 'page', + 'twentytwentyfour_page', array( 'label' => _x( 'Pages', 'Block pattern category', 'twentytwentyfour' ), 'description' => __( 'A collection of full page layouts.', 'twentytwentyfour' ), diff --git a/src/wp-content/themes/twentytwentyfour/patterns/hidden-portfolio-hero.php b/src/wp-content/themes/twentytwentyfour/patterns/hidden-portfolio-hero.php index b0922d2ec27f4..1af3e7dbd17c1 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/hidden-portfolio-hero.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/hidden-portfolio-hero.php @@ -13,7 +13,7 @@

-

Leia Acosta, a passionate photographer who finds inspiration in capturing the fleeting beauty of life.', 'twentytwentyfour ' ) ); ?>

+

Leia Acosta, a passionate photographer who finds inspiration in capturing the fleeting beauty of life.', 'twentytwentyfour' ) ); ?>

diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-about-business.php b/src/wp-content/themes/twentytwentyfour/patterns/page-about-business.php index f04b20940c519..ad626980e177b 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-about-business.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-about-business.php @@ -2,7 +2,7 @@ /** * Title: About * Slug: twentytwentyfour/page-about-business - * Categories: page + * Categories: twentytwentyfour_page * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-home-blogging.php b/src/wp-content/themes/twentytwentyfour/patterns/page-home-blogging.php index ae88e9006c74c..5cef5d4e5028c 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-home-blogging.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-home-blogging.php @@ -2,7 +2,7 @@ /** * Title: Blogging home * Slug: twentytwentyfour/page-home-blogging - * Categories: page + * Categories: twentytwentyfour_page * Keywords: page, starter * Post Types: page, wp_template * Viewport width: 1400 diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-home-business.php b/src/wp-content/themes/twentytwentyfour/patterns/page-home-business.php index ca22952174553..415c42f1d913b 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-home-business.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-home-business.php @@ -2,7 +2,7 @@ /** * Title: Business home * Slug: twentytwentyfour/page-home-business - * Categories: page + * Categories: twentytwentyfour_page * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio-gallery.php b/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio-gallery.php index cce1fdb106845..a9ce9d760a682 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio-gallery.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio-gallery.php @@ -2,7 +2,7 @@ /** * Title: Portfolio home image gallery * Slug: twentytwentyfour/page-home-gallery - * Categories: page + * Categories: twentytwentyfour_page * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio.php b/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio.php index 4f3c473f0b21a..40d4fa1e751bd 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-home-portfolio.php @@ -2,7 +2,7 @@ /** * Title: Portfolio home with post featured images * Slug: twentytwentyfour/page-home-portfolio - * Categories: page + * Categories: twentytwentyfour_page * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-newsletter-landing.php b/src/wp-content/themes/twentytwentyfour/patterns/page-newsletter-landing.php index 24c6d9a86fb57..5ee552e5008df 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-newsletter-landing.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-newsletter-landing.php @@ -2,7 +2,7 @@ /** * Title: Newsletter landing * Slug: twentytwentyfour/page-newsletter-landing - * Categories: call-to-action, page, featured + * Categories: call-to-action, twentytwentyfour_page, featured * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-portfolio-overview.php b/src/wp-content/themes/twentytwentyfour/patterns/page-portfolio-overview.php index 3e29b11387a27..b0bf21b480285 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-portfolio-overview.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-portfolio-overview.php @@ -2,7 +2,7 @@ /** * Title: Portfolio project overview * Slug: twentytwentyfour/page-portfolio-overview - * Categories: page, featured + * Categories: twentytwentyfour_page, featured * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/patterns/page-rsvp-landing.php b/src/wp-content/themes/twentytwentyfour/patterns/page-rsvp-landing.php index 469a13426a477..8b3ba8316cf86 100644 --- a/src/wp-content/themes/twentytwentyfour/patterns/page-rsvp-landing.php +++ b/src/wp-content/themes/twentytwentyfour/patterns/page-rsvp-landing.php @@ -2,7 +2,7 @@ /** * Title: RSVP landing * Slug: twentytwentyfour/page-rsvp-landing - * Categories: page + * Categories: twentytwentyfour_page * Keywords: starter * Block Types: core/post-content * Post Types: page, wp_template diff --git a/src/wp-content/themes/twentytwentyfour/readme.txt b/src/wp-content/themes/twentytwentyfour/readme.txt index c1837d46368a7..fcb6cdc9f9d7e 100644 --- a/src/wp-content/themes/twentytwentyfour/readme.txt +++ b/src/wp-content/themes/twentytwentyfour/readme.txt @@ -33,7 +33,33 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +This theme bundles the following third-party resources: + +=== Fonts === + +Cardo Font +Copyright (c) 2002-2011, David J. Perry (hospes02@scholarsfonts.net) +License: SIL Open Font License, 1.1, https://opensource.org/licenses/OFL-1.1 +Source: http://scholarsfonts.net + +Instrument Sans Font +Copyright 2022 The Instrument Sans Project Authors. +License: SIL Open Font License, 1.1, https://opensource.org/licenses/OFL-1.1 +Source: https://github.com/Instrument/instrument-sans + +Inter Font +Copyright 2020 The Inter Project Authors. +License: SIL Open Font License, 1.1, https://opensource.org/licenses/OFL-1.1 +Source: https://github.com/rsms/inter + +Jost Font +Copyright 2020 The Jost Project Authors. +License: SIL Open Font License, 1.1, https://opensource.org/licenses/OFL-1.1 +Source: https://github.com/indestructible-type/Jost + === Images === + License: CC0 https://creativecommons.org/publicdomain/zero/1.0/ museum.webp - https://www.rawpixel.com/image/3297419/free-photo-image-interior-hallway-architecture diff --git a/src/wp-content/themes/twentytwentyfour/screenshot.png b/src/wp-content/themes/twentytwentyfour/screenshot.png index 40c5b99e9f874..74fa4c7a1e3e5 100644 Binary files a/src/wp-content/themes/twentytwentyfour/screenshot.png and b/src/wp-content/themes/twentytwentyfour/screenshot.png differ diff --git a/src/wp-content/themes/twentytwentyfour/theme.json b/src/wp-content/themes/twentytwentyfour/theme.json index 7988b1af5c15f..8acd7993c342e 100644 --- a/src/wp-content/themes/twentytwentyfour/theme.json +++ b/src/wp-content/themes/twentytwentyfour/theme.json @@ -247,7 +247,7 @@ { "fontFamily": "Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol", "name": "System Serif", - "slug": "system-Serif" + "slug": "system-serif" } ], "fontSizes": [ @@ -795,9 +795,7 @@ }, "outline": { "color": "var(--wp--preset--color--contrast)", - "offset": "2px", - "style": "dotted", - "width": "1px" + "offset": "2px" }, "border": { "color": "var(--wp--preset--color--contrast-2)" @@ -908,7 +906,8 @@ "fontStyle": "normal", "fontWeight": "400", "lineHeight": "1.55" - } + }, + "css": ".wp-site-blocks *:focus{outline-width:2px;outline-style:solid}" }, "templateParts": [ { diff --git a/src/wp-content/themes/twentytwentyone/assets/images/Daffodils.jpg b/src/wp-content/themes/twentytwentyone/assets/images/Daffodils.jpg index bd062b93cc74d..409522aeb1f53 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/Daffodils.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/Daffodils.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/Reading.jpg b/src/wp-content/themes/twentytwentyone/assets/images/Reading.jpg index cfa732a57aa05..841e45ce50344 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/Reading.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/Reading.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/in-the-bois-de-boulogne.jpg b/src/wp-content/themes/twentytwentyone/assets/images/in-the-bois-de-boulogne.jpg index cf74f5df046fb..0e03bde64ccd9 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/in-the-bois-de-boulogne.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/in-the-bois-de-boulogne.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/playing-in-the-sand.jpg b/src/wp-content/themes/twentytwentyone/assets/images/playing-in-the-sand.jpg index 557ae0f7cdd44..6a90421102a96 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/playing-in-the-sand.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/playing-in-the-sand.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/roses-tremieres-hollyhocks-1884.jpg b/src/wp-content/themes/twentytwentyone/assets/images/roses-tremieres-hollyhocks-1884.jpg index ff534164dcb34..0386999e241be 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/roses-tremieres-hollyhocks-1884.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/roses-tremieres-hollyhocks-1884.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/self-portrait-1885.jpg b/src/wp-content/themes/twentytwentyone/assets/images/self-portrait-1885.jpg index 623598b60c4b2..f6adaed8d19af 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/self-portrait-1885.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/self-portrait-1885.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/the-garden-at-bougival-1884.jpg b/src/wp-content/themes/twentytwentyone/assets/images/the-garden-at-bougival-1884.jpg index 6594d3d1bd868..4315638330856 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/the-garden-at-bougival-1884.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/the-garden-at-bougival-1884.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/villa-with-orange-trees-nice.jpg b/src/wp-content/themes/twentytwentyone/assets/images/villa-with-orange-trees-nice.jpg index 5b78d968d5b57..652a24702a4d7 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/villa-with-orange-trees-nice.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/villa-with-orange-trees-nice.jpg differ diff --git a/src/wp-content/themes/twentytwentyone/assets/images/young-woman-in-mauve.jpg b/src/wp-content/themes/twentytwentyone/assets/images/young-woman-in-mauve.jpg index df00d3147c6c7..cedf9cb5daf60 100644 Binary files a/src/wp-content/themes/twentytwentyone/assets/images/young-woman-in-mauve.jpg and b/src/wp-content/themes/twentytwentyone/assets/images/young-woman-in-mauve.jpg differ diff --git a/src/wp-content/themes/twentytwentythree/screenshot.png b/src/wp-content/themes/twentytwentythree/screenshot.png index d405921ea31a0..dee93e471f8d2 100644 Binary files a/src/wp-content/themes/twentytwentythree/screenshot.png and b/src/wp-content/themes/twentytwentythree/screenshot.png differ diff --git a/src/wp-content/themes/twentytwentythree/theme.json b/src/wp-content/themes/twentytwentythree/theme.json index 68e17a87e9adc..f28146fa05b86 100644 --- a/src/wp-content/themes/twentytwentythree/theme.json +++ b/src/wp-content/themes/twentytwentythree/theme.json @@ -730,7 +730,7 @@ { "area": "uncategorized", "name": "comments", - "title": "Comments" + "title": "Comments Template Part" }, { "area": "uncategorized", diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-black.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-black.jpg index 684affb490bfa..15194fe0ef8c9 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-black.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-black.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-gray.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-gray.jpg index 4fde7652c330d..7f9092608cf1b 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-gray.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-gray.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-green.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-green.jpg index f1b5505478129..86f626508d039 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-green.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-green.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-salmon.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-salmon.jpg index 727c70ea6b335..d854f6d061aa8 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-salmon.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/bird-on-salmon.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/ducks.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/ducks.jpg index 6c65eb4313aa3..ff8c3a2d3c0a2 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/ducks.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/ducks.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-a.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-a.jpg index b533d0f140d3e..f2b0c1ded8f5a 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-a.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-a.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-b.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-b.jpg index 9314e9fa2fd94..78df7e72f7cc0 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-b.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-b.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-c.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-c.jpg index 3797692068362..ffca8031b3de5 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-c.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-gray-c.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-salmon.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-salmon.jpg index 86731b9037e6d..28114804915c2 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-salmon.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-salmon.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/assets/images/icon-bird.jpg b/src/wp-content/themes/twentytwentytwo/assets/images/icon-bird.jpg index dbfa90e40a3ed..308cadc37bd0c 100644 Binary files a/src/wp-content/themes/twentytwentytwo/assets/images/icon-bird.jpg and b/src/wp-content/themes/twentytwentytwo/assets/images/icon-bird.jpg differ diff --git a/src/wp-content/themes/twentytwentytwo/screenshot.png b/src/wp-content/themes/twentytwentytwo/screenshot.png index 9e8710cde3419..0bba95b9b4a6e 100644 Binary files a/src/wp-content/themes/twentytwentytwo/screenshot.png and b/src/wp-content/themes/twentytwentytwo/screenshot.png differ diff --git a/src/wp-content/themes/twentytwentytwo/theme.json b/src/wp-content/themes/twentytwentytwo/theme.json index 41ffb72d98cbd..ec9ff8644e4af 100644 --- a/src/wp-content/themes/twentytwentytwo/theme.json +++ b/src/wp-content/themes/twentytwentytwo/theme.json @@ -1,4 +1,5 @@ { + "$schema": "https://schemas.wp.org/trunk/theme.json", "version": 2, "customTemplates": [ { diff --git a/src/wp-includes/Text/Diff.php b/src/wp-includes/Text/Diff.php index 40dba1a4a37cf..eee4e4f8ea531 100644 --- a/src/wp-includes/Text/Diff.php +++ b/src/wp-includes/Text/Diff.php @@ -296,7 +296,7 @@ class Text_MappedDiff extends Text_Diff { /** * Computes a diff between sequences of strings. * - * This can be used to compute things like case-insensitve diffs, or diffs + * This can be used to compute things like case-insensitive diffs, or diffs * which ignore changes in white-space. * * @param array $from_lines An array of strings. diff --git a/src/wp-includes/assets/script-loader-packages.min.php b/src/wp-includes/assets/script-loader-packages.min.php index 11257c8d1e0c0..2b05108405693 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', 'wp-polyfill'), 'version' => '7032343a947cfccf5608'), 'annotations.min.js' => array('dependencies' => array('wp-data', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-rich-text'), 'version' => 'c4843f8e435a9d7a87bb'), 'api-fetch.min.js' => array('dependencies' => array('wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '0fa4dabf8bf2c7adf21a'), 'autop.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'dacd785d109317df2707'), 'blob.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '10a1c5c0acdef3d15657'), 'block-directory.min.js' => array('dependencies' => array('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-polyfill', 'wp-primitives', 'wp-url'), 'version' => '5b7cd5ab23c9d68e0b1e'), 'block-editor.min.js' => array('dependencies' => array('react', 'react-dom', '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-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-shortcode', 'wp-style-engine', 'wp-token-list', 'wp-url', 'wp-warning', 'wp-wordcount'), 'version' => '8a070b748cf406a8d42e'), 'block-library.min.js' => array('dependencies' => array('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-keycodes', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport', 'wp-wordcount'), 'version' => '9c5365423f60fac3c287'), 'block-serialization-default-parser.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '30ffd7e7e199f10b2a6d'), 'blocks.min.js' => array('dependencies' => array('wp-autop', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-private-apis', 'wp-shortcode'), 'version' => '7204d43123223474471a'), 'commands.min.js' => array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-polyfill', 'wp-primitives', 'wp-private-apis'), 'version' => '07ff2b66990783ecd068'), 'components.min.js' => array('dependencies' => array('react', 'react-dom', '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-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-warning'), 'version' => '387d6480ace3103ccd8b'), 'compose.min.js' => array('dependencies' => array('react', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-polyfill', 'wp-priority-queue'), 'version' => '3189b344ff39fef940b7'), 'core-commands.min.js' => array('dependencies' => array('wp-commands', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-router', 'wp-url'), 'version' => 'ade490de79d35734e06d'), 'core-data.min.js' => array('dependencies' => array('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-polyfill', 'wp-private-apis', 'wp-url'), 'version' => '99b262137df116eb6013'), 'customize-widgets.min.js' => array('dependencies' => array('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-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-widgets'), 'version' => 'bb454c7f10757887ce5a'), 'data.min.js' => array('dependencies' => array('wp-compose', 'wp-deprecated', 'wp-element', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-priority-queue', 'wp-private-apis', 'wp-redux-routine'), 'version' => 'dc5f255634f3da29c8d5'), 'data-controls.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-data', 'wp-deprecated', 'wp-polyfill'), 'version' => 'fe4ccc8a1782ea8e2cb1'), 'date.min.js' => array('dependencies' => array('moment', 'wp-deprecated', 'wp-polyfill'), 'version' => '936c461ad5dce9c2c8ea'), 'deprecated.min.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => '73ad3591e7bc95f4777a'), 'dom.min.js' => array('dependencies' => array('wp-deprecated', 'wp-polyfill'), 'version' => '49ff2869626fbeaacc23'), 'dom-ready.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '392bdd43726760d1f3ca'), 'edit-post.min.js' => array('dependencies' => array('wp-a11y', '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-dom', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport', 'wp-warning', 'wp-widgets'), 'version' => '6720d8a86f225f3ce492'), 'edit-site.min.js' => array('dependencies' => array('react', 'wp-a11y', '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-date', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-reusable-blocks', 'wp-router', 'wp-url', 'wp-viewport', 'wp-widgets', 'wp-wordcount'), 'version' => 'c25cbb9d6b28255c1cb6'), 'edit-widgets.min.js' => array('dependencies' => array('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-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport', 'wp-widgets'), 'version' => '64e3e5b8558ec09ac4ba'), 'editor.min.js' => array('dependencies' => array('react', 'wp-a11y', 'wp-api-fetch', '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-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-wordcount'), 'version' => '5abe10197275cf7808ee'), 'element.min.js' => array('dependencies' => array('react', 'react-dom', 'wp-escape-html', 'wp-polyfill'), 'version' => 'ed1c7604880e8b574b40'), 'escape-html.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '03e27a7b6ae14f7afaa6'), 'format-library.min.js' => array('dependencies' => array('wp-a11y', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-url'), 'version' => '57955a6a6df65c1fb8b6'), 'hooks.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'c6aec9a8d4e5a5d543a1'), 'html-entities.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '36a4a255da7dd2e1bf8e'), 'i18n.min.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => '7701b0c3857f914212ef'), 'is-shallow-equal.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '20c2b06ecf04afb14fee'), 'keyboard-shortcuts.min.js' => array('dependencies' => array('wp-data', 'wp-element', 'wp-keycodes', 'wp-polyfill'), 'version' => '525da859946d4df24898'), 'keycodes.min.js' => array('dependencies' => array('wp-i18n', 'wp-polyfill'), 'version' => '3460bd0fac9859d6886c'), 'list-reusable-blocks.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '4d77f2834116824e70c8'), 'media-utils.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-blob', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => 'bcd60e7a2fb568f38015'), 'notices.min.js' => array('dependencies' => array('wp-data', 'wp-polyfill'), 'version' => '38e88f4b627cf873edd0'), 'nux.min.js' => array('dependencies' => array('wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '59718fab5e39f9dd21b0'), 'patterns.min.js' => array('dependencies' => array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => 'efcce5c1b2c28e8b2865'), 'plugins.min.js' => array('dependencies' => array('wp-compose', 'wp-element', 'wp-hooks', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-primitives'), 'version' => 'c485ff6186cdddabcf91'), 'preferences.min.js' => array('dependencies' => array('wp-a11y', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => 'ca088ba0a612bff77aa3'), 'preferences-persistence.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-polyfill'), 'version' => '6c6b220422eb35541489'), 'primitives.min.js' => array('dependencies' => array('wp-element', 'wp-polyfill'), 'version' => '6984e6eb5d6157c4fe44'), 'priority-queue.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '422e19e9d48b269c5219'), 'private-apis.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '11cb2ebaa70a9f1f0ab5'), 'redux-routine.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '0be1b2a6a79703e28531'), 'reusable-blocks.min.js' => array('dependencies' => array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => 'f43df5cec4d4061a74f0'), 'rich-text.min.js' => array('dependencies' => array('wp-a11y', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes', 'wp-polyfill'), 'version' => '6222504ebedf0627981b'), 'router.min.js' => array('dependencies' => array('wp-element', 'wp-polyfill', 'wp-private-apis', 'wp-url'), 'version' => 'd1ae6718bab1f7073adb'), 'server-side-render.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '81299db67c0fa2c65479'), 'shortcode.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'c128a3008a96e820aa86'), 'style-engine.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '17cbc030cba88a42ccb5'), 'token-list.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '199103fc7cec3b9eef5a'), 'undo-manager.min.js' => array('dependencies' => array('wp-is-shallow-equal', 'wp-polyfill'), 'version' => '312610424b40059d9f44'), 'url.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'b4979979018b684be209'), 'viewport.min.js' => array('dependencies' => array('wp-compose', 'wp-data', 'wp-element', 'wp-polyfill'), 'version' => '1fbef8175bb335c5603b'), 'warning.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '122829a085511691f14d'), 'widgets.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives'), 'version' => '938735ae45e739ac8b70'), 'wordcount.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '5a74890fd7c610679e34')); + array('dependencies' => array('wp-dom-ready', 'wp-i18n', 'wp-polyfill'), 'version' => 'd90eebea464f6c09bfd5'), 'annotations.min.js' => array('dependencies' => array('wp-data', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-rich-text'), 'version' => 'ffc4fc3374b0ab000805'), 'api-fetch.min.js' => array('dependencies' => array('wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '4c185334c5ec26e149cc'), 'autop.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '9fb50649848277dd318d'), 'blob.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '9113eed771d446f4a556'), 'block-directory.min.js' => array('dependencies' => array('react', '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-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '9159053f41b8ec09d91b'), 'block-editor.min.js' => array('dependencies' => array('react', 'react-dom', '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-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-style-engine', 'wp-token-list', 'wp-url', 'wp-warning', 'wp-wordcount'), 'version' => 'a982b7f1ddc404c9763b'), 'block-library.min.js' => array('dependencies' => array('react', '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-keycodes', 'wp-notices', 'wp-patterns', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport', 'wp-wordcount'), 'version' => '8a0c07fbc60b644cf03e'), 'block-serialization-default-parser.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '14d44daebf663d05d330'), 'blocks.min.js' => array('dependencies' => array('react', 'wp-autop', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-private-apis', 'wp-rich-text', 'wp-shortcode'), 'version' => '64a9ab28b62423f79e07'), 'commands.min.js' => array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-polyfill', 'wp-primitives', 'wp-private-apis'), 'version' => '0674417708cae5031b37'), 'components.min.js' => array('dependencies' => array('react', 'react-dom', '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-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-warning'), 'version' => '629594950877ccba0f43'), 'compose.min.js' => array('dependencies' => array('react', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-polyfill', 'wp-priority-queue'), 'version' => '1f65d1d8719bc97357e7'), 'core-commands.min.js' => array('dependencies' => array('react', 'wp-commands', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-router', 'wp-url'), 'version' => 'dbbc54588f73c5b23fa3'), 'core-data.min.js' => array('dependencies' => array('react', '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-polyfill', 'wp-private-apis', 'wp-rich-text', 'wp-url'), 'version' => 'dba2ea4cdd526475d52a'), 'customize-widgets.min.js' => array('dependencies' => array('react', '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-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-widgets'), 'version' => 'a44197f146efda4b8ad1'), 'data.min.js' => array('dependencies' => array('react', 'wp-compose', 'wp-deprecated', 'wp-element', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-priority-queue', 'wp-private-apis', 'wp-redux-routine'), 'version' => '70790e390a9624c9cef4'), 'data-controls.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-data', 'wp-deprecated', 'wp-polyfill'), 'version' => '49f5587e8b90f9e7cc7e'), 'date.min.js' => array('dependencies' => array('moment', 'wp-deprecated', 'wp-polyfill'), 'version' => 'ddd596bc6f2a45364bf2'), 'deprecated.min.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => 'e1f84915c5e8ae38964c'), 'dom.min.js' => array('dependencies' => array('wp-deprecated', 'wp-polyfill'), 'version' => '4ecffbffba91b10c5c7a'), 'dom-ready.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'f77871ff7694fffea381'), 'edit-post.min.js' => array('dependencies' => array('react', 'wp-a11y', '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-media-utils', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport', 'wp-warning', 'wp-widgets'), 'version' => 'e4fe739cfb5191e88233'), 'edit-site.min.js' => array('dependencies' => array('react', '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-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-reusable-blocks', 'wp-router', 'wp-url', 'wp-viewport', 'wp-widgets', 'wp-wordcount'), 'version' => '3bf825ca8a8349b15d7d'), 'edit-widgets.min.js' => array('dependencies' => array('react', '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-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport', 'wp-widgets'), 'version' => '863e8bac0af20fba6e4a'), 'editor.min.js' => array('dependencies' => array('react', '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-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-wordcount'), 'version' => '817d3c3d465ea165610a'), 'element.min.js' => array('dependencies' => array('react', 'react-dom', 'wp-escape-html', 'wp-polyfill'), 'version' => '603185df201aa54181a6'), 'escape-html.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '6561a406d2d232a6fbd2'), 'format-library.min.js' => array('dependencies' => array('react', 'wp-a11y', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-url'), 'version' => 'f2c401cc63ed8a35897d'), 'hooks.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '2810c76e705dd1a53b18'), 'html-entities.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '2cd3358363e0675638fb'), 'i18n.min.js' => array('dependencies' => array('wp-hooks', 'wp-polyfill'), 'version' => 'aee497d955fe7a29a7d6'), 'is-shallow-equal.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'e0f9f1d78d83f5196979'), 'keyboard-shortcuts.min.js' => array('dependencies' => array('react', 'wp-data', 'wp-element', 'wp-keycodes', 'wp-polyfill'), 'version' => '4d239ebc17efd846a168'), 'keycodes.min.js' => array('dependencies' => array('wp-i18n', 'wp-polyfill'), 'version' => '034ff647a54b018581d3'), 'list-reusable-blocks.min.js' => array('dependencies' => array('react', 'wp-api-fetch', 'wp-blob', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => 'b9d73b532124daefd2c7'), 'media-utils.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-blob', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '03fbd6c4f505a9385efe'), 'notices.min.js' => array('dependencies' => array('wp-data', 'wp-polyfill'), 'version' => '673a68a7ac2f556ed50b'), 'nux.min.js' => array('dependencies' => array('react', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '46c93a71c3e2c2bf37f0'), 'patterns.min.js' => array('dependencies' => array('react', '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-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => 'c81cbc7df425eaca0182'), 'plugins.min.js' => array('dependencies' => array('react', 'wp-compose', 'wp-element', 'wp-hooks', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-primitives'), 'version' => '2d369cbfdcb887111e06'), 'preferences.min.js' => array('dependencies' => array('react', 'wp-a11y', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-private-apis'), 'version' => '3f5baaf6d334123043d3'), 'preferences-persistence.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-polyfill'), 'version' => '3f5184d775ed9dfb154f'), 'primitives.min.js' => array('dependencies' => array('wp-element', 'wp-polyfill'), 'version' => '81082ab8cc08e6b73043'), 'priority-queue.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '391948bb0355121a7f52'), 'private-apis.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '5e7fdf55d04b8c2aadef'), 'redux-routine.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '72ec9ed71190c996fe2e'), 'reusable-blocks.min.js' => array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '008366ba172a4f4b92b4'), 'rich-text.min.js' => array('dependencies' => array('wp-a11y', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes', 'wp-polyfill'), 'version' => '88a44b54270a7c0b39eb'), 'router.min.js' => array('dependencies' => array('react', 'wp-element', 'wp-polyfill', 'wp-private-apis', 'wp-url'), 'version' => '92fd517f31b92695552a'), 'server-side-render.min.js' => array('dependencies' => array('react', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => '8e53ef39c9065ebf9e46'), 'shortcode.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'b7747eee0efafd2f0c3b'), 'style-engine.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '5bd98acb9813a2d90abf'), 'token-list.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '05f8a6df6258f0081718'), 'undo-manager.min.js' => array('dependencies' => array('wp-is-shallow-equal', 'wp-polyfill'), 'version' => 'f1701372eeeb8b605515'), 'url.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'f93d00b28dd08ca5a662'), 'viewport.min.js' => array('dependencies' => array('react', 'wp-compose', 'wp-data', 'wp-polyfill'), 'version' => 'e555fda1d93ecf1fb1e0'), 'warning.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'ed7c8b0940914f4fe44b'), 'widgets.min.js' => array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-polyfill', 'wp-primitives'), 'version' => 'c732b69b0507c9a5462b'), 'wordcount.min.js' => array('dependencies' => array('wp-polyfill'), 'version' => '55d8c2bf3dc99e7ea5ec')); diff --git a/src/wp-includes/block-bindings.php b/src/wp-includes/block-bindings.php new file mode 100644 index 0000000000000..d1f0421621bab --- /dev/null +++ b/src/wp-includes/block-bindings.php @@ -0,0 +1,130 @@ + __( 'My Custom Source', 'my-plugin' ), + * 'get_value_callback' => 'my_plugin_get_custom_source_value', + * ) ); + * } + * add_action( 'init', 'my_plugin_register_block_bindings_sources' ); + * + * ### Usage in a block + * + * In a block's `metadata.bindings` attribute, you can specify the source and + * its arguments. Such a block will use the source to override the block + * attribute's value. For example: + * + * + *

Fallback text that gets replaced.

+ * + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. It must be a string containing a namespace prefix, i.e. + * `my-plugin/my-custom-source`. It must only contain lowercase alphanumeric + * characters, the forward slash `/` and dashes. + * @param array $source_properties { + * The array of arguments that are used to register a source. + * + * @type string $label The label of the source. + * @type callback $get_value_callback A callback executed when the source is processed during block rendering. + * The callback should have the following signature: + * + * `function ($source_args, $block_instance,$attribute_name): mixed` + * - @param array $source_args Array containing source arguments + * used to look up the override value, + * i.e. {"key": "foo"}. + * - @param WP_Block $block_instance The block instance. + * - @param string $attribute_name The name of an attribute . + * The callback has a mixed return type; it may return a string to override + * the block's original value, null, false to remove an attribute, etc. + * } + * @return WP_Block_Bindings_Source|false Source when the registration was successful, or `false` on failure. + */ +function register_block_bindings_source( string $source_name, array $source_properties ) { + return WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_properties ); +} + +/** + * Unregisters a block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name Block bindings source name including namespace. + * @return WP_Block_Bindings_Source|false The unregistered block bindings source on success and `false` otherwise. + */ +function unregister_block_bindings_source( string $source_name ) { + return WP_Block_Bindings_Registry::get_instance()->unregister( $source_name ); +} + +/** + * Retrieves the list of all registered block bindings sources. + * + * @since 6.5.0 + * + * @return WP_Block_Bindings_Source[] The array of registered block bindings sources. + */ +function get_all_registered_block_bindings_sources() { + return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); +} + +/** + * Retrieves a registered block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return WP_Block_Bindings_Source|null The registered block bindings source, or `null` if it is not registered. + */ +function get_block_bindings_source( string $source_name ) { + return WP_Block_Bindings_Registry::get_instance()->get_registered( $source_name ); +} diff --git a/src/wp-includes/block-bindings/pattern-overrides.php b/src/wp-includes/block-bindings/pattern-overrides.php new file mode 100644 index 0000000000000..6f719101ef5dd --- /dev/null +++ b/src/wp-includes/block-bindings/pattern-overrides.php @@ -0,0 +1,46 @@ + "foo" ). + * @param WP_Block $block_instance The block instance. + * @param string $attribute_name The name of the target attribute. + * @return mixed The value computed for the source. + */ +function _block_bindings_pattern_overrides_get_value( array $source_args, $block_instance, string $attribute_name ) { + if ( empty( $block_instance->attributes['metadata']['id'] ) ) { + return null; + } + $block_id = $block_instance->attributes['metadata']['id']; + return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); +} + +/** + * Registers Pattern Overrides source in the Block Bindings registry. + * + * @since 6.5.0 + * @access private + */ +function _register_block_bindings_pattern_overrides_source() { + register_block_bindings_source( + 'core/pattern-overrides', + array( + 'label' => _x( 'Pattern Overrides', 'block bindings source' ), + 'get_value_callback' => '_block_bindings_pattern_overrides_get_value', + ) + ); +} + +add_action( 'init', '_register_block_bindings_pattern_overrides_source' ); diff --git a/src/wp-includes/block-bindings/post-meta.php b/src/wp-includes/block-bindings/post-meta.php new file mode 100644 index 0000000000000..4be1ae96b7620 --- /dev/null +++ b/src/wp-includes/block-bindings/post-meta.php @@ -0,0 +1,58 @@ + "foo" ). + * @return mixed The value computed for the source. + */ +function _block_bindings_post_meta_get_value( array $source_args ) { + if ( ! isset( $source_args['key'] ) ) { + return null; + } + + // Use the postId attribute if available. + if ( isset( $source_args['postId'] ) ) { + $post_id = $source_args['postId']; + } else { + // $block_instance->context['postId'] is not available in the Image block. + $post_id = get_the_ID(); + } + + // If a post isn't public, we need to prevent unauthorized users from accessing the post meta. + $post = get_post( $post_id ); + if ( ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post ) ) { + return null; + } + + return get_post_meta( $post_id, $source_args['key'], true ); +} + +/** + * Registers Post Meta source in the block bindings registry. + * + * @since 6.5.0 + * @access private + */ +function _register_block_bindings_post_meta_source() { + register_block_bindings_source( + 'core/post-meta', + array( + 'label' => _x( 'Post Meta', 'block bindings source' ), + 'get_value_callback' => '_block_bindings_post_meta_get_value', + ) + ); +} + +add_action( 'init', '_register_block_bindings_post_meta_source' ); diff --git a/src/wp-includes/block-patterns.php b/src/wp-includes/block-patterns.php index 66bdfd68e7caa..e5c770e1d4330 100644 --- a/src/wp-includes/block-patterns.php +++ b/src/wp-includes/block-patterns.php @@ -135,6 +135,20 @@ function _register_core_block_patterns_and_categories() { 'description' => __( 'Different layouts containing video or audio.' ), ) ); + register_block_pattern_category( + 'videos', + array( + 'label' => _x( 'Videos', 'Block pattern category' ), + 'description' => __( 'Different layouts containing videos.' ), + ) + ); + register_block_pattern_category( + 'audio', + array( + 'label' => _x( 'Audio', 'Block pattern category' ), + 'description' => __( 'Different layouts containing audio.' ), + ) + ); register_block_pattern_category( 'posts', array( diff --git a/src/wp-includes/block-supports/background.php b/src/wp-includes/block-supports/background.php index 283833991f57b..9b82e6a9d598d 100644 --- a/src/wp-includes/block-supports/background.php +++ b/src/wp-includes/block-supports/background.php @@ -70,13 +70,13 @@ function wp_render_background_support( $block_content, $block ) { return $block_content; } - $background_size = isset( $block_attributes['style']['background']['backgroundSize'] ) + $background_size = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; - $background_position = isset( $block_attributes['style']['background']['backgroundPosition'] ) + $background_position = isset( $block_attributes['style']['background']['backgroundPosition'] ) ? $block_attributes['style']['background']['backgroundPosition'] : null; - $background_repeat = isset( $block_attributes['style']['background']['backgroundRepeat'] ) + $background_repeat = isset( $block_attributes['style']['background']['backgroundRepeat'] ) ? $block_attributes['style']['background']['backgroundRepeat'] : null; diff --git a/src/wp-includes/block-supports/dimensions.php b/src/wp-includes/block-supports/dimensions.php index a889e78a16973..da68f187c3915 100644 --- a/src/wp-includes/block-supports/dimensions.php +++ b/src/wp-includes/block-supports/dimensions.php @@ -83,6 +83,86 @@ function wp_apply_dimensions_support( $block_type, $block_attributes ) { return $attributes; } +/** + * Renders server-side dimensions styles to the block wrapper. + * This block support uses the `render_block` hook to ensure that + * it is also applied to non-server-rendered blocks. + * + * @since 6.5.0 + * @access private + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function wp_render_dimensions_support( $block_content, $block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $block_attributes = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array(); + $has_aspect_ratio_support = block_has_support( $block_type, array( 'dimensions', 'aspectRatio' ), false ); + + if ( + ! $has_aspect_ratio_support || + wp_should_skip_block_supports_serialization( $block_type, 'dimensions', 'aspectRatio' ) + ) { + return $block_content; + } + + $dimensions_block_styles = array(); + $dimensions_block_styles['aspectRatio'] = $block_attributes['style']['dimensions']['aspectRatio'] ?? null; + + // To ensure the aspect ratio does not get overridden by `minHeight` unset any existing rule. + if ( + isset( $dimensions_block_styles['aspectRatio'] ) + ) { + $dimensions_block_styles['minHeight'] = 'unset'; + } elseif ( + isset( $block_attributes['style']['dimensions']['minHeight'] ) || + isset( $block_attributes['minHeight'] ) + ) { + $dimensions_block_styles['aspectRatio'] = 'unset'; + } + + $styles = wp_style_engine_get_styles( array( 'dimensions' => $dimensions_block_styles ) ); + + if ( ! empty( $styles['css'] ) ) { + // Inject dimensions styles to the first element, presuming it's the wrapper, if it exists. + $tags = new WP_HTML_Tag_Processor( $block_content ); + + if ( $tags->next_tag() ) { + $existing_style = $tags->get_attribute( 'style' ); + $updated_style = ''; + + if ( ! empty( $existing_style ) ) { + $updated_style = $existing_style; + if ( ! str_ends_with( $existing_style, ';' ) ) { + $updated_style .= ';'; + } + } + + $updated_style .= $styles['css']; + $tags->set_attribute( 'style', $updated_style ); + + if ( ! empty( $styles['classnames'] ) ) { + foreach ( explode( ' ', $styles['classnames'] ) as $class_name ) { + if ( + str_contains( $class_name, 'aspect-ratio' ) && + ! isset( $block_attributes['style']['dimensions']['aspectRatio'] ) + ) { + continue; + } + $tags->add_class( $class_name ); + } + } + } + + return $tags->get_updated_html(); + } + + return $block_content; +} + +add_filter( 'render_block', 'wp_render_dimensions_support', 10, 2 ); + // Register the block support. WP_Block_Supports::get_instance()->register( 'dimensions', diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index 67d6a3f1b77ac..f5acd75a72d7c 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -615,6 +615,9 @@ function wp_render_layout_support_flag( $block_content, $block ) { $processor->add_class( $class_name ); } return $processor->get_updated_html(); + } elseif ( ! $block_supports_layout ) { + // Ensure layout classnames are not injected if there is no layout support. + return $block_content; } $global_settings = wp_get_global_settings(); @@ -834,7 +837,8 @@ function wp_render_layout_support_flag( $block_content, $block ) { break; } - if ( false !== strpos( $processor->get_attribute( 'class' ), $inner_block_wrapper_classes ) ) { + $class_attribute = $processor->get_attribute( 'class' ); + if ( is_string( $class_attribute ) && str_contains( $class_attribute, $inner_block_wrapper_classes ) ) { break; } } while ( $processor->next_tag() ); diff --git a/src/wp-includes/block-supports/shadow.php b/src/wp-includes/block-supports/shadow.php index 6fa05b248f4c4..0ccaf3fd44b76 100644 --- a/src/wp-includes/block-supports/shadow.php +++ b/src/wp-includes/block-supports/shadow.php @@ -58,9 +58,8 @@ function wp_apply_shadow_support( $block_type, $block_attributes ) { $shadow_block_styles = array(); - $preset_shadow = array_key_exists( 'shadow', $block_attributes ) ? "var:preset|shadow|{$block_attributes['shadow']}" : null; - $custom_shadow = isset( $block_attributes['style']['shadow'] ) ? $block_attributes['style']['shadow'] : null; - $shadow_block_styles['shadow'] = $preset_shadow ? $preset_shadow : $custom_shadow; + $custom_shadow = $block_attributes['style']['shadow'] ?? null; + $shadow_block_styles['shadow'] = $custom_shadow; $attributes = array(); $styles = wp_style_engine_get_styles( $shadow_block_styles ); diff --git a/src/wp-includes/block-supports/typography.php b/src/wp-includes/block-supports/typography.php index bccde4f5c0221..e7d081c937ee4 100644 --- a/src/wp-includes/block-supports/typography.php +++ b/src/wp-includes/block-supports/typography.php @@ -398,6 +398,7 @@ function wp_get_typography_value_and_unit( $raw_value, $options = array() ) { * * @since 6.1.0 * @since 6.3.0 Checks for unsupported min/max viewport values that cause invalid clamp values. + * @since 6.5.0 Returns early when min and max viewport subtraction is zero to avoid division by zero. * @access private * * @param array $args { @@ -468,12 +469,18 @@ function wp_get_computed_fluid_typography_value( $args = array() ) { return null; } + // Calculates the linear factor denominator. If it's 0, we cannot calculate a fluid value. + $linear_factor_denominator = $maximum_viewport_width['value'] - $minimum_viewport_width['value']; + if ( empty( $linear_factor_denominator ) ) { + return null; + } + /* * Build CSS rule. * Borrowed from https://websemantics.uk/tools/responsive-font-calculator/. */ $view_port_width_offset = round( $minimum_viewport_width['value'] / 100, 3 ) . $font_size_unit; - $linear_factor = 100 * ( ( $maximum_font_size['value'] - $minimum_font_size['value'] ) / ( $maximum_viewport_width['value'] - $minimum_viewport_width['value'] ) ); + $linear_factor = 100 * ( ( $maximum_font_size['value'] - $minimum_font_size['value'] ) / ( $linear_factor_denominator ) ); $linear_factor_scaled = round( $linear_factor * $scale_factor, 3 ); $linear_factor_scaled = empty( $linear_factor_scaled ) ? 1 : $linear_factor_scaled; $fluid_target_font_size = implode( '', $minimum_font_size_rem ) . " + ((1vw - $view_port_width_offset) * $linear_factor_scaled)"; diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index e2cd7c1587b9f..3f912ad0611b6 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -251,7 +251,7 @@ function _get_block_templates_paths( $base_directory ) { * @param string $template_type 'wp_template' or 'wp_template_part'. * @param string $slug Template slug. * @return array|null { - * Array with template metadata if $template_type is one of 'wp_template' or 'wp_template_part'. + * Array with template metadata if $template_type is one of 'wp_template' or 'wp_template_part', * null otherwise. * * @type string $slug Template slug. diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 431e2b015332c..b7c2c2c433190 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -64,6 +64,7 @@ function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) { 'viewScript' => 'view-script', 'editorStyle' => 'editor-style', 'style' => 'style', + 'viewStyle' => 'view-style', ); $asset_handle = str_replace( '/', '-', $block_name ) . '-' . $field_mappings[ $field_name ]; @@ -123,12 +124,13 @@ function get_block_asset_url( $path ) { /** * Finds a script handle for the selected block metadata field. It detects - * when a path to file was provided and finds a corresponding asset file - * with details necessary to register the script under automatically + * when a path to file was provided and optionally finds a corresponding asset + * file with details necessary to register the script under automatically * generated handle name. It returns unprocessed script handle otherwise. * * @since 5.5.0 * @since 6.1.0 Added `$index` parameter. + * @since 6.5.0 The asset file is optional. * * @param array $metadata Block metadata. * @param string $field_name Field name to pick from metadata. @@ -162,21 +164,6 @@ function register_block_script_handle( $metadata, $field_name, $index = 0 ) { realpath( $script_asset_raw_path ) ); - if ( empty( $script_asset_path ) ) { - _doing_it_wrong( - __FUNCTION__, - sprintf( - /* translators: 1: Asset file location, 2: Field name, 3: Block name. */ - __( 'The asset file (%1$s) for the "%2$s" defined in "%3$s" block definition is missing.' ), - $script_asset_raw_path, - $field_name, - $metadata['name'] - ), - '5.5.0' - ); - return false; - } - $script_path_norm = wp_normalize_path( realpath( $path . '/' . $script_path ) ); $script_uri = get_block_asset_url( $script_path_norm ); @@ -185,7 +172,8 @@ function register_block_script_handle( $metadata, $field_name, $index = 0 ) { $script_args['strategy'] = 'defer'; } - $script_asset = require $script_asset_path; + // Asset file for blocks is optional. See https://core.trac.wordpress.org/ticket/60460. + $script_asset = ! empty( $script_asset_path ) ? require $script_asset_path : array(); $script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array(); $result = wp_register_script( $script_handle, @@ -326,6 +314,7 @@ function get_block_metadata_i18n_schema() { * @since 6.1.0 Added support for `render` field. * @since 6.3.0 Added `selectors` field. * @since 6.4.0 Added support for `blockHooks` field. + * @since 6.5.0 Added support for `allowedBlocks` and `viewStyle` fields. * * @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. @@ -422,6 +411,7 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { 'styles' => 'styles', 'variations' => 'variations', 'example' => 'example', + 'allowedBlocks' => 'allowed_blocks', ); $textdomain = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null; $i18n_schema = get_block_metadata_i18n_schema(); @@ -503,6 +493,7 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { $style_fields = array( 'editorStyle' => 'editor_style_handles', 'style' => 'style_handles', + 'viewStyle' => 'view_style_handles', ); foreach ( $style_fields as $metadata_field_name => $settings_field_name ) { if ( ! empty( $settings[ $metadata_field_name ] ) ) { @@ -758,27 +749,33 @@ function get_hooked_blocks() { } /** - * Conditionally returns the markup for a given hooked block type. + * Conditionally returns the markup for a given hooked block. * - * Accepts two arguments: A reference to an anchor block, and the name of a hooked block type. + * Accepts three arguments: A hooked block, its type, and a reference to an anchor block. * If the anchor block has already been processed, and the given hooked block type is in the list * of ignored hooked blocks, an empty string is returned. * + * The hooked block type is specified separately as it's possible that a filter might've modified + * the hooked block such that `$hooked_block['blockName']` does no longer reflect the original type. + * * This function is meant for internal use only. * * @since 6.5.0 * @access private * - * @param array $anchor_block The anchor block. Passed by reference. - * @param string $hooked_block_type The name of the hooked block type. - * @return string The markup for the given hooked block type, or an empty string if the block is ignored. + * @param array $hooked_block The hooked block, represented as a parsed block array. + * @param string $hooked_block_type The type of the hooked block. This could be different from + * $hooked_block['blockName'], as a filter might've modified the latter. + * @param array $anchor_block The anchor block, represented as a parsed block array. + * Passed by reference. + * @return string The markup for the given hooked block, or an empty string if the block is ignored. */ -function get_hooked_block_markup( &$anchor_block, $hooked_block_type ) { +function get_hooked_block_markup( $hooked_block, $hooked_block_type, &$anchor_block ) { if ( ! isset( $anchor_block['attrs']['metadata']['ignoredHookedBlocks'] ) ) { $anchor_block['attrs']['metadata']['ignoredHookedBlocks'] = array(); } - if ( in_array( $hooked_block_type, $anchor_block['attrs']['metadata']['ignoredHookedBlocks'] ) ) { + if ( in_array( $hooked_block_type, $anchor_block['attrs']['metadata']['ignoredHookedBlocks'], true ) ) { return ''; } @@ -786,7 +783,72 @@ function get_hooked_block_markup( &$anchor_block, $hooked_block_type ) { // However, its presence does not affect the frontend. $anchor_block['attrs']['metadata']['ignoredHookedBlocks'][] = $hooked_block_type; - return get_comment_delimited_block_content( $hooked_block_type, array(), '' ); + return serialize_block( $hooked_block ); +} + +/** + * Returns the markup for blocks hooked to the given anchor block in a specific relative position. + * + * @since 6.5.0 + * @access private + * + * @param array $parsed_anchor_block The anchor block, in parsed block array format. + * @param string $relative_position The relative position of the hooked blocks. + * Can be one of 'before', 'after', 'first_child', or 'last_child'. + * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position. + * @param WP_Block_Template|array $context The block template, template part, or pattern that the anchor block belongs to. + * @return string + */ +function insert_hooked_blocks( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) { + $anchor_block_type = $parsed_anchor_block['blockName']; + $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) + ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] + : array(); + + /** + * Filters the list of hooked block types for a given anchor block type and relative position. + * + * @since 6.4.0 + * + * @param string[] $hooked_block_types The list of hooked block types. + * @param string $relative_position The relative position of the hooked blocks. + * Can be one of 'before', 'after', 'first_child', or 'last_child'. + * @param string $anchor_block_type The anchor block type. + * @param WP_Block_Template|WP_Post|array $context The block template, template part, `wp_navigation` post type, + * or pattern that the anchor block belongs to. + */ + $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); + + $markup = ''; + foreach ( $hooked_block_types as $hooked_block_type ) { + $parsed_hooked_block = array( + 'blockName' => $hooked_block_type, + 'attrs' => array(), + 'innerBlocks' => array(), + 'innerContent' => array(), + ); + + /** + * Filters the parsed block array for a given hooked block. + * + * The dynamic portion of the hook name, `$hooked_block_type`, refers to the block type name of the specific hooked block. + * + * @since 6.5.0 + * + * @param array $parsed_hooked_block The parsed block array for the given hooked block type. + * @param string $relative_position The relative position of the hooked block. + * @param array $parsed_anchor_block The anchor block, in parsed block array format. + * @param WP_Block_Template|WP_Post|array $context The block template, template part, `wp_navigation` post type, + * or pattern that the anchor block belongs to. + */ + $parsed_hooked_block = apply_filters( "hooked_block_{$hooked_block_type}", $parsed_hooked_block, $relative_position, $parsed_anchor_block, $context ); + + // It's possible that the `hooked_block_{$hooked_block_type}` filter returned a block of a different type, + // so we need to pass the original $hooked_block_type as well. + $markup .= get_hooked_block_markup( $parsed_hooked_block, $hooked_block_type, $parsed_anchor_block ); + } + + return $markup; } /** @@ -801,8 +863,9 @@ function get_hooked_block_markup( &$anchor_block, $hooked_block_type ) { * @since 6.4.0 * @access private * - * @param array $hooked_blocks An array of blocks hooked to another given block. - * @param WP_Block_Template|array $context A block template, template part, or pattern that the blocks belong to. + * @param array $hooked_blocks An array of blocks hooked to another given block. + * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object, + * or pattern that the blocks belong to. * @return callable A function that returns the serialized markup for the given block, * including the markup for any hooked blocks before it. */ @@ -826,40 +889,10 @@ function make_before_block_visitor( $hooked_blocks, $context ) { if ( $parent_block && ! $prev ) { // Candidate for first-child insertion. - $relative_position = 'first_child'; - $anchor_block_type = $parent_block['blockName']; - $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) - ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] - : array(); - - /** - * Filters the list of hooked block types for a given anchor block type and relative position. - * - * @since 6.4.0 - * - * @param string[] $hooked_block_types The list of hooked block types. - * @param string $relative_position The relative position of the hooked blocks. - * Can be one of 'before', 'after', 'first_child', or 'last_child'. - * @param string $anchor_block_type The anchor block type. - * @param WP_Block_Template|array $context The block template, template part, or pattern that the anchor block belongs to. - */ - $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); - foreach ( $hooked_block_types as $hooked_block_type ) { - $markup .= get_hooked_block_markup( $parent_block, $hooked_block_type ); - } + $markup .= insert_hooked_blocks( $parent_block, 'first_child', $hooked_blocks, $context ); } - $relative_position = 'before'; - $anchor_block_type = $block['blockName']; - $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) - ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] - : array(); - - /** This filter is documented in wp-includes/blocks.php */ - $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); - foreach ( $hooked_block_types as $hooked_block_type ) { - $markup .= get_hooked_block_markup( $block, $hooked_block_type ); - } + $markup .= insert_hooked_blocks( $block, 'before', $hooked_blocks, $context ); return $markup; }; @@ -877,8 +910,9 @@ function make_before_block_visitor( $hooked_blocks, $context ) { * @since 6.4.0 * @access private * - * @param array $hooked_blocks An array of blocks hooked to another block. - * @param WP_Block_Template|array $context A block template, template part, or pattern that the blocks belong to. + * @param array $hooked_blocks An array of blocks hooked to another block. + * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object, + * or pattern that the blocks belong to. * @return callable A function that returns the serialized markup for the given block, * including the markup for any hooked blocks after it. */ @@ -895,33 +929,11 @@ function make_after_block_visitor( $hooked_blocks, $context ) { * @return string The serialized markup for the given block, with the markup for any hooked blocks appended to it. */ return function ( &$block, &$parent_block = null, $next = null ) use ( $hooked_blocks, $context ) { - $markup = ''; - - $relative_position = 'after'; - $anchor_block_type = $block['blockName']; - $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) - ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] - : array(); - - /** This filter is documented in wp-includes/blocks.php */ - $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); - foreach ( $hooked_block_types as $hooked_block_type ) { - $markup .= get_hooked_block_markup( $block, $hooked_block_type ); - } + $markup = insert_hooked_blocks( $block, 'after', $hooked_blocks, $context ); if ( $parent_block && ! $next ) { // Candidate for last-child insertion. - $relative_position = 'last_child'; - $anchor_block_type = $parent_block['blockName']; - $hooked_block_types = isset( $hooked_blocks[ $anchor_block_type ][ $relative_position ] ) - ? $hooked_blocks[ $anchor_block_type ][ $relative_position ] - : array(); - - /** This filter is documented in wp-includes/blocks.php */ - $hooked_block_types = apply_filters( 'hooked_block_types', $hooked_block_types, $relative_position, $anchor_block_type, $context ); - foreach ( $hooked_block_types as $hooked_block_type ) { - $markup .= get_hooked_block_markup( $parent_block, $hooked_block_type ); - } + $markup .= insert_hooked_blocks( $parent_block, 'last_child', $hooked_blocks, $context ); } return $markup; diff --git a/src/wp-includes/blocks/audio/block.json b/src/wp-includes/blocks/audio/block.json index a4740e304451c..04df268a74a63 100644 --- a/src/wp-includes/blocks/audio/block.json +++ b/src/wp-includes/blocks/audio/block.json @@ -16,8 +16,8 @@ "__experimentalRole": "content" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/src/wp-includes/blocks/avatar/block.json b/src/wp-includes/blocks/avatar/block.json index 3b4ac7c84f617..fa86541b2963f 100644 --- a/src/wp-includes/blocks/avatar/block.json +++ b/src/wp-includes/blocks/avatar/block.json @@ -30,7 +30,11 @@ "alignWide": false, "spacing": { "margin": true, - "padding": true + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } }, "__experimentalBorder": { "__experimentalSkipSerialization": true, diff --git a/src/wp-includes/blocks/block.php b/src/wp-includes/blocks/block.php index d51b35d68b23d..444001fa49859 100644 --- a/src/wp-includes/blocks/block.php +++ b/src/wp-includes/blocks/block.php @@ -46,8 +46,28 @@ function render_block_core_block( $attributes ) { $content = $wp_embed->run_shortcode( $reusable_block->post_content ); $content = $wp_embed->autoembed( $content ); + $has_pattern_overrides = isset( $attributes['overrides'] ); + + /** + * We set the `pattern/overrides` context through the `render_block_context` + * filter so that it is available when a pattern's inner blocks are + * rendering via do_blocks given it only receives the inner content. + */ + if ( $has_pattern_overrides ) { + $filter_block_context = static function ( $context ) use ( $attributes ) { + $context['pattern/overrides'] = $attributes['overrides']; + return $context; + }; + add_filter( 'render_block_context', $filter_block_context, 1 ); + } + $content = do_blocks( $content ); unset( $seen_refs[ $attributes['ref'] ] ); + + if ( $has_pattern_overrides ) { + remove_filter( 'render_block_context', $filter_block_context, 1 ); + } + return $content; } diff --git a/src/wp-includes/blocks/block/block.json b/src/wp-includes/blocks/block/block.json index 4cb53960725d2..b30c865e57a7f 100644 --- a/src/wp-includes/blocks/block/block.json +++ b/src/wp-includes/blocks/block/block.json @@ -10,11 +10,15 @@ "attributes": { "ref": { "type": "number" + }, + "overrides": { + "type": "object" } }, "supports": { "customClassName": false, "html": false, - "inserter": false + "inserter": false, + "renaming": false } } diff --git a/src/wp-includes/blocks/blocks-json.php b/src/wp-includes/blocks/blocks-json.php index 85656181b047b..99eb57a0eddf0 100644 --- a/src/wp-includes/blocks/blocks-json.php +++ b/src/wp-includes/blocks/blocks-json.php @@ -75,8 +75,8 @@ '__experimentalRole' => 'content' ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'figcaption', '__experimentalRole' => 'content' ), @@ -154,7 +154,11 @@ 'alignWide' => false, 'spacing' => array( 'margin' => true, - 'padding' => true + 'padding' => true, + '__experimentalDefaultControls' => array( + 'margin' => false, + 'padding' => false + ) ), '__experimentalBorder' => array( '__experimentalSkipSerialization' => true, @@ -192,12 +196,16 @@ 'attributes' => array( 'ref' => array( 'type' => 'number' + ), + 'overrides' => array( + 'type' => 'object' ) ), 'supports' => array( 'customClassName' => false, 'html' => false, - 'inserter' => false + 'inserter' => false, + 'renaming' => false ) ), 'button' => array( @@ -214,6 +222,9 @@ 'link' ), 'textdomain' => 'default', + 'usesContext' => array( + 'pattern/overrides' + ), 'attributes' => array( 'tagName' => array( 'type' => 'string', @@ -245,8 +256,8 @@ '__experimentalRole' => 'content' ), 'text' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'a,button', '__experimentalRole' => 'content' ), @@ -516,8 +527,8 @@ 'textdomain' => 'default', 'attributes' => array( 'content' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'code', '__unstablePreserveWhiteSpace' => true ) @@ -1314,7 +1325,7 @@ 'ancestor' => array( 'core/comments' ), - 'description' => 'Displays a title with the number of comments', + 'description' => 'Displays a title with the number of comments.', 'textdomain' => 'default', 'usesContext' => array( 'postId', @@ -1397,9 +1408,6 @@ ), 'alt' => array( 'type' => 'string', - 'source' => 'attribute', - 'selector' => 'img', - 'attribute' => 'alt', 'default' => '' ), 'hasParallax' => array( @@ -1420,6 +1428,9 @@ 'customOverlayColor' => array( 'type' => 'string' ), + 'isUserOverlayColor' => array( + 'type' => 'boolean' + ), 'backgroundType' => array( 'type' => 'string', 'default' => 'image' @@ -1508,6 +1519,9 @@ ), 'enableContrastChecker' => false ), + 'dimensions' => array( + 'aspectRatio' => true + ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, @@ -1548,8 +1562,8 @@ 'default' => false ), 'summary' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'summary' ) ), @@ -1615,8 +1629,8 @@ '__experimentalRole' => 'content' ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'figcaption', '__experimentalRole' => 'content' ), @@ -1679,8 +1693,8 @@ 'attribute' => 'id' ), 'fileName' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'a:not([download])' ), 'textLinkHref' => array( @@ -1700,8 +1714,8 @@ 'default' => true ), 'downloadButtonText' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'a[download]' ), 'displayPreview' => array( @@ -1730,7 +1744,6 @@ ), 'interactivity' => true ), - 'viewScript' => 'file:./view.min.js', 'editorStyle' => 'wp-block-file-editor', 'style' => 'wp-block-file' ), @@ -1740,7 +1753,7 @@ 'name' => 'core/footnotes', 'title' => 'Footnotes', 'category' => 'text', - 'description' => '', + 'description' => 'Display footnotes added to the page.', 'keywords' => array( 'references' ), @@ -1774,6 +1787,7 @@ 'html' => false, 'multiple' => false, 'reusable' => false, + 'inserter' => false, 'spacing' => array( 'margin' => true, 'padding' => true, @@ -1873,8 +1887,8 @@ 'attribute' => 'data-id' ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => '.blocks-gallery-item__caption' ) ) @@ -1903,14 +1917,18 @@ 'maximum' => 8 ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => '.blocks-gallery-caption' ), 'imageCrop' => array( 'type' => 'boolean', 'default' => true ), + 'randomOrder' => array( + 'type' => 'boolean', + 'default' => false + ), 'fixedHeight' => array( 'type' => 'boolean', 'default' => true @@ -2018,7 +2036,6 @@ '__experimentalOnEnter' => true, '__experimentalOnMerge' => true, '__experimentalSettings' => true, - '__experimentalMetadata' => true, 'align' => array( 'wide', 'full' @@ -2027,7 +2044,11 @@ 'ariaLabel' => true, 'html' => false, 'background' => array( - 'backgroundImage' => true + 'backgroundImage' => true, + 'backgroundSize' => true, + '__experimentalDefaultControls' => array( + 'backgroundImage' => true + ) ), 'color' => array( 'gradients' => true, @@ -2101,15 +2122,17 @@ 'subtitle' ), 'textdomain' => 'default', + 'usesContext' => array( + 'pattern/overrides' + ), 'attributes' => array( 'textAlign' => array( 'type' => 'string' ), 'content' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'h1,h2,h3,h4,h5,h6', - 'default' => '', '__experimentalRole' => 'content' ), 'level' => array( @@ -2154,9 +2177,7 @@ '__experimentalTextDecoration' => true, '__experimentalWritingMode' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'fontAppearance' => true, - 'textTransform' => true + 'fontSize' => true ) ), '__unstablePasteTextInline' => true, @@ -2243,7 +2264,8 @@ 'usesContext' => array( 'allowResize', 'imageCrop', - 'fixedHeight' + 'fixedHeight', + 'pattern/overrides' ), 'description' => 'Insert an image to make a visual statement.', 'keywords' => array( @@ -2253,9 +2275,6 @@ ), 'textdomain' => 'default', 'attributes' => array( - 'align' => array( - 'type' => 'string' - ), 'url' => array( 'type' => 'string', 'source' => 'attribute', @@ -2272,8 +2291,8 @@ '__experimentalRole' => 'content' ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'figcaption', '__experimentalRole' => 'content' ), @@ -2339,6 +2358,14 @@ ) ), 'supports' => array( + 'interactivity' => true, + 'align' => array( + 'left', + 'center', + 'right', + 'wide', + 'full' + ), 'anchor' => true, 'color' => array( 'text' => false, @@ -2377,8 +2404,7 @@ ) ), 'editorStyle' => 'wp-block-image-editor', - 'style' => 'wp-block-image', - 'viewScript' => 'file:./view.min.js' + 'style' => 'wp-block-image' ), 'latest-comments' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', @@ -2559,6 +2585,7 @@ 'style' => 'wp-block-latest-posts' ), 'legacy-widget' => array( + '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/legacy-widget', 'title' => 'Legacy Widget', @@ -2686,16 +2713,23 @@ 'type' => 'string' ), 'content' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'li', - 'default' => '', '__experimentalRole' => 'content' ) ), 'supports' => array( 'className' => false, '__experimentalSelector' => 'li', + 'spacing' => array( + 'margin' => true, + 'padding' => true, + '__experimentalDefaultControls' => array( + 'margin' => false, + 'padding' => false + ) + ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, @@ -2736,6 +2770,14 @@ ), 'supports' => array( 'className' => true, + 'spacing' => array( + 'margin' => true, + 'padding' => true, + '__experimentalDefaultControls' => array( + 'margin' => false, + 'padding' => false + ) + ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, @@ -2904,7 +2946,7 @@ ), 'originalContent' => array( 'type' => 'string', - 'source' => 'html' + 'source' => 'raw' ) ), 'supports' => array( @@ -3101,9 +3143,9 @@ ) ) ), - 'interactivity' => true + 'interactivity' => true, + 'renaming' => false ), - 'viewScript' => 'file:./view.min.js', 'editorStyle' => 'wp-block-navigation-editor', 'style' => 'wp-block-navigation' ), @@ -3182,7 +3224,8 @@ '__experimentalDefaultControls' => array( 'fontSize' => true ) - ) + ), + 'renaming' => false ), 'editorStyle' => 'wp-block-navigation-link-editor', 'style' => 'wp-block-navigation-link' @@ -3403,17 +3446,17 @@ ), 'textdomain' => 'default', 'usesContext' => array( - 'postId' + 'postId', + 'pattern/overrides' ), 'attributes' => array( 'align' => array( 'type' => 'string' ), 'content' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'p', - 'default' => '', '__experimentalRole' => 'content' ), 'dropCap' => array( @@ -3442,7 +3485,6 @@ 'text' => true ) ), - '__experimentalConnections' => true, 'spacing' => array( 'margin' => true, 'padding' => true, @@ -3480,7 +3522,8 @@ 'description' => 'Show a block pattern.', 'supports' => array( 'html' => false, - 'inserter' => false + 'inserter' => false, + 'renaming' => false ), 'textdomain' => 'default', 'attributes' => array( @@ -3934,6 +3977,10 @@ ), 'customGradient' => array( 'type' => 'string' + ), + 'useFirstImageFromPost' => array( + 'type' => 'boolean', + 'default' => false ) ), 'usesContext' => array( @@ -4005,8 +4052,18 @@ 'arrow' => array( 'type' => 'string', 'default' => 'none' + ), + 'inSameTerm' => array( + 'type' => 'boolean' + ), + 'taxonomy' => array( + 'type' => 'string', + 'default' => '' ) ), + 'usesContext' => array( + 'postType' + ), 'supports' => array( 'reusable' => false, 'html' => false, @@ -4044,7 +4101,6 @@ 'usesContext' => array( 'queryId', 'query', - 'queryContext', 'displayLayout', 'templateSlug', 'previewPostType', @@ -4218,9 +4274,7 @@ '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'fontAppearance' => true, - 'textTransform' => true + 'fontSize' => true ) ) ), @@ -4236,10 +4290,9 @@ 'textdomain' => 'default', 'attributes' => array( 'content' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'pre', - 'default' => '', '__unstablePreserveWhiteSpace' => true, '__experimentalRole' => 'content' ) @@ -4283,16 +4336,15 @@ 'textdomain' => 'default', 'attributes' => array( 'value' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'p', '__experimentalRole' => 'content' ), 'citation' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'cite', - 'default' => '', '__experimentalRole' => 'content' ), 'textAlign' => array( @@ -4316,6 +4368,10 @@ 'text' => true ) ), + 'spacing' => array( + 'margin' => true, + 'padding' => true + ), 'typography' => array( 'fontSize' => true, 'lineHeight' => true, @@ -4326,8 +4382,7 @@ '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'fontAppearance' => true + 'fontSize' => true ) ), '__experimentalBorder' => array( @@ -4413,8 +4468,7 @@ 'layout' => true ), 'editorStyle' => 'wp-block-query-editor', - 'style' => 'wp-block-query', - 'viewScript' => 'file:./view.min.js' + 'style' => 'wp-block-query' ), 'query-no-results' => array( '$schema' => 'https://schemas.wp.org/trunk/block.json', @@ -4577,7 +4631,7 @@ 'parent' => array( 'core/query-pagination' ), - 'description' => 'Displays a list of page numbers for pagination', + 'description' => 'Displays a list of page numbers for pagination.', 'textdomain' => 'default', 'attributes' => array( 'midSize' => array( @@ -4719,9 +4773,7 @@ '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'fontAppearance' => true, - 'textTransform' => true + 'fontSize' => true ) ) ), @@ -4749,10 +4801,9 @@ '__experimentalRole' => 'content' ), 'citation' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'cite', - 'default' => '', '__experimentalRole' => 'content' ), 'align' => array( @@ -4774,8 +4825,7 @@ '__experimentalTextDecoration' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'fontAppearance' => true + 'fontSize' => true ) ), 'color' => array( @@ -4786,6 +4836,12 @@ 'background' => true, 'text' => true ) + ), + 'layout' => array( + 'allowEditing' => false + ), + 'spacing' => array( + 'blockGap' => true ) ), 'styles' => array( @@ -4965,10 +5021,6 @@ ) ), - 'buttonBehavior' => array( - 'type' => 'string', - 'default' => 'expand-searchfield' - ), 'isSearchFieldHidden' => array( 'type' => 'boolean', 'default' => false @@ -5017,7 +5069,6 @@ ), 'html' => false ), - 'viewScript' => 'file:./view.min.js', 'editorStyle' => 'wp-block-search-editor', 'style' => 'wp-block-search' ), @@ -5283,11 +5334,7 @@ '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'lineHeight' => true, - 'fontAppearance' => true, - 'letterSpacing' => true, - 'textTransform' => true + 'fontSize' => true ) ) ), @@ -5496,10 +5543,9 @@ 'default' => false ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', - 'selector' => 'figcaption', - 'default' => '' + 'type' => 'rich-text', + 'source' => 'rich-text', + 'selector' => 'figcaption' ), 'head' => array( 'type' => 'array', @@ -5518,8 +5564,8 @@ 'selector' => 'td,th', 'query' => array( 'content' => array( - 'type' => 'string', - 'source' => 'html' + 'type' => 'rich-text', + 'source' => 'rich-text' ), 'tag' => array( 'type' => 'string', @@ -5567,8 +5613,8 @@ 'selector' => 'td,th', 'query' => array( 'content' => array( - 'type' => 'string', - 'source' => 'html' + 'type' => 'rich-text', + 'source' => 'rich-text' ), 'tag' => array( 'type' => 'string', @@ -5616,8 +5662,8 @@ 'selector' => 'td,th', 'query' => array( 'content' => array( - 'type' => 'string', - 'source' => 'html' + 'type' => 'rich-text', + 'source' => 'rich-text' ), 'tag' => array( 'type' => 'string', @@ -5794,7 +5840,8 @@ 'supports' => array( 'align' => true, 'html' => false, - 'reusable' => false + 'reusable' => false, + 'renaming' => false ), 'editorStyle' => 'wp-block-template-part-editor' ), @@ -5900,10 +5947,9 @@ 'textdomain' => 'default', 'attributes' => array( 'content' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'pre', - 'default' => '', '__unstablePreserveWhiteSpace' => true, '__experimentalRole' => 'content' ), @@ -5931,8 +5977,7 @@ '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, '__experimentalDefaultControls' => array( - 'fontSize' => true, - 'fontAppearance' => true + 'fontSize' => true ) ), 'spacing' => array( @@ -5972,8 +6017,8 @@ 'attribute' => 'autoplay' ), 'caption' => array( - 'type' => 'string', - 'source' => 'html', + 'type' => 'rich-text', + 'source' => 'rich-text', 'selector' => 'figcaption', '__experimentalRole' => 'content' ), @@ -6053,6 +6098,7 @@ 'style' => 'wp-block-video' ), 'widget-group' => array( + '$schema' => 'https://schemas.wp.org/trunk/block.json', 'apiVersion' => 3, 'name' => 'core/widget-group', 'category' => 'widgets', diff --git a/src/wp-includes/blocks/button/block.json b/src/wp-includes/blocks/button/block.json index eec327b4ca48e..f04d4642bb98e 100644 --- a/src/wp-includes/blocks/button/block.json +++ b/src/wp-includes/blocks/button/block.json @@ -8,6 +8,7 @@ "description": "Prompt visitors to take action with a button-style link.", "keywords": [ "link" ], "textdomain": "default", + "usesContext": [ "pattern/overrides" ], "attributes": { "tagName": { "type": "string", @@ -36,8 +37,8 @@ "__experimentalRole": "content" }, "text": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "a,button", "__experimentalRole": "content" }, diff --git a/src/wp-includes/blocks/calendar.php b/src/wp-includes/blocks/calendar.php index f1f7967235620..04b888972b1dd 100644 --- a/src/wp-includes/blocks/calendar.php +++ b/src/wp-includes/blocks/calendar.php @@ -33,10 +33,8 @@ function render_block_core_calendar( $attributes ) { str_contains( $permalink_structure, '%monthnum%' ) && str_contains( $permalink_structure, '%year%' ) ) { - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited $monthnum = $attributes['month']; - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited - $year = $attributes['year']; + $year = $attributes['year']; } } @@ -70,10 +68,8 @@ function render_block_core_calendar( $attributes ) { $calendar ); - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited $monthnum = $previous_monthnum; - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited - $year = $previous_year; + $year = $previous_year; return $output; } diff --git a/src/wp-includes/blocks/categories.php b/src/wp-includes/blocks/categories.php index 7e3979b7aefe2..c35376505b134 100644 --- a/src/wp-includes/blocks/categories.php +++ b/src/wp-includes/blocks/categories.php @@ -70,8 +70,7 @@ function render_block_core_categories( $attributes ) { function build_dropdown_script_block_core_categories( $dropdown_id ) { ob_start(); ?> - ', '' ), '', ob_get_clean() ) ); } /** diff --git a/src/wp-includes/blocks/code/block.json b/src/wp-includes/blocks/code/block.json index 80df74b5062b5..bd5db3c918b96 100644 --- a/src/wp-includes/blocks/code/block.json +++ b/src/wp-includes/blocks/code/block.json @@ -8,8 +8,8 @@ "textdomain": "default", "attributes": { "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "code", "__unstablePreserveWhiteSpace": true } diff --git a/src/wp-includes/blocks/comments-title/block.json b/src/wp-includes/blocks/comments-title/block.json index 12b105afe9a31..4107f5d590cde 100644 --- a/src/wp-includes/blocks/comments-title/block.json +++ b/src/wp-includes/blocks/comments-title/block.json @@ -5,7 +5,7 @@ "title": "Comments Title", "category": "theme", "ancestor": [ "core/comments" ], - "description": "Displays a title with the number of comments", + "description": "Displays a title with the number of comments.", "textdomain": "default", "usesContext": [ "postId", "postType" ], "attributes": { diff --git a/src/wp-includes/blocks/cover/block.json b/src/wp-includes/blocks/cover/block.json index e88dd2d65a372..80562da309899 100644 --- a/src/wp-includes/blocks/cover/block.json +++ b/src/wp-includes/blocks/cover/block.json @@ -19,9 +19,6 @@ }, "alt": { "type": "string", - "source": "attribute", - "selector": "img", - "attribute": "alt", "default": "" }, "hasParallax": { @@ -42,6 +39,9 @@ "customOverlayColor": { "type": "string" }, + "isUserOverlayColor": { + "type": "boolean" + }, "backgroundType": { "type": "string", "default": "image" @@ -114,6 +114,9 @@ "__experimentalSkipSerialization": [ "gradients" ], "enableContrastChecker": false }, + "dimensions": { + "aspectRatio": true + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/src/wp-includes/blocks/details/block.json b/src/wp-includes/blocks/details/block.json index d449d42e1e10c..a71d3af2a5ed3 100644 --- a/src/wp-includes/blocks/details/block.json +++ b/src/wp-includes/blocks/details/block.json @@ -13,8 +13,8 @@ "default": false }, "summary": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "summary" } }, diff --git a/src/wp-includes/blocks/embed/block.json b/src/wp-includes/blocks/embed/block.json index 9ca54db871db1..5aac8bbd6b8ca 100644 --- a/src/wp-includes/blocks/embed/block.json +++ b/src/wp-includes/blocks/embed/block.json @@ -12,8 +12,8 @@ "__experimentalRole": "content" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/src/wp-includes/blocks/file.php b/src/wp-includes/blocks/file.php index 042ea89970736..06eb10cf1d895 100644 --- a/src/wp-includes/blocks/file.php +++ b/src/wp-includes/blocks/file.php @@ -14,25 +14,8 @@ * * @return string Returns the block content. */ -function render_block_core_file( $attributes, $content, $block ) { - $should_load_view_script = ! empty( $attributes['displayPreview'] ); - $view_js_file = 'wp-block-file-view'; - // If the script already exists, there is no point in removing it from viewScript. - if ( ! wp_script_is( $view_js_file ) ) { - $script_handles = $block->block_type->view_script_handles; - - // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); - } - // If the script is needed, but it was previously removed, add it again. - if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); - } - } - +function render_block_core_file( $attributes, $content ) { // Update object's aria-label attribute if present in block HTML. - // Match an aria-label attribute from an object tag. $pattern = '@aria-label="(?[^"]+)?")@i'; $content = preg_replace_callback( @@ -53,13 +36,15 @@ static function ( $matches ) { $content ); - // If it uses the Interactivity API, add the directives. - if ( $should_load_view_script ) { + // If it's interactive, enqueue the script module and add the directives. + if ( ! empty( $attributes['displayPreview'] ) ) { + wp_enqueue_script_module( '@wordpress/block-library/file' ); + $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag(); - $processor->set_attribute( 'data-wp-interactive', '' ); + $processor->set_attribute( 'data-wp-interactive', '{"namespace":"core/file"}' ); $processor->next_tag( 'object' ); - $processor->set_attribute( 'data-wp-bind--hidden', '!selectors.core.file.hasPdfPreview' ); + $processor->set_attribute( 'data-wp-bind--hidden', '!state.hasPdfPreview' ); $processor->set_attribute( 'hidden', true ); return $processor->get_updated_html(); } @@ -67,25 +52,6 @@ static function ( $matches ) { return $content; } -/** - * Ensure that the view script has the `wp-interactivity` dependency. - * - * @since 6.4.0 - * - * @global WP_Scripts $wp_scripts - */ -function block_core_file_ensure_interactivity_dependency() { - global $wp_scripts; - if ( - isset( $wp_scripts->registered['wp-block-file-view'] ) && - ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-file-view']->deps, true ) - ) { - $wp_scripts->registered['wp-block-file-view']->deps[] = 'wp-interactivity'; - } -} - -add_action( 'wp_print_scripts', 'block_core_file_ensure_interactivity_dependency' ); - /** * Registers the `core/file` block on server. */ @@ -96,5 +62,12 @@ function register_block_core_file() { 'render_callback' => 'render_block_core_file', ) ); + + wp_register_script_module( + '@wordpress/block-library/file', + defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ? gutenberg_url( '/build/interactivity/file.min.js' ) : includes_url( 'blocks/file/view.min.js' ), + array( '@wordpress/interactivity' ), + defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) + ); } add_action( 'init', 'register_block_core_file' ); diff --git a/src/wp-includes/blocks/file/block.json b/src/wp-includes/blocks/file/block.json index 0cc20b3f501e9..fd5da67d284f4 100644 --- a/src/wp-includes/blocks/file/block.json +++ b/src/wp-includes/blocks/file/block.json @@ -21,8 +21,8 @@ "attribute": "id" }, "fileName": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "a:not([download])" }, "textLinkHref": { @@ -42,8 +42,8 @@ "default": true }, "downloadButtonText": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "a[download]" }, "displayPreview": { @@ -72,7 +72,6 @@ }, "interactivity": true }, - "viewScript": "file:./view.min.js", "editorStyle": "wp-block-file-editor", "style": "wp-block-file" } diff --git a/src/wp-includes/blocks/file/view.asset.php b/src/wp-includes/blocks/file/view.asset.php index 31a7a74abccb2..ce536d2611595 100644 --- a/src/wp-includes/blocks/file/view.asset.php +++ b/src/wp-includes/blocks/file/view.asset.php @@ -1 +1 @@ - array(), 'version' => '6d205911bc72812dd293'); + array(), 'version' => '498971a8a9512421f3b5'); diff --git a/src/wp-includes/blocks/file/view.min.asset.php b/src/wp-includes/blocks/file/view.min.asset.php index 990e381b31921..76f2ca1fbc30c 100644 --- a/src/wp-includes/blocks/file/view.min.asset.php +++ b/src/wp-includes/blocks/file/view.min.asset.php @@ -1 +1 @@ - array(), 'version' => '8a0237493a27c0d781aa'); + array(), 'version' => '9c04187f1796859989c3'); diff --git a/src/wp-includes/blocks/footnotes.php b/src/wp-includes/blocks/footnotes.php index bc6291dd21c38..0cd2ad73ef3d4 100644 --- a/src/wp-includes/blocks/footnotes.php +++ b/src/wp-includes/blocks/footnotes.php @@ -68,17 +68,26 @@ function render_block_core_footnotes( $attributes, $content, $block ) { * @since 6.3.0 */ function register_block_core_footnotes() { - foreach ( array( 'post', 'page' ) as $post_type ) { - register_post_meta( - $post_type, - 'footnotes', - array( - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', - 'revisions_enabled' => true, - ) - ); + $post_types = get_post_types( + array( + 'show_in_rest' => true, + 'public' => true, + ) + ); + foreach ( $post_types as $post_type ) { + // Only register the meta field if the post type supports the editor, custom fields, and revisions. + if ( post_type_supports( $post_type, 'editor' ) && post_type_supports( $post_type, 'custom-fields' ) && post_type_supports( $post_type, 'revisions' ) ) { + register_post_meta( + $post_type, + 'footnotes', + array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'revisions_enabled' => true, + ) + ); + } } register_block_type_from_metadata( __DIR__ . '/footnotes', diff --git a/src/wp-includes/blocks/footnotes/block.json b/src/wp-includes/blocks/footnotes/block.json index 28b094f24f916..3192df7796978 100644 --- a/src/wp-includes/blocks/footnotes/block.json +++ b/src/wp-includes/blocks/footnotes/block.json @@ -4,7 +4,7 @@ "name": "core/footnotes", "title": "Footnotes", "category": "text", - "description": "", + "description": "Display footnotes added to the page.", "keywords": [ "references" ], "textdomain": "default", "usesContext": [ "postId", "postType" ], @@ -33,6 +33,7 @@ "html": false, "multiple": false, "reusable": false, + "inserter": false, "spacing": { "margin": true, "padding": true, diff --git a/src/wp-includes/blocks/gallery.php b/src/wp-includes/blocks/gallery.php index edde9b4da101a..342264de6fce3 100644 --- a/src/wp-includes/blocks/gallery.php +++ b/src/wp-includes/blocks/gallery.php @@ -32,6 +32,21 @@ function block_core_gallery_data_id_backcompatibility( $parsed_block ) { add_filter( 'render_block_data', 'block_core_gallery_data_id_backcompatibility' ); +/** + * Filter to randomize the order of image blocks. + * + * @param array $parsed_block The block being rendered. + * @return array The block object with randomized order of image blocks. + */ +function block_core_gallery_random_order( $parsed_block ) { + if ( 'core/gallery' === $parsed_block['blockName'] && ! empty( $parsed_block['attrs']['randomOrder'] ) ) { + shuffle( $parsed_block['innerBlocks'] ); + } + return $parsed_block; +} + +add_filter( 'render_block_data', 'block_core_gallery_random_order' ); + /** * Adds a style tag for the --wp--style--unstable-gallery-gap var. * diff --git a/src/wp-includes/blocks/gallery/block.json b/src/wp-includes/blocks/gallery/block.json index 0867989af4ec7..a5425c55381f9 100644 --- a/src/wp-includes/blocks/gallery/block.json +++ b/src/wp-includes/blocks/gallery/block.json @@ -46,8 +46,8 @@ "attribute": "data-id" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": ".blocks-gallery-item__caption" } } @@ -72,14 +72,18 @@ "maximum": 8 }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": ".blocks-gallery-caption" }, "imageCrop": { "type": "boolean", "default": true }, + "randomOrder": { + "type": "boolean", + "default": false + }, "fixedHeight": { "type": "boolean", "default": true diff --git a/src/wp-includes/blocks/group/block.json b/src/wp-includes/blocks/group/block.json index 4b89d86539117..df59c25a7751f 100644 --- a/src/wp-includes/blocks/group/block.json +++ b/src/wp-includes/blocks/group/block.json @@ -24,13 +24,16 @@ "__experimentalOnEnter": true, "__experimentalOnMerge": true, "__experimentalSettings": true, - "__experimentalMetadata": true, "align": [ "wide", "full" ], "anchor": true, "ariaLabel": true, "html": false, "background": { - "backgroundImage": true + "backgroundImage": true, + "backgroundSize": true, + "__experimentalDefaultControls": { + "backgroundImage": true + } }, "color": { "gradients": true, diff --git a/src/wp-includes/blocks/heading/block.json b/src/wp-includes/blocks/heading/block.json index 7c018f8472cb4..a1eb3fce32ef1 100644 --- a/src/wp-includes/blocks/heading/block.json +++ b/src/wp-includes/blocks/heading/block.json @@ -7,15 +7,15 @@ "description": "Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.", "keywords": [ "title", "subtitle" ], "textdomain": "default", + "usesContext": [ "pattern/overrides" ], "attributes": { "textAlign": { "type": "string" }, "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "h1,h2,h3,h4,h5,h6", - "default": "", "__experimentalRole": "content" }, "level": { @@ -57,9 +57,7 @@ "__experimentalTextDecoration": true, "__experimentalWritingMode": true, "__experimentalDefaultControls": { - "fontSize": true, - "fontAppearance": true, - "textTransform": true + "fontSize": true } }, "__unstablePasteTextInline": true, diff --git a/src/wp-includes/blocks/image.php b/src/wp-includes/blocks/image.php index acefd5714bbd4..f926890c1a3fc 100644 --- a/src/wp-includes/blocks/image.php +++ b/src/wp-includes/blocks/image.php @@ -37,9 +37,6 @@ function render_block_core_image( $attributes, $content, $block ) { $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; $lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block ); - $view_js_file_handle = 'wp-block-image-view'; - $script_handles = $block->block_type->view_script_handles; - /* * If the lightbox is enabled and the image is not linked, add the filter * and the JavaScript view file. @@ -50,31 +47,22 @@ function render_block_core_image( $attributes, $content, $block ) { isset( $lightbox_settings['enabled'] ) && true === $lightbox_settings['enabled'] ) { - $block->block_type->supports['interactivity'] = true; - - if ( ! in_array( $view_js_file_handle, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file_handle ) ); - } + wp_enqueue_script_module( '@wordpress/block-library/image' ); /* - * This render needs to happen in a filter with priority 15 to ensure - * that it runs after the duotone filter and that duotone styles are - * applied to the image in the lightbox. We also need to ensure that the - * lightbox works with any plugins that might use filters as well. We - * can consider removing this in the future if the way the blocks are - * rendered changes, or if a new kind of filter is introduced. + * This render needs to happen in a filter with priority 15 to ensure that + * it runs after the duotone filter and that duotone styles are applied to + * the image in the lightbox. Lightbox has to work with any plugins that + * might use filters as well. Removing this can be considered in the + * future if the way the blocks are rendered changes, or if a + * new kind of filter is introduced. */ add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 2 ); } else { /* - * Remove the filter and the JavaScript view file if previously added by - * other Image blocks. + * Remove the filter if previously added by other Image blocks. */ remove_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15 ); - // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( in_array( $view_js_file_handle, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file_handle ) ); - } } return $processor->get_updated_html(); @@ -93,12 +81,6 @@ function block_core_image_get_lightbox_settings( $block ) { // Get the lightbox setting from the block attributes. if ( isset( $block['attrs']['lightbox'] ) ) { $lightbox_settings = $block['attrs']['lightbox']; - // If the lightbox setting is not set in the block attributes, - // check the legacy lightbox settings that are set using the - // `gutenberg_should_render_lightbox` filter. - // We can remove this elseif statement when the legacy lightbox settings are removed. - } elseif ( isset( $block['legacyLightboxSettings'] ) ) { - $lightbox_settings = $block['legacyLightboxSettings']; } if ( ! isset( $lightbox_settings ) ) { @@ -187,27 +169,23 @@ function block_core_image_render_lightbox( $block_content, $block ) { $w = new WP_HTML_Tag_Processor( $block_content ); $w->next_tag( 'figure' ); $w->add_class( 'wp-lightbox-container' ); - $w->set_attribute( 'data-wp-interactive', true ); + $w->set_attribute( 'data-wp-interactive', '{"namespace":"core/image"}' ); $w->set_attribute( 'data-wp-context', sprintf( - '{ "core": - { "image": - { "imageLoaded": false, - "initialized": false, - "lightboxEnabled": false, - "hideAnimationEnabled": false, - "preloadInitialized": false, - "lightboxAnimation": "%s", - "imageUploadedSrc": "%s", - "imageCurrentSrc": "", - "targetWidth": "%s", - "targetHeight": "%s", - "scaleAttr": "%s", - "dialogLabel": "%s" - } - } + '{ "imageLoaded": false, + "initialized": false, + "lightboxEnabled": false, + "hideAnimationEnabled": false, + "preloadInitialized": false, + "lightboxAnimation": "%s", + "imageUploadedSrc": "%s", + "imageCurrentSrc": "", + "targetWidth": "%s", + "targetHeight": "%s", + "scaleAttr": "%s", + "dialogLabel": "%s" }', $lightbox_animation, $img_uploaded_src, @@ -218,14 +196,14 @@ function block_core_image_render_lightbox( $block_content, $block ) { ) ); $w->next_tag( 'img' ); - $w->set_attribute( 'data-wp-init', 'effects.core.image.initOriginImage' ); - $w->set_attribute( 'data-wp-on--load', 'actions.core.image.handleLoad' ); - $w->set_attribute( 'data-wp-effect', 'effects.core.image.setButtonStyles' ); + $w->set_attribute( 'data-wp-init', 'callbacks.initOriginImage' ); + $w->set_attribute( 'data-wp-on--load', 'actions.handleLoad' ); + $w->set_attribute( 'data-wp-watch', 'callbacks.setButtonStyles' ); // We need to set an event callback on the `img` specifically // because the `figure` element can also contain a caption, and // we don't want to trigger the lightbox when the caption is clicked. - $w->set_attribute( 'data-wp-on--click', 'actions.core.image.showLightbox' ); - $w->set_attribute( 'data-wp-effect--setStylesOnResize', 'effects.core.image.setStylesOnResize' ); + $w->set_attribute( 'data-wp-on--click', 'actions.showLightbox' ); + $w->set_attribute( 'data-wp-watch--setStylesOnResize', 'callbacks.setStylesOnResize' ); $body_content = $w->get_updated_html(); // Add a button alongside image in the body content. @@ -239,9 +217,10 @@ class="lightbox-trigger" type="button" aria-haspopup="dialog" aria-label="' . esc_attr( $aria_label ) . '" - data-wp-on--click="actions.core.image.showLightbox" - data-wp-style--right="context.core.image.imageButtonRight" - data-wp-style--top="context.core.image.imageButtonTop" + data-wp-init="callbacks.initTriggerButton" + data-wp-on--click="actions.showLightbox" + data-wp-style--right="context.imageButtonRight" + data-wp-style--top="context.imageButtonTop" > @@ -267,8 +246,8 @@ class="lightbox-trigger" // use the exact same image as in the content when the lightbox is first opened while // we wait for the larger image to load. $m->set_attribute( 'src', '' ); - $m->set_attribute( 'data-wp-bind--src', 'context.core.image.imageCurrentSrc' ); - $m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' ); + $m->set_attribute( 'data-wp-bind--src', 'context.imageCurrentSrc' ); + $m->set_attribute( 'data-wp-style--object-fit', 'state.lightboxObjectFit' ); $initial_image_content = $m->get_updated_html(); $q = new WP_HTML_Tag_Processor( $block_content ); @@ -283,8 +262,8 @@ class="lightbox-trigger" // and Chrome (see https://github.com/WordPress/gutenberg/pull/52765#issuecomment-1674008151). Until that // is resolved, manually setting the 'src' seems to be the best solution to load the large image on demand. $q->set_attribute( 'src', '' ); - $q->set_attribute( 'data-wp-bind--src', 'selectors.core.image.enlargedImgSrc' ); - $q->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' ); + $q->set_attribute( 'data-wp-bind--src', 'state.enlargedImgSrc' ); + $q->set_attribute( 'data-wp-style--object-fit', 'state.lightboxObjectFit' ); $enlarged_image_content = $q->get_updated_html(); // If the current theme does NOT have a `theme.json`, or the colors are not defined, @@ -307,21 +286,21 @@ class="lightbox-trigger" $lightbox_html = << - @@ -333,25 +312,6 @@ class="lightbox-trigger" return str_replace( '', $lightbox_html . '', $body_content ); } -/** - * Ensures that the view script has the `wp-interactivity` dependency. - * - * @since 6.4.0 - * - * @global WP_Scripts $wp_scripts - */ -function block_core_image_ensure_interactivity_dependency() { - global $wp_scripts; - if ( - isset( $wp_scripts->registered['wp-block-image-view'] ) && - ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-image-view']->deps, true ) - ) { - $wp_scripts->registered['wp-block-image-view']->deps[] = 'wp-interactivity'; - } -} - -add_action( 'wp_print_scripts', 'block_core_image_ensure_interactivity_dependency' ); - /** * Registers the `core/image` block on server. */ @@ -362,5 +322,12 @@ function register_block_core_image() { 'render_callback' => 'render_block_core_image', ) ); + + wp_register_script_module( + '@wordpress/block-library/image', + defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ? gutenberg_url( '/build/interactivity/image.min.js' ) : includes_url( 'blocks/image/view.min.js' ), + array( '@wordpress/interactivity' ), + defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) + ); } add_action( 'init', 'register_block_core_image' ); diff --git a/src/wp-includes/blocks/image/block.json b/src/wp-includes/blocks/image/block.json index d665a8a8f7708..d60bcadf0eec7 100644 --- a/src/wp-includes/blocks/image/block.json +++ b/src/wp-includes/blocks/image/block.json @@ -4,14 +4,16 @@ "name": "core/image", "title": "Image", "category": "media", - "usesContext": [ "allowResize", "imageCrop", "fixedHeight" ], + "usesContext": [ + "allowResize", + "imageCrop", + "fixedHeight", + "pattern/overrides" + ], "description": "Insert an image to make a visual statement.", "keywords": [ "img", "photo", "picture" ], "textdomain": "default", "attributes": { - "align": { - "type": "string" - }, "url": { "type": "string", "source": "attribute", @@ -28,8 +30,8 @@ "__experimentalRole": "content" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, @@ -95,6 +97,8 @@ } }, "supports": { + "interactivity": true, + "align": [ "left", "center", "right", "wide", "full" ], "anchor": true, "color": { "text": false, @@ -130,6 +134,5 @@ { "name": "rounded", "label": "Rounded" } ], "editorStyle": "wp-block-image-editor", - "style": "wp-block-image", - "viewScript": "file:./view.min.js" + "style": "wp-block-image" } diff --git a/src/wp-includes/blocks/image/view.asset.php b/src/wp-includes/blocks/image/view.asset.php index 568cdb6f733d8..58058b1408c81 100644 --- a/src/wp-includes/blocks/image/view.asset.php +++ b/src/wp-includes/blocks/image/view.asset.php @@ -1 +1 @@ - array(), 'version' => '62ff3b968891a0e3ca3a'); + array(), 'version' => '7500eb032759d407a71d'); diff --git a/src/wp-includes/blocks/image/view.min.asset.php b/src/wp-includes/blocks/image/view.min.asset.php index d1b4a615a9aef..5b46fcb7d531e 100644 --- a/src/wp-includes/blocks/image/view.min.asset.php +++ b/src/wp-includes/blocks/image/view.min.asset.php @@ -1 +1 @@ - array(), 'version' => '32caaf5e7c6834efef4c'); + array(), 'version' => 'ff354d5368d64857fef0'); diff --git a/src/wp-includes/blocks/legacy-widget/block.json b/src/wp-includes/blocks/legacy-widget/block.json index 6b0c1e2a916fd..a03eb090633fc 100644 --- a/src/wp-includes/blocks/legacy-widget/block.json +++ b/src/wp-includes/blocks/legacy-widget/block.json @@ -1,4 +1,5 @@ { + "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/legacy-widget", "title": "Legacy Widget", diff --git a/src/wp-includes/blocks/list-item/block.json b/src/wp-includes/blocks/list-item/block.json index 41221f1c31772..06997c2ac23f8 100644 --- a/src/wp-includes/blocks/list-item/block.json +++ b/src/wp-includes/blocks/list-item/block.json @@ -12,16 +12,23 @@ "type": "string" }, "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "li", - "default": "", "__experimentalRole": "content" } }, "supports": { "className": false, "__experimentalSelector": "li", + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/src/wp-includes/blocks/loginout/block.json b/src/wp-includes/blocks/loginout/block.json index 3593961c09cfd..59fceec596e37 100644 --- a/src/wp-includes/blocks/loginout/block.json +++ b/src/wp-includes/blocks/loginout/block.json @@ -19,6 +19,14 @@ }, "supports": { "className": true, + "spacing": { + "margin": true, + "padding": true, + "__experimentalDefaultControls": { + "margin": false, + "padding": false + } + }, "typography": { "fontSize": true, "lineHeight": true, diff --git a/src/wp-includes/blocks/missing/block.json b/src/wp-includes/blocks/missing/block.json index 0bc512bbbf709..242a1d2c6b21a 100644 --- a/src/wp-includes/blocks/missing/block.json +++ b/src/wp-includes/blocks/missing/block.json @@ -15,7 +15,7 @@ }, "originalContent": { "type": "string", - "source": "html" + "source": "raw" } }, "supports": { diff --git a/src/wp-includes/blocks/navigation-link.php b/src/wp-includes/blocks/navigation-link.php index 5333ab6ea3dc9..71ef26b630d51 100644 --- a/src/wp-includes/blocks/navigation-link.php +++ b/src/wp-includes/blocks/navigation-link.php @@ -1,6 +1,6 @@ get_registered( 'core/navigation-link' ); + // If the block is not registered yet, bail early. + // Variation will be registered in register_block_core_navigation_link then. + if ( ! $navigation_block_type ) { + return; + } + + $navigation_block_type->variations = array_merge( + $navigation_block_type->variations, + array( $variation ) + ); +} + +/** + * Unregister a variation for a post type / taxonomy for the navigation link block. + * + * @param string $name Name of the post type / taxonomy (which was used as variation name). + * @return void + */ +function block_core_navigation_link_unregister_variation( $name ) { + // Directly get the variations from the registered block type + // because there's no server side (un)registration for variations (see #47170). + $navigation_block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/navigation-link' ); + // If the block is not registered (yet), there's no need to remove a variation. + if ( ! $navigation_block_type || empty( $navigation_block_type->variations ) ) { + return; + } + $variations = $navigation_block_type->variations; + // Search for the variation and remove it from the array. + foreach ( $variations as $i => $variation ) { + if ( $variation['name'] === $name ) { + unset( $variations[ $i ] ); + break; + } + } + // Reindex array after removing one variation. + $navigation_block_type->variations = array_values( $variations ); +} + /** * Register the navigation link block. + * Returns an array of variations for the navigation link block. * - * @uses render_block_core_navigation() - * @throws WP_Error An WP_Error exception parsing the block definition. + * @return array */ -function register_block_core_navigation_link() { +function build_navigation_link_block_variations() { + // This will only handle post types and taxonomies registered until this point (init on priority 9). + // See action hooks below for other post types and taxonomies. + // See https://github.com/WordPress/gutenberg/issues/53826 for details. $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' ); $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' ); @@ -360,12 +411,80 @@ function register_block_core_navigation_link() { } } + return array_merge( $built_ins, $variations ); +} + +/** + * Register the navigation link block. + * + * @uses render_block_core_navigation() + * @throws WP_Error An WP_Error exception parsing the block definition. + */ +function register_block_core_navigation_link() { register_block_type_from_metadata( __DIR__ . '/navigation-link', array( - 'render_callback' => 'render_block_core_navigation_link', - 'variations' => array_merge( $built_ins, $variations ), + 'render_callback' => 'render_block_core_navigation_link', + 'variation_callback' => 'build_navigation_link_block_variations', ) ); } add_action( 'init', 'register_block_core_navigation_link' ); +// Register actions for all post types and taxonomies, to add variations when they are registered. +// All post types/taxonomies registered before register_block_core_navigation_link, will be handled by that function. +add_action( 'registered_post_type', 'block_core_navigation_link_register_post_type_variation', 10, 2 ); +add_action( 'registered_taxonomy', 'block_core_navigation_link_register_taxonomy_variation', 10, 3 ); +// Handle unregistering of post types and taxonomies and remove the variations. +add_action( 'unregistered_post_type', 'block_core_navigation_link_unregister_post_type_variation' ); +add_action( 'unregistered_taxonomy', 'block_core_navigation_link_unregister_taxonomy_variation' ); + +/** + * Register custom post type variations for navigation link on post type registration + * Handles all post types registered after the block is registered in register_navigation_link_post_type_variations + * + * @param string $post_type The post type name passed from registered_post_type action hook. + * @param WP_Post_Type $post_type_object The post type object passed from registered_post_type. + * @return void + */ +function block_core_navigation_link_register_post_type_variation( $post_type, $post_type_object ) { + if ( $post_type_object->show_in_nav_menus ) { + $variation = build_variation_for_navigation_link( $post_type_object, 'post-type' ); + block_core_navigation_link_register_variation( $variation ); + } +} + +/** + * Register a custom taxonomy variation for navigation link on taxonomy registration + * Handles all taxonomies registered after the block is registered in register_navigation_link_post_type_variations + * + * @param string $taxonomy Taxonomy slug. + * @param array|string $object_type Object type or array of object types. + * @param array $args Array of taxonomy registration arguments. + * @return void + */ +function block_core_navigation_link_register_taxonomy_variation( $taxonomy, $object_type, $args ) { + if ( isset( $args['show_in_nav_menus'] ) && $args['show_in_nav_menus'] ) { + $variation = build_variation_for_navigation_link( (object) $args, 'post-type' ); + block_core_navigation_link_register_variation( $variation ); + } +} + +/** + * Unregisters a custom post type variation for navigation link on post type unregistration. + * + * @param string $post_type The post type name passed from unregistered_post_type action hook. + * @return void + */ +function block_core_navigation_link_unregister_post_type_variation( $post_type ) { + block_core_navigation_link_unregister_variation( $post_type ); +} + +/** + * Unregisters a custom taxonomy variation for navigation link on taxonomy unregistration. + * + * @param string $taxonomy The taxonomy name passed from unregistered_taxonomy action hook. + * @return void + */ +function block_core_navigation_link_unregister_taxonomy_variation( $taxonomy ) { + block_core_navigation_link_unregister_variation( $taxonomy ); +} diff --git a/src/wp-includes/blocks/navigation-link/block.json b/src/wp-includes/blocks/navigation-link/block.json index b2cbeaed63d3e..d8f2fe31aef9d 100644 --- a/src/wp-includes/blocks/navigation-link/block.json +++ b/src/wp-includes/blocks/navigation-link/block.json @@ -71,7 +71,8 @@ "__experimentalDefaultControls": { "fontSize": true } - } + }, + "renaming": false }, "editorStyle": "wp-block-navigation-link-editor", "style": "wp-block-navigation-link" diff --git a/src/wp-includes/blocks/navigation.php b/src/wp-includes/blocks/navigation.php index 4d9fe4a08c6bf..40ae2f045d7bf 100644 --- a/src/wp-includes/blocks/navigation.php +++ b/src/wp-includes/blocks/navigation.php @@ -5,6 +5,648 @@ * @package WordPress */ +/** + * Helper functions used to render the navigation block. + */ +class WP_Navigation_Block_Renderer { + /** + * Used to determine which blocks are wrapped in an
  • . + * + * @var array + */ + private static $nav_blocks_wrapped_in_list_item = array( + 'core/navigation-link', + 'core/home-link', + 'core/site-title', + 'core/site-logo', + 'core/navigation-submenu', + ); + + /** + * Used to determine which blocks need an
  • wrapper. + * + * @var array + */ + private static $needs_list_item_wrapper = array( + 'core/site-title', + 'core/site-logo', + ); + + /** + * Keeps track of all the navigation names that have been seen. + * + * @var array + */ + private static $seen_menu_names = array(); + + /** + * Returns whether or not this is responsive navigation. + * + * @param array $attributes The block attributes. + * @return bool Returns whether or not this is responsive navigation. + */ + private static function is_responsive( $attributes ) { + /** + * This is for backwards compatibility after the `isResponsive` attribute was been removed. + */ + + $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive']; + return isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute; + } + + /** + * Returns whether or not a navigation has a submenu. + * + * @param WP_Block_List $inner_blocks The list of inner blocks. + * @return bool Returns whether or not a navigation has a submenu. + */ + private static function has_submenus( $inner_blocks ) { + foreach ( $inner_blocks as $inner_block ) { + $inner_block_content = $inner_block->render(); + $p = new WP_HTML_Tag_Processor( $inner_block_content ); + if ( $p->next_tag( + array( + 'name' => 'LI', + 'class_name' => 'has-child', + ) + ) ) { + return true; + } + } + return false; + } + + /** + * Determine whether the navigation blocks is interactive. + * + * @param array $attributes The block attributes. + * @param WP_Block_List $inner_blocks The list of inner blocks. + * @return bool Returns whether or not to load the view script. + */ + private static function is_interactive( $attributes, $inner_blocks ) { + $has_submenus = static::has_submenus( $inner_blocks ); + $is_responsive_menu = static::is_responsive( $attributes ); + return ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu; + } + + /** + * Returns whether or not a block needs a list item wrapper. + * + * @param WP_Block $block The block. + * @return bool Returns whether or not a block needs a list item wrapper. + */ + private static function does_block_need_a_list_item_wrapper( $block ) { + return in_array( $block->name, static::$needs_list_item_wrapper, true ); + } + + /** + * Returns the markup for a single inner block. + * + * @param WP_Block $inner_block The inner block. + * @return string Returns the markup for a single inner block. + */ + private static function get_markup_for_inner_block( $inner_block ) { + $inner_block_content = $inner_block->render(); + if ( ! empty( $inner_block_content ) ) { + if ( static::does_block_need_a_list_item_wrapper( $inner_block ) ) { + return '
  • ' . $inner_block_content . '
  • '; + } + + return $inner_block_content; + } + } + + /** + * Returns the html for the inner blocks of the navigation block. + * + * @param array $attributes The block attributes. + * @param WP_Block_List $inner_blocks The list of inner blocks. + * @return string Returns the html for the inner blocks of the navigation block. + */ + private static function get_inner_blocks_html( $attributes, $inner_blocks ) { + $has_submenus = static::has_submenus( $inner_blocks ); + $is_interactive = static::is_interactive( $attributes, $inner_blocks ); + + $style = static::get_styles( $attributes ); + $class = static::get_classes( $attributes ); + $container_attributes = get_block_wrapper_attributes( + array( + 'class' => 'wp-block-navigation__container ' . $class, + 'style' => $style, + ) + ); + + $inner_blocks_html = ''; + $is_list_open = false; + + foreach ( $inner_blocks as $inner_block ) { + $is_list_item = in_array( $inner_block->name, static::$nav_blocks_wrapped_in_list_item, true ); + + if ( $is_list_item && ! $is_list_open ) { + $is_list_open = true; + $inner_blocks_html .= sprintf( + '
      ', + $container_attributes + ); + } + + if ( ! $is_list_item && $is_list_open ) { + $is_list_open = false; + $inner_blocks_html .= '
    '; + } + + $inner_blocks_html .= static::get_markup_for_inner_block( $inner_block ); + } + + if ( $is_list_open ) { + $inner_blocks_html .= ''; + } + + // Add directives to the submenu if needed. + if ( $has_submenus && $is_interactive ) { + $tags = new WP_HTML_Tag_Processor( $inner_blocks_html ); + $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes ); + } + + return $inner_blocks_html; + } + + /** + * Gets the inner blocks for the navigation block from the navigation post. + * + * @param array $attributes The block attributes. + * @return WP_Block_List Returns the inner blocks for the navigation block. + */ + private static function get_inner_blocks_from_navigation_post( $attributes ) { + $navigation_post = get_post( $attributes['ref'] ); + if ( ! isset( $navigation_post ) ) { + return new WP_Block_List( array(), $attributes ); + } + + // Only published posts are valid. If this is changed then a corresponding change + // must also be implemented in `use-navigation-menu.js`. + if ( 'publish' === $navigation_post->post_status ) { + $parsed_blocks = parse_blocks( $navigation_post->post_content ); + + // 'parse_blocks' includes a null block with '\n\n' as the content when + // it encounters whitespace. This code strips it. + $blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); + + if ( function_exists( 'get_hooked_block_markup' ) ) { + // Run Block Hooks algorithm to inject hooked blocks. + $markup = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post ); + $root_nav_block = parse_blocks( $markup )[0]; + + $blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks; + } + + // TODO - this uses the full navigation block attributes for the + // context which could be refined. + return new WP_Block_List( $blocks, $attributes ); + } + } + + /** + * Gets the inner blocks for the navigation block from the fallback. + * + * @param array $attributes The block attributes. + * @return WP_Block_List Returns the inner blocks for the navigation block. + */ + private static function get_inner_blocks_from_fallback( $attributes ) { + $fallback_blocks = block_core_navigation_get_fallback_blocks(); + + // Fallback my have been filtered so do basic test for validity. + if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) { + return new WP_Block_List( array(), $attributes ); + } + + return new WP_Block_List( $fallback_blocks, $attributes ); + } + + /** + * Gets the inner blocks for the navigation block. + * + * @param array $attributes The block attributes. + * @param WP_Block $block The parsed block. + * @return WP_Block_List Returns the inner blocks for the navigation block. + */ + private static function get_inner_blocks( $attributes, $block ) { + $inner_blocks = $block->inner_blocks; + + // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render. + if ( array_key_exists( 'navigationMenuId', $attributes ) ) { + $attributes['ref'] = $attributes['navigationMenuId']; + } + + // If: + // - the gutenberg plugin is active + // - `__unstableLocation` is defined + // - we have menu items at the defined location + // - we don't have a relationship to a `wp_navigation` Post (via `ref`). + // ...then create inner blocks from the classic menu assigned to that location. + if ( + defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && + array_key_exists( '__unstableLocation', $attributes ) && + ! array_key_exists( 'ref', $attributes ) && + ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) + ) { + $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ); + } + + // Load inner blocks from the navigation post. + if ( array_key_exists( 'ref', $attributes ) ) { + $inner_blocks = static::get_inner_blocks_from_navigation_post( $attributes ); + } + + // If there are no inner blocks then fallback to rendering an appropriate fallback. + if ( empty( $inner_blocks ) ) { + $inner_blocks = static::get_inner_blocks_from_fallback( $attributes ); + } + + /** + * Filter navigation block $inner_blocks. + * Allows modification of a navigation block menu items. + * + * @since 6.1.0 + * + * @param \WP_Block_List $inner_blocks + */ + $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks ); + + $post_ids = block_core_navigation_get_post_ids( $inner_blocks ); + if ( $post_ids ) { + _prime_post_caches( $post_ids, false, false ); + } + + return $inner_blocks; + } + + /** + * Gets the name of the current navigation, if it has one. + * + * @param array $attributes The block attributes. + * @return string Returns the name of the navigation. + */ + private static function get_navigation_name( $attributes ) { + + $navigation_name = $attributes['ariaLabel'] ?? ''; + + // Load the navigation post. + if ( array_key_exists( 'ref', $attributes ) ) { + $navigation_post = get_post( $attributes['ref'] ); + if ( ! isset( $navigation_post ) ) { + return $navigation_name; + } + + // Only published posts are valid. If this is changed then a corresponding change + // must also be implemented in `use-navigation-menu.js`. + if ( 'publish' === $navigation_post->post_status ) { + $navigation_name = $navigation_post->post_title; + + // This is used to count the number of times a navigation name has been seen, + // so that we can ensure every navigation has a unique id. + if ( isset( static::$seen_menu_names[ $navigation_name ] ) ) { + ++static::$seen_menu_names[ $navigation_name ]; + } else { + static::$seen_menu_names[ $navigation_name ] = 1; + } + } + } + + return $navigation_name; + } + + /** + * Returns the layout class for the navigation block. + * + * @param array $attributes The block attributes. + * @return string Returns the layout class for the navigation block. + */ + private static function get_layout_class( $attributes ) { + $layout_justification = array( + 'left' => 'items-justified-left', + 'right' => 'items-justified-right', + 'center' => 'items-justified-center', + 'space-between' => 'items-justified-space-between', + ); + + $layout_class = ''; + if ( + isset( $attributes['layout']['justifyContent'] ) && + isset( $layout_justification[ $attributes['layout']['justifyContent'] ] ) + ) { + $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ]; + } + if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) { + $layout_class .= ' is-vertical'; + } + + if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) { + $layout_class .= ' no-wrap'; + } + return $layout_class; + } + + /** + * Return classes for the navigation block. + * + * @param array $attributes The block attributes. + * @return string Returns the classes for the navigation block. + */ + private static function get_classes( $attributes ) { + // Restore legacy classnames for submenu positioning. + $layout_class = static::get_layout_class( $attributes ); + $colors = block_core_navigation_build_css_colors( $attributes ); + $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); + $is_responsive_menu = static::is_responsive( $attributes ); + + // Manually add block support text decoration as CSS class. + $text_decoration = $attributes['style']['typography']['textDecoration'] ?? null; + $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration ); + + // Sets the is-collapsed class when the navigation is set to always use the overlay. + // This saves us from needing to do this check in the view.js file (see the collapseNav function). + $is_collapsed_class = static::is_always_overlay( $attributes ) ? array( 'is-collapsed' ) : array(); + + $classes = array_merge( + $colors['css_classes'], + $font_sizes['css_classes'], + $is_responsive_menu ? array( 'is-responsive' ) : array(), + $layout_class ? array( $layout_class ) : array(), + $text_decoration ? array( $text_decoration_class ) : array(), + $is_collapsed_class + ); + return implode( ' ', $classes ); + } + + private static function is_always_overlay( $attributes ) { + return isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; + } + + /** + * Get styles for the navigation block. + * + * @param array $attributes The block attributes. + * @return string Returns the styles for the navigation block. + */ + private static function get_styles( $attributes ) { + $colors = block_core_navigation_build_css_colors( $attributes ); + $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); + $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; + return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles']; + } + + /** + * Get the responsive container markup + * + * @param array $attributes The block attributes. + * @param WP_Block_List $inner_blocks The list of inner blocks. + * @param string $inner_blocks_html The markup for the inner blocks. + * @return string Returns the container markup. + */ + private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) { + $is_interactive = static::is_interactive( $attributes, $inner_blocks ); + $colors = block_core_navigation_build_css_colors( $attributes ); + $modal_unique_id = wp_unique_id( 'modal-' ); + + $responsive_container_classes = array( + 'wp-block-navigation__responsive-container', + implode( ' ', $colors['overlay_css_classes'] ), + ); + $open_button_classes = array( + 'wp-block-navigation__responsive-container-open', + ); + + $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; + $toggle_button_icon = ''; + if ( isset( $attributes['icon'] ) ) { + if ( 'menu' === $attributes['icon'] ) { + $toggle_button_icon = ''; + } + } + $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' ); + $toggle_close_button_icon = ''; + $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' ); + $toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label. + $toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label. + + // Add Interactivity API directives to the markup if needed. + $open_button_directives = ''; + $responsive_container_directives = ''; + $responsive_dialog_directives = ''; + $close_button_directives = ''; + if ( $is_interactive ) { + $open_button_directives = ' + data-wp-on--click="actions.openMenuOnClick" + data-wp-on--keydown="actions.handleMenuKeydown" + '; + $responsive_container_directives = ' + data-wp-class--has-modal-open="state.isMenuOpen" + data-wp-class--is-menu-open="state.isMenuOpen" + data-wp-watch="callbacks.initMenu" + data-wp-on--keydown="actions.handleMenuKeydown" + data-wp-on--focusout="actions.handleMenuFocusout" + tabindex="-1" + '; + $responsive_dialog_directives = ' + data-wp-bind--aria-modal="state.ariaModal" + data-wp-bind--aria-label="state.ariaLabel" + data-wp-bind--role="state.roleAttribute" + '; + $close_button_directives = ' + data-wp-on--click="actions.closeMenuOnClick" + '; + $responsive_container_content_directives = ' + data-wp-watch="callbacks.focusFirstElement" + '; + } + + return sprintf( + ' +
    +
    +
    + +
    + %2$s +
    +
    +
    +
    ', + esc_attr( $modal_unique_id ), + $inner_blocks_html, + $toggle_aria_label_open, + $toggle_aria_label_close, + esc_attr( implode( ' ', $responsive_container_classes ) ), + esc_attr( implode( ' ', $open_button_classes ) ), + esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ), + $toggle_button_content, + $toggle_close_button_content, + $open_button_directives, + $responsive_container_directives, + $responsive_dialog_directives, + $close_button_directives, + $responsive_container_content_directives + ); + } + + /** + * Get the wrapper attributes + * + * @param array $attributes The block attributes. + * @param WP_Block_List $inner_blocks A list of inner blocks. + * @return string Returns the navigation block markup. + */ + private static function get_nav_wrapper_attributes( $attributes, $inner_blocks ) { + $nav_menu_name = static::get_unique_navigation_name( $attributes ); + $is_interactive = static::is_interactive( $attributes, $inner_blocks ); + $is_responsive_menu = static::is_responsive( $attributes ); + $style = static::get_styles( $attributes ); + $class = static::get_classes( $attributes ); + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $class, + 'style' => $style, + 'aria-label' => $nav_menu_name, + ) + ); + + if ( $is_responsive_menu ) { + $nav_element_directives = static::get_nav_element_directives( $is_interactive, $attributes ); + $wrapper_attributes .= ' ' . $nav_element_directives; + } + + return $wrapper_attributes; + } + + /** + * Gets the nav element directives. + * + * @param bool $is_interactive Whether the block is interactive. + * @param array $attributes The block attributes. + * @return string the directives for the navigation element. + */ + private static function get_nav_element_directives( $is_interactive, $attributes ) { + if ( ! $is_interactive ) { + return ''; + } + // When adding to this array be mindful of security concerns. + $nav_element_context = wp_json_encode( + array( + 'overlayOpenedBy' => array(), + 'type' => 'overlay', + 'roleAttribute' => '', + 'ariaLabel' => __( 'Menu' ), + ), + JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP + ); + $nav_element_directives = ' + data-wp-interactive=\'{"namespace":"core/navigation"}\' + data-wp-context=\'' . $nav_element_context . '\' + '; + + /* + * When the navigation's 'overlayMenu' attribute is set to 'always', JavaScript + * is not needed for collapsing the menu because the class is set manually. + */ + if ( ! static::is_always_overlay( $attributes ) ) { + $nav_element_directives .= 'data-wp-init="callbacks.initNav"'; + $nav_element_directives .= ' '; // space separator + $nav_element_directives .= 'data-wp-class--is-collapsed="context.isCollapsed"'; + } + + return $nav_element_directives; + } + + /** + * Handle view script module loading. + * + * @param array $attributes The block attributes. + * @param WP_Block $block The parsed block. + * @param WP_Block_List $inner_blocks The list of inner blocks. + */ + private static function handle_view_script_module_loading( $attributes, $block, $inner_blocks ) { + if ( static::is_interactive( $attributes, $inner_blocks ) ) { + wp_enqueue_script_module( '@wordpress/block-library/navigation' ); + } + } + + /** + * Returns the markup for the navigation block. + * + * @param array $attributes The block attributes. + * @param WP_Block_List $inner_blocks The list of inner blocks. + * @return string Returns the navigation wrapper markup. + */ + private static function get_wrapper_markup( $attributes, $inner_blocks ) { + $inner_blocks_html = static::get_inner_blocks_html( $attributes, $inner_blocks ); + if ( static::is_responsive( $attributes ) ) { + return static::get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ); + } + return $inner_blocks_html; + } + + /** + * Returns a unique name for the navigation. + * + * @param array $attributes The block attributes. + * @return string Returns a unique name for the navigation. + */ + private static function get_unique_navigation_name( $attributes ) { + $nav_menu_name = static::get_navigation_name( $attributes ); + + // If the menu name has been used previously then append an ID + // to the name to ensure uniqueness across a given post. + if ( isset( static::$seen_menu_names[ $nav_menu_name ] ) && static::$seen_menu_names[ $nav_menu_name ] > 1 ) { + $count = static::$seen_menu_names[ $nav_menu_name ]; + $nav_menu_name = $nav_menu_name . ' ' . ( $count ); + } + + return $nav_menu_name; + } + + /** + * Renders the navigation block. + * + * @param array $attributes The block attributes. + * @param string $content The saved content. + * @param WP_Block $block The parsed block. + * @return string Returns the navigation block markup. + */ + public static function render( $attributes, $content, $block ) { + /** + * Deprecated: + * The rgbTextColor and rgbBackgroundColor attributes + * have been deprecated in favor of + * customTextColor and customBackgroundColor ones. + * Move the values from old attrs to the new ones. + */ + if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) { + $attributes['customTextColor'] = $attributes['rgbTextColor']; + } + + if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) { + $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor']; + } + + unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] ); + + $inner_blocks = static::get_inner_blocks( $attributes, $block ); + // Prevent navigation blocks referencing themselves from rendering. + if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) { + return ''; + } + + static::handle_view_script_module_loading( $attributes, $block, $inner_blocks ); + + return sprintf( + '', + static::get_nav_wrapper_attributes( $attributes, $inner_blocks ), + static::get_wrapper_markup( $attributes, $inner_blocks ) + ); + } +} + // These functions are used for the __unstableLocation feature and only active // when the gutenberg plugin is active. if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { @@ -65,68 +707,84 @@ function block_core_navigation_sort_menu_items_by_parent_id( $menu_items ) { return $menu_items_by_parent_id; } -} + /** + * Gets the inner blocks for the navigation block from the unstable location attribute. + * + * @param array $attributes The block attributes. + * @return WP_Block_List Returns the inner blocks for the navigation block. + */ + function block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ) { + $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ); + if ( empty( $menu_items ) ) { + return new WP_Block_List( array(), $attributes ); + } + + $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items ); + $parsed_blocks = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); + return new WP_Block_List( $parsed_blocks, $attributes ); + } +} /** * Add Interactivity API directives to the navigation-submenu and page-list * blocks markup using the Tag Processor. * - * @param string $w Markup of the navigation block. - * @param array $block_attributes Block attributes. + * @param WP_HTML_Tag_Processor $tags Markup of the navigation block. + * @param array $block_attributes Block attributes. * * @return string Submenu markup with the directives injected. */ -function block_core_navigation_add_directives_to_submenu( $w, $block_attributes ) { - while ( $w->next_tag( +function block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ) { + while ( $tags->next_tag( array( 'tag_name' => 'LI', 'class_name' => 'has-child', ) ) ) { // Add directives to the parent `
  • `. - $w->set_attribute( 'data-wp-interactive', true ); - $w->set_attribute( 'data-wp-context', '{ "core": { "navigation": { "submenuOpenedBy": {}, "type": "submenu" } } }' ); - $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); - $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); - $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); + $tags->set_attribute( 'data-wp-interactive', '{ "namespace": "core/navigation" }' ); + $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": {}, "type": "submenu" }' ); + $tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' ); + $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' ); + $tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' ); // This is a fix for Safari. Without it, Safari doesn't change the active // element when the user clicks on a button. It can be removed once we add // an overlay to capture the clicks, instead of relying on the focusout // event. - $w->set_attribute( 'tabindex', '-1' ); + $tags->set_attribute( 'tabindex', '-1' ); if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { - $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' ); - $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' ); + $tags->set_attribute( 'data-wp-on--mouseenter', 'actions.openMenuOnHover' ); + $tags->set_attribute( 'data-wp-on--mouseleave', 'actions.closeMenuOnHover' ); } // Add directives to the toggle submenu button. - if ( $w->next_tag( + if ( $tags->next_tag( array( 'tag_name' => 'BUTTON', 'class_name' => 'wp-block-navigation-submenu__toggle', ) ) ) { - $w->set_attribute( 'data-wp-on--click', 'actions.core.navigation.toggleMenuOnClick' ); - $w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' ); + $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' ); + $tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' ); // The `aria-expanded` attribute for SSR is already added in the submenu block. } // Add directives to the submenu. - if ( $w->next_tag( + if ( $tags->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'wp-block-navigation__submenu-container', ) ) ) { - $w->set_attribute( 'data-wp-on--focus', 'actions.core.navigation.openMenuOnFocus' ); + $tags->set_attribute( 'data-wp-on--focus', 'actions.openMenuOnFocus' ); } // Iterate through subitems if exist. - block_core_navigation_add_directives_to_submenu( $w, $block_attributes ); + block_core_navigation_add_directives_to_submenu( $tags, $block_attributes ); } - return $w->get_updated_html(); + return $tags->get_updated_html(); } /** @@ -333,6 +991,17 @@ function block_core_navigation_get_fallback_blocks() { // Normalizing blocks may result in an empty array of blocks if they were all `null` blocks. // In this case default to the (Page List) fallback. $fallback_blocks = ! empty( $maybe_fallback ) ? $maybe_fallback : $fallback_blocks; + + if ( function_exists( 'get_hooked_block_markup' ) ) { + // Run Block Hooks algorithm to inject hooked blocks. + // We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks. + $markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post ); + $blocks = parse_blocks( $markup ); + + if ( isset( $blocks[0]['innerBlocks'] ) ) { + $fallback_blocks = $blocks[0]['innerBlocks']; + } + } } /** @@ -344,7 +1013,7 @@ function block_core_navigation_get_fallback_blocks() { * * @since 5.9.0 * - * @param array[] default fallback blocks provided by the default block mechanic. + * @param array[] $fallback_blocks default fallback blocks provided by the default block mechanic. */ return apply_filters( 'block_core_navigation_render_fallback', $fallback_blocks ); } @@ -391,391 +1060,10 @@ function block_core_navigation_from_block_get_post_ids( $block ) { * @param string $content The saved content. * @param WP_Block $block The parsed block. * - * @return string Returns the post content with the legacy widget added. + * @return string Returns the navigation block markup. */ function render_block_core_navigation( $attributes, $content, $block ) { - static $seen_menu_names = array(); - - // Flag used to indicate whether the rendered output is considered to be - // a fallback (i.e. the block has no menu associated with it). - $is_fallback = false; - - $nav_menu_name = $attributes['ariaLabel'] ?? ''; - - /** - * Deprecated: - * The rgbTextColor and rgbBackgroundColor attributes - * have been deprecated in favor of - * customTextColor and customBackgroundColor ones. - * Move the values from old attrs to the new ones. - */ - if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) { - $attributes['customTextColor'] = $attributes['rgbTextColor']; - } - - if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) { - $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor']; - } - - unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] ); - - /** - * This is for backwards compatibility after `isResponsive` attribute has been removed. - */ - $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive']; - $is_responsive_menu = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute; - - $inner_blocks = $block->inner_blocks; - - // Ensure that blocks saved with the legacy ref attribute name (navigationMenuId) continue to render. - if ( array_key_exists( 'navigationMenuId', $attributes ) ) { - $attributes['ref'] = $attributes['navigationMenuId']; - } - - // If: - // - the gutenberg plugin is active - // - `__unstableLocation` is defined - // - we have menu items at the defined location - // - we don't have a relationship to a `wp_navigation` Post (via `ref`). - // ...then create inner blocks from the classic menu assigned to that location. - if ( - defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && - array_key_exists( '__unstableLocation', $attributes ) && - ! array_key_exists( 'ref', $attributes ) && - ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) - ) { - $menu_items = block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ); - if ( empty( $menu_items ) ) { - return ''; - } - - $menu_items_by_parent_id = block_core_navigation_sort_menu_items_by_parent_id( $menu_items ); - $parsed_blocks = block_core_navigation_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id ); - $inner_blocks = new WP_Block_List( $parsed_blocks, $attributes ); - } - - // Load inner blocks from the navigation post. - if ( array_key_exists( 'ref', $attributes ) ) { - $navigation_post = get_post( $attributes['ref'] ); - if ( ! isset( $navigation_post ) ) { - return ''; - } - - // Only published posts are valid. If this is changed then a corresponding change - // must also be implemented in `use-navigation-menu.js`. - if ( 'publish' === $navigation_post->post_status ) { - $nav_menu_name = $navigation_post->post_title; - - if ( isset( $seen_menu_names[ $nav_menu_name ] ) ) { - ++$seen_menu_names[ $nav_menu_name ]; - } else { - $seen_menu_names[ $nav_menu_name ] = 1; - } - - $parsed_blocks = parse_blocks( $navigation_post->post_content ); - - // 'parse_blocks' includes a null block with '\n\n' as the content when - // it encounters whitespace. This code strips it. - $compacted_blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); - - // TODO - this uses the full navigation block attributes for the - // context which could be refined. - $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes ); - } - } - - // If there are no inner blocks then fallback to rendering an appropriate fallback. - if ( empty( $inner_blocks ) ) { - $is_fallback = true; // indicate we are rendering the fallback. - - $fallback_blocks = block_core_navigation_get_fallback_blocks(); - - // Fallback my have been filtered so do basic test for validity. - if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) { - return ''; - } - - $inner_blocks = new WP_Block_List( $fallback_blocks, $attributes ); - } - - if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) { - return ''; - } - - /** - * Filter navigation block $inner_blocks. - * Allows modification of a navigation block menu items. - * - * @since 6.1.0 - * - * @param \WP_Block_List $inner_blocks - */ - $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks ); - - $layout_justification = array( - 'left' => 'items-justified-left', - 'right' => 'items-justified-right', - 'center' => 'items-justified-center', - 'space-between' => 'items-justified-space-between', - ); - - // Restore legacy classnames for submenu positioning. - $layout_class = ''; - if ( - isset( $attributes['layout']['justifyContent'] ) && - isset( $layout_justification[ $attributes['layout']['justifyContent'] ] ) - ) { - $layout_class .= $layout_justification[ $attributes['layout']['justifyContent'] ]; - } - if ( isset( $attributes['layout']['orientation'] ) && 'vertical' === $attributes['layout']['orientation'] ) { - $layout_class .= ' is-vertical'; - } - - if ( isset( $attributes['layout']['flexWrap'] ) && 'nowrap' === $attributes['layout']['flexWrap'] ) { - $layout_class .= ' no-wrap'; - } - - // Manually add block support text decoration as CSS class. - $text_decoration = $attributes['style']['typography']['textDecoration'] ?? null; - $text_decoration_class = sprintf( 'has-text-decoration-%s', $text_decoration ); - - $colors = block_core_navigation_build_css_colors( $attributes ); - $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); - $classes = array_merge( - $colors['css_classes'], - $font_sizes['css_classes'], - $is_responsive_menu ? array( 'is-responsive' ) : array(), - $layout_class ? array( $layout_class ) : array(), - $is_fallback ? array( 'is-fallback' ) : array(), - $text_decoration ? array( $text_decoration_class ) : array() - ); - - $post_ids = block_core_navigation_get_post_ids( $inner_blocks ); - if ( $post_ids ) { - _prime_post_caches( $post_ids, false, false ); - } - - $list_item_nav_blocks = array( - 'core/navigation-link', - 'core/home-link', - 'core/site-title', - 'core/site-logo', - 'core/navigation-submenu', - ); - - $needs_list_item_wrapper = array( - 'core/site-title', - 'core/site-logo', - ); - - $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; - $style = $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles']; - $class = implode( ' ', $classes ); - - // If the menu name has been used previously then append an ID - // to the name to ensure uniqueness across a given post. - if ( isset( $seen_menu_names[ $nav_menu_name ] ) && $seen_menu_names[ $nav_menu_name ] > 1 ) { - $count = $seen_menu_names[ $nav_menu_name ]; - $nav_menu_name = $nav_menu_name . ' ' . ( $count ); - } - - $wrapper_attributes = get_block_wrapper_attributes( - array( - 'class' => $class, - 'style' => $style, - 'aria-label' => $nav_menu_name, - ) - ); - - $container_attributes = get_block_wrapper_attributes( - array( - 'class' => 'wp-block-navigation__container ' . $class, - 'style' => $style, - ) - ); - - $inner_blocks_html = ''; - $is_list_open = false; - $has_submenus = false; - foreach ( $inner_blocks as $inner_block ) { - $is_list_item = in_array( $inner_block->name, $list_item_nav_blocks, true ); - - if ( $is_list_item && ! $is_list_open ) { - $is_list_open = true; - $inner_blocks_html .= sprintf( - '
      ', - $container_attributes - ); - } - - if ( ! $is_list_item && $is_list_open ) { - $is_list_open = false; - $inner_blocks_html .= '
    '; - } - - $inner_block_content = $inner_block->render(); - $p = new WP_HTML_Tag_Processor( $inner_block_content ); - if ( $p->next_tag( - array( - 'name' => 'LI', - 'class_name' => 'has-child', - ) - ) ) { - $has_submenus = true; - } - if ( ! empty( $inner_block_content ) ) { - if ( in_array( $inner_block->name, $needs_list_item_wrapper, true ) ) { - $inner_blocks_html .= '
  • ' . $inner_block_content . '
  • '; - } else { - $inner_blocks_html .= $inner_block_content; - } - } - } - - if ( $is_list_open ) { - $inner_blocks_html .= ''; - } - - $should_load_view_script = ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) || $is_responsive_menu; - $view_js_file = 'wp-block-navigation-view'; - - // If the script already exists, there is no point in removing it from viewScript. - if ( ! wp_script_is( $view_js_file ) ) { - $script_handles = $block->block_type->view_script_handles; - - // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); - } - // If the script is needed, but it was previously removed, add it again. - if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); - } - } - - // Add directives to the submenu if needed. - if ( $has_submenus && $should_load_view_script ) { - $w = new WP_HTML_Tag_Processor( $inner_blocks_html ); - $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $w, $attributes ); - } - - $modal_unique_id = wp_unique_id( 'modal-' ); - - // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive, - // return early if they don't. - if ( ! $is_responsive_menu ) { - return sprintf( - '', - $wrapper_attributes, - $inner_blocks_html - ); - } - - $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; - - $responsive_container_classes = array( - 'wp-block-navigation__responsive-container', - $is_hidden_by_default ? 'hidden-by-default' : '', - implode( ' ', $colors['overlay_css_classes'] ), - ); - $open_button_classes = array( - 'wp-block-navigation__responsive-container-open', - $is_hidden_by_default ? 'always-shown' : '', - ); - - $should_display_icon_label = isset( $attributes['hasIcon'] ) && true === $attributes['hasIcon']; - $toggle_button_icon = ''; - if ( isset( $attributes['icon'] ) ) { - if ( 'menu' === $attributes['icon'] ) { - $toggle_button_icon = ''; - } - } - $toggle_button_content = $should_display_icon_label ? $toggle_button_icon : __( 'Menu' ); - $toggle_close_button_icon = ''; - $toggle_close_button_content = $should_display_icon_label ? $toggle_close_button_icon : __( 'Close' ); - $toggle_aria_label_open = $should_display_icon_label ? 'aria-label="' . __( 'Open menu' ) . '"' : ''; // Open button label. - $toggle_aria_label_close = $should_display_icon_label ? 'aria-label="' . __( 'Close menu' ) . '"' : ''; // Close button label. - - // Add Interactivity API directives to the markup if needed. - $nav_element_directives = ''; - $open_button_directives = ''; - $responsive_container_directives = ''; - $responsive_dialog_directives = ''; - $close_button_directives = ''; - if ( $should_load_view_script ) { - $nav_element_context = wp_json_encode( - array( - 'core' => array( - 'navigation' => array( - 'overlayOpenedBy' => array(), - 'type' => 'overlay', - 'roleAttribute' => '', - 'ariaLabel' => __( 'Menu' ), - ), - ), - ), - JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP - ); - $nav_element_directives = ' - data-wp-interactive - data-wp-context=\'' . $nav_element_context . '\' - '; - $open_button_directives = ' - data-wp-on--click="actions.core.navigation.openMenuOnClick" - data-wp-on--keydown="actions.core.navigation.handleMenuKeydown" - '; - $responsive_container_directives = ' - data-wp-class--has-modal-open="selectors.core.navigation.isMenuOpen" - data-wp-class--is-menu-open="selectors.core.navigation.isMenuOpen" - data-wp-effect="effects.core.navigation.initMenu" - data-wp-on--keydown="actions.core.navigation.handleMenuKeydown" - data-wp-on--focusout="actions.core.navigation.handleMenuFocusout" - tabindex="-1" - '; - $responsive_dialog_directives = ' - data-wp-bind--aria-modal="selectors.core.navigation.ariaModal" - data-wp-bind--aria-label="selectors.core.navigation.ariaLabel" - data-wp-bind--role="selectors.core.navigation.roleAttribute" - data-wp-effect="effects.core.navigation.focusFirstElement" - '; - $close_button_directives = ' - data-wp-on--click="actions.core.navigation.closeMenuOnClick" - '; - } - - $responsive_container_markup = sprintf( - ' -
    -
    -
    - -
    - %2$s -
    -
    -
    -
    ', - esc_attr( $modal_unique_id ), - $inner_blocks_html, - $toggle_aria_label_open, - $toggle_aria_label_close, - esc_attr( implode( ' ', $responsive_container_classes ) ), - esc_attr( implode( ' ', $open_button_classes ) ), - esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ), - $toggle_button_content, - $toggle_close_button_content, - $open_button_directives, - $responsive_container_directives, - $responsive_dialog_directives, - $close_button_directives - ); - - return sprintf( - '', - $wrapper_attributes, - $responsive_container_markup, - $nav_element_directives - ); + return WP_Navigation_Block_Renderer::render( $attributes, $content, $block ); } /** @@ -791,6 +1079,13 @@ function register_block_core_navigation() { 'render_callback' => 'render_block_core_navigation', ) ); + + wp_register_script_module( + '@wordpress/block-library/navigation', + defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ? gutenberg_url( '/build/interactivity/navigation.min.js' ) : includes_url( 'blocks/navigation/view.min.js' ), + array( '@wordpress/interactivity' ), + defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) + ); } add_action( 'init', 'register_block_core_navigation' ); @@ -829,25 +1124,6 @@ function block_core_navigation_typographic_presets_backcompatibility( $parsed_bl add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' ); -/** - * Ensure that the view script has the `wp-interactivity` dependency. - * - * @since 6.4.0 - * - * @global WP_Scripts $wp_scripts - */ -function block_core_navigation_ensure_interactivity_dependency() { - global $wp_scripts; - if ( - isset( $wp_scripts->registered['wp-block-navigation-view'] ) && - ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-navigation-view']->deps, true ) - ) { - $wp_scripts->registered['wp-block-navigation-view']->deps[] = 'wp-interactivity'; - } -} - -add_action( 'wp_print_scripts', 'block_core_navigation_ensure_interactivity_dependency' ); - /** * Turns menu item data into a nested array of parsed blocks * @@ -1066,3 +1342,116 @@ function block_core_navigation_get_most_recently_published_navigation() { return null; } + +/** + * Insert hooked blocks into a Navigation block. + * + * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object, + * this function inserts hooked blocks into it, and returns the serialized inner blocks in a + * mock Navigation block wrapper. + * + * If there are any hooked blocks that need to be inserted as the Navigation block's first or last + * children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any + * of those hooked blocks should be exempted from insertion. + * + * @param array $inner_blocks Parsed inner blocks of a Navigation block. + * @param WP_Post $post `wp_navigation` post object corresponding to the block. + * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any. + */ +function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) { + $before_block_visitor = null; + $after_block_visitor = null; + $hooked_blocks = get_hooked_blocks(); + $attributes = array(); + + if ( isset( $post->ID ) ) { + $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $ignored_hooked_blocks ) ) { + $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => $ignored_hooked_blocks, + ); + } + } + + $mock_anchor_parent_block = array( + 'blockName' => 'core/navigation', + 'attrs' => $attributes, + 'innerBlocks' => $inner_blocks, + 'innerContent' => array_fill( 0, count( $inner_blocks ), null ), + ); + $before_block_visitor = null; + $after_block_visitor = null; + + if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { + $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post ); + $after_block_visitor = make_after_block_visitor( $hooked_blocks, $post ); + } + + return traverse_and_serialize_block( $mock_anchor_parent_block, $before_block_visitor, $after_block_visitor ); +} + +/** + * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API. + * + * @param WP_Post $post Post object. + */ +function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) { + // We run the Block Hooks mechanism so it will return the list of ignored hooked blocks + // in the mock root Navigation block's metadata attribute. + // We ignore the rest of the returned `$markup`; `$post->post_content` already has the hooked + // blocks inserted, whereas `$markup` will have them inserted twice. + $blocks = parse_blocks( $post->post_content ); + $markup = block_core_navigation_insert_hooked_blocks( $blocks, $post ); + $root_nav_block = parse_blocks( $markup )[0]; + $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] ) + ? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] + : array(); + + if ( ! empty( $ignored_hooked_blocks ) ) { + $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $existing_ignored_hooked_blocks ) ) { + $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true ); + $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) ); + } + update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) ); + } +} + +// Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5 +// that are not present in Gutenberg's WP 6.5 compatibility layer. +if ( function_exists( 'get_hooked_block_markup' ) ) { + add_action( 'rest_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10, 3 ); +} + +/** + * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks. + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Post object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response The response object. + */ +function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) { + if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) { + return $response; + } + $parsed_blocks = parse_blocks( $response->data['content']['raw'] ); + $content = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post ); + + // Remove mock Navigation block wrapper. + $start = strpos( $content, '-->' ) + strlen( '-->' ); + $end = strrpos( $content, '`. Support these by defaulting an undefined label and @@ -36,7 +36,6 @@ function render_block_core_search( $attributes, $content, $block ) { $show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true; $button_position = $show_button ? $attributes['buttonPosition'] : null; $query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array(); - $button_behavior = ( ! empty( $attributes['buttonBehavior'] ) ) ? $attributes['buttonBehavior'] : 'default'; $button = ''; $query_params_markup = ''; $inline_styles = styles_for_block_core_search( $attributes ); @@ -78,28 +77,18 @@ function render_block_core_search( $attributes, $content, $block ) { $input->set_attribute( 'value', get_search_query() ); $input->set_attribute( 'placeholder', $attributes['placeholder'] ); - $is_expandable_searchfield = 'button-only' === $button_position && 'expand-searchfield' === $button_behavior; + // If it's interactive, enqueue the script module and add the directives. + $is_expandable_searchfield = 'button-only' === $button_position; if ( $is_expandable_searchfield ) { - $input->set_attribute( 'data-wp-bind--aria-hidden', '!context.core.search.isSearchInputVisible' ); - $input->set_attribute( 'data-wp-bind--tabindex', 'selectors.core.search.tabindex' ); - // Adding these attributes manually is needed until the Interactivity API SSR logic is added to core. - $input->set_attribute( 'aria-hidden', 'true' ); - $input->set_attribute( 'tabindex', '-1' ); - } + wp_enqueue_script_module( '@wordpress/block-library/search' ); - // If the script already exists, there is no point in removing it from viewScript. - $view_js_file = 'wp-block-search-view'; - if ( ! wp_script_is( $view_js_file ) ) { - $script_handles = $block->block_type->view_script_handles; + $input->set_attribute( 'data-wp-bind--aria-hidden', '!context.isSearchInputVisible' ); + $input->set_attribute( 'data-wp-bind--tabindex', 'state.tabindex' ); - // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( ! $is_expandable_searchfield && in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); - } - // If the script is needed, but it was previously removed, add it again. - if ( $is_expandable_searchfield && ! in_array( $view_js_file, $script_handles, true ) ) { - $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); - } + // Adding these attributes manually is needed until the Interactivity API + // SSR logic is added to core. + $input->set_attribute( 'aria-hidden', 'true' ); + $input->set_attribute( 'tabindex', '-1' ); } } @@ -144,13 +133,15 @@ function render_block_core_search( $attributes, $content, $block ) { if ( $button->next_tag() ) { $button->add_class( implode( ' ', $button_classes ) ); - if ( 'expand-searchfield' === $attributes['buttonBehavior'] && 'button-only' === $attributes['buttonPosition'] ) { - $button->set_attribute( 'data-wp-bind--aria-label', 'selectors.core.search.ariaLabel' ); - $button->set_attribute( 'data-wp-bind--aria-controls', 'selectors.core.search.ariaControls' ); - $button->set_attribute( 'data-wp-bind--aria-expanded', 'context.core.search.isSearchInputVisible' ); - $button->set_attribute( 'data-wp-bind--type', 'selectors.core.search.type' ); - $button->set_attribute( 'data-wp-on--click', 'actions.core.search.openSearchInput' ); - // Adding these attributes manually is needed until the Interactivity API SSR logic is added to core. + if ( 'button-only' === $attributes['buttonPosition'] ) { + $button->set_attribute( 'data-wp-bind--aria-label', 'state.ariaLabel' ); + $button->set_attribute( 'data-wp-bind--aria-controls', 'state.ariaControls' ); + $button->set_attribute( 'data-wp-bind--aria-expanded', 'context.isSearchInputVisible' ); + $button->set_attribute( 'data-wp-bind--type', 'state.type' ); + $button->set_attribute( 'data-wp-on--click', 'actions.openSearchInput' ); + + // Adding these attributes manually is needed until the Interactivity + // API SSR logic is added to core. $button->set_attribute( 'aria-label', __( 'Expand search field' ) ); $button->set_attribute( 'aria-controls', 'wp-block-search__input-' . $input_id ); $button->set_attribute( 'aria-expanded', 'false' ); @@ -172,15 +163,17 @@ function render_block_core_search( $attributes, $content, $block ) { array( 'class' => $classnames ) ); $form_directives = ''; + + // If it's interactive, add the directives. if ( $is_expandable_searchfield ) { $aria_label_expanded = __( 'Submit Search' ); $aria_label_collapsed = __( 'Expand search field' ); $form_directives = ' - data-wp-interactive - data-wp-context=\'{ "core": { "search": { "isSearchInputVisible": ' . $open_by_default . ', "inputId": "' . $input_id . '", "ariaLabelExpanded": "' . $aria_label_expanded . '", "ariaLabelCollapsed": "' . $aria_label_collapsed . '" } } }\' - data-wp-class--wp-block-search__searchfield-hidden="!context.core.search.isSearchInputVisible" - data-wp-on--keydown="actions.core.search.handleSearchKeydown" - data-wp-on--focusout="actions.core.search.handleSearchFocusout" + data-wp-interactive=\'{ "namespace": "core/search" }\' + data-wp-context=\'{ "isSearchInputVisible": ' . $open_by_default . ', "inputId": "' . $input_id . '", "ariaLabelExpanded": "' . $aria_label_expanded . '", "ariaLabelCollapsed": "' . $aria_label_collapsed . '" }\' + data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" + data-wp-on--keydown="actions.handleSearchKeydown" + data-wp-on--focusout="actions.handleSearchFocusout" '; } @@ -203,27 +196,15 @@ function register_block_core_search() { 'render_callback' => 'render_block_core_search', ) ); -} -add_action( 'init', 'register_block_core_search' ); -/** - * Ensure that the view script has the `wp-interactivity` dependency. - * - * @since 6.4.0 - * - * @global WP_Scripts $wp_scripts - */ -function block_core_search_ensure_interactivity_dependency() { - global $wp_scripts; - if ( - isset( $wp_scripts->registered['wp-block-search-view'] ) && - ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-search-view']->deps, true ) - ) { - $wp_scripts->registered['wp-block-search-view']->deps[] = 'wp-interactivity'; - } + wp_register_script_module( + '@wordpress/block-library/search', + defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ? gutenberg_url( '/build/interactivity/search.min.js' ) : includes_url( 'blocks/search/view.min.js' ), + array( '@wordpress/interactivity' ), + defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) + ); } - -add_action( 'wp_print_scripts', 'block_core_search_ensure_interactivity_dependency' ); +add_action( 'init', 'register_block_core_search' ); /** * Builds the correct top level classnames for the 'core/search' block. @@ -249,10 +230,7 @@ function classnames_for_block_core_search( $attributes ) { } if ( 'button-only' === $attributes['buttonPosition'] ) { - $classnames[] = 'wp-block-search__button-only'; - if ( ! empty( $attributes['buttonBehavior'] ) && 'expand-searchfield' === $attributes['buttonBehavior'] ) { - $classnames[] = 'wp-block-search__button-behavior-expand wp-block-search__searchfield-hidden'; - } + $classnames[] = 'wp-block-search__button-only wp-block-search__searchfield-hidden'; } } diff --git a/src/wp-includes/blocks/search/block.json b/src/wp-includes/blocks/search/block.json index 5669a9089d0e0..8d5e208045068 100644 --- a/src/wp-includes/blocks/search/block.json +++ b/src/wp-includes/blocks/search/block.json @@ -43,10 +43,6 @@ "type": "object", "default": {} }, - "buttonBehavior": { - "type": "string", - "default": "expand-searchfield" - }, "isSearchFieldHidden": { "type": "boolean", "default": false @@ -91,7 +87,6 @@ }, "html": false }, - "viewScript": "file:./view.min.js", "editorStyle": "wp-block-search-editor", "style": "wp-block-search" } diff --git a/src/wp-includes/blocks/search/view.asset.php b/src/wp-includes/blocks/search/view.asset.php index e2b70b1990bb1..e9b5021ae35c5 100644 --- a/src/wp-includes/blocks/search/view.asset.php +++ b/src/wp-includes/blocks/search/view.asset.php @@ -1 +1 @@ - array(), 'version' => 'e81408b3448113f37705'); + array(), 'version' => '2a0784014283afdd3c25'); diff --git a/src/wp-includes/blocks/search/view.min.asset.php b/src/wp-includes/blocks/search/view.min.asset.php index dc204c9390558..f9f2fddc7dd06 100644 --- a/src/wp-includes/blocks/search/view.min.asset.php +++ b/src/wp-includes/blocks/search/view.min.asset.php @@ -1 +1 @@ - array(), 'version' => 'ff76b5016de2df424c55'); + array(), 'version' => '765a40956d200c79d99e'); diff --git a/src/wp-includes/blocks/site-title/block.json b/src/wp-includes/blocks/site-title/block.json index e936bad0e4515..4a2685e6941fc 100644 --- a/src/wp-includes/blocks/site-title/block.json +++ b/src/wp-includes/blocks/site-title/block.json @@ -56,11 +56,7 @@ "__experimentalFontWeight": true, "__experimentalLetterSpacing": true, "__experimentalDefaultControls": { - "fontSize": true, - "lineHeight": true, - "fontAppearance": true, - "letterSpacing": true, - "textTransform": true + "fontSize": true } } }, diff --git a/src/wp-includes/blocks/social-link.php b/src/wp-includes/blocks/social-link.php index cda8e125097a5..fe256879fa4ff 100644 --- a/src/wp-includes/blocks/social-link.php +++ b/src/wp-includes/blocks/social-link.php @@ -33,7 +33,7 @@ function render_block_core_social_link( $attributes, $content, $block ) { * The `is_email` returns false for emails with schema. */ if ( is_email( $url ) ) { - $url = 'mailto:' . $url; + $url = 'mailto:' . antispambot( $url ); } /** @@ -62,10 +62,10 @@ function render_block_core_social_link( $attributes, $content, $block ) { $processor = new WP_HTML_Tag_Processor( $link ); $processor->next_tag( 'a' ); if ( $open_in_new_tab ) { - $processor->set_attribute( 'rel', esc_attr( $rel ) . ' noopener nofollow' ); + $processor->set_attribute( 'rel', trim( $rel . ' noopener nofollow' ) ); $processor->set_attribute( 'target', '_blank' ); } elseif ( '' !== $rel ) { - $processor->set_attribute( 'rel', esc_attr( $rel ) ); + $processor->set_attribute( 'rel', trim( $rel ) ); } return $processor->get_updated_html(); } @@ -194,6 +194,10 @@ function block_core_social_link_services( $service = '', $field = '' ) { 'name' => 'GitHub', 'icon' => '', ), + 'gravatar' => array( + 'name' => 'Gravatar', + 'icon' => '', + ), 'instagram' => array( 'name' => 'Instagram', 'icon' => '', diff --git a/src/wp-includes/blocks/table/block.json b/src/wp-includes/blocks/table/block.json index d1139d6c55add..470886a1247fe 100644 --- a/src/wp-includes/blocks/table/block.json +++ b/src/wp-includes/blocks/table/block.json @@ -12,10 +12,9 @@ "default": false }, "caption": { - "type": "string", - "source": "html", - "selector": "figcaption", - "default": "" + "type": "rich-text", + "source": "rich-text", + "selector": "figcaption" }, "head": { "type": "array", @@ -30,8 +29,8 @@ "selector": "td,th", "query": { "content": { - "type": "string", - "source": "html" + "type": "rich-text", + "source": "rich-text" }, "tag": { "type": "string", @@ -75,8 +74,8 @@ "selector": "td,th", "query": { "content": { - "type": "string", - "source": "html" + "type": "rich-text", + "source": "rich-text" }, "tag": { "type": "string", @@ -120,8 +119,8 @@ "selector": "td,th", "query": { "content": { - "type": "string", - "source": "html" + "type": "rich-text", + "source": "rich-text" }, "tag": { "type": "string", diff --git a/src/wp-includes/blocks/template-part.php b/src/wp-includes/blocks/template-part.php index 3ad400906945b..86a17f33c92f3 100644 --- a/src/wp-includes/blocks/template-part.php +++ b/src/wp-includes/blocks/template-part.php @@ -43,10 +43,10 @@ function render_block_core_template_part( $attributes ) { if ( $template_part_post ) { // A published post might already exist if this template part was customized elsewhere // or if it's part of a customized template. - $content = $template_part_post->post_content; - $area_terms = get_the_terms( $template_part_post, 'wp_template_part_area' ); - if ( ! is_wp_error( $area_terms ) && false !== $area_terms ) { - $area = $area_terms[0]->name; + $block_template = _build_block_template_result_from_post( $template_part_post ); + $content = $block_template->content; + if ( isset( $block_template->area ) ) { + $area = $block_template->area; } /** * Fires when a block template part is loaded from a template post stored in the database. @@ -70,6 +70,12 @@ function render_block_core_template_part( $attributes ) { if ( isset( $block_template->area ) ) { $area = $block_template->area; } + + // Needed for the `render_block_core_template_part_file` and `render_block_core_template_part_none` actions below. + $block_template_file = _get_block_template_file( 'wp_template_part', $attributes['slug'] ); + if ( $block_template_file ) { + $template_part_file_path = $block_template_file['path']; + } } if ( '' !== $content && null !== $content ) { @@ -275,8 +281,8 @@ function register_block_core_template_part() { register_block_type_from_metadata( __DIR__ . '/template-part', array( - 'render_callback' => 'render_block_core_template_part', - 'variations' => build_template_part_block_variations(), + 'render_callback' => 'render_block_core_template_part', + 'variation_callback' => 'build_template_part_block_variations', ) ); } diff --git a/src/wp-includes/blocks/template-part/block.json b/src/wp-includes/blocks/template-part/block.json index 9fe431150ae39..3b0946718bcb9 100644 --- a/src/wp-includes/blocks/template-part/block.json +++ b/src/wp-includes/blocks/template-part/block.json @@ -23,7 +23,8 @@ "supports": { "align": true, "html": false, - "reusable": false + "reusable": false, + "renaming": false }, "editorStyle": "wp-block-template-part-editor" } diff --git a/src/wp-includes/blocks/verse/block.json b/src/wp-includes/blocks/verse/block.json index d0fffc8ae5076..846a1dc99caaf 100644 --- a/src/wp-includes/blocks/verse/block.json +++ b/src/wp-includes/blocks/verse/block.json @@ -9,10 +9,9 @@ "textdomain": "default", "attributes": { "content": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "pre", - "default": "", "__unstablePreserveWhiteSpace": true, "__experimentalRole": "content" }, @@ -40,8 +39,7 @@ "__experimentalTextTransform": true, "__experimentalTextDecoration": true, "__experimentalDefaultControls": { - "fontSize": true, - "fontAppearance": true + "fontSize": true } }, "spacing": { diff --git a/src/wp-includes/blocks/video/block.json b/src/wp-includes/blocks/video/block.json index debe6f20fe53f..5d4680f39e79a 100644 --- a/src/wp-includes/blocks/video/block.json +++ b/src/wp-includes/blocks/video/block.json @@ -15,8 +15,8 @@ "attribute": "autoplay" }, "caption": { - "type": "string", - "source": "html", + "type": "rich-text", + "source": "rich-text", "selector": "figcaption", "__experimentalRole": "content" }, diff --git a/src/wp-includes/blocks/widget-group/block.json b/src/wp-includes/blocks/widget-group/block.json index c29e811554ac1..0e59e58aca224 100644 --- a/src/wp-includes/blocks/widget-group/block.json +++ b/src/wp-includes/blocks/widget-group/block.json @@ -1,4 +1,5 @@ { + "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "core/widget-group", "category": "widgets", diff --git a/src/wp-includes/canonical.php b/src/wp-includes/canonical.php index 0b94791fd9b45..093493731f91a 100644 --- a/src/wp-includes/canonical.php +++ b/src/wp-includes/canonical.php @@ -550,13 +550,23 @@ function redirect_canonical( $requested_url = null, $do_redirect = true ) { $is_attachment_redirect = false; if ( is_attachment() && ! get_option( 'wp_attachment_pages_enabled' ) ) { - $attachment_id = get_query_var( 'attachment_id' ); - - if ( current_user_can( 'read_post', $attachment_id ) ) { - $redirect_url = wp_get_attachment_url( $attachment_id ); - - $is_attachment_redirect = true; + $attachment_id = get_query_var( 'attachment_id' ); + $attachment_post = get_post( $attachment_id ); + $attachment_parent_id = $attachment_post ? $attachment_post->post_parent : 0; + + $attachment_url = wp_get_attachment_url( $attachment_id ); + if ( $attachment_url !== $redirect_url ) { + /* + * If an attachment is attached to a post, it inherits the parent post's status. Fetch the + * parent post to check its status later. + */ + if ( $attachment_parent_id ) { + $redirect_obj = get_post( $attachment_parent_id ); + } + $redirect_url = $attachment_url; } + + $is_attachment_redirect = true; } $redirect['query'] = preg_replace( '#^\??&*?#', '', $redirect['query'] ); diff --git a/src/wp-includes/class-avif-info.php b/src/wp-includes/class-avif-info.php new file mode 100644 index 0000000000000..93280b3806dad --- /dev/null +++ b/src/wp-includes/class-avif-info.php @@ -0,0 +1,781 @@ += 2^31 on 32-bit systems. + // See https://www.php.net/manual/en/function.unpack.php#106041 + return unpack( 'N', $input ) [1]; + } +} + +/** + * Reads bytes and advances the stream position by the same count. + * + * @param stream $handle Bytes will be read from this resource. + * @param int $num_bytes Number of bytes read. Must be greater than 0. + * @return binary string|false The raw bytes or false on failure. + */ +function read( $handle, $num_bytes ) { + $data = fread( $handle, $num_bytes ); + return ( $data !== false && strlen( $data ) >= $num_bytes ) ? $data : false; +} + +/** + * Advances the stream position by the given offset. + * + * @param stream $handle Bytes will be skipped from this resource. + * @param int $num_bytes Number of skipped bytes. Can be 0. + * @return bool True on success or false on failure. + */ +// Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. +function skip( $handle, $num_bytes ) { + return ( fseek( $handle, $num_bytes, SEEK_CUR ) == 0 ); +} + +//------------------------------------------------------------------------------ +// Features are parsed into temporary property associations. + +class Tile { // Tile item id <-> parent item id associations. + public $tile_item_id; + public $parent_item_id; +} + +class Prop { // Property index <-> item id associations. + public $property_index; + public $item_id; +} + +class Dim_Prop { // Property <-> features associations. + public $property_index; + public $width; + public $height; +} + +class Chan_Prop { // Property <-> features associations. + public $property_index; + public $bit_depth; + public $num_channels; +} + +class Features { + public $has_primary_item = false; // True if "pitm" was parsed. + public $has_alpha = false; // True if an alpha "auxC" was parsed. + public $primary_item_id; + public $primary_item_features = array( // Deduced from the data below. + 'width' => UNDEFINED, // In number of pixels. + 'height' => UNDEFINED, // Ignores mirror and rotation. + 'bit_depth' => UNDEFINED, // Likely 8, 10 or 12 bits per channel per pixel. + 'num_channels' => UNDEFINED // Likely 1, 2, 3 or 4 channels: + // (1 monochrome or 3 colors) + (0 or 1 alpha) + ); + + public $tiles = array(); // Tile[] + public $props = array(); // Prop[] + public $dim_props = array(); // Dim_Prop[] + public $chan_props = array(); // Chan_Prop[] + + /** + * Binds the width, height, bit depth and number of channels from stored internal features. + * + * @param int $target_item_id Id of the item whose features will be bound. + * @param int $tile_depth Maximum recursion to search within tile-parent relations. + * @return Status FOUND on success or NOT_FOUND on failure. + */ + private function get_item_features( $target_item_id, $tile_depth ) { + foreach ( $this->props as $prop ) { + if ( $prop->item_id != $target_item_id ) { + continue; + } + + // Retrieve the width and height of the primary item if not already done. + if ( $target_item_id == $this->primary_item_id && + ( $this->primary_item_features['width'] == UNDEFINED || + $this->primary_item_features['height'] == UNDEFINED ) ) { + foreach ( $this->dim_props as $dim_prop ) { + if ( $dim_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['width'] = $dim_prop->width; + $this->primary_item_features['height'] = $dim_prop->height; + if ( $this->primary_item_features['bit_depth'] != UNDEFINED && + $this->primary_item_features['num_channels'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + // Retrieve the bit depth and number of channels of the target item if not + // already done. + if ( $this->primary_item_features['bit_depth'] == UNDEFINED || + $this->primary_item_features['num_channels'] == UNDEFINED ) { + foreach ( $this->chan_props as $chan_prop ) { + if ( $chan_prop->property_index != $prop->property_index ) { + continue; + } + $this->primary_item_features['bit_depth'] = $chan_prop->bit_depth; + $this->primary_item_features['num_channels'] = $chan_prop->num_channels; + if ( $this->primary_item_features['width'] != UNDEFINED && + $this->primary_item_features['height'] != UNDEFINED ) { + return FOUND; + } + break; + } + } + } + + // Check for the bit_depth and num_channels in a tile if not yet found. + if ( $tile_depth < 3 ) { + foreach ( $this->tiles as $tile ) { + if ( $tile->parent_item_id != $target_item_id ) { + continue; + } + $status = get_item_features( $tile->tile_item_id, $tile_depth + 1 ); + if ( $status != NOT_FOUND ) { + return $status; + } + } + } + return NOT_FOUND; + } + + /** + * Finds the width, height, bit depth and number of channels of the primary item. + * + * @return Status FOUND on success or NOT_FOUND on failure. + */ + public function get_primary_item_features() { + // Nothing to do without the primary item ID. + if ( !$this->has_primary_item ) { + return NOT_FOUND; + } + // Early exit. + if ( empty( $this->dim_props ) || empty( $this->chan_props ) ) { + return NOT_FOUND; + } + $status = $this->get_item_features( $this->primary_item_id, /*tile_depth=*/ 0 ); + if ( $status != FOUND ) { + return $status; + } + + // "auxC" is parsed before the "ipma" properties so it is known now, if any. + if ( $this->has_alpha ) { + ++$this->primary_item_features['num_channels']; + } + return FOUND; + } +} + +//------------------------------------------------------------------------------ + +class Box { + public $size; // In bytes. + public $type; // Four characters. + public $version; // 0 or actual version if this is a full box. + public $flags; // 0 or actual value if this is a full box. + public $content_size; // 'size' minus the header size. + + /** + * Reads the box header. + * + * @param stream $handle The resource the header will be parsed from. + * @param int $num_parsed_boxes The total number of parsed boxes. Prevents timeouts. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + public function parse( $handle, &$num_parsed_boxes, $num_remaining_bytes = MAX_SIZE ) { + // See ISO/IEC 14496-12:2012(E) 4.2 + $header_size = 8; // box 32b size + 32b type (at least) + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + $this->size = read_big_endian( $data, 4 ); + $this->type = substr( $data, 4, 4 ); + // 'box->size==1' means 64-bit size should be read after the box type. + // 'box->size==0' means this box extends to all remaining bytes. + if ( $this->size == 1 ) { + $header_size += 8; + if ( $header_size > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $handle, 8 ) ) ) { + return TRUNCATED; + } + // Stop the parsing if any box has a size greater than 4GB. + if ( read_big_endian( $data, 4 ) != 0 ) { + return ABORTED; + } + // Read the 32 least-significant bits. + $this->size = read_big_endian( substr( $data, 4, 4 ), 4 ); + } else if ( $this->size == 0 ) { + $this->size = $num_remaining_bytes; + } + if ( $this->size < $header_size ) { + return INVALID; + } + if ( $this->size > $num_remaining_bytes ) { + return INVALID; + } + + $has_fullbox_header = $this->type == 'meta' || $this->type == 'pitm' || + $this->type == 'ipma' || $this->type == 'ispe' || + $this->type == 'pixi' || $this->type == 'iref' || + $this->type == 'auxC'; + if ( $has_fullbox_header ) { + $header_size += 4; + } + if ( $this->size < $header_size ) { + return INVALID; + } + $this->content_size = $this->size - $header_size; + // Avoid timeouts. The maximum number of parsed boxes is arbitrary. + ++$num_parsed_boxes; + if ( $num_parsed_boxes >= MAX_NUM_BOXES ) { + return ABORTED; + } + + $this->version = 0; + $this->flags = 0; + if ( $has_fullbox_header ) { + if ( !( $data = read( $handle, 4 ) ) ) { + return TRUNCATED; + } + $this->version = read_big_endian( $data, 1 ); + $this->flags = read_big_endian( substr( $data, 1, 3 ), 3 ); + // See AV1 Image File Format (AVIF) 8.1 + // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when + // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged). + $is_parsable = ( $this->type == 'meta' && $this->version <= 0 ) || + ( $this->type == 'pitm' && $this->version <= 1 ) || + ( $this->type == 'ipma' && $this->version <= 1 ) || + ( $this->type == 'ispe' && $this->version <= 0 ) || + ( $this->type == 'pixi' && $this->version <= 0 ) || + ( $this->type == 'iref' && $this->version <= 1 ) || + ( $this->type == 'auxC' && $this->version <= 0 ); + // Instead of considering this file as invalid, skip unparsable boxes. + if ( !$is_parsable ) { + $this->type = 'unknownversion'; + } + } + // print_r( $this ); // Uncomment to print all boxes. + return FOUND; + } +} + +//------------------------------------------------------------------------------ + +class Parser { + private $handle; // Input stream. + private $num_parsed_boxes = 0; + private $data_was_skipped = false; + public $features; + + function __construct( $handle ) { + $this->handle = $handle; + $this->features = new Features(); + } + + /** + * Parses an "ipco" box. + * + * "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth + * and number of channels, and "auxC" is used for alpha. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_ipco( $num_remaining_bytes ) { + $box_index = 1; // 1-based index. Used for iterating over properties. + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ispe' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.3.2 + if ( $box->content_size < 8 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 8 ) ) ) { + return TRUNCATED; + } + $width = read_big_endian( substr( $data, 0, 4 ), 4 ); + $height = read_big_endian( substr( $data, 4, 4 ), 4 ); + if ( $width == 0 || $height == 0 ) { + return INVALID; + } + if ( count( $this->features->dim_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $dim_prop_count = count( $this->features->dim_props ); + $this->features->dim_props[$dim_prop_count] = new Dim_Prop(); + $this->features->dim_props[$dim_prop_count]->property_index = $box_index; + $this->features->dim_props[$dim_prop_count]->width = $width; + $this->features->dim_props[$dim_prop_count]->height = $height; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - 8 ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'pixi' ) { + // See ISO/IEC 23008-12:2017(E) 6.5.6.2 + if ( $box->content_size < 1 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $num_channels = read_big_endian( $data, 1 ); + if ( $num_channels < 1 ) { + return INVALID; + } + if ( $box->content_size < 1 + $num_channels ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + $bit_depth = read_big_endian( $data, 1 ); + if ( $bit_depth < 1 ) { + return INVALID; + } + for ( $i = 1; $i < $num_channels; ++$i ) { + if ( !( $data = read( $this->handle, 1 ) ) ) { + return TRUNCATED; + } + // Bit depth should be the same for all channels. + if ( read_big_endian( $data, 1 ) != $bit_depth ) { + return INVALID; + } + if ( $i > 32 ) { + return ABORTED; // Be reasonable. + } + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE && + $num_channels <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); + $this->features->chan_props[$chan_prop_count]->property_index = $box_index; + $this->features->chan_props[$chan_prop_count]->bit_depth = $bit_depth; + $this->features->chan_props[$chan_prop_count]->num_channels = $num_channels; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'av1C' ) { + // See AV1 Codec ISO Media File Format Binding 2.3.1 + // at https://aomediacodec.github.io/av1-isobmff/#av1c + // Only parse the necessary third byte. Assume that the others are valid. + if ( $box->content_size < 3 ) { + return INVALID; + } + if ( !( $data = read( $this->handle, 3 ) ) ) { + return TRUNCATED; + } + $byte = read_big_endian( substr( $data, 2, 1 ), 1 ); + $high_bitdepth = ( $byte & 0x40 ) != 0; + $twelve_bit = ( $byte & 0x20 ) != 0; + $monochrome = ( $byte & 0x10 ) != 0; + if ( $twelve_bit && !$high_bitdepth ) { + return INVALID; + } + if ( count( $this->features->chan_props ) <= MAX_FEATURES && + $box_index <= MAX_VALUE ) { + $chan_prop_count = count( $this->features->chan_props ); + $this->features->chan_props[$chan_prop_count] = new Chan_Prop(); + $this->features->chan_props[$chan_prop_count]->property_index = $box_index; + $this->features->chan_props[$chan_prop_count]->bit_depth = + $high_bitdepth ? $twelve_bit ? 12 : 10 : 8; + $this->features->chan_props[$chan_prop_count]->num_channels = $monochrome ? 1 : 3; + } else { + $this->data_was_skipped = true; + } + if ( !skip( $this->handle, $box->content_size - 3 ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'auxC' ) { + // See AV1 Image File Format (AVIF) 4 + // at https://aomediacodec.github.io/av1-avif/#auxiliary-images + $kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha\0"; + $kAlphaStrLength = 44; // Includes terminating character. + if ( $box->content_size >= $kAlphaStrLength ) { + if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) { + return TRUNCATED; + } + if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) { + // Note: It is unlikely but it is possible that this alpha plane does + // not belong to the primary item or a tile. Ignore this issue. + $this->features->has_alpha = true; + } + if ( !skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + ++$box_index; + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iprp" box. + * + * The "ipco" box contain the properties which are linked to items by the "ipma" box. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iprp( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'ipco' ) { + $status = $this->parse_ipco( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else if ( $box->type == 'ipma' ) { + // See ISO/IEC 23008-12:2017(E) 9.3.2 + $num_read_bytes = 4; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $entry_count = read_big_endian( $data, 4 ); + $id_num_bytes = ( $box->version < 1 ) ? 2 : 4; + $index_num_bytes = ( $box->flags & 1 ) ? 2 : 1; + $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80; + + for ( $entry = 0; $entry < $entry_count; ++$entry ) { + if ( $entry >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $id_num_bytes + 1; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) { + return TRUNCATED; + } + $item_id = read_big_endian( + substr( $data, 0, $id_num_bytes ), $id_num_bytes ); + $association_count = read_big_endian( + substr( $data, $id_num_bytes, 1 ), 1 ); + + for ( $property = 0; $property < $association_count; ++$property ) { + if ( $property >= MAX_PROPS || + count( $this->features->props ) >= MAX_PROPS ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $index_num_bytes; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $index_num_bytes ) ) ) { + return TRUNCATED; + } + $value = read_big_endian( $data, $index_num_bytes ); + // $essential = ($value & $essential_bit_mask); // Unused. + $property_index = ( $value & ~$essential_bit_mask ); + if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) { + $prop_count = count( $this->features->props ); + $this->features->props[$prop_count] = new Prop(); + $this->features->props[$prop_count]->property_index = $property_index; + $this->features->props[$prop_count]->item_id = $item_id; + } else { + $this->data_was_skipped = true; + } + } + if ( $property < $association_count ) { + break; // Do not read garbage. + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses an "iref" box. + * + * The "dimg" boxes contain links between tiles and their parent items, which + * can be used to infer bit depth and number of channels for the primary item + * when the latter does not have these properties. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_iref( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'dimg' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.12.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + $num_read_bytes = $num_bytes_per_id + 2; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) { + return TRUNCATED; + } + $from_item_id = read_big_endian( $data, $num_bytes_per_id ); + $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 ); + + for ( $i = 0; $i < $reference_count; ++$i ) { + if ( $i >= MAX_TILES ) { + $this->data_was_skipped = true; + break; + } + $num_read_bytes += $num_bytes_per_id; + if ( $box->content_size < $num_read_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $to_item_id = read_big_endian( $data, $num_bytes_per_id ); + $tile_count = count( $this->features->tiles ); + if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE && + $tile_count < MAX_TILES ) { + $this->features->tiles[$tile_count] = new Tile(); + $this->features->tiles[$tile_count]->tile_item_id = $to_item_id; + $this->features->tiles[$tile_count]->parent_item_id = $from_item_id; + } else { + $this->data_was_skipped = true; + } + } + + // If all features are available now, do not look further. + $status = $this->features->get_primary_item_features(); + if ( $status != NOT_FOUND ) { + return $status; + } + + // Mostly if 'data_was_skipped'. + if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) { + return TRUNCATED; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes > 0 ); + return NOT_FOUND; + } + + /** + * Parses a "meta" box. + * + * It looks for the primary item ID in the "pitm" box and recurses into other boxes + * to find its features. + * + * @param stream $handle The resource the box will be parsed from. + * @param int $num_remaining_bytes The number of bytes that should be available from the resource. + * @return Status FOUND on success or an error on failure. + */ + private function parse_meta( $num_remaining_bytes ) { + do { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes ); + if ( $status != FOUND ) { + return $status; + } + + if ( $box->type == 'pitm' ) { + // See ISO/IEC 14496-12:2015(E) 8.11.4.2 + $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4; + if ( $num_bytes_per_id > $num_remaining_bytes ) { + return INVALID; + } + if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) { + return TRUNCATED; + } + $primary_item_id = read_big_endian( $data, $num_bytes_per_id ); + if ( $primary_item_id > MAX_VALUE ) { + return ABORTED; + } + $this->features->has_primary_item = true; + $this->features->primary_item_id = $primary_item_id; + if ( !skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) { + return TRUNCATED; + } + } else if ( $box->type == 'iprp' ) { + $status = $this->parse_iprp( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else if ( $box->type == 'iref' ) { + $status = $this->parse_iref( $box->content_size ); + if ( $status != NOT_FOUND ) { + return $status; + } + } else { + if ( !skip( $this->handle, $box->content_size ) ) { + return TRUNCATED; + } + } + $num_remaining_bytes -= $box->size; + } while ( $num_remaining_bytes != 0 ); + // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta". + return INVALID; + } + + /** + * Parses a file stream. + * + * The file type is checked through the "ftyp" box. + * + * @return bool True if the input stream is an AVIF bitstream or false. + */ + public function parse_ftyp() { + $box = new Box(); + $status = $box->parse( $this->handle, $this->num_parsed_boxes ); + if ( $status != FOUND ) { + return false; + } + + if ( $box->type != 'ftyp' ) { + return false; + } + // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 + if ( $box->content_size < 8 ) { + return false; + } + for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) { + if ( !( $data = read( $this->handle, 4 ) ) ) { + return false; + } + if ( $i == 4 ) { + continue; // Skip minor_version. + } + if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) { + return skip( $this->handle, $box->content_size - ( $i + 4 ) ); + } + if ( $i > 32 * 4 ) { + return false; // Be reasonable. + } + + } + return false; // No AVIF brand no good. + } + + /** + * Parses a file stream. + * + * Features are extracted from the "meta" box. + * + * @return bool True if the main features of the primary item were parsed or false. + */ + public function parse_file() { + $box = new Box(); + while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) { + if ( $box->type === 'meta' ) { + if ( $this->parse_meta( $box->content_size ) != FOUND ) { + return false; + } + return true; + } + if ( !skip( $this->handle, $box->content_size ) ) { + return false; + } + } + return false; // No "meta" no good. + } +} diff --git a/src/wp-includes/class-wp-block-bindings-registry.php b/src/wp-includes/class-wp-block-bindings-registry.php new file mode 100644 index 0000000000000..65f5f13c36eec --- /dev/null +++ b/src/wp-includes/class-wp-block-bindings-registry.php @@ -0,0 +1,230 @@ +is_registered( $source_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block bindings source name. */ + sprintf( __( 'Block bindings source "%s" already registered.' ), $source_name ), + '6.5.0' + ); + return false; + } + + /* Validate that the source properties contain the label */ + if ( ! isset( $source_properties['label'] ) ) { + _doing_it_wrong( + __METHOD__, + __( 'The $source_properties must contain a "label".' ), + '6.5.0' + ); + return false; + } + + /* Validate that the source properties contain the get_value_callback */ + if ( ! isset( $source_properties['get_value_callback'] ) ) { + _doing_it_wrong( + __METHOD__, + __( 'The $source_properties must contain a "get_value_callback".' ), + '6.5.0' + ); + return false; + } + + /* Validate that the get_value_callback is a valid callback */ + if ( ! is_callable( $source_properties['get_value_callback'] ) ) { + _doing_it_wrong( + __METHOD__, + __( 'The "get_value_callback" parameter must be a valid callback.' ), + '6.5.0' + ); + return false; + } + + $source = new WP_Block_Bindings_Source( + $source_name, + $source_properties + ); + + $this->sources[ $source_name ] = $source; + + return $source; + } + + /** + * Unregisters a block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name Block bindings source name including namespace. + * @return WP_Block_Bindings_Source|false The unregistered block bindings source on success and `false` otherwise. + */ + public function unregister( string $source_name ) { + if ( ! $this->is_registered( $source_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block bindings source name. */ + sprintf( __( 'Block binding "%s" not found.' ), $source_name ), + '6.5.0' + ); + return false; + } + + $unregistered_source = $this->sources[ $source_name ]; + unset( $this->sources[ $source_name ] ); + + return $unregistered_source; + } + + /** + * Retrieves the list of all registered block bindings sources. + * + * @since 6.5.0 + * + * @return WP_Block_Bindings_Source[] The array of registered sources. + */ + public function get_all_registered() { + return $this->sources; + } + + /** + * Retrieves a registered block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return WP_Block_Bindings_Source|null The registered block bindings source, or `null` if it is not registered. + */ + public function get_registered( string $source_name ) { + if ( ! $this->is_registered( $source_name ) ) { + return null; + } + + return $this->sources[ $source_name ]; + } + + /** + * Checks if a block bindings source is registered. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return bool `true` if the block bindings source is registered, `false` otherwise. + */ + public function is_registered( $source_name ) { + return isset( $this->sources[ $source_name ] ); + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.5.0 + * + * @return WP_Block_Bindings_Registry The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } +} diff --git a/src/wp-includes/class-wp-block-bindings-source.php b/src/wp-includes/class-wp-block-bindings-source.php new file mode 100644 index 0000000000000..c2a3d4f8ae2b8 --- /dev/null +++ b/src/wp-includes/class-wp-block-bindings-source.php @@ -0,0 +1,88 @@ +name = $name; + $this->label = $source_properties['label']; + $this->get_value_callback = $source_properties['get_value_callback']; + } + + /** + * Retrieves the value from the source. + * + * @since 6.5.0 + * + * @param array $source_args Array containing source arguments used to look up the override value, i.e. {"key": "foo"}. + * @param WP_Block $block_instance The block instance. + * @param string $attribute_name The name of the target attribute. + * + * @return mixed The value of the source. + */ + public function get_value( array $source_args, $block_instance, string $attribute_name ) { + return call_user_func_array( $this->get_value_callback, array( $source_args, $block_instance, $attribute_name ) ); + } + + /** + * Wakeup magic method. + * + * @since 6.5.0 + */ + public function __wakeup() { + throw new \LogicException( __CLASS__ . ' should never be unserialized' ); + } +} diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php index 9caa366032a75..33825a7888320 100644 --- a/src/wp-includes/class-wp-block-type.php +++ b/src/wp-includes/class-wp-block-type.php @@ -68,6 +68,14 @@ class WP_Block_Type { */ public $ancestor = null; + /** + * Limits which block types can be inserted as children of this block type. + * + * @since 6.5.0 + * @var string[]|null + */ + public $allowed_blocks = null; + /** * Block type icon. * @@ -113,9 +121,18 @@ class WP_Block_Type { * Block variations. * * @since 5.8.0 - * @var array[] + * @since 6.5.0 Only accessible through magic getter. null by default. + * @var array[]|null + */ + private $variations = null; + + /** + * Block variations callback. + * + * @since 6.5.0 + * @var callable|null */ - public $variations = array(); + public $variation_callback = null; /** * Custom CSS selectors for theme.json style generation. @@ -225,6 +242,14 @@ class WP_Block_Type { */ public $style_handles = array(); + /** + * Block type front end only style handles. + * + * @since 6.5.0 + * @var string[] + */ + public $view_style_handles = array(); + /** * Deprecated block type properties for script and style handles. * @@ -269,6 +294,7 @@ class WP_Block_Type { * Deprecated the `editor_script`, `script`, `view_script`, `editor_style`, and `style` properties. * @since 6.3.0 Added the `selectors` property. * @since 6.4.0 Added the `block_hooks` property. + * @since 6.5.0 Added the `view_style_handles` property. * * @see register_block_type() * @@ -285,6 +311,7 @@ class WP_Block_Type { * available when nested within the specified blocks. * @type string[]|null $ancestor Setting ancestor makes a block available only inside the specified * block types at any position of the ancestor's block subtree. + * @type string[]|null $allowed_blocks Limits which block types can be inserted as children of this block type. * @type string|null $icon Block type icon. * @type string $description A detailed block type description. * @type string[] $keywords Additional keywords to produce block type as @@ -296,6 +323,7 @@ class WP_Block_Type { * @type array|null $supports Supported features. * @type array|null $example Structured data for the block preview. * @type callable|null $render_callback Block type render callback. + * @type callable|null $variation_callback Block type variations callback. * @type array|null $attributes Block type attributes property schemas. * @type string[] $uses_context Context values inherited by blocks of this type. * @type string[]|null $provides_context Context provided by blocks of this type. @@ -305,6 +333,7 @@ class WP_Block_Type { * @type string[] $view_script_handles Block type front end only script handles. * @type string[] $editor_style_handles Block type editor only style handles. * @type string[] $style_handles Block type front end and editor style handles. + * @type string[] $view_style_handles Block type front end only style handles. * } */ public function __construct( $block_type, $args = array() ) { @@ -325,6 +354,10 @@ public function __construct( $block_type, $args = array() ) { * null when value not found, or void when unknown property name provided. */ public function __get( $name ) { + if ( 'variations' === $name ) { + return $this->get_variations(); + } + if ( ! in_array( $name, $this->deprecated_properties, true ) ) { return; } @@ -353,6 +386,10 @@ public function __get( $name ) { * or false otherwise. */ public function __isset( $name ) { + if ( 'variations' === $name ) { + return true; + } + if ( ! in_array( $name, $this->deprecated_properties, true ) ) { return false; } @@ -372,6 +409,11 @@ public function __isset( $name ) { * @param mixed $value Property value. */ public function __set( $name, $value ) { + if ( 'variations' === $name ) { + $this->variations = $value; + return; + } + if ( ! in_array( $name, $this->deprecated_properties, true ) ) { $this->{$name} = $value; return; @@ -540,4 +582,30 @@ public function get_attributes() { $this->attributes : array(); } + + /** + * Get block variations. + * + * @since 6.5.0 + * + * @return array[] + */ + public function get_variations() { + if ( ! isset( $this->variations ) ) { + $this->variations = array(); + if ( is_callable( $this->variation_callback ) ) { + $this->variations = call_user_func( $this->variation_callback ); + } + } + + /** + * Filters the registered variations for a block type. + * + * @since 6.5.0 + * + * @param array $variations Array of registered variations for a block type. + * @param WP_Block_Type $block_type The full block type object. + */ + return apply_filters( 'get_block_type_variations', $this->variations, $this ); + } } diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 65d3af6a12f1a..a965ef13727e1 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -191,6 +191,202 @@ public function __get( $name ) { return null; } + /** + * Processes the block bindings in block's attributes. + * + * A block might contain bindings in its attributes. Bindings are mappings + * between an attribute of the block and a source. A "source" is a function + * registered with `register_block_bindings_source()` that defines how to + * retrieve a value from outside the block, e.g. from post meta. + * + * This function will process those bindings and replace the HTML with the value of the binding. + * The value is retrieved from the source of the binding. + * + * ### Example + * + * The "bindings" property for an Image block might look like this: + * + * ```json + * { + * "metadata": { + * "bindings": { + * "title": { + * "source": "core/post-meta", + * "args": { "key": "text_custom_field" } + * }, + * "url": { + * "source": "core/post-meta", + * "args": { "key": "url_custom_field" } + * } + * } + * } + * } + * ``` + * + * The above example will replace the `title` and `url` attributes of the Image + * block with the values of the `text_custom_field` and `url_custom_field` post meta. + * + * @since 6.5.0 + * + * @param string $block_content Block content. + * @param array $block The full block, including name and attributes. + * @return string The modified block content. + */ + private function process_block_bindings( $block_content ) { + $parsed_block = $this->parsed_block; + + // Allowed blocks that support block bindings. + // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? + $allowed_blocks = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text' ), + ); + + // If the block doesn't have the bindings property, isn't one of the allowed + // block types, or the bindings property is not an array, return the block content. + if ( + ! isset( $allowed_blocks[ $this->name ] ) || + empty( $parsed_block['attrs']['metadata']['bindings'] ) || + ! is_array( $parsed_block['attrs']['metadata']['bindings'] ) + ) { + return $block_content; + } + + $modified_block_content = $block_content; + foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) { + // If the attribute is not in the allowed list, process next attribute. + if ( ! in_array( $attribute_name, $allowed_blocks[ $this->name ], true ) ) { + continue; + } + // If no source is provided, or that source is not registered, process next attribute. + if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) { + continue; + } + + $block_binding_source = get_block_bindings_source( $block_binding['source'] ); + if ( null === $block_binding_source ) { + continue; + } + + $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array(); + $source_value = $block_binding_source->get_value( $source_args, $this, $attribute_name ); + + // If the value is not null, process the HTML based on the block and the attribute. + if ( ! is_null( $source_value ) ) { + $modified_block_content = $this->replace_html( $modified_block_content, $attribute_name, $source_value ); + } + } + + return $modified_block_content; + } + + /** + * Depending on the block attribute name, replace its value in the HTML based on the value provided. + * + * @since 6.5.0 + * + * @param string $block_content Block content. + * @param string $attribute_name The attribute name to replace. + * @param mixed $source_value The value used to replace in the HTML. + * @return string The modified block content. + */ + private function replace_html( string $block_content, string $attribute_name, $source_value ) { + $block_type = $this->block_type; + if ( ! isset( $block_type->attributes[ $attribute_name ] ) ) { + return $block_content; + } + + // Depending on the attribute source, the processing will be different. + switch ( $block_type->attributes[ $attribute_name ]['source'] ) { + case 'html': + case 'rich-text': + $block_reader = new WP_HTML_Tag_Processor( $block_content ); + + // TODO: Support for CSS selectors whenever they are ready in the HTML API. + // In the meantime, support comma-separated selectors by exploding them into an array. + $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] ); + // Add a bookmark to the first tag to be able to iterate over the selectors. + $block_reader->next_tag(); + $block_reader->set_bookmark( 'iterate-selectors' ); + + // TODO: This shouldn't be needed when the `set_inner_html` function is ready. + // Store the parent tag and its attributes to be able to restore them later in the button. + // The button block has a wrapper while the paragraph and heading blocks don't. + if ( 'core/button' === $this->name ) { + $button_wrapper = $block_reader->get_tag(); + $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); + $button_wrapper_attrs = array(); + foreach ( $button_wrapper_attribute_names as $name ) { + $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); + } + } + + foreach ( $selectors as $selector ) { + // If the parent tag, or any of its children, matches the selector, replace the HTML. + if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( + array( + 'tag_name' => $selector, + ) + ) ) { + $block_reader->release_bookmark( 'iterate-selectors' ); + + // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. + // Until then, it is hardcoded for the paragraph, heading, and button blocks. + // Store the tag and its attributes to be able to restore them later. + $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); + $selector_attrs = array(); + foreach ( $selector_attribute_names as $name ) { + $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); + } + $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . ""; + $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); + $amended_content->next_tag(); + foreach ( $selector_attrs as $attribute_key => $attribute_value ) { + $amended_content->set_attribute( $attribute_key, $attribute_value ); + } + if ( 'core/paragraph' === $this->name || 'core/heading' === $this->name ) { + return $amended_content->get_updated_html(); + } + if ( 'core/button' === $this->name ) { + $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}"; + $amended_button = new WP_HTML_Tag_Processor( $button_markup ); + $amended_button->next_tag(); + foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { + $amended_button->set_attribute( $attribute_key, $attribute_value ); + } + return $amended_button->get_updated_html(); + } + } else { + $block_reader->seek( 'iterate-selectors' ); + } + } + $block_reader->release_bookmark( 'iterate-selectors' ); + return $block_content; + + case 'attribute': + $amended_content = new WP_HTML_Tag_Processor( $block_content ); + if ( ! $amended_content->next_tag( + array( + // TODO: build the query from CSS selector. + 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'], + ) + ) ) { + return $block_content; + } + $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value ); + return $amended_content->get_updated_html(); + break; + + default: + return $block_content; + break; + } + return; + } + + /** * Generates the render output for the block. * @@ -280,6 +476,16 @@ public function render( $options = array() ) { } } + if ( ( ! empty( $this->block_type->view_style_handles ) ) ) { + foreach ( $this->block_type->view_style_handles as $view_style_handle ) { + wp_enqueue_style( $view_style_handle ); + } + } + + // Process the block bindings for this block, if any are registered. This + // will replace the block content with the value from a registered binding source. + $block_content = $this->process_block_bindings( $block_content ); + /** * Filters the content of a single block. * diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index 9ebddd1c74e15..e2ea55a22c2c6 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -329,7 +329,7 @@ public function __construct( $query = '' ) { * * @since 4.2.0 Extracted from WP_Comment_Query::query(). * - * @param string|array $query WP_Comment_Query arguments. See WP_Comment_Query::__construct() + * @param string|array $query WP_Comment_Query arguments. See WP_Comment_Query::__construct(). */ public function parse_query( $query = '' ) { if ( empty( $query ) ) { diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php index 30e8c2c63914b..055f6cf670fb7 100644 --- a/src/wp-includes/class-wp-customize-control.php +++ b/src/wp-includes/class-wp-customize-control.php @@ -553,7 +553,7 @@ protected function render_content() { diff --git a/src/wp-includes/class-wp-customize-widgets.php b/src/wp-includes/class-wp-customize-widgets.php index c8a00c741ff8a..8c822ea28f1f5 100644 --- a/src/wp-includes/class-wp-customize-widgets.php +++ b/src/wp-includes/class-wp-customize-widgets.php @@ -823,7 +823,7 @@ public function enqueue_scripts() { ); foreach ( $settings['registeredWidgets'] as &$registered_widget ) { - unset( $registered_widget['callback'] ); // May not be JSON-serializeable. + unset( $registered_widget['callback'] ); // May not be JSON-serializable. } $wp_scripts->add_data( @@ -1308,7 +1308,7 @@ public function export_preview_data() { ); foreach ( $settings['registeredWidgets'] as &$registered_widget ) { - unset( $registered_widget['callback'] ); // May not be JSON-serializeable. + unset( $registered_widget['callback'] ); // May not be JSON-serializable. } wp_print_inline_script_tag( sprintf( 'var _wpWidgetCustomizerPreviewSettings = %s;', wp_json_encode( $settings ) ) diff --git a/src/wp-includes/class-wp-http.php b/src/wp-includes/class-wp-http.php index a30f9f7931a5d..bbb6dd41ae0ec 100644 --- a/src/wp-includes/class-wp-http.php +++ b/src/wp-includes/class-wp-http.php @@ -467,9 +467,9 @@ static function ( $attr ) { return null !== $attr; } ); - $cookie_jar[ $value->name ] = new WpOrg\Requests\Cookie( $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) ); + $cookie_jar[ $value->name ] = new WpOrg\Requests\Cookie( (string) $value->name, $value->value, $attributes, array( 'host-only' => $value->host_only ) ); } elseif ( is_scalar( $value ) ) { - $cookie_jar[ $name ] = new WpOrg\Requests\Cookie( $name, (string) $value ); + $cookie_jar[ $name ] = new WpOrg\Requests\Cookie( (string) $name, (string) $value ); } } diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php index de079357fb860..23331c7f196cc 100644 --- a/src/wp-includes/class-wp-image-editor-gd.php +++ b/src/wp-includes/class-wp-image-editor-gd.php @@ -71,6 +71,8 @@ public static function supports_mime_type( $mime_type ) { return ( $image_types & IMG_GIF ) != 0; case 'image/webp': return ( $image_types & IMG_WEBP ) != 0; + case 'image/avif': + return ( $image_types & IMG_AVIF ) != 0; } return false; @@ -111,6 +113,16 @@ function_exists( 'imagecreatefromwebp' ) && $this->image = @imagecreatefromstring( $file_contents ); } + // AVIF may not work with imagecreatefromstring(). + if ( + function_exists( 'imagecreatefromavif' ) && + ( 'image/avif' === wp_get_image_mime( $this->file ) ) + ) { + $this->image = @imagecreatefromavif( $this->file ); + } else { + $this->image = @imagecreatefromstring( $file_contents ); + } + if ( ! is_gd_image( $this->image ) ) { return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); } @@ -513,6 +525,10 @@ protected function _save( $image, $filename = null, $mime_type = null ) { if ( ! function_exists( 'imagewebp' ) || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) ) { return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); } + } elseif ( 'image/avif' == $mime_type ) { + if ( ! function_exists( 'imageavif' ) || ! $this->make_image( $filename, 'imageavif', array( $image, $filename, $this->get_quality() ) ) ) { + return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); + } } else { return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); } @@ -561,8 +577,17 @@ public function stream( $mime_type = null ) { if ( function_exists( 'imagewebp' ) ) { header( 'Content-Type: image/webp' ); return imagewebp( $this->image, null, $this->get_quality() ); + } else { + // Fall back to JPEG. + header( 'Content-Type: image/jpeg' ); + return imagejpeg( $this->image, null, $this->get_quality() ); + } + case 'image/avif': + if ( function_exists( 'imageavif' ) ) { + header( 'Content-Type: image/avif' ); + return imageavif( $this->image, null, $this->get_quality() ); } - // Fall back to the default if webp isn't supported. + // Fall back to JPEG. default: header( 'Content-Type: image/jpeg' ); return imagejpeg( $this->image, null, $this->get_quality() ); diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 03fe0bca6975f..546ad3e7991bf 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -219,6 +219,7 @@ public function set_quality( $quality = null ) { $this->image->setImageCompressionQuality( $quality ); } break; + case 'image/avif': default: $this->image->setImageCompressionQuality( $quality ); } @@ -256,6 +257,16 @@ protected function update_size( $width = null, $height = null ) { $height = $size['height']; } + /* + * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images + * are properly sized without affecting previous `getImageGeometry` behavior. + */ + if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) { + $size = wp_getimagesize( $this->file ); + $width = $size[0]; + $height = $size[1]; + } + return parent::update_size( $width, $height ); } diff --git a/src/wp-includes/class-wp-image-editor.php b/src/wp-includes/class-wp-image-editor.php index 3c636dc6ba5a7..6604685a024f0 100644 --- a/src/wp-includes/class-wp-image-editor.php +++ b/src/wp-includes/class-wp-image-editor.php @@ -318,6 +318,7 @@ protected function get_default_quality( $mime_type ) { $quality = 86; break; case 'image/jpeg': + case 'image/avif': default: $quality = $this->default_quality; } diff --git a/src/wp-includes/class-wp-locale-switcher.php b/src/wp-includes/class-wp-locale-switcher.php index d07490f107d1d..9f1c4831ed446 100644 --- a/src/wp-includes/class-wp-locale-switcher.php +++ b/src/wp-includes/class-wp-locale-switcher.php @@ -283,6 +283,8 @@ private function change_locale( $locale ) { $wp_locale = new WP_Locale(); + WP_Translation_Controller::get_instance()->set_locale( $locale ); + /** * Fires when the locale is switched to or restored. * diff --git a/src/wp-includes/class-wp-matchesmapregex.php b/src/wp-includes/class-wp-matchesmapregex.php index 558bd9866781c..ddca4f2cb2e02 100644 --- a/src/wp-includes/class-wp-matchesmapregex.php +++ b/src/wp-includes/class-wp-matchesmapregex.php @@ -63,8 +63,8 @@ public function __construct( $subject, $matches ) { * @return string */ public static function apply( $subject, $matches ) { - $oSelf = new WP_MatchesMapRegex( $subject, $matches ); - return $oSelf->output; + $result = new WP_MatchesMapRegex( $subject, $matches ); + return $result->output; } /** diff --git a/src/wp-includes/class-wp-plugin-dependencies.php b/src/wp-includes/class-wp-plugin-dependencies.php new file mode 100644 index 0000000000000..c8b9bebb5245f --- /dev/null +++ b/src/wp-includes/class-wp-plugin-dependencies.php @@ -0,0 +1,949 @@ + $dependencies ) { + if ( in_array( $slug, $dependencies, true ) ) { + $dependents[] = $dependent; + } + } + + return $dependents; + } + + /** + * Gets the slugs of plugins that the dependent requires. + * + * @since 6.5.0 + * + * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. + * @return array An array of dependency plugin slugs. + */ + public static function get_dependencies( $plugin_file ) { + if ( isset( self::$dependencies[ $plugin_file ] ) ) { + return self::$dependencies[ $plugin_file ]; + } + + return array(); + } + + /** + * Gets a dependent plugin's filepath. + * + * @since 6.5.0 + * + * @param string $slug The dependent plugin's slug. + * @return string|false The dependent plugin's filepath, relative to the plugins directory, + * or false if the plugin has no dependencies. + */ + public static function get_dependent_filepath( $slug ) { + $filepath = array_search( $slug, self::$dependent_slugs, true ); + + return $filepath ? $filepath : false; + } + + /** + * Determines whether the plugin has unmet dependencies. + * + * @since 6.5.0 + * + * @param string $plugin_file The plugin's filepath, relative to the plugins directory. + * @return bool Whether the plugin has unmet dependencies. + */ + public static function has_unmet_dependencies( $plugin_file ) { + if ( ! isset( self::$dependencies[ $plugin_file ] ) ) { + return false; + } + + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + + foreach ( self::$dependencies[ $plugin_file ] as $dependency ) { + $dependency_filepath = self::get_dependency_filepath( $dependency ); + + if ( false === $dependency_filepath || is_plugin_inactive( $dependency_filepath ) ) { + return true; + } + } + + return false; + } + + /** + * Determines whether the plugin has a circular dependency. + * + * @since 6.5.0 + * + * @param string $plugin_file The plugin's filepath, relative to the plugins directory. + * @return bool Whether the plugin has a circular dependency. + */ + public static function has_circular_dependency( $plugin_file ) { + if ( ! is_array( self::$circular_dependencies_slugs ) ) { + self::get_circular_dependencies(); + } + + if ( ! empty( self::$circular_dependencies_slugs ) ) { + $slug = self::convert_to_slug( $plugin_file ); + + if ( in_array( $slug, self::$circular_dependencies_slugs, true ) ) { + return true; + } + } + + return false; + } + + /** + * Gets the names of plugins that require the plugin. + * + * @since 6.5.0 + * + * @param string $plugin_file The plugin's filepath, relative to the plugins directory. + * @return array An array of dependent names. + */ + public static function get_dependent_names( $plugin_file ) { + $dependent_names = array(); + $plugins = self::get_plugins(); + $slug = self::convert_to_slug( $plugin_file ); + + foreach ( self::get_dependents( $slug ) as $dependent ) { + $dependent_names[ $dependent ] = $plugins[ $dependent ]['Name']; + } + sort( $dependent_names ); + + return $dependent_names; + } + + /** + * Gets the names of plugins required by the plugin. + * + * @since 6.5.0 + * + * @param string $plugin_file The dependent plugin's filepath, relative to the plugins directory. + * @return array An array of dependency names. + */ + public static function get_dependency_names( $plugin_file ) { + $dependency_api_data = self::get_dependency_api_data(); + $dependencies = self::get_dependencies( $plugin_file ); + $plugins = self::get_plugins(); + + $dependency_names = array(); + foreach ( $dependencies as $dependency ) { + // Use the name if it's available, otherwise fall back to the slug. + if ( isset( $dependency_api_data[ $dependency ]['name'] ) ) { + $name = $dependency_api_data[ $dependency ]['name']; + } else { + $dependency_filepath = self::get_dependency_filepath( $dependency ); + if ( false !== $dependency_filepath ) { + $name = $plugins[ $dependency_filepath ]['Name']; + } else { + $name = $dependency; + } + } + + $dependency_names[ $dependency ] = $name; + } + + return $dependency_names; + } + + /** + * Gets the filepath for a dependency, relative to the plugin's directory. + * + * @since 6.5.0 + * + * @param string $slug The dependency's slug. + * @return string|false If installed, the dependency's filepath relative to the plugins directory, otherwise false. + */ + public static function get_dependency_filepath( $slug ) { + $dependency_filepaths = self::get_dependency_filepaths(); + + if ( ! isset( $dependency_filepaths[ $slug ] ) ) { + return false; + } + + return $dependency_filepaths[ $slug ]; + } + + /** + * Returns API data for the dependency. + * + * @since 6.5.0 + * + * @param string $slug The dependency's slug. + * @return array|false The dependency's API data on success, otherwise false. + */ + public static function get_dependency_data( $slug ) { + $dependency_api_data = self::get_dependency_api_data(); + + if ( isset( $dependency_api_data[ $slug ] ) ) { + return $dependency_api_data[ $slug ]; + } + + return false; + } + + /** + * Displays an admin notice if dependencies are not installed. + * + * @since 6.5.0 + */ + public static function display_admin_notice_for_unmet_dependencies() { + if ( in_array( false, self::get_dependency_filepaths(), true ) ) { + wp_admin_notice( + __( 'There are additional plugin dependencies that must be installed.' ), + array( + 'type' => 'info', + ) + ); + } + } + + /** + * Displays an admin notice if dependencies have been deactivated. + * + * @since 6.5.0 + */ + public static function display_admin_notice_for_deactivated_dependents() { + /* + * Plugin deactivated if dependencies not met. + * Transient on a 10 second timeout. + */ + $deactivate_requires = get_site_transient( 'wp_plugin_dependencies_deactivated_plugins' ); + if ( ! empty( $deactivate_requires ) ) { + $deactivated_plugins = ''; + foreach ( $deactivate_requires as $deactivated ) { + $deactivated_plugins .= '
  • ' . esc_html( self::$plugins[ $deactivated ]['Name'] ) . '
  • '; + } + wp_admin_notice( + sprintf( + /* translators: 1: plugin names */ + __( 'The following plugin(s) have been deactivated due to uninstalled or inactive dependencies: %s' ), + "
      $deactivated_plugins
    " + ), + array( + 'type' => 'error', + 'dismissible' => true, + ) + ); + } + } + + /** + * Displays an admin notice if circular dependencies are installed. + * + * @since 6.5.0 + */ + public static function display_admin_notice_for_circular_dependencies() { + $circular_dependencies = self::get_circular_dependencies(); + if ( ! empty( $circular_dependencies ) && count( $circular_dependencies ) > 1 ) { + $circular_dependencies = array_unique( $circular_dependencies, SORT_REGULAR ); + $plugins = self::get_plugins(); + $plugin_dirnames = self::get_plugin_dirnames(); + + // Build output lines. + $circular_dependency_lines = ''; + foreach ( $circular_dependencies as $circular_dependency ) { + $first_filepath = $plugin_dirnames[ $circular_dependency[0] ]; + $second_filepath = $plugin_dirnames[ $circular_dependency[1] ]; + $circular_dependency_lines .= sprintf( + /* translators: 1: First plugin name, 2: Second plugin name. */ + '
  • ' . _x( '%1$s requires %2$s', 'The first plugin requires the second plugin.' ) . '
  • ', + '' . esc_html( $plugins[ $first_filepath ]['Name'] ) . '', + '' . esc_html( $plugins[ $second_filepath ]['Name'] ) . '' + ); + } + + wp_admin_notice( + sprintf( + '

    %1$s

      %2$s

    %3$s

    ', + __( 'These plugins cannot be activated because their requirements are invalid. ' ), + $circular_dependency_lines, + __( 'Please contact the plugin authors for more information.' ) + ), + array( + 'type' => 'warning', + 'paragraph_wrap' => false, + ) + ); + } + } + + /** + * Checks plugin dependencies after a plugin is installed via AJAX. + * + * @since 6.5.0 + */ + public static function check_plugin_dependencies_during_ajax() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['slug'] ) ) { + wp_send_json_error( + array( + 'slug' => '', + 'pluginName' => '', + 'errorCode' => 'no_plugin_specified', + 'errorMessage' => __( 'No plugin specified.' ), + ) + ); + } + + $slug = sanitize_key( wp_unslash( $_POST['slug'] ) ); + $status = array( 'slug' => $slug ); + + self::get_plugins(); + self::get_plugin_dirnames(); + + if ( ! isset( self::$plugin_dirnames[ $slug ] ) ) { + $status['errorCode'] = 'plugin_not_installed'; + $status['errorMessage'] = __( 'The plugin is not installed.' ); + wp_send_json_error( $status ); + } + + $plugin_file = self::$plugin_dirnames[ $slug ]; + $status['pluginName'] = self::$plugins[ $plugin_file ]['Name']; + $status['plugin'] = $plugin_file; + + if ( current_user_can( 'activate_plugin', $plugin_file ) && is_plugin_inactive( $plugin_file ) ) { + $status['activateUrl'] = add_query_arg( + array( + '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ), + 'action' => 'activate', + 'plugin' => $plugin_file, + ), + is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ) + ); + } + + if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { + $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] ); + } + + $dependencies = self::get_dependencies( $plugin_file ); + if ( empty( $dependencies ) ) { + $status['message'] = __( 'The plugin has no required plugins.' ); + wp_send_json_success( $status ); + } + + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + + $inactive_dependencies = array(); + foreach ( $dependencies as $dependency ) { + if ( false === self::$plugin_dirnames[ $dependency ] || is_plugin_inactive( self::$plugin_dirnames[ $dependency ] ) ) { + $inactive_dependencies[] = $dependency; + } + } + + if ( ! empty( $inactive_dependencies ) ) { + $inactive_dependency_names = array_map( + function ( $dependency ) { + if ( isset( self::$dependency_api_data[ $dependency ]['Name'] ) ) { + $inactive_dependency_name = self::$dependency_api_data[ $dependency ]['Name']; + } else { + $inactive_dependency_name = $dependency; + } + return $inactive_dependency_name; + }, + $inactive_dependencies + ); + + $status['errorCode'] = 'inactive_dependencies'; + $status['errorMessage'] = sprintf( + /* translators: %s: A list of inactive dependency plugin names. */ + __( 'The following plugins must be activated first: %s.' ), + implode( ', ', $inactive_dependency_names ) + ); + $status['errorData'] = array_combine( $inactive_dependencies, $inactive_dependency_names ); + + wp_send_json_error( $status ); + } + + $status['message'] = __( 'All required plugins are installed and activated.' ); + wp_send_json_success( $status ); + } + + /** + * Gets data for installed plugins. + * + * @since 6.5.0 + * + * @return array An array of plugin data. + */ + protected static function get_plugins() { + if ( is_array( self::$plugins ) ) { + return self::$plugins; + } + + $all_plugin_data = get_option( 'plugin_data', array() ); + + if ( empty( $all_plugin_data ) ) { + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + $all_plugin_data = get_plugins(); + } + + self::$plugins = $all_plugin_data; + + return self::$plugins; + } + + /** + * Reads and stores dependency slugs from a plugin's 'Requires Plugins' header. + * + * @since 6.5.0 + * + * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. + */ + protected static function read_dependencies_from_plugin_headers() { + self::$dependencies = array(); + self::$dependency_slugs = array(); + self::$dependent_slugs = array(); + $plugins = self::get_plugins(); + foreach ( $plugins as $plugin => $header ) { + if ( '' === $header['RequiresPlugins'] ) { + continue; + } + + $dependency_slugs = self::sanitize_dependency_slugs( $header['RequiresPlugins'] ); + self::$dependencies[ $plugin ] = $dependency_slugs; + self::$dependency_slugs = array_merge( self::$dependency_slugs, $dependency_slugs ); + + $dependent_slug = self::convert_to_slug( $plugin ); + self::$dependent_slugs[ $plugin ] = $dependent_slug; + } + self::$dependency_slugs = array_unique( self::$dependency_slugs ); + } + + /** + * Sanitizes slugs. + * + * @since 6.5.0 + * + * @param string $slugs A comma-separated string of plugin dependency slugs. + * @return array An array of sanitized plugin dependency slugs. + */ + protected static function sanitize_dependency_slugs( $slugs ) { + $sanitized_slugs = array(); + $slugs = explode( ',', $slugs ); + + foreach ( $slugs as $slug ) { + $slug = trim( $slug ); + + /** + * Filters a plugin dependency's slug before matching to + * the WordPress.org slug format. + * + * Can be used to switch between free and premium plugin slugs, for example. + * + * @since 6.5.0 + * + * @param string $slug The slug. + */ + $slug = apply_filters( 'wp_plugin_dependencies_slug', $slug ); + + // Match to WordPress.org slug format. + if ( preg_match( '/^[a-z0-9]+(-[a-z0-9]+)*$/mu', $slug ) ) { + $sanitized_slugs[] = $slug; + } + } + $sanitized_slugs = array_unique( $sanitized_slugs ); + sort( $sanitized_slugs ); + + return $sanitized_slugs; + } + + /** + * Gets plugin filepaths for active plugins that depend on the dependency. + * + * Recurses for each dependent that is also a dependency. + * + * @param string $plugin_file The dependency's filepath, relative to the plugin directory. + * @return string[] An array of active dependent plugin filepaths, relative to the plugin directory. + */ + protected static function get_active_dependents_in_dependency_tree( $plugin_file ) { + $all_dependents = array(); + $dependents = self::get_dependents( self::convert_to_slug( $plugin_file ) ); + + if ( empty( $dependents ) ) { + return $all_dependents; + } + + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + foreach ( $dependents as $dependent ) { + if ( is_plugin_active( $dependent ) ) { + $all_dependents[] = $dependent; + $all_dependents = array_merge( + $all_dependents, + self::get_active_dependents_in_dependency_tree( $dependent ) + ); + } + } + + return $all_dependents; + } + + /** + * Deactivates dependent plugins with unmet dependencies. + * + * @since 6.5.0 + */ + protected static function deactivate_dependents_with_unmet_dependencies() { + $dependents_to_deactivate = array(); + $circular_dependencies = array_reduce( + self::get_circular_dependencies(), + function ( $all_circular, $circular_pair ) { + return array_merge( $all_circular, $circular_pair ); + }, + array() + ); + + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + foreach ( self::$dependencies as $dependent => $dependencies ) { + // Skip dependents that are no longer installed or aren't active. + if ( ! array_key_exists( $dependent, self::$plugins ) || is_plugin_inactive( $dependent ) ) { + continue; + } + + // Skip plugins within a circular dependency tree or plugins that have no unmet dependencies. + if ( in_array( $dependent, $circular_dependencies, true ) || ! self::has_unmet_dependencies( $dependent ) ) { + continue; + } + + $dependents_to_deactivate[] = $dependent; + + // Also add any plugins that rely on any of this plugin's dependents. + $dependents_to_deactivate = array_merge( + $dependents_to_deactivate, + self::get_active_dependents_in_dependency_tree( $dependent ) + ); + } + + $dependents_to_deactivate = array_unique( $dependents_to_deactivate ); + + deactivate_plugins( $dependents_to_deactivate ); + set_site_transient( 'wp_plugin_dependencies_deactivated_plugins', $dependents_to_deactivate, 10 ); + } + + /** + * Gets the filepath of installed dependencies. + * If a dependency is not installed, the filepath defaults to false. + * + * @since 6.5.0 + * + * @return array An array of install dependencies filepaths, relative to the plugins directory. + */ + protected static function get_dependency_filepaths() { + if ( is_array( self::$dependency_filepaths ) ) { + return self::$dependency_filepaths; + } + + self::$dependency_filepaths = array(); + + $plugin_dirnames = self::get_plugin_dirnames(); + foreach ( self::$dependency_slugs as $slug ) { + if ( isset( $plugin_dirnames[ $slug ] ) ) { + self::$dependency_filepaths[ $slug ] = $plugin_dirnames[ $slug ]; + continue; + } + + self::$dependency_filepaths[ $slug ] = false; + } + + return self::$dependency_filepaths; + } + + /** + * Retrieves and stores dependency plugin data from the WordPress.org Plugin API. + * + * @since 6.5.0 + * + * @global string $pagenow The filename of the current screen. + * + * @return array|void An array of dependency API data, or void on early exit. + */ + protected static function get_dependency_api_data() { + global $pagenow; + + if ( ! is_admin() || ( 'plugins.php' !== $pagenow && 'plugin-install.php' !== $pagenow ) ) { + return; + } + + if ( is_array( self::$dependency_api_data ) ) { + return self::$dependency_api_data; + } + + $plugins = self::get_plugins(); + self::$dependency_api_data = (array) get_site_transient( 'wp_plugin_dependencies_plugin_data' ); + foreach ( self::$dependency_slugs as $slug ) { + // Set transient for individual data, remove from self::$dependency_api_data if transient expired. + if ( ! get_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}" ) ) { + unset( self::$dependency_api_data[ $slug ] ); + set_site_transient( "wp_plugin_dependencies_plugin_timeout_{$slug}", true, 12 * HOUR_IN_SECONDS ); + } + + if ( isset( self::$dependency_api_data[ $slug ] ) ) { + if ( false === self::$dependency_api_data[ $slug ] ) { + $dependency_file = self::get_dependency_filepath( $slug ); + + if ( false === $dependency_file ) { + self::$dependency_api_data[ $slug ] = array( 'Name' => $slug ); + } else { + self::$dependency_api_data[ $slug ] = array( 'Name' => $plugins[ $dependency_file ]['Name'] ); + } + continue; + } + + // Don't hit the Plugin API if data exists. + if ( ! empty( self::$dependency_api_data[ $slug ]['last_updated'] ) ) { + continue; + } + } + + if ( ! function_exists( 'plugins_api' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + } + + $information = plugins_api( + 'plugin_information', + array( + 'slug' => $slug, + 'fields' => array( + 'short_description' => true, + 'icons' => true, + ), + ) + ); + + if ( is_wp_error( $information ) ) { + continue; + } + + self::$dependency_api_data[ $slug ] = (array) $information; + // plugins_api() returns 'name' not 'Name'. + self::$dependency_api_data[ $information->slug ]['Name'] = self::$dependency_api_data[ $information->slug ]['name']; + set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); + } + + // Remove from self::$dependency_api_data if slug no longer a dependency. + $differences = array_diff( array_keys( self::$dependency_api_data ), self::$dependency_slugs ); + foreach ( $differences as $difference ) { + unset( self::$dependency_api_data[ $difference ] ); + } + + ksort( self::$dependency_api_data ); + // Remove empty elements. + self::$dependency_api_data = array_filter( self::$dependency_api_data ); + set_site_transient( 'wp_plugin_dependencies_plugin_data', self::$dependency_api_data, 0 ); + + return self::$dependency_api_data; + } + + /** + * Gets plugin directory names. + * + * @since 6.5.0 + * + * @return array An array of plugin directory names. + */ + protected static function get_plugin_dirnames() { + if ( is_array( self::$plugin_dirnames ) ) { + return self::$plugin_dirnames; + } + + self::$plugin_dirnames = array(); + + $plugin_files = array_keys( self::get_plugins() ); + foreach ( $plugin_files as $plugin_file ) { + $slug = self::convert_to_slug( $plugin_file ); + self::$plugin_dirnames[ $slug ] = $plugin_file; + } + + return self::$plugin_dirnames; + } + + /** + * Gets circular dependency data. + * + * @since 6.5.0 + * + * @return array[] An array of circular dependency pairings. + */ + protected static function get_circular_dependencies() { + if ( is_array( self::$circular_dependencies_pairs ) ) { + return self::$circular_dependencies_pairs; + } + + self::$circular_dependencies_slugs = array(); + + self::$circular_dependencies_pairs = array(); + foreach ( self::$dependencies as $dependent => $dependencies ) { + /* + * $dependent is in 'a/a.php' format. Dependencies are stored as slugs, i.e. 'a'. + * + * Convert $dependent to slug format for checking. + */ + $dependent_slug = self::convert_to_slug( $dependent ); + + self::$circular_dependencies_pairs = array_merge( + self::$circular_dependencies_pairs, + self::check_for_circular_dependencies( array( $dependent_slug ), $dependencies ) + ); + } + + return self::$circular_dependencies_pairs; + } + + /** + * Checks for circular dependencies. + * + * @since 6.5.0 + * + * @param array $dependents Array of dependent plugins. + * @param array $dependencies Array of plugins dependencies. + * @return array A circular dependency pairing, or an empty array if none exists. + */ + protected static function check_for_circular_dependencies( $dependents, $dependencies ) { + $circular_dependencies_pairs = array(); + + // Check for a self-dependency. + $dependents_location_in_its_own_dependencies = array_intersect( $dependents, $dependencies ); + if ( ! empty( $dependents_location_in_its_own_dependencies ) ) { + foreach ( $dependents_location_in_its_own_dependencies as $self_dependency ) { + self::$circular_dependencies_slugs[] = $self_dependency; + $circular_dependencies_pairs[] = array( $self_dependency, $self_dependency ); + + // No need to check for itself again. + unset( $dependencies[ array_search( $self_dependency, $dependencies, true ) ] ); + } + } + + /* + * Check each dependency to see: + * 1. If it has dependencies. + * 2. If its list of dependencies includes one of its own dependents. + */ + foreach ( $dependencies as $dependency ) { + // Check if the dependency is also a dependent. + $dependency_location_in_dependents = array_search( $dependency, self::$dependent_slugs, true ); + + if ( false !== $dependency_location_in_dependents ) { + $dependencies_of_the_dependency = self::$dependencies[ $dependency_location_in_dependents ]; + + foreach ( $dependents as $dependent ) { + // Check if its dependencies includes one of its own dependents. + $dependent_location_in_dependency_dependencies = array_search( + $dependent, + $dependencies_of_the_dependency, + true + ); + + if ( false !== $dependent_location_in_dependency_dependencies ) { + self::$circular_dependencies_slugs[] = $dependent; + self::$circular_dependencies_slugs[] = $dependency; + $circular_dependencies_pairs[] = array( $dependent, $dependency ); + + // Remove the dependent from its dependency's dependencies. + unset( $dependencies_of_the_dependency[ $dependent_location_in_dependency_dependencies ] ); + } + } + + $dependents[] = $dependency; + + /* + * Now check the dependencies of the dependency's dependencies for the dependent. + * + * Yes, that does make sense. + */ + $circular_dependencies_pairs = array_merge( + $circular_dependencies_pairs, + self::check_for_circular_dependencies( $dependents, array_unique( $dependencies_of_the_dependency ) ) + ); + } + } + + return $circular_dependencies_pairs; + } + + /** + * Converts a plugin filepath to a slug. + * + * @since 6.5.0 + * + * @param string $plugin_file The plugin's filepath, relative to the plugins directory. + * @return string The plugin's slug. + */ + protected static function convert_to_slug( $plugin_file ) { + if ( 'hello.php' === $plugin_file ) { + return 'hello-dolly'; + } + return str_contains( $plugin_file, '/' ) ? dirname( $plugin_file ) : str_replace( '.php', '', $plugin_file ); + } +} diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index e6a3e6b743450..f683a52bd1080 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -1027,7 +1027,7 @@ public function parse_query( $query = '' ) { } if ( ! ( $this->is_singular || $this->is_archive || $this->is_search || $this->is_feed - || ( defined( 'REST_REQUEST' ) && REST_REQUEST && $this->is_main_query() ) + || ( wp_is_serving_rest_request() && $this->is_main_query() ) || $this->is_trackback || $this->is_404 || $this->is_admin || $this->is_robots || $this->is_favicon ) ) { $this->is_home = true; } diff --git a/src/wp-includes/class-wp-script-modules.php b/src/wp-includes/class-wp-script-modules.php index d097785a7d244..b2413d0f2442e 100644 --- a/src/wp-includes/class-wp-script-modules.php +++ b/src/wp-includes/class-wp-script-modules.php @@ -1,6 +1,6 @@ */ private $enqueued_before_registered = array(); /** - * Registers the module if no module with that module identifier has already - * been registered. + * Registers the script module if no script module with that script module + * identifier has already been registered. * * @since 6.5.0 * - * @param string $module_id The identifier of the module. - * Should be unique. It will be used - * in the final import map. - * @param string $src Full URL of the module, or path of - * the module relative to the - * WordPress root directory. - * @param array $deps Optional. An array of module - * identifiers of the dependencies of - * this module. The dependencies can - * be strings or arrays. If they are - * arrays, they need an `id` key with - * the module identifier, and can - * contain an `import` key with either - * `static` or `dynamic`. By default, - * dependencies that don't contain an - * `import` key are considered static. - * @param string|false|null $version Optional. String specifying the - * module version number. Defaults to - * false. It is added to the URL as a - * query string for cache busting - * purposes. If $version is set to - * false, the version number is the - * currently installed WordPress - * version. If $version is set to - * null, no version is added. + * @param string $id The identifier of the script module. Should be unique. It will be used in the + * final import map. + * @param string $src Optional. Full URL of the script module, or path of the script module relative + * to the WordPress root directory. If it is provided and the script module has + * not been registered yet, it will be registered. + * @param array $deps { + * Optional. List of dependencies. + * + * @type string|array $0... { + * An array of script module identifiers of the dependencies of this script + * module. The dependencies can be strings or arrays. If they are arrays, + * they need an `id` key with the script module identifier, and can contain + * an `import` key with either `static` or `dynamic`. By default, + * dependencies that don't contain an `import` key are considered static. + * + * @type string $id The script module identifier. + * @type string $import Optional. Import type. May be either `static` or + * `dynamic`. Defaults to `static`. + * } + * } + * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. + * It is added to the URL as a query string for cache busting purposes. If $version + * is set to false, the version number is the currently installed WordPress version. + * If $version is set to null, no version is added. */ - public function register( $module_id, $src, $deps = array(), $version = false ) { - if ( ! isset( $this->registered[ $module_id ] ) ) { + public function register( string $id, string $src, array $deps = array(), $version = false ) { + if ( ! isset( $this->registered[ $id ] ) ) { $dependencies = array(); foreach ( $deps as $dependency ) { if ( is_array( $dependency ) ) { @@ -85,155 +84,124 @@ public function register( $module_id, $src, $deps = array(), $version = false ) } } - $this->registered[ $module_id ] = array( + $this->registered[ $id ] = array( 'src' => $src, 'version' => $version, - 'enqueue' => isset( $this->enqueued_before_registered[ $module_id ] ), + 'enqueue' => isset( $this->enqueued_before_registered[ $id ] ), 'dependencies' => $dependencies, - 'enqueued' => false, - 'preloaded' => false, ); } } /** - * Marks the module to be enqueued in the page the next time - * `prints_enqueued_modules` is called. + * Marks the script module to be enqueued in the page. * - * If a src is provided and the module has not been registered yet, it will be - * registered. + * If a src is provided and the script module has not been registered yet, it + * will be registered. * * @since 6.5.0 * - * @param string $module_id The identifier of the module. - * Should be unique. It will be used - * in the final import map. - * @param string $src Optional. Full URL of the module, - * or path of the module relative to - * the WordPress root directory. If - * it is provided and the module has - * not been registered yet, it will be - * registered. - * @param array $deps Optional. An array of module - * identifiers of the dependencies of - * this module. The dependencies can - * be strings or arrays. If they are - * arrays, they need an `id` key with - * the module identifier, and can - * contain an `import` key with either - * `static` or `dynamic`. By default, - * dependencies that don't contain an - * `import` key are considered static. - * @param string|false|null $version Optional. String specifying the - * module version number. Defaults to - * false. It is added to the URL as a - * query string for cache busting - * purposes. If $version is set to - * false, the version number is the - * currently installed WordPress - * version. If $version is set to - * null, no version is added. + * @param string $id The identifier of the script module. Should be unique. It will be used in the + * final import map. + * @param string $src Optional. Full URL of the script module, or path of the script module relative + * to the WordPress root directory. If it is provided and the script module has + * not been registered yet, it will be registered. + * @param array $deps { + * Optional. List of dependencies. + * + * @type string|array $0... { + * An array of script module identifiers of the dependencies of this script + * module. The dependencies can be strings or arrays. If they are arrays, + * they need an `id` key with the script module identifier, and can contain + * an `import` key with either `static` or `dynamic`. By default, + * dependencies that don't contain an `import` key are considered static. + * + * @type string $id The script module identifier. + * @type string $import Optional. Import type. May be either `static` or + * `dynamic`. Defaults to `static`. + * } + * } + * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. + * It is added to the URL as a query string for cache busting purposes. If $version + * is set to false, the version number is the currently installed WordPress version. + * If $version is set to null, no version is added. */ - public function enqueue( $module_id, $src = '', $deps = array(), $version = false ) { - if ( isset( $this->registered[ $module_id ] ) ) { - $this->registered[ $module_id ]['enqueue'] = true; + public function enqueue( string $id, string $src = '', array $deps = array(), $version = false ) { + if ( isset( $this->registered[ $id ] ) ) { + $this->registered[ $id ]['enqueue'] = true; } elseif ( $src ) { - $this->register( $module_id, $src, $deps, $version ); - $this->registered[ $module_id ]['enqueue'] = true; + $this->register( $id, $src, $deps, $version ); + $this->registered[ $id ]['enqueue'] = true; } else { - $this->enqueued_before_registered[ $module_id ] = true; + $this->enqueued_before_registered[ $id ] = true; } } /** - * Unmarks the module so it will no longer be enqueued in the page. + * Unmarks the script module so it will no longer be enqueued in the page. * * @since 6.5.0 * - * @param string $module_id The identifier of the module. + * @param string $id The identifier of the script module. */ - public function dequeue( $module_id ) { - if ( isset( $this->registered[ $module_id ] ) ) { - $this->registered[ $module_id ]['enqueue'] = false; + public function dequeue( string $id ) { + if ( isset( $this->registered[ $id ] ) ) { + $this->registered[ $id ]['enqueue'] = false; } - unset( $this->enqueued_before_registered[ $module_id ] ); + unset( $this->enqueued_before_registered[ $id ] ); } /** - * Adds the hooks to print the import map, enqueued modules and module - * preloads. - * - * It adds the actions to print the enqueued modules and module preloads to - * both `wp_head` and `wp_footer` because in classic themes, the modules - * used by the theme and plugins will likely be able to be printed in the - * `head`, but the ones used by the blocks will need to be enqueued in the - * `footer`. + * Adds the hooks to print the import map, enqueued script modules and script + * module preloads. * - * As all modules are deferred and dependencies are handled by the browser, - * the order of the modules is not important, but it's still better to print - * the ones that are available when the `wp_head` is rendered, so the browser - * starts downloading those as soon as possible. - * - * The import map is also printed in the footer to be able to include the - * dependencies of all the modules, including the ones printed in the footer. + * In classic themes, the script modules used by the blocks are not yet known + * when the `wp_head` actions is fired, so it needs to print everything in the + * footer. * * @since 6.5.0 */ public function add_hooks() { - add_action( 'wp_head', array( $this, 'print_enqueued_modules' ) ); - add_action( 'wp_head', array( $this, 'print_module_preloads' ) ); - add_action( 'wp_footer', array( $this, 'print_enqueued_modules' ) ); - add_action( 'wp_footer', array( $this, 'print_module_preloads' ) ); - add_action( 'wp_footer', array( $this, 'print_import_map' ) ); + $position = wp_is_block_theme() ? 'wp_head' : 'wp_footer'; + add_action( $position, array( $this, 'print_import_map' ) ); + add_action( $position, array( $this, 'print_enqueued_script_modules' ) ); + add_action( $position, array( $this, 'print_script_module_preloads' ) ); } /** - * Prints the enqueued modules using script tags with type="module" + * Prints the enqueued script modules using script tags with type="module" * attributes. * - * If a enqueued module has already been printed, it will not be printed again - * on subsequent calls to this function. - * * @since 6.5.0 */ - public function print_enqueued_modules() { - foreach ( $this->get_marked_for_enqueue() as $module_id => $module ) { - if ( false === $module['enqueued'] ) { - // Mark it as enqueued so it doesn't get enqueued again. - $this->registered[ $module_id ]['enqueued'] = true; - - wp_print_script_tag( - array( - 'type' => 'module', - 'src' => $this->get_versioned_src( $module ), - 'id' => $module_id . '-js-module', - ) - ); - } + public function print_enqueued_script_modules() { + foreach ( $this->get_marked_for_enqueue() as $id => $script_module ) { + wp_print_script_tag( + array( + 'type' => 'module', + 'src' => $this->get_versioned_src( $script_module ), + 'id' => $id . '-js-module', + ) + ); } } /** - * Prints the the static dependencies of the enqueued modules using link tags - * with rel="modulepreload" attributes. + * Prints the the static dependencies of the enqueued script modules using + * link tags with rel="modulepreload" attributes. * - * If a module is marked for enqueue, it will not be preloaded. If a preloaded - * module has already been printed, it will not be printed again on subsequent - * calls to this function. + * If a script module is marked for enqueue, it will not be preloaded. * * @since 6.5.0 */ - public function print_module_preloads() { - foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $module_id => $module ) { - // Don't preload if it's marked for enqueue or has already been preloaded. - if ( true !== $module['enqueue'] && false === $module['preloaded'] ) { - // Mark it as preloaded so it doesn't get preloaded again. - $this->registered[ $module_id ]['preloaded'] = true; - + public function print_script_module_preloads() { + foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $id => $script_module ) { + // Don't preload if it's marked for enqueue. + if ( true !== $script_module['enqueue'] ) { echo sprintf( '', - esc_url( $this->get_versioned_src( $module ) ), - esc_attr( $module_id . '-js-modulepreload' ) + esc_url( $this->get_versioned_src( $script_module ) ), + esc_attr( $id . '-js-modulepreload' ) ); } } @@ -243,10 +211,26 @@ public function print_module_preloads() { * Prints the import map using a script tag with a type="importmap" attribute. * * @since 6.5.0 + * + * @global WP_Scripts $wp_scripts The WP_Scripts object for printing the polyfill. */ public function print_import_map() { $import_map = $this->get_import_map(); if ( ! empty( $import_map['imports'] ) ) { + global $wp_scripts; + if ( isset( $wp_scripts ) ) { + wp_print_inline_script_tag( + wp_get_script_polyfill( + $wp_scripts, + array( + 'HTMLScriptElement.supports && HTMLScriptElement.supports("importmap")' => 'wp-polyfill-importmap', + ) + ), + array( + 'id' => 'wp-load-polyfill-importmap', + ) + ); + } wp_print_inline_script_tag( wp_json_encode( $import_map, JSON_HEX_TAG | JSON_HEX_AMP ), array( @@ -262,37 +246,37 @@ public function print_import_map() { * * @since 6.5.0 * - * @return array Array with an `imports` key mapping to an array of module identifiers and their respective URLs, - * including the version query. + * @return array Array with an `imports` key mapping to an array of script module identifiers and their respective + * URLs, including the version query. */ - private function get_import_map() { + private function get_import_map(): array { $imports = array(); - foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $module_id => $module ) { - $imports[ $module_id ] = $this->get_versioned_src( $module ); + foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $id => $script_module ) { + $imports[ $id ] = $this->get_versioned_src( $script_module ); } return array( 'imports' => $imports ); } /** - * Retrieves the list of modules marked for enqueue. + * Retrieves the list of script modules marked for enqueue. * * @since 6.5.0 * - * @return array Modules marked for enqueue, keyed by module identifier. + * @return array Script modules marked for enqueue, keyed by script module identifier. */ - private function get_marked_for_enqueue() { + private function get_marked_for_enqueue(): array { $enqueued = array(); - foreach ( $this->registered as $module_id => $module ) { - if ( true === $module['enqueue'] ) { - $enqueued[ $module_id ] = $module; + foreach ( $this->registered as $id => $script_module ) { + if ( true === $script_module['enqueue'] ) { + $enqueued[ $id ] = $script_module; } } return $enqueued; } /** - * Retrieves all the dependencies for the given module identifiers, filtered - * by import types. + * Retrieves all the dependencies for the given script module identifiers, + * filtered by import types. * * It will consolidate an array containing a set of unique dependencies based * on the requested import types: 'static', 'dynamic', or both. This method is @@ -300,33 +284,34 @@ private function get_marked_for_enqueue() { * * @since 6.5.0 * - * @param array $module_ids The identifiers of the modules for which to gather dependencies. - * @param array $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. - * Default is both. - * @return array List of dependencies, keyed by module identifier. + + * @param string[] $ids The identifiers of the script modules for which to gather dependencies. + * @param array $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. + * Default is both. + * @return array List of dependencies, keyed by script module identifier. */ - private function get_dependencies( $module_ids, $import_types = array( 'static', 'dynamic' ) ) { + private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ) { return array_reduce( - $module_ids, - function ( $dependency_modules, $module_id ) use ( $import_types ) { + $ids, + function ( $dependency_script_modules, $id ) use ( $import_types ) { $dependencies = array(); - foreach ( $this->registered[ $module_id ]['dependencies'] as $dependency ) { + foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { if ( in_array( $dependency['import'], $import_types, true ) && isset( $this->registered[ $dependency['id'] ] ) && - ! isset( $dependency_modules[ $dependency['id'] ] ) + ! isset( $dependency_script_modules[ $dependency['id'] ] ) ) { $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ]; } } - return array_merge( $dependency_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) ); + return array_merge( $dependency_script_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) ); }, array() ); } /** - * Gets the versioned URL for a module src. + * Gets the versioned URL for a script module src. * * If $version is set to false, the version number is the currently installed * WordPress version. If $version is set to null, no version is added. @@ -334,19 +319,19 @@ function ( $dependency_modules, $module_id ) use ( $import_types ) { * * @since 6.5.0 * - * @param array $module The module. - * @return string The module src with a version if relevant. + * @param array $script_module The script module. + * @return string The script module src with a version if relevant. */ - private function get_versioned_src( array $module ) { + private function get_versioned_src( array $script_module ): string { $args = array(); - if ( false === $module['version'] ) { + if ( false === $script_module['version'] ) { $args['ver'] = get_bloginfo( 'version' ); - } elseif ( null !== $module['version'] ) { - $args['ver'] = $module['version']; + } elseif ( null !== $script_module['version'] ) { + $args['ver'] = $script_module['version']; } if ( $args ) { - return add_query_arg( $args, $module['src'] ); + return add_query_arg( $args, $script_module['src'] ); } - return $module['src']; + return $script_module['src']; } } diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php index 116e98f673bca..7e4bd2732a85f 100644 --- a/src/wp-includes/class-wp-scripts.php +++ b/src/wp-includes/class-wp-scripts.php @@ -524,33 +524,6 @@ public function get_inline_script_data( $handle, $position = 'after' ) { return trim( implode( "\n", $data ), "\n" ); } - /** - * Gets unaliased dependencies. - * - * An alias is a dependency whose src is false. It is used as a way to bundle multiple dependencies in a single - * handle. This in effect flattens an alias dependency tree. - * - * @since 6.3.0 - * - * @param string[] $deps Dependency handles. - * @return string[] Unaliased handles. - */ - private function get_unaliased_deps( array $deps ) { - $flattened = array(); - foreach ( $deps as $dep ) { - if ( ! isset( $this->registered[ $dep ] ) ) { - continue; - } - - if ( $this->registered[ $dep ]->src ) { - $flattened[] = $dep; - } elseif ( $this->registered[ $dep ]->deps ) { - array_push( $flattened, ...$this->get_unaliased_deps( $this->registered[ $dep ]->deps ) ); - } - } - return $flattened; - } - /** * Gets tags for inline scripts registered for a specific handle. * diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php index 8ff8ad0ae23de..b2cbd72fa52bb 100644 --- a/src/wp-includes/class-wp-textdomain-registry.php +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -150,21 +150,21 @@ public function set_custom_path( $domain, $path ) { } /** - * Retrieves .mo files from the specified path. + * Retrieves translation files from the specified path. * * Allows early retrieval through the {@see 'pre_get_mo_files_from_path'} filter to optimize * performance, especially in directories with many files. * * @since 6.5.0 * - * @param string $path The directory path to search for .mo files. - * @return array Array of .mo file paths. + * @param string $path The directory path to search for translation files. + * @return array Array of translation file paths. Can contain .mo and .l10n.php files. */ public function get_language_files_from_path( $path ) { $path = rtrim( $path, '/' ) . '/'; /** - * Filters the .mo files retrieved from a specified path before the actual lookup. + * Filters the translation files retrieved from a specified path before the actual lookup. * * Returning a non-null value from the filter will effectively short-circuit * the MO files lookup, returning that value instead. @@ -174,27 +174,33 @@ public function get_language_files_from_path( $path ) { * * @since 6.5.0 * - * @param null|array $mo_files List of .mo files. Default null. - * @param string $path The path from which .mo files are being fetched. + * @param null|array $files List of translation files. Default null. + * @param string $path The path from which translation files are being fetched. **/ - $mo_files = apply_filters( 'pre_get_language_files_from_path', null, $path ); + $files = apply_filters( 'pre_get_language_files_from_path', null, $path ); - if ( null !== $mo_files ) { - return $mo_files; + if ( null !== $files ) { + return $files; } $cache_key = 'cached_mo_files_' . md5( $path ); - $mo_files = wp_cache_get( $cache_key, 'translations' ); + $files = wp_cache_get( $cache_key, 'translations' ); - if ( false === $mo_files ) { - $mo_files = glob( $path . '*.mo' ); - if ( false === $mo_files ) { - $mo_files = array(); + if ( false === $files ) { + $files = glob( $path . '*.mo' ); + if ( false === $files ) { + $files = array(); } - wp_cache_set( $cache_key, $mo_files, 'translations' ); + + $php_files = glob( $path . '*.l10n.php' ); + if ( is_array( $php_files ) ) { + $files = array_merge( $files, $php_files ); + } + + wp_cache_set( $cache_key, $files, 'translations' ); } - return $mo_files; + return $files; } /** @@ -225,10 +231,13 @@ public function get_language_files_from_path( $path ) { * @type string $version The version of a theme, plugin, or core. * } * } - * @return void */ public function invalidate_mo_files_cache( $upgrader, $hook_extra ) { - if ( 'translation' !== $hook_extra['type'] || array() === $hook_extra['translations'] ) { + if ( + ! isset( $hook_extra['type'] ) || + 'translation' !== $hook_extra['type'] || + array() === $hook_extra['translations'] + ) { return; } @@ -292,17 +301,18 @@ private function get_path_from_lang_dir( $domain, $locale ) { foreach ( $locations as $location ) { $files = $this->get_language_files_from_path( $location ); - $path = "$location/$domain-$locale.mo"; + $mo_path = "$location/$domain-$locale.mo"; + $php_path = "$location/$domain-$locale.l10n.php"; - foreach ( $files as $mo_path ) { + foreach ( $files as $file_path ) { if ( ! in_array( $domain, $this->domains_with_translations, true ) && - str_starts_with( str_replace( "$location/", '', $mo_path ), "$domain-" ) + str_starts_with( str_replace( "$location/", '', $file_path ), "$domain-" ) ) { $this->domains_with_translations[] = $domain; } - if ( $mo_path === $path ) { + if ( $file_path === $mo_path || $file_path === $php_path ) { $found_location = rtrim( $location, '/' ) . '/'; } } diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 37f4d11ce9fb4..6fd954705e85e 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -204,10 +204,12 @@ class WP_Theme_JSON { * @since 6.2.0 Added `outline-*`, and `min-height` properties. * @since 6.3.0 Added `column-count` property. * @since 6.4.0 Added `writing-mode` property. + * @since 6.5.0 Added `aspect-ratio` property. * * @var array */ const PROPERTIES_METADATA = array( + 'aspect-ratio' => array( 'dimensions', 'aspectRatio' ), 'background' => array( 'color', 'gradient' ), 'background-color' => array( 'color', 'background' ), 'border-radius' => array( 'border', 'radius' ), @@ -344,8 +346,8 @@ class WP_Theme_JSON { * @since 6.3.0 Added support for `typography.textColumns`, removed `layout.definitions`. * @since 6.4.0 Added support for `layout.allowEditing`, `background.backgroundImage`, * `typography.writingMode`, `lightbox.enabled` and `lightbox.allowEditing`. - * @since 6.5.0 Added support for `layout.allowCustomContentAndWideSize` and - * `background.backgroundSize`. + * @since 6.5.0 Added support for `layout.allowCustomContentAndWideSize`, + * `background.backgroundSize` and `dimensions.aspectRatio`. * @var array */ const VALID_SETTINGS = array( @@ -380,7 +382,8 @@ class WP_Theme_JSON { ), 'custom' => null, 'dimensions' => array( - 'minHeight' => null, + 'aspectRatio' => null, + 'minHeight' => null, ), 'layout' => array( 'contentSize' => null, @@ -426,6 +429,38 @@ class WP_Theme_JSON { ), ); + /* + * The valid properties for fontFamilies under settings key. + * + * @since 6.5.0 + * + * @var array + */ + const FONT_FAMILY_SCHEMA = array( + array( + 'fontFamily' => null, + 'name' => null, + 'slug' => null, + 'fontFace' => array( + array( + 'ascentOverride' => null, + 'descentOverride' => null, + 'fontDisplay' => null, + 'fontFamily' => null, + 'fontFeatureSettings' => null, + 'fontStyle' => null, + 'fontStretch' => null, + 'fontVariationSettings' => null, + 'fontWeight' => null, + 'lineGapOverride' => null, + 'sizeAdjust' => null, + 'src' => null, + 'unicodeRange' => null, + ), + ), + ), + ); + /** * The valid properties under the styles key. * @@ -438,6 +473,7 @@ class WP_Theme_JSON { * updated `blockGap` to be allowed at any level. * @since 6.2.0 Added `outline`, and `minHeight` properties. * @since 6.3.0 Added support for `typography.textColumns`. + * @since 6.5.0 Added support for `dimensions.aspectRatio`. * * @var array */ @@ -458,7 +494,8 @@ class WP_Theme_JSON { 'text' => null, ), 'dimensions' => array( - 'minHeight' => null, + 'aspectRatio' => null, + 'minHeight' => null, ), 'filter' => array( 'duotone' => null, @@ -551,6 +588,52 @@ class WP_Theme_JSON { 'typography' => 'typography', ); + /** + * Return the input schema at the root and per origin. + * + * @since 6.5.0 + * + * @param array $schema The base schema. + * @return array The schema at the root and per origin. + * + * Example: + * schema_in_root_and_per_origin( + * array( + * 'fontFamily' => null, + * 'slug' => null, + * ) + * ) + * + * Returns: + * array( + * 'fontFamily' => null, + * 'slug' => null, + * 'default' => array( + * 'fontFamily' => null, + * 'slug' => null, + * ), + * 'blocks' => array( + * 'fontFamily' => null, + * 'slug' => null, + * ), + * 'theme' => array( + * 'fontFamily' => null, + * 'slug' => null, + * ), + * 'custom' => array( + * 'fontFamily' => null, + * 'slug' => null, + * ), + * ) + */ + protected static function schema_in_root_and_per_origin( $schema ) { + $schema_in_root_and_per_origin = $schema; + foreach ( static::VALID_ORIGINS as $origin ) { + $schema_in_root_and_per_origin[ $origin ] = $schema; + } + return $schema_in_root_and_per_origin; + } + /** * Returns a class name by an element name. * @@ -575,7 +658,7 @@ public static function get_element_class_name( $element ) { * @since 6.0.0 * @since 6.2.0 Added `dimensions.minHeight` and `position.sticky`. * @since 6.4.0 Added `background.backgroundImage`. - * @since 6.5.0 Added `background.backgroundSize`. + * @since 6.5.0 Added `background.backgroundSize` and `dimensions.aspectRatio`. * @var array */ const APPEARANCE_TOOLS_OPT_INS = array( @@ -589,6 +672,7 @@ public static function get_element_class_name( $element ) { array( 'color', 'heading' ), array( 'color', 'button' ), array( 'color', 'caption' ), + array( 'dimensions', 'aspectRatio' ), array( 'dimensions', 'minHeight' ), array( 'position', 'sticky' ), array( 'spacing', 'blockGap' ), @@ -791,11 +875,12 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; } - $schema['styles'] = static::VALID_STYLES; - $schema['styles']['blocks'] = $schema_styles_blocks; - $schema['styles']['elements'] = $schema_styles_elements; - $schema['settings'] = static::VALID_SETTINGS; - $schema['settings']['blocks'] = $schema_settings_blocks; + $schema['styles'] = static::VALID_STYLES; + $schema['styles']['blocks'] = $schema_styles_blocks; + $schema['styles']['elements'] = $schema_styles_elements; + $schema['settings'] = static::VALID_SETTINGS; + $schema['settings']['blocks'] = $schema_settings_blocks; + $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { @@ -935,7 +1020,7 @@ protected static function get_blocks_metadata() { if ( $duotone_support ) { $root_selector = wp_get_block_css_selector( $block_type ); - $duotone_selector = WP_Theme_JSON::scope_selector( $root_selector, $duotone_support ); + $duotone_selector = static::scope_selector( $root_selector, $duotone_support ); } } @@ -947,8 +1032,7 @@ protected static function get_blocks_metadata() { if ( ! empty( $block_type->styles ) ) { $style_selectors = array(); foreach ( $block_type->styles as $style ) { - // The style variation classname is duplicated in the selector to ensure that it overrides core block styles. - $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'] . '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } @@ -969,18 +1053,39 @@ protected static function get_blocks_metadata() { * @return array The modified $tree. */ protected static function remove_keys_not_in_schema( $tree, $schema ) { - $tree = array_intersect_key( $tree, $schema ); + if ( ! is_array( $tree ) ) { + return $tree; + } - foreach ( $schema as $key => $data ) { - if ( ! isset( $tree[ $key ] ) ) { + foreach ( $tree as $key => $value ) { + // Remove keys not in the schema or with null/empty values. + if ( ! array_key_exists( $key, $schema ) ) { + unset( $tree[ $key ] ); continue; } - if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) { - $tree[ $key ] = static::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); + // Check if the value is an array and requires further processing. + if ( is_array( $value ) && is_array( $schema[ $key ] ) ) { + // Determine if it is an associative or indexed array. + $schema_is_assoc = self::is_assoc( $value ); + + if ( $schema_is_assoc ) { + // If associative, process as a single object. + $tree[ $key ] = self::remove_keys_not_in_schema( $value, $schema[ $key ] ); - if ( empty( $tree[ $key ] ) ) { - unset( $tree[ $key ] ); + if ( empty( $tree[ $key ] ) ) { + unset( $tree[ $key ] ); + } + } else { + // If indexed, process each item in the array. + foreach ( $value as $item_key => $item_value ) { + if ( isset( $schema[ $key ][0] ) && is_array( $schema[ $key ][0] ) ) { + $tree[ $key ][ $item_key ] = self::remove_keys_not_in_schema( $item_value, $schema[ $key ][0] ); + } else { + // If the schema does not define a further structure, keep the value as is. + $tree[ $key ][ $item_key ] = $item_value; + } + } } } elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) { unset( $tree[ $key ] ); @@ -990,6 +1095,20 @@ protected static function remove_keys_not_in_schema( $tree, $schema ) { return $tree; } + /** + * Checks if the given array is associative. + * + * @since 6.5.0 + * @param array $data The array to check. + * @return bool True if the array is associative, false otherwise. + */ + protected static function is_assoc( $data ) { + if ( array() === $data ) { + return false; + } + return array_keys( $data ) !== range( 0, count( $data ) - 1 ); + } + /** * Returns the existing settings for each block. * @@ -1070,6 +1189,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' foreach ( $style_nodes as &$node ) { $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); } + unset( $node ); } if ( ! empty( $options['root_selector'] ) ) { @@ -1077,7 +1197,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $setting_nodes[ $root_settings_key ]['selector'] = $options['root_selector']; } if ( false !== $root_style_key ) { - $setting_nodes[ $root_style_key ]['selector'] = $options['root_selector']; + $style_nodes[ $root_style_key ]['selector'] = $options['root_selector']; } } @@ -1921,6 +2041,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * @since 5.8.0 * @since 5.9.0 Added the `$settings` and `$properties` parameters. * @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. * * @param array $styles Styles to process. * @param array $settings Theme settings. @@ -1992,6 +2113,15 @@ protected static function compute_style_properties( $styles, $settings = array() $value = wp_get_typography_font_size_value( array( 'size' => $value ) ); } + if ( 'aspect-ratio' === $css_property ) { + // For aspect ratio to work, other dimensions rules must be unset. + // This ensures that a fixed height does not override the aspect ratio. + $declarations[] = array( + 'name' => 'min-height', + 'value' => 'unset', + ); + } + $declarations[] = array( 'name' => $css_property, 'value' => $value, @@ -3795,4 +3925,38 @@ function ( $carry, $item ) { $theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars ); return $theme_json; } + + /** + * Generates a selector for a block style variation. + * + * @since 6.5.0 + * + * @param string $variation_name Name of the block style variation. + * @param string $block_selector CSS selector for the block. + * @return string Block selector with block style variation selector added to it. + */ + protected static function get_block_style_variation_selector( $variation_name, $block_selector ) { + $variation_class = ".is-style-$variation_name"; + + if ( ! $block_selector ) { + return $variation_class; + } + + $limit = 1; + $selector_parts = explode( ',', $block_selector ); + $result = array(); + + foreach ( $selector_parts as $part ) { + $result[] = preg_replace_callback( + '/((?::\([^)]+\))?\s*)([^\s:]+)/', + function ( $matches ) use ( $variation_class ) { + return $matches[1] . $matches[2] . $variation_class; + }, + $part, + $limit + ); + } + + return implode( ',', $result ); + } } diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index 09905bee1b93f..2058a9e557c46 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -1263,7 +1263,7 @@ public function get_screenshot( $uri = 'uri' ) { return false; } - foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp' ) as $ext ) { + foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp', 'avif' ) as $ext ) { if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) { $this->cache_add( 'screenshot', 'screenshot.' . $ext ); if ( 'relative' === $uri ) { diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index de23a72130e0f..37d36364ce3da 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -3174,10 +3174,10 @@ function privacy_ping_filter( $sites ) { * @param string $trackback_url URL to send trackbacks. * @param string $title Title of post. * @param string $excerpt Excerpt of post. - * @param int $ID Post ID. + * @param int $post_id Post ID. * @return int|false|void Database query from update. */ -function trackback( $trackback_url, $title, $excerpt, $ID ) { +function trackback( $trackback_url, $title, $excerpt, $post_id ) { global $wpdb; if ( empty( $trackback_url ) ) { @@ -3188,7 +3188,7 @@ function trackback( $trackback_url, $title, $excerpt, $ID ) { $options['timeout'] = 10; $options['body'] = array( 'title' => $title, - 'url' => get_permalink( $ID ), + 'url' => get_permalink( $post_id ), 'blog_name' => get_option( 'blogname' ), 'excerpt' => $excerpt, ); @@ -3199,8 +3199,8 @@ function trackback( $trackback_url, $title, $excerpt, $ID ) { return; } - $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $ID ) ); - return $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $ID ) ); + $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET pinged = CONCAT(pinged, '\n', %s) WHERE ID = %d", $trackback_url, $post_id ) ); + return $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $trackback_url, $post_id ) ); } /** diff --git a/src/wp-includes/compat.php b/src/wp-includes/compat.php index 5bfdbc23d6d60..95c4af484d8ea 100644 --- a/src/wp-includes/compat.php +++ b/src/wp-includes/compat.php @@ -420,6 +420,38 @@ function array_key_last( array $array ) { // phpcs:ignore Universal.NamingConven } } +if ( ! function_exists( 'array_is_list' ) ) { + /** + * Polyfill for `array_is_list()` function added in PHP 8.1. + * + * Determines if the given array is a list. + * + * An array is considered a list if its keys consist of consecutive numbers from 0 to count($array)-1. + * + * @see https://github.com/symfony/polyfill-php81/tree/main + * + * @since 6.5.0 + * + * @param array $arr The array being evaluated. + * @return bool True if array is a list, false otherwise. + */ + function array_is_list( $arr ) { + if ( ( array() === $arr ) || ( array_values( $arr ) === $arr ) ) { + return true; + } + + $next_key = -1; + + foreach ( $arr as $k => $v ) { + if ( ++$next_key !== $k ) { + return false; + } + } + + return true; + } +} + if ( ! function_exists( 'str_contains' ) ) { /** * Polyfill for `str_contains()` function added in PHP 8.0. @@ -497,3 +529,13 @@ function str_ends_with( $haystack, $needle ) { if ( ! defined( 'IMG_WEBP' ) ) { define( 'IMG_WEBP', IMAGETYPE_WEBP ); } + +// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later. +if ( ! defined( 'IMAGETYPE_AVIF' ) ) { + define( 'IMAGETYPE_AVIF', 19 ); +} + +// IMG_AVIF constant is only defined in PHP 8.x or later. +if ( ! defined( 'IMG_AVIF' ) ) { + define( 'IMG_AVIF', IMAGETYPE_AVIF ); +} diff --git a/src/wp-includes/css/buttons.css b/src/wp-includes/css/buttons.css index 787efacab6953..5146be4274254 100644 --- a/src/wp-includes/css/buttons.css +++ b/src/wp-includes/css/buttons.css @@ -185,6 +185,11 @@ TABLE OF CONTENTS: transform: none !important; } +.wp-core-ui .button[aria-disabled="true"], +.wp-core-ui .button-secondary[aria-disabled="true"] { + cursor: default; +} + /* Buttons that look like links, for a cross of good semantics with the visual */ .wp-core-ui .button-link { margin: 0; @@ -210,11 +215,9 @@ TABLE OF CONTENTS: .wp-core-ui .button-link:focus { color: #043959; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .wp-core-ui .button-link-delete { @@ -283,6 +286,10 @@ TABLE OF CONTENTS: cursor: default; } +.wp-core-ui .button-primary[aria-disabled="true"] { + cursor: default; +} + /* ---------------------------------------------------------------------------- 4.0 - Button Groups ---------------------------------------------------------------------------- */ diff --git a/src/wp-includes/css/editor.css b/src/wp-includes/css/editor.css index c445a32b43910..86952c7c9b213 100644 --- a/src/wp-includes/css/editor.css +++ b/src/wp-includes/css/editor.css @@ -87,8 +87,9 @@ .mce-window-head .mce-close:focus .mce-i-remove, div.mce-tab:focus { - box-shadow: 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .mce-window .mce-window-head .mce-dragh { @@ -113,7 +114,9 @@ div.mce-tab:focus { .mce-checkbox:focus i.mce-i-checkbox, #wp-link .query-results:focus { border-color: #4f94d4; - box-shadow: 0 0 2px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .mce-window .mce-wp-help { @@ -409,10 +412,10 @@ div.mce-path { .qt-dfw:hover, .qt-dfw:focus { background: #f6f7f7; - border-color: #50575e; color: #1d2327; - box-shadow: inset 0 1px 0 #fff, 0 1px 0 rgba(0, 0, 0, 0.08); - outline: none; + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .mce-toolbar .mce-btn-group .mce-btn.mce-active, @@ -420,7 +423,6 @@ div.mce-path { .qt-dfw.active { background: #f0f0f1; border-color: #50575e; - box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.3); } .mce-btn.mce-active, @@ -532,12 +534,13 @@ div.mce-path { direction: ltr; background: #fff; border: 1px solid #dcdcde; - box-shadow: inset 0 1px 1px -1px rgba(0, 0, 0, 0.2); } .mce-toolbar .mce-btn-group .mce-btn.mce-listbox:hover, .mce-toolbar .mce-btn-group .mce-btn.mce-listbox:focus { - border-color: #c3c4c7; + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } .mce-panel .mce-btn i.mce-caret { @@ -635,9 +638,9 @@ div.mce-menubar { .mce-menubar .mce-menubtn:focus { color: #043959; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; } div.mce-menu .mce-menu-item-sep, @@ -1131,10 +1134,9 @@ i.mce-i-wp_code:before { } .wp-switch-editor:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); - outline: none; + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; color: #1d2327; } @@ -1445,10 +1447,7 @@ i.mce-i-wp_code:before { } #wp-link-close:focus { - outline: none; - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; outline-offset: -2px; @@ -1845,7 +1844,6 @@ html:lang(he-il) .rtl .quicktags-toolbar input { /* HiDPI */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .wp-media-buttons .add_media span.wp-media-buttons-icon { background: none; diff --git a/src/wp-includes/css/jquery-ui-dialog.css b/src/wp-includes/css/jquery-ui-dialog.css index e457b38ddd33e..528d368251889 100644 --- a/src/wp-includes/css/jquery-ui-dialog.css +++ b/src/wp-includes/css/jquery-ui-dialog.css @@ -312,9 +312,7 @@ } .ui-button.ui-dialog-titlebar-close:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; outline-offset: -2px; diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css index eaa9562fca27f..c681f6df78d77 100644 --- a/src/wp-includes/css/media-views.css +++ b/src/wp-includes/css/media-views.css @@ -45,12 +45,10 @@ } .media-frame a:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; color: #043959; /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .media-frame a.button { @@ -649,12 +647,10 @@ } .media-menu .media-menu-item:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; color: #043959; /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .media-menu .separator { @@ -704,12 +700,10 @@ } .media-router .media-menu-item:focus { - box-shadow: - 0 0 0 1px #4f94d4, - 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; color: #043959; /* Only visible in Windows High Contrast mode */ - outline: 1px solid transparent; + outline: 2px solid transparent; } .media-router .active, @@ -2017,8 +2011,8 @@ .wp-core-ui.media-modal .image-editor .imgedit-help-toggle:focus { color: #2271b1; - border-color: #4f94d4; - box-shadow: 0 0 3px rgba(34, 113, 177, 0.8); + border-color: #2271b1; + box-shadow: 0 0 0 1px #2271b1; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; } @@ -2134,8 +2128,8 @@ } .mejs-container:focus { - outline: 1px solid #4f94d4; - box-shadow: 0 0 2px 1px rgba(79, 148, 212, 0.8); + outline: 1px solid #2271b1; + box-shadow: 0 0 0 2px #2271b1; } .image-details .media-modal { @@ -2905,7 +2899,6 @@ * HiDPI Displays */ @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .wp-core-ui .media-modal-icon { diff --git a/src/wp-includes/css/wp-auth-check.css b/src/wp-includes/css/wp-auth-check.css index 8994f4933ab63..42f623e8e13ec 100644 --- a/src/wp-includes/css/wp-auth-check.css +++ b/src/wp-includes/css/wp-auth-check.css @@ -68,7 +68,6 @@ } @media print, - (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { #wp-auth-check-form.loading:before { diff --git a/src/wp-includes/css/wp-embed-template.css b/src/wp-includes/css/wp-embed-template.css index c1f1cc5c847b9..6b1ca43f0bc61 100644 --- a/src/wp-includes/css/wp-embed-template.css +++ b/src/wp-includes/css/wp-embed-template.css @@ -216,7 +216,9 @@ p.wp-embed-heading { .wp-embed-share-dialog-open:focus .dashicons, .wp-embed-share-dialog-close:focus .dashicons { - box-shadow: 0 0 0 1px #4f94d4, 0 0 2px 1px rgba(79, 148, 212, 0.8); + box-shadow: 0 0 0 2px #2271b1; + /* Only visible in Windows High Contrast mode */ + outline: 2px solid transparent; border-radius: 100%; } diff --git a/src/wp-includes/customize/class-wp-customize-media-control.php b/src/wp-includes/customize/class-wp-customize-media-control.php index f63689292522b..3bdc4e2dc1a7c 100644 --- a/src/wp-includes/customize/class-wp-customize-media-control.php +++ b/src/wp-includes/customize/class-wp-customize-media-control.php @@ -93,7 +93,7 @@ public function to_json() { * Note that the default value must be a URL, NOT an attachment ID. */ $ext = substr( $this->setting->default, -3 ); - $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp' ), true ) ? 'image' : 'document'; + $type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp', 'avif' ), true ) ? 'image' : 'document'; $default_attachment = array( 'id' => 1, diff --git a/src/wp-includes/customize/class-wp-customize-nav-menu-location-control.php b/src/wp-includes/customize/class-wp-customize-nav-menu-location-control.php index 4877ada9e16b7..b0340b1eeed4f 100644 --- a/src/wp-includes/customize/class-wp-customize-nav-menu-location-control.php +++ b/src/wp-includes/customize/class-wp-customize-nav-menu-location-control.php @@ -77,7 +77,7 @@ public function render_content() { diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 9cb447181aefd..2d9f5368d46c8 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -687,6 +687,7 @@ add_action( 'embed_head', 'wp_robots' ); add_action( 'embed_head', 'rel_canonical' ); add_action( 'embed_head', 'locale_stylesheet', 30 ); +add_action( 'enqueue_embed_scripts', 'wp_enqueue_emoji_styles' ); add_action( 'embed_content_meta', 'print_embed_comments_button' ); add_action( 'embed_content_meta', 'print_embed_sharing_button' ); @@ -747,5 +748,8 @@ // Font management. add_action( 'wp_head', 'wp_print_font_faces', 50 ); +add_action( 'deleted_post', '_wp_after_delete_font_family', 10, 2 ); +add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); +add_action( 'init', '_wp_register_default_font_collections' ); unset( $filter, $action ); diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index e63708f91bb50..45b4f89a9e4be 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -3336,7 +3336,9 @@ function gd_edit_image_support($mime_type) { return (imagetypes() & IMG_GIF) != 0; case 'image/webp': return (imagetypes() & IMG_WEBP) != 0; - } + case 'image/avif': + return (imagetypes() & IMG_AVIF) != 0; + } } else { switch( $mime_type ) { case 'image/jpeg': @@ -3347,6 +3349,8 @@ function gd_edit_image_support($mime_type) { return function_exists('imagecreatefromgif'); case 'image/webp': return function_exists('imagecreatefromwebp'); + case 'image/avif': + return function_exists('imagecreatefromavif'); } } return false; @@ -5436,7 +5440,7 @@ function _wp_theme_json_webfonts_handler() { $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings(); // If in the editor, add webfonts defined in variations. - if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + if ( is_admin() || wp_is_rest_endpoint() ) { $variations = WP_Theme_JSON_Resolver::get_style_variations(); foreach ( $variations as $variation ) { // Skip if fontFamilies are not defined in the variation. @@ -6239,3 +6243,60 @@ function the_block_template_skip_link() { registered['wp-block-query-view'] ) && + ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-query-view']->deps, true ) + ) { + $wp_scripts->registered['wp-block-query-view']->deps[] = 'wp-interactivity'; + } +} + +/** + * Ensure that the view script has the `wp-interactivity` dependency. + * + * @since 6.4.0 + * @deprecated 6.5.0 + * + * @global WP_Scripts $wp_scripts + */ +function block_core_file_ensure_interactivity_dependency() { + _deprecated_function( __FUNCTION__, '6.5.0', 'wp_register_script_module' ); + global $wp_scripts; + if ( + isset( $wp_scripts->registered['wp-block-file-view'] ) && + ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-file-view']->deps, true ) + ) { + $wp_scripts->registered['wp-block-file-view']->deps[] = 'wp-interactivity'; + } +} + +/** + * Ensures that the view script has the `wp-interactivity` dependency. + * + * @since 6.4.0 + * @deprecated 6.5.0 + * + * @global WP_Scripts $wp_scripts + */ +function block_core_image_ensure_interactivity_dependency() { + _deprecated_function( __FUNCTION__, '6.5.0', 'wp_register_script_module' ); + global $wp_scripts; + if ( + isset( $wp_scripts->registered['wp-block-image-view'] ) && + ! in_array( 'wp-interactivity', $wp_scripts->registered['wp-block-image-view']->deps, true ) + ) { + $wp_scripts->registered['wp-block-image-view']->deps[] = 'wp-interactivity'; + } +} diff --git a/src/wp-includes/fonts.php b/src/wp-includes/fonts.php index 87503c275f390..dfd0b45857a0e 100644 --- a/src/wp-includes/fonts.php +++ b/src/wp-includes/fonts.php @@ -51,3 +51,150 @@ function wp_print_font_faces( $fonts = array() ) { $wp_font_face = new WP_Font_Face(); $wp_font_face->generate_and_print( $fonts ); } + +/** + * Registers a new Font Collection in the Font Library. + * + * @since 6.5.0 + * + * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, + * and underscores. See sanitize_title(). + * @param array|string $data_or_file { + * Font collection data array or a path/URL to a JSON file containing the font collection. + * + * @link https://schemas.wp.org/trunk/font-collection.json + * + * @type string $name Required. Name of the font collection shown in the Font Library. + * @type string $description Optional. A short descriptive summary of the font collection. Default empty. + * @type array $font_families Required. Array of font family definitions that are in the collection. + * @type array $categories Optional. Array of categories, each with a name and slug, that are used by the + * fonts in the collection. Default empty. + * } + * @return WP_Font_Collection|WP_Error A font collection if it was registered + * successfully, or WP_Error object on failure. + */ +function wp_register_font_collection( $slug, $data_or_file ) { + return WP_Font_Library::get_instance()->register_font_collection( $slug, $data_or_file ); +} + +/** + * Unregisters a font collection from the Font Library. + * + * @since 6.5.0 + * + * @param string $slug Font collection slug. + * @return bool True if the font collection was unregistered successfully, else false. + */ +function wp_unregister_font_collection( $slug ) { + return WP_Font_Library::get_instance()->unregister_font_collection( $slug ); +} + +/** + * Returns an array containing the current fonts upload directory's path and URL. + * + * @since 6.5.0 + * + * @param array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } + * @return array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } + */ +function wp_get_font_dir( $defaults = array() ) { + $site_path = ''; + if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { + $site_path = '/sites/' . get_current_blog_id(); + } + + // Sets the defaults. + $defaults['path'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['url'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['subdir'] = ''; + $defaults['basedir'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['baseurl'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['error'] = false; + + /** + * Filters the fonts directory data. + * + * This filter allows developers to modify the fonts directory data. + * + * @since 6.5.0 + * + * @param array $defaults The original fonts directory data. + */ + return apply_filters( 'font_dir', $defaults ); +} + +/** + * Deletes child font faces when a font family is deleted. + * + * @access private + * @since 6.5.0 + * + * @param int $post_id Post ID. + * @param WP_Post $post Post object. + */ +function _wp_after_delete_font_family( $post_id, $post ) { + if ( 'wp_font_family' !== $post->post_type ) { + return; + } + + $font_faces = get_children( + array( + 'post_parent' => $post_id, + 'post_type' => 'wp_font_face', + ) + ); + + foreach ( $font_faces as $font_face ) { + wp_delete_post( $font_face->ID, true ); + } +} + +/** + * Deletes associated font files when a font face is deleted. + * + * @access private + * @since 6.5.0 + * + * @param int $post_id Post ID. + * @param WP_Post $post Post object. + */ +function _wp_before_delete_font_face( $post_id, $post ) { + if ( 'wp_font_face' !== $post->post_type ) { + return; + } + + $font_files = get_post_meta( $post_id, '_wp_font_face_file', false ); + $font_dir = wp_get_font_dir()['path']; + + foreach ( $font_files as $font_file ) { + wp_delete_file( $font_dir . '/' . $font_file ); + } +} + +/** + * Register the default font collections. + * + * @access private + * @since 6.5.0 + */ +function _wp_register_default_font_collections() { + wp_register_font_collection( 'google-fonts', 'https://s.w.org/images/fonts/17.7/collections/google-fonts-with-preview.json' ); +} diff --git a/src/wp-includes/fonts/class-wp-font-collection.php b/src/wp-includes/fonts/class-wp-font-collection.php new file mode 100644 index 0000000000000..240bba35e94f8 --- /dev/null +++ b/src/wp-includes/fonts/class-wp-font-collection.php @@ -0,0 +1,260 @@ +slug = sanitize_title( $slug ); + if ( $this->slug !== $slug ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Font collection slug. */ + sprintf( __( 'Font collection slug "%s" is not valid. Slugs must use only alphanumeric characters, dashes, and underscores.' ), $slug ), + '6.5.0' + ); + } + + if ( is_array( $data_or_file ) ) { + $this->data = $this->sanitize_and_validate_data( $data_or_file ); + } else { + // JSON data is lazy loaded by ::get_data(). + $this->src = $data_or_file; + } + } + + /** + * Retrieves the font collection data. + * + * @since 6.5.0 + * + * @return array|WP_Error An array containing the font collection data, or a WP_Error on failure. + */ + public function get_data() { + // If the collection uses JSON data, load it and cache the data/error. + if ( $this->src && empty( $this->data ) ) { + $this->data = $this->load_from_json( $this->src ); + } + + if ( is_wp_error( $this->data ) ) { + return $this->data; + } + + // Set defaults for optional properties. + $defaults = array( + 'description' => '', + 'categories' => array(), + ); + + return wp_parse_args( $this->data, $defaults ); + } + + /** + * Loads font collection data from a JSON file or URL. + * + * @since 6.5.0 + * + * @param string $file_or_url File path or URL to a JSON file containing the font collection data. + * @return array|WP_Error An array containing the font collection data on success, + * else an instance of WP_Error on failure. + */ + private function load_from_json( $file_or_url ) { + $url = wp_http_validate_url( $file_or_url ); + $file = file_exists( $file_or_url ) ? wp_normalize_path( realpath( $file_or_url ) ) : false; + + if ( ! $url && ! $file ) { + // translators: %s: File path or URL to font collection JSON file. + $message = __( 'Font collection JSON file is invalid or does not exist.' ); + _doing_it_wrong( __METHOD__, $message, '6.5.0' ); + return new WP_Error( 'font_collection_json_missing', $message ); + } + + return $url ? $this->load_from_url( $url ) : $this->load_from_file( $file ); + } + + /** + * Loads the font collection data from a JSON file path. + * + * @since 6.5.0 + * + * @param string $file File path to a JSON file containing the font collection data. + * @return array|WP_Error An array containing the font collection data on success, + * else an instance of WP_Error on failure. + */ + private function load_from_file( $file ) { + $data = wp_json_file_decode( $file, array( 'associative' => true ) ); + if ( empty( $data ) ) { + return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection JSON file contents.' ) ); + } + + return $this->sanitize_and_validate_data( $data ); + } + + /** + * Loads the font collection data from a JSON file URL. + * + * @since 6.5.0 + * + * @param string $url URL to a JSON file containing the font collection data. + * @return array|WP_Error An array containing the font collection data on success, + * else an instance of WP_Error on failure. + */ + private function load_from_url( $url ) { + // Limit key to 167 characters to avoid failure in the case of a long URL. + $transient_key = substr( 'wp_font_collection_url_' . $url, 0, 167 ); + $data = get_site_transient( $transient_key ); + + if ( false === $data ) { + $response = wp_safe_remote_get( $url ); + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + // translators: %s: Font collection URL. + return new WP_Error( 'font_collection_request_error', sprintf( __( 'Error fetching the font collection data from "%s".' ), $url ) ); + } + + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( empty( $data ) ) { + return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection data from the HTTP response JSON.' ) ); + } + + // Make sure the data is valid before storing it in a transient. + $data = $this->sanitize_and_validate_data( $data ); + if ( is_wp_error( $data ) ) { + return $data; + } + + set_site_transient( $transient_key, $data, DAY_IN_SECONDS ); + } + + return $data; + } + + /** + * Sanitizes and validates the font collection data. + * + * @since 6.5.0 + * + * @param array $data Font collection data to sanitize and validate. + * @return array|WP_Error Sanitized data if valid, otherwise a WP_Error instance. + */ + private function sanitize_and_validate_data( $data ) { + $schema = self::get_sanitization_schema(); + $data = WP_Font_Utils::sanitize_from_schema( $data, $schema ); + + $required_properties = array( 'name', 'font_families' ); + foreach ( $required_properties as $property ) { + if ( empty( $data[ $property ] ) ) { + $message = sprintf( + // translators: 1: Font collection slug, 2: Missing property name, e.g. "font_families". + __( 'Font collection "%1$s" has missing or empty property: "%2$s".' ), + $this->slug, + $property + ); + _doing_it_wrong( __METHOD__, $message, '6.5.0' ); + return new WP_Error( 'font_collection_missing_property', $message ); + } + } + + return $data; + } + + /** + * Retrieves the font collection sanitization schema. + * + * @since 6.5.0 + * + * @return array Font collection sanitization schema. + */ + private static function get_sanitization_schema() { + return array( + 'name' => 'sanitize_text_field', + 'description' => 'sanitize_text_field', + 'font_families' => array( + array( + 'font_family_settings' => array( + 'name' => 'sanitize_text_field', + 'slug' => 'sanitize_title', + 'fontFamily' => 'sanitize_text_field', + 'preview' => 'sanitize_url', + 'fontFace' => array( + array( + 'fontFamily' => 'sanitize_text_field', + 'fontStyle' => 'sanitize_text_field', + 'fontWeight' => 'sanitize_text_field', + 'src' => static function ( $value ) { + return is_array( $value ) + ? array_map( 'sanitize_text_field', $value ) + : sanitize_text_field( $value ); + }, + 'preview' => 'sanitize_url', + 'fontDisplay' => 'sanitize_text_field', + 'fontStretch' => 'sanitize_text_field', + 'ascentOverride' => 'sanitize_text_field', + 'descentOverride' => 'sanitize_text_field', + 'fontVariant' => 'sanitize_text_field', + 'fontFeatureSettings' => 'sanitize_text_field', + 'fontVariationSettings' => 'sanitize_text_field', + 'lineGapOverride' => 'sanitize_text_field', + 'sizeAdjust' => 'sanitize_text_field', + 'unicodeRange' => 'sanitize_text_field', + ), + ), + ), + 'categories' => array( 'sanitize_title' ), + ), + ), + 'categories' => array( + array( + 'name' => 'sanitize_text_field', + 'slug' => 'sanitize_title', + ), + ), + ); + } +} diff --git a/src/wp-includes/fonts/class-wp-font-library.php b/src/wp-includes/fonts/class-wp-font-library.php new file mode 100644 index 0000000000000..f9ca90327121d --- /dev/null +++ b/src/wp-includes/fonts/class-wp-font-library.php @@ -0,0 +1,143 @@ +is_collection_registered( $new_collection->slug ) ) { + $error_message = sprintf( + /* translators: %s: Font collection slug. */ + __( 'Font collection with slug: "%s" is already registered.' ), + $new_collection->slug + ); + _doing_it_wrong( + __METHOD__, + $error_message, + '6.5.0' + ); + return new WP_Error( 'font_collection_registration_error', $error_message ); + } + $this->collections[ $new_collection->slug ] = $new_collection; + return $new_collection; + } + + /** + * Unregisters a previously registered font collection. + * + * @since 6.5.0 + * + * @param string $slug Font collection slug. + * @return bool True if the font collection was unregistered successfully and false otherwise. + */ + public function unregister_font_collection( $slug ) { + if ( ! $this->is_collection_registered( $slug ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Font collection slug. */ + sprintf( __( 'Font collection "%s" not found.' ), $slug ), + '6.5.0' + ); + return false; + } + unset( $this->collections[ $slug ] ); + return true; + } + + /** + * Checks if a font collection is registered. + * + * @since 6.5.0 + * + * @param string $slug Font collection slug. + * @return bool True if the font collection is registered and false otherwise. + */ + private function is_collection_registered( $slug ) { + return array_key_exists( $slug, $this->collections ); + } + + /** + * Gets all the font collections available. + * + * @since 6.5.0 + * + * @return array List of font collections. + */ + public function get_font_collections() { + return $this->collections; + } + + /** + * Gets a font collection. + * + * @since 6.5.0 + * + * @param string $slug Font collection slug. + * @return WP_Font_Collection|null Font collection object, or null if the font collection doesn't exist. + */ + public function get_font_collection( $slug ) { + if ( $this->is_collection_registered( $slug ) ) { + return $this->collections[ $slug ]; + } + return null; + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.5.0 + * + * @return WP_Font_Library The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } +} diff --git a/src/wp-includes/fonts/class-wp-font-utils.php b/src/wp-includes/fonts/class-wp-font-utils.php new file mode 100644 index 0000000000000..5bca536d2d58a --- /dev/null +++ b/src/wp-includes/fonts/class-wp-font-utils.php @@ -0,0 +1,238 @@ + '', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'fontStretch' => '100%', + 'unicodeRange' => 'U+0-10FFFF', + ); + $settings = wp_parse_args( $settings, $defaults ); + + $font_family = mb_strtolower( $settings['fontFamily'] ); + $font_style = strtolower( $settings['fontStyle'] ); + $font_weight = strtolower( $settings['fontWeight'] ); + $font_stretch = strtolower( $settings['fontStretch'] ); + $unicode_range = strtoupper( $settings['unicodeRange'] ); + + // Convert weight keywords to numeric strings. + $font_weight = str_replace( array( 'normal', 'bold' ), array( '400', '700' ), $font_weight ); + + // Convert stretch keywords to numeric strings. + $font_stretch_map = array( + 'ultra-condensed' => '50%', + 'extra-condensed' => '62.5%', + 'condensed' => '75%', + 'semi-condensed' => '87.5%', + 'normal' => '100%', + 'semi-expanded' => '112.5%', + 'expanded' => '125%', + 'extra-expanded' => '150%', + 'ultra-expanded' => '200%', + ); + $font_stretch = str_replace( array_keys( $font_stretch_map ), array_values( $font_stretch_map ), $font_stretch ); + + $slug_elements = array( $font_family, $font_style, $font_weight, $font_stretch, $unicode_range ); + + $slug_elements = array_map( + function ( $elem ) { + // Remove quotes to normalize font-family names, and ';' to use as a separator. + $elem = trim( str_replace( array( '"', "'", ';' ), '', $elem ) ); + + // Normalize comma separated lists by removing whitespace in between items, + // but keep whitespace within items (e.g. "Open Sans" and "OpenSans" are different fonts). + // CSS spec for whitespace includes: U+000A LINE FEED, U+0009 CHARACTER TABULATION, or U+0020 SPACE, + // which by default are all matched by \s in PHP. + return preg_replace( '/,\s+/', ',', $elem ); + }, + $slug_elements + ); + + return sanitize_text_field( join( ';', $slug_elements ) ); + } + + /** + * Sanitizes a tree of data using a schema. + * + * The schema structure should mirror the data tree. Each value provided in the + * schema should be a callable that will be applied to sanitize the corresponding + * value in the data tree. Keys that are in the data tree, but not present in the + * schema, will be removed in the sanitized data. Nested arrays are traversed recursively. + * + * @since 6.5.0 + * + * @access private + * + * @param array $tree The data to sanitize. + * @param array $schema The schema used for sanitization. + * @return array The sanitized data. + */ + public static function sanitize_from_schema( $tree, $schema ) { + if ( ! is_array( $tree ) || ! is_array( $schema ) ) { + return array(); + } + + foreach ( $tree as $key => $value ) { + // Remove keys not in the schema or with null/empty values. + if ( ! array_key_exists( $key, $schema ) ) { + unset( $tree[ $key ] ); + continue; + } + + $is_value_array = is_array( $value ); + $is_schema_array = is_array( $schema[ $key ] ) && ! is_callable( $schema[ $key ] ); + + if ( $is_value_array && $is_schema_array ) { + if ( wp_is_numeric_array( $value ) ) { + // If indexed, process each item in the array. + foreach ( $value as $item_key => $item_value ) { + $tree[ $key ][ $item_key ] = isset( $schema[ $key ][0] ) && is_array( $schema[ $key ][0] ) + ? self::sanitize_from_schema( $item_value, $schema[ $key ][0] ) + : self::apply_sanitizer( $item_value, $schema[ $key ][0] ); + } + } else { + // If it is an associative or indexed array, process as a single object. + $tree[ $key ] = self::sanitize_from_schema( $value, $schema[ $key ] ); + } + } elseif ( ! $is_value_array && $is_schema_array ) { + // If the value is not an array but the schema is, remove the key. + unset( $tree[ $key ] ); + } elseif ( ! $is_schema_array ) { + // If the schema is not an array, apply the sanitizer to the value. + $tree[ $key ] = self::apply_sanitizer( $value, $schema[ $key ] ); + } + + // Remove keys with null/empty values. + if ( empty( $tree[ $key ] ) ) { + unset( $tree[ $key ] ); + } + } + + return $tree; + } + + /** + * Applies a sanitizer function to a value. + * + * @since 6.5.0 + * + * @param mixed $value The value to sanitize. + * @param mixed $sanitizer The sanitizer function to apply. + * @return mixed The sanitized value. + */ + private static function apply_sanitizer( $value, $sanitizer ) { + if ( null === $sanitizer ) { + return $value; + + } + return call_user_func( $sanitizer, $value ); + } + + /** + * Returns the expected mime-type values for font files, depending on PHP version. + * + * This is needed because font mime types vary by PHP version, so checking the PHP version + * is necessary until a list of valid mime-types for each file extension can be provided to + * the 'upload_mimes' filter. + * + * @since 6.5.0 + * + * @access private + * + * @return array A collection of mime types keyed by file extension. + */ + public static function get_allowed_font_mime_types() { + $php_7_ttf_mime_type = PHP_VERSION_ID >= 70300 ? 'application/font-sfnt' : 'application/x-font-ttf'; + + return array( + 'otf' => 'application/vnd.ms-opentype', + 'ttf' => PHP_VERSION_ID >= 70400 ? 'font/sfnt' : $php_7_ttf_mime_type, + 'woff' => PHP_VERSION_ID >= 80100 ? 'font/woff' : 'application/font-woff', + 'woff2' => PHP_VERSION_ID >= 80100 ? 'font/woff2' : 'application/font-woff2', + ); + } +} diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 05b103e6f755f..ce8d0354209e2 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -3464,7 +3464,7 @@ function translate_smiley( $matches ) { $matches = array(); $ext = preg_match( '/\.([^.]+)$/', $img, $matches ) ? strtolower( $matches[1] ) : false; - $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' ); + $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); // Don't convert smilies that aren't images - they're probably emoji. if ( ! in_array( $ext, $image_exts, true ) ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index a3a5e56bfad18..461918bd53299 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -1548,11 +1548,11 @@ function nocache_headers() { * @since 2.1.0 */ function cache_javascript_headers() { - $expiresOffset = 10 * DAY_IN_SECONDS; + $expires_offset = 10 * DAY_IN_SECONDS; header( 'Content-Type: text/javascript; charset=' . get_bloginfo( 'charset' ) ); header( 'Vary: Accept-Encoding' ); // Handle proxies. - header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expiresOffset ) . ' GMT' ); + header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expires_offset ) . ' GMT' ); } /** @@ -1692,7 +1692,7 @@ function do_feed_atom( $for_comments ) { * Displays the default robots.txt file content. * * @since 2.1.0 - * @since 5.3.0 Remove the "Disallow: /" output if search engine visiblity is + * @since 5.3.0 Remove the "Disallow: /" output if search engine visibility is * discouraged in favor of robots meta HTML tag via wp_robots_no_robots() * filter callback. */ @@ -3117,6 +3117,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 'image/bmp' => 'bmp', 'image/tiff' => 'tif', 'image/webp' => 'webp', + 'image/avif' => 'avif', ) ); @@ -3295,6 +3296,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. * * @param string $file Full path to the file. * @return string|false The actual mime type or false if the type cannot be determined. @@ -3349,6 +3351,25 @@ function wp_get_image_mime( $file ) { ) { $mime = 'image/webp'; } + + /** + * Add AVIF fallback detection when image library doesn't support AVIF. + * + * Detection based on section 4.3.1 File-type box definition of the ISO/IEC 14496-12 + * specification and the AV1-AVIF spec, see https://aomediacodec.github.io/av1-avif/v1.1.0.html#brands. + */ + + // Divide the header string into 4 byte groups. + $magic = str_split( $magic, 8 ); + + if ( + isset( $magic[1] ) && + isset( $magic[2] ) && + 'ftyp' === hex2bin( $magic[1] ) && + ( 'avif' === hex2bin( $magic[2] ) || 'avis' === hex2bin( $magic[2] ) ) + ) { + $mime = 'image/avif'; + } } catch ( Exception $e ) { $mime = false; } @@ -3388,6 +3409,7 @@ function wp_get_mime_types() { 'bmp' => 'image/bmp', 'tiff|tif' => 'image/tiff', 'webp' => 'image/webp', + 'avif' => 'image/avif', 'ico' => 'image/x-icon', 'heic' => 'image/heic', // Video formats. @@ -3509,7 +3531,7 @@ function wp_get_ext_types() { return apply_filters( 'ext2type', array( - 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp' ), + 'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp', 'avif' ), 'audio' => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ), 'video' => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ), 'document' => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ), @@ -3718,7 +3740,7 @@ function wp_die( $message = '', $title = '', $args = array() ) { * @param callable $callback Callback function name. */ $callback = apply_filters( 'wp_die_json_handler', '_json_wp_die_handler' ); - } elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST && wp_is_jsonp_request() ) { + } elseif ( wp_is_serving_rest_request() && wp_is_jsonp_request() ) { /** * Filters the callback for killing WordPress execution for JSONP REST requests. * @@ -3883,21 +3905,16 @@ function _default_wp_die_handler( $message, $title = '', $args = array() ) { font-size: 14px ; } a { - color: #0073aa; + color: #2271b1; } a:hover, a:active { - color: #006799; + color: #135e96; } a:focus { - color: #124964; - -webkit-box-shadow: - 0 0 0 1px #5b9dd9, - 0 0 2px 1px rgba(30, 140, 190, 0.8); - box-shadow: - 0 0 0 1px #5b9dd9, - 0 0 2px 1px rgba(30, 140, 190, 0.8); - outline: none; + color: #043959; + box-shadow: 0 0 0 2px #2271b1; + outline: 2px solid transparent; } .button { background: #f3f5f6; @@ -4441,7 +4458,7 @@ function _wp_json_prepare_data( $value ) { * @param int $flags Optional. Options to be passed to json_encode(). Default 0. */ function wp_send_json( $response, $status_code = null, $flags = 0 ) { - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { _doing_it_wrong( __FUNCTION__, sprintf( @@ -4697,6 +4714,23 @@ function _mce_set_direction( $mce_init ) { return $mce_init; } +/** + * Determines whether WordPress is currently serving a REST API request. + * + * The function relies on the 'REST_REQUEST' global. As such, it only returns true when an actual REST _request_ is + * being made. It does not return true when a REST endpoint is hit as part of another request, e.g. for preloading a + * REST response. See {@see wp_is_rest_endpoint()} for that purpose. + * + * This function should not be called until the {@see 'parse_request'} action, as the constant is only defined then, + * even for an actual REST request. + * + * @since 6.5.0 + * + * @return bool True if it's a WordPress REST API request, false otherwise. + */ +function wp_is_serving_rest_request() { + return defined( 'REST_REQUEST' ) && REST_REQUEST; +} /** * Converts smiley code to the icon graphic file equivalent. @@ -5356,7 +5390,7 @@ function wp_widgets_add_menu() { if ( wp_is_block_theme() || current_theme_supports( 'block-template-parts' ) ) { $submenu['themes.php'][] = array( $menu_name, 'edit_theme_options', 'widgets.php' ); } else { - $submenu['themes.php'][7] = array( $menu_name, 'edit_theme_options', 'widgets.php' ); + $submenu['themes.php'][8] = array( $menu_name, 'edit_theme_options', 'widgets.php' ); } ksort( $submenu['themes.php'], SORT_NUMERIC ); @@ -6533,7 +6567,7 @@ function wp_timezone_choice( $selected_zone, $locale = null ) { if ( ! $mo_loaded || $locale !== $locale_loaded ) { $locale_loaded = $locale ? $locale : get_locale(); $mofile = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo'; - unload_textdomain( 'continents-cities' ); + unload_textdomain( 'continents-cities', true ); load_textdomain( 'continents-cities', $mofile, $locale_loaded ); $mo_loaded = true; } diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index acca33be1e844..fae7ae6ffb6ea 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -54,7 +54,7 @@ function wp_get_global_settings( $path = array(), $context = array() ) { * 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 behaviour: + * See some of the existing hooks to modify theme.json behavior: * https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ * * A different alternative considered was to invalidate the cache upon certain @@ -298,8 +298,12 @@ function wp_get_global_styles_custom_css() { * Adds global style rules to the inline style for each block. * * @since 6.1.0 + * + * @global WP_Styles $wp_styles */ function wp_add_global_styles_for_blocks() { + global $wp_styles; + $tree = WP_Theme_JSON_Resolver::get_merged_data(); $block_nodes = $tree->get_styles_block_nodes(); foreach ( $block_nodes as $metadata ) { @@ -311,17 +315,26 @@ function wp_add_global_styles_for_blocks() { } $stylesheet_handle = 'global-styles'; + + /* + * When `wp_should_load_separate_core_block_assets()` is true, block styles are + * enqueued for each block on the page in class WP_Block's render function. + * This means there will be a handle in the styles queue for each of those blocks. + * Block-specific global styles should be attached to the global-styles handle, but + * only for blocks on the page, thus we check if the block's handle is in the queue + * before adding the inline style. + * This conditional loading only applies to core blocks. + */ if ( isset( $metadata['name'] ) ) { - /* - * These block styles are added on block_render. - * This hooks inline CSS to them so that they are loaded conditionally - * based on whether or not the block is used on the page. - */ if ( str_starts_with( $metadata['name'], 'core/' ) ) { - $block_name = str_replace( 'core/', '', $metadata['name'] ); - $stylesheet_handle = 'wp-block-' . $block_name; + $block_name = str_replace( 'core/', '', $metadata['name'] ); + $block_handle = 'wp-block-' . $block_name; + if ( in_array( $block_handle, $wp_styles->queue ) ) { + wp_add_inline_style( $stylesheet_handle, $block_css ); + } + } else { + wp_add_inline_style( $stylesheet_handle, $block_css ); } - wp_add_inline_style( $stylesheet_handle, $block_css ); } // The likes of block element styles from theme.json do not have $metadata['name'] set. @@ -329,10 +342,14 @@ function wp_add_global_styles_for_blocks() { $block_name = wp_get_block_name_from_theme_json_path( $metadata['path'] ); if ( $block_name ) { if ( str_starts_with( $block_name, 'core/' ) ) { - $block_name = str_replace( 'core/', '', $block_name ); - $stylesheet_handle = 'wp-block-' . $block_name; + $block_name = str_replace( 'core/', '', $block_name ); + $block_handle = 'wp-block-' . $block_name; + if ( in_array( $block_handle, $wp_styles->queue ) ) { + wp_add_inline_style( $stylesheet_handle, $block_css ); + } + } else { + wp_add_inline_style( $stylesheet_handle, $block_css ); } - wp_add_inline_style( $stylesheet_handle, $block_css ); } } } 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 cce26a60c5350..dd8e8d5f2d6e8 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -101,18 +101,18 @@ * * - Containers: ADDRESS, BLOCKQUOTE, DETAILS, DIALOG, DIV, FOOTER, HEADER, MAIN, MENU, SPAN, SUMMARY. * - Custom elements: All custom elements are supported. :) - * - Form elements: BUTTON, DATALIST, FIELDSET, LABEL, LEGEND, METER, PROGRESS, SEARCH. - * - Formatting elements: B, BIG, CODE, EM, FONT, I, SMALL, STRIKE, STRONG, TT, U. + * - 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, LI. - * - Media elements: AUDIO, CANVAS, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, VIDEO. - * - Paragraph: P. - * - Phrasing elements: ABBR, BDI, BDO, CITE, DATA, DEL, DFN, INS, MARK, OUTPUT, Q, SAMP, SUB, SUP, TIME, VAR. - * - Sectioning elements: ARTICLE, ASIDE, NAV, SECTION. + * - 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, MULTICOL, NEXTID, SPACER. + * - Deprecated elements: ACRONYM, BLINK, CENTER, DIR, ISINDEX, KEYGEN, LISTING, MULTICOL, NEXTID, PARAM, SPACER. * * ### Supported markup * @@ -149,17 +149,6 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { */ const MAX_BOOKMARKS = 100; - /** - * Static query for instructing the Tag Processor to visit every token. - * - * @access private - * - * @since 6.4.0 - * - * @var array - */ - const VISIT_EVERYTHING = array( 'tag_closers' => 'visit' ); - /** * Holds the working state of the parser, including the stack of * open elements and the stack of active formatting elements. @@ -424,6 +413,30 @@ public function next_tag( $query = null ) { return false; } + /** + * Ensures internal accounting is maintained for HTML semantic rules while + * the underlying Tag Processor class is seeking to a bookmark. + * + * This doesn't currently have a way to represent non-tags and doesn't process + * semantic rules for text nodes. For access to the raw tokens consider using + * WP_HTML_Tag_Processor instead. + * + * @since 6.5.0 Added for internal support; do not use. + * + * @access private + * + * @return bool + */ + public function next_token() { + $found_a_token = parent::next_token(); + + if ( '#tag' === $this->get_token_type() ) { + $this->step( self::PROCESS_CURRENT_NODE ); + } + + return $found_a_token; + } + /** * Indicates if the currently-matched tag matches the given breadcrumbs. * @@ -500,7 +513,7 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { return false; } - if ( self::PROCESS_NEXT_NODE === $node_to_process ) { + if ( self::REPROCESS_CURRENT_NODE !== $node_to_process ) { /* * Void elements still hop onto the stack of open elements even though * there's no corresponding closing tag. This is important for managing @@ -519,8 +532,12 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { if ( $top_node && self::is_void( $top_node->node_name ) ) { $this->state->stack_of_open_elements->pop(); } + } - parent::next_tag( self::VISIT_EVERYTHING ); + if ( self::PROCESS_NEXT_NODE === $node_to_process ) { + while ( parent::next_token() && '#tag' !== $this->get_token_type() ) { + continue; + } } // Finish stepping when there are no more tokens in the document. @@ -531,7 +548,7 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { $this->state->current_token = new WP_HTML_Token( $this->bookmark_tag(), $this->get_tag(), - $this->is_tag_closer(), + $this->has_self_closing_flag(), $this->release_internal_bookmark_on_destruct ); @@ -684,10 +701,12 @@ private function step_in_body() { case '-FOOTER': case '-HEADER': case '-HGROUP': + case '-LISTING': case '-MAIN': case '-MENU': case '-NAV': case '-OL': + case '-PRE': case '-SEARCH': case '-SECTION': case '-SUMMARY': @@ -732,6 +751,18 @@ private function step_in_body() { $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(); + } + $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" */ @@ -934,11 +965,64 @@ private function step_in_body() { $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 '-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; + + /* + * > 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; + } + 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 one of: "param", "source", "track" + */ + case '+PARAM': + case '+SOURCE': + case '+TRACK': $this->insert_html_element( $this->state->current_token ); return true; } @@ -961,30 +1045,20 @@ private function step_in_body() { */ switch ( $tag_name ) { case 'APPLET': - case 'AREA': case 'BASE': case 'BASEFONT': case 'BGSOUND': case 'BODY': - case 'BR': case 'CAPTION': case 'COL': case 'COLGROUP': - case 'DD': - case 'DT': - case 'EMBED': case 'FORM': case 'FRAME': case 'FRAMESET': case 'HEAD': - case 'HR': case 'HTML': case 'IFRAME': - case 'INPUT': - case 'KEYGEN': - case 'LI': case 'LINK': - case 'LISTING': case 'MARQUEE': case 'MATH': case 'META': @@ -993,12 +1067,9 @@ private function step_in_body() { case 'NOFRAMES': case 'NOSCRIPT': case 'OBJECT': - case 'OL': case 'OPTGROUP': case 'OPTION': - case 'PARAM': case 'PLAINTEXT': - case 'PRE': case 'RB': case 'RP': case 'RT': @@ -1006,7 +1077,6 @@ private function step_in_body() { case 'SARCASM': case 'SCRIPT': case 'SELECT': - case 'SOURCE': case 'STYLE': case 'SVG': case 'TABLE': @@ -1019,9 +1089,6 @@ private function step_in_body() { case 'THEAD': case 'TITLE': case 'TR': - case 'TRACK': - case 'UL': - case 'WBR': case 'XMP': $this->last_error = self::ERROR_UNSUPPORTED; throw new WP_HTML_Unsupported_Exception( "Cannot process {$tag_name} element." ); @@ -1675,14 +1742,19 @@ public static function is_void( $tag_name ) { return ( 'AREA' === $tag_name || 'BASE' === $tag_name || + 'BASEFONT' === $tag_name || // Obsolete but still treated as void. + 'BGSOUND' === $tag_name || // Obsolete but still treated as void. 'BR' === $tag_name || 'COL' === $tag_name || 'EMBED' === $tag_name || + 'FRAME' === $tag_name || 'HR' === $tag_name || 'IMG' === $tag_name || 'INPUT' === $tag_name || + 'KEYGEN' === $tag_name || // Obsolete but still treated as void. 'LINK' === $tag_name || 'META' === $tag_name || + 'PARAM' === $tag_name || // Obsolete but still treated as void. 'SOURCE' === $tag_name || 'TRACK' === $tag_name || 'WBR' === $tag_name @@ -1711,6 +1783,15 @@ public static function is_void( $tag_name ) { */ const REPROCESS_CURRENT_NODE = 'reprocess-current-node'; + /** + * Indicates that the current HTML token should be processed without advancing the parser. + * + * @since 6.5.0 + * + * @var string + */ + const PROCESS_CURRENT_NODE = 'process-current-node'; + /** * Indicates that the parser encountered unsupported markup and has bailed. * 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 ee6209c69e0ae..447f4dac1b6e2 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 @@ -247,6 +247,95 @@ * } * } * + * ## Tokens and finer-grained processing. + * + * It's possible to scan through every lexical token in the + * HTML document using the `next_token()` function. This + * alternative form takes no argument and provides no built-in + * query syntax. + * + * Example: + * + * $title = '(untitled)'; + * $text = ''; + * while ( $processor->next_token() ) { + * switch ( $processor->get_token_name() ) { + * case '#text': + * $text .= $processor->get_modifiable_text(); + * break; + * + * case 'BR': + * $text .= "\n"; + * break; + * + * case 'TITLE': + * $title = $processor->get_modifiable_text(); + * break; + * } + * } + * return trim( "# {$title}\n\n{$text}" ); + * + * ### Tokens and _modifiable text_. + * + * #### Special "atomic" HTML elements. + * + * Not all HTML elements are able to contain other elements inside of them. + * For instance, the contents inside a TITLE element are plaintext (except + * that character references like & will be decoded). This means that + * if the string `` appears inside a TITLE element, then it's not an + * image tag, but rather it's text describing an image tag. Likewise, the + * contents of a SCRIPT or STYLE element are handled entirely separately in + * a browser than the contents of other elements because they represent a + * different language than HTML. + * + * For these elements the Tag Processor treats the entire sequence as one, + * from the opening tag, including its contents, through its closing tag. + * This means that the it's not possible to match the closing tag for a + * SCRIPT element unless it's unexpected; the Tag Processor already matched + * it when it found the opening tag. + * + * The inner contents of these elements are that element's _modifiable text_. + * + * The special elements are: + * - `SCRIPT` whose contents are treated as raw plaintext but supports a legacy + * style of including Javascript inside of HTML comments to avoid accidentally + * closing the SCRIPT from inside a Javascript string. E.g. `console.log( '' )`. + * - `TITLE` and `TEXTAREA` whose contents are treated as plaintext and then any + * character references are decoded. E.g. `1 < 2 < 3` becomes `1 < 2 < 3`. + * - `IFRAME`, `NOSCRIPT`, `NOEMBED`, `NOFRAME`, `STYLE` whose contents are treated as + * raw plaintext and left as-is. E.g. `1 < 2 < 3` remains `1 < 2 < 3`. + * + * #### Other tokens with modifiable text. + * + * There are also non-elements which are void/self-closing in nature and contain + * modifiable text that is part of that individual syntax token itself. + * + * - `#text` nodes, whose entire token _is_ the modifiable text. + * - HTML comments and tokens that become comments due to some syntax error. The + * text for these tokens is the portion of the comment inside of the syntax. + * E.g. for `` the text is `" comment "` (note the spaces are included). + * - `CDATA` sections, whose text is the content inside of the section itself. E.g. for + * `` the text is `"some content"` (with restrictions [1]). + * - "Funky comments," which are a special case of invalid closing tags whose name is + * invalid. The text for these nodes is the text that a browser would transform into + * an HTML comment when parsing. E.g. for `` the text is `%post_author`. + * - `DOCTYPE` declarations like `` which have no closing tag. + * - XML Processing instruction nodes like `` (with restrictions [2]). + * - The empty end tag `` which is ignored in the browser and DOM. + * + * [1]: There are no CDATA sections in HTML. When encountering `` becomes a bogus HTML comment, meaning there can be no CDATA + * section in an HTML document containing `>`. The Tag Processor will first find + * all valid and bogus HTML comments, and then if the comment _would_ have been a + * CDATA section _were they to exist_, it will indicate this as the type of comment. + * + * [2]: XML allows a broader range of characters in a processing instruction's target name + * and disallows "xml" as a name, since it's special. The Tag Processor only recognizes + * target names with an ASCII-representable subset of characters. It also exhibits the + * same constraint as with CDATA sections, in that `>` cannot exist within the token + * since Processing Instructions do no exist within HTML and their syntax transforms + * into a bogus comment in the DOM. + * * ## Design and limitations * * The Tag Processor is designed to linearly scan HTML documents and tokenize @@ -320,7 +409,8 @@ * @since 6.2.1 Fix: Support for various invalid comments; attribute updates are case-insensitive. * @since 6.3.2 Fix: Skip HTML-like content inside rawtext elements such as STYLE. * @since 6.5.0 Pauses processor when input ends in an incomplete syntax token. - * Introduces "special" elements which act like void elements, e.g. STYLE. + * Introduces "special" elements which act like void elements, e.g. TITLE, STYLE. + * Allows scanning through all tokens and processing modifiable text, where applicable. */ class WP_HTML_Tag_Processor { /** @@ -396,23 +486,47 @@ class WP_HTML_Tag_Processor { /** * Specifies mode of operation of the parser at any given time. * - * | State | Meaning | - * | --------------|----------------------------------------------------------------------| - * | *Ready* | The parser is ready to run. | - * | *Complete* | There is nothing left to parse. | - * | *Incomplete* | The HTML ended in the middle of a token; nothing more can be parsed. | - * | *Matched tag* | Found an HTML tag; it's possible to modify its attributes. | + * | State | Meaning | + * | ----------------|----------------------------------------------------------------------| + * | *Ready* | The parser is ready to run. | + * | *Complete* | There is nothing left to parse. | + * | *Incomplete* | The HTML ended in the middle of a token; nothing more can be parsed. | + * | *Matched tag* | Found an HTML tag; it's possible to modify its attributes. | + * | *Text node* | Found a #text node; this is plaintext and modifiable. | + * | *CDATA node* | Found a CDATA section; this is modifiable. | + * | *Comment* | Found a comment or bogus comment; this is modifiable. | + * | *Presumptuous* | Found an empty tag closer: ``. | + * | *Funky comment* | Found a tag closer with an invalid tag name; this is modifiable. | * * @since 6.5.0 * * @see WP_HTML_Tag_Processor::STATE_READY * @see WP_HTML_Tag_Processor::STATE_COMPLETE - * @see WP_HTML_Tag_Processor::STATE_INCOMPLETE + * @see WP_HTML_Tag_Processor::STATE_INCOMPLETE_INPUT * @see WP_HTML_Tag_Processor::STATE_MATCHED_TAG + * @see WP_HTML_Tag_Processor::STATE_TEXT_NODE + * @see WP_HTML_Tag_Processor::STATE_CDATA_NODE + * @see WP_HTML_Tag_Processor::STATE_COMMENT + * @see WP_HTML_Tag_Processor::STATE_DOCTYPE + * @see WP_HTML_Tag_Processor::STATE_PRESUMPTUOUS_TAG + * @see WP_HTML_Tag_Processor::STATE_FUNKY_COMMENT * * @var string */ - private $parser_state = self::STATE_READY; + protected $parser_state = self::STATE_READY; + + /** + * What kind of syntax token became an HTML comment. + * + * Since there are many ways in which HTML syntax can create an HTML comment, + * this indicates which of those caused it. This allows the Tag Processor to + * represent more from the original input document than would appear in the DOM. + * + * @since 6.5.0 + * + * @var string|null + */ + protected $comment_type = null; /** * How many bytes from the original HTML document have been read and parsed. @@ -490,6 +604,24 @@ class WP_HTML_Tag_Processor { */ private $tag_name_length; + /** + * Byte offset into input document where current modifiable text starts. + * + * @since 6.5.0 + * + * @var int + */ + private $text_starts_at; + + /** + * Byte length of modifiable text. + * + * @since 6.5.0 + * + * @var string + */ + private $text_length; + /** * Whether the current tag is an opening tag, e.g.
    , or a closing tag, e.g.
    . * @@ -705,13 +837,13 @@ public function next_tag( $query = null ) { * @return bool Whether a token was parsed. */ public function next_token() { - $this->get_updated_html(); $was_at = $this->bytes_already_parsed; + $this->get_updated_html(); // Don't proceed if there's nothing more to scan. if ( self::STATE_COMPLETE === $this->parser_state || - self::STATE_INCOMPLETE === $this->parser_state + self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { return false; } @@ -729,13 +861,27 @@ public function next_token() { // Find the next tag if it exists. if ( false === $this->parse_next_tag() ) { - if ( self::STATE_INCOMPLETE === $this->parser_state ) { + if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { $this->bytes_already_parsed = $was_at; } return false; } + /* + * For legacy reasons the rest of this function handles tags and their + * attributes. If the processor has reached the end of the document + * or if it matched any other token then it should return here to avoid + * attempting to process tag-specific syntax. + */ + if ( + self::STATE_INCOMPLETE_INPUT !== $this->parser_state && + self::STATE_COMPLETE !== $this->parser_state && + self::STATE_MATCHED_TAG !== $this->parser_state + ) { + return true; + } + // Parse all of its attributes. while ( $this->parse_next_attribute() ) { continue; @@ -743,11 +889,11 @@ public function next_token() { // Ensure that the tag closes before the end of the document. if ( - self::STATE_INCOMPLETE === $this->parser_state || + self::STATE_INCOMPLETE_INPUT === $this->parser_state || $this->bytes_already_parsed >= strlen( $this->html ) ) { // Does this appropriately clear state (parsed attributes)? - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; $this->bytes_already_parsed = $was_at; return false; @@ -755,14 +901,14 @@ public function next_token() { $tag_ends_at = strpos( $this->html, '>', $this->bytes_already_parsed ); if ( false === $tag_ends_at ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; $this->bytes_already_parsed = $was_at; return false; } $this->parser_state = self::STATE_MATCHED_TAG; $this->token_length = $tag_ends_at - $this->token_starts_at; - $this->bytes_already_parsed = $tag_ends_at; + $this->bytes_already_parsed = $tag_ends_at + 1; /* * For non-DATA sections which might contain text that looks like HTML tags but @@ -771,8 +917,8 @@ public function next_token() { */ $t = $this->html[ $this->tag_name_starts_at ]; if ( - ! $this->is_closing_tag && - ( + $this->is_closing_tag || + ! ( 'i' === $t || 'I' === $t || 'n' === $t || 'N' === $t || 's' === $t || 'S' === $t || @@ -780,38 +926,81 @@ public function next_token() { 'x' === $t || 'X' === $t ) ) { - $tag_name = $this->get_tag(); + return true; + } - if ( 'SCRIPT' === $tag_name && ! $this->skip_script_data() ) { - $this->parser_state = self::STATE_INCOMPLETE; - $this->bytes_already_parsed = $was_at; + $tag_name = $this->get_tag(); - return false; - } elseif ( - ( 'TEXTAREA' === $tag_name || 'TITLE' === $tag_name ) && - ! $this->skip_rcdata( $tag_name ) - ) { - $this->parser_state = self::STATE_INCOMPLETE; - $this->bytes_already_parsed = $was_at; + /* + * 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 + * tag sequence. + */ + $tag_name_starts_at = $this->tag_name_starts_at; + $tag_name_length = $this->tag_name_length; + $tag_ends_at = $this->token_starts_at + $this->token_length; + $attributes = $this->attributes; + $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(); + break; - return false; - } elseif ( - ( - 'IFRAME' === $tag_name || - 'NOEMBED' === $tag_name || - 'NOFRAMES' === $tag_name || - 'STYLE' === $tag_name || - 'XMP' === $tag_name - ) && - ! $this->skip_rawtext( $tag_name ) - ) { - $this->parser_state = self::STATE_INCOMPLETE; - $this->bytes_already_parsed = $was_at; + case 'TEXTAREA': + case 'TITLE': + $found_closer = $this->skip_rcdata( $tag_name ); + break; - return false; - } + /* + * In the browser this list would include the NOSCRIPT element, + * but the Tag Processor is an environment with the scripting + * flag disabled, meaning that it needs to descend into the + * NOSCRIPT element to be able to properly process what will be + * sent to a browser. + * + * Note that this rule makes HTML5 syntax incompatible with XML, + * because the parsing of this token depends on client application. + * The NOSCRIPT element cannot be represented in the XHTML syntax. + */ + case 'IFRAME': + case 'NOEMBED': + case 'NOFRAMES': + case 'STYLE': + case 'XMP': + $found_closer = $this->skip_rawtext( $tag_name ); + break; + + // No other tags should be treated in their entirety here. + default: + return true; + } + + if ( ! $found_closer ) { + $this->parser_state = self::STATE_INCOMPLETE_INPUT; + $this->bytes_already_parsed = $was_at; + return false; } + /* + * The values here look like they reference the opening tag but they reference + * the closing tag instead. This is why the opening tag values were stored + * above in a variable. It reads confusingly here, but that's because the + * functions that skip the contents have moved all the internal cursors past + * the inner content of the tag. + */ + $this->token_starts_at = $was_at; + $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; + $this->text_starts_at = $tag_ends_at + 1; + $this->text_length = $this->tag_name_starts_at - $this->text_starts_at; + $this->tag_name_starts_at = $tag_name_starts_at; + $this->tag_name_length = $tag_name_length; + $this->attributes = $attributes; + $this->duplicate_attributes = $duplicate_attributes; + return true; } @@ -830,7 +1019,7 @@ public function next_token() { * @return bool Whether the parse paused at the start of an incomplete token. */ public function paused_at_incomplete_token() { - return self::STATE_INCOMPLETE === $this->parser_state; + return self::STATE_INCOMPLETE_INPUT === $this->parser_state; } /** @@ -1007,7 +1196,10 @@ public function has_class( $wanted_class ) { */ public function set_bookmark( $name ) { // It only makes sense to set a bookmark if the parser has paused on a concrete token. - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { + if ( + self::STATE_COMPLETE === $this->parser_state || + self::STATE_INCOMPLETE_INPUT === $this->parser_state + ) { return false; } @@ -1082,15 +1274,15 @@ private function skip_rcdata( $tag_name ) { $at = $this->bytes_already_parsed; while ( false !== $at && $at < $doc_length ) { - $at = strpos( $this->html, 'html, 'tag_name_starts_at = $at; // Fail if there is no possible tag closer. if ( false === $at || ( $at + $tag_length ) >= $doc_length ) { return false; } - $closer_potentially_starts_at = $at; - $at += 2; + $at += 2; /* * Find a case-insensitive match to the tag name. @@ -1131,13 +1323,23 @@ private function skip_rcdata( $tag_name ) { while ( $this->parse_next_attribute() ) { continue; } + $at = $this->bytes_already_parsed; if ( $at >= strlen( $this->html ) ) { return false; } - if ( '>' === $html[ $at ] || '/' === $html[ $at ] ) { - $this->bytes_already_parsed = $closer_potentially_starts_at; + if ( '>' === $html[ $at ] ) { + $this->bytes_already_parsed = $at + 1; + return true; + } + + if ( $at + 1 >= strlen( $this->html ) ) { + return false; + } + + if ( '/' === $html[ $at ] && '>' === $html[ $at + 1 ] ) { + $this->bytes_already_parsed = $at + 2; return true; } } @@ -1259,6 +1461,7 @@ private function skip_script_data() { if ( $is_closing ) { $this->bytes_already_parsed = $closer_potentially_starts_at; + $this->tag_name_starts_at = $closer_potentially_starts_at; if ( $this->bytes_already_parsed >= $doc_length ) { return false; } @@ -1268,13 +1471,13 @@ private function skip_script_data() { } if ( $this->bytes_already_parsed >= $doc_length ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } if ( '>' === $html[ $this->bytes_already_parsed ] ) { - $this->bytes_already_parsed = $closer_potentially_starts_at; + ++$this->bytes_already_parsed; return true; } } @@ -1303,7 +1506,8 @@ private function parse_next_tag() { $html = $this->html; $doc_length = strlen( $html ); - $at = $this->bytes_already_parsed; + $was_at = $this->bytes_already_parsed; + $at = $was_at; while ( false !== $at && $at < $doc_length ) { $at = strpos( $html, '<', $at ); @@ -1313,7 +1517,50 @@ private function parse_next_tag() { * can be nothing left in the document other than a #text node. */ if ( false === $at ) { - return false; + $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; + } + + if ( $at > $was_at ) { + /* + * A "<" normally starts a new HTML tag or syntax token, but in cases where the + * following character can't produce a valid token, the "<" is instead treated + * as plaintext and the parser should skip over it. This avoids a problem when + * following earlier practices of typing emoji with text, e.g. "<3". This + * should be a heart, not a tag. It's supposed to be rendered, not hidden. + * + * At this point the parser checks if this is one of those cases and if it is + * will continue searching for the next "<" in search of a token boundary. + * + * @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; + } + } + + $this->parser_state = self::STATE_TEXT_NODE; + $this->token_starts_at = $was_at; + $this->token_length = $at - $was_at; + $this->text_starts_at = $was_at; + $this->text_length = $this->token_length; + $this->bytes_already_parsed = $at; + return true; } $this->token_starts_at = $at; @@ -1342,8 +1589,9 @@ private function parse_next_tag() { $tag_name_prefix_length = strspn( $html, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', $at + 1 ); if ( $tag_name_prefix_length > 0 ) { ++$at; - $this->tag_name_length = $tag_name_prefix_length + strcspn( $html, " \t\f\r\n/>", $at + $tag_name_prefix_length ); + $this->parser_state = self::STATE_MATCHED_TAG; $this->tag_name_starts_at = $at; + $this->tag_name_length = $tag_name_prefix_length + strcspn( $html, " \t\f\r\n/>", $at + $tag_name_prefix_length ); $this->bytes_already_parsed = $at + $this->tag_name_length; return true; } @@ -1353,18 +1601,18 @@ private function parse_next_tag() { * the document. There is nothing left to parse. */ if ( $at + 1 >= $doc_length ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } /* - * + * ``. Unlike other comment + * and bogus comment syntax, these leave no clear insertion point for text and + * they need to be modified specially in order to contain text. E.g. to store + * `?` as the modifiable text, the `` needs to become ``, which + * involves inserting an additional `-` into the token after the modifiable text. + */ + $this->parser_state = self::STATE_COMMENT; + $this->comment_type = self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT; + $this->token_length = $closer_at + $span_of_dashes + 1 - $this->token_starts_at; + + // Only provide modifiable text if the token is long enough to contain it. + if ( $span_of_dashes >= 2 ) { + $this->comment_type = self::COMMENT_AS_HTML_COMMENT; + $this->text_starts_at = $this->token_starts_at + 4; + $this->text_length = $span_of_dashes - 2; + } + + $this->bytes_already_parsed = $closer_at + $span_of_dashes + 1; + return true; } /* @@ -1397,51 +1664,39 @@ private function parse_next_tag() { while ( ++$closer_at < $doc_length ) { $closer_at = strpos( $html, '--', $closer_at ); if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } if ( $closer_at + 2 < $doc_length && '>' === $html[ $closer_at + 2 ] ) { - $at = $closer_at + 3; - continue 2; + $this->parser_state = self::STATE_COMMENT; + $this->comment_type = self::COMMENT_AS_HTML_COMMENT; + $this->token_length = $closer_at + 3 - $this->token_starts_at; + $this->text_starts_at = $this->token_starts_at + 4; + $this->text_length = $closer_at - $this->text_starts_at; + $this->bytes_already_parsed = $closer_at + 3; + return true; } - if ( $closer_at + 3 < $doc_length && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) { - $at = $closer_at + 4; - continue 2; + if ( + $closer_at + 3 < $doc_length && + '!' === $html[ $closer_at + 2 ] && + '>' === $html[ $closer_at + 3 ] + ) { + $this->parser_state = self::STATE_COMMENT; + $this->comment_type = self::COMMENT_AS_HTML_COMMENT; + $this->token_length = $closer_at + 4 - $this->token_starts_at; + $this->text_starts_at = $this->token_starts_at + 4; + $this->text_length = $closer_at - $this->text_starts_at; + $this->bytes_already_parsed = $closer_at + 4; + return true; } } } /* - * - * The CDATA is case-sensitive. - * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state - */ - if ( - $doc_length > $at + 8 && - '[' === $html[ $at + 2 ] && - 'C' === $html[ $at + 3 ] && - 'D' === $html[ $at + 4 ] && - 'A' === $html[ $at + 5 ] && - 'T' === $html[ $at + 6 ] && - 'A' === $html[ $at + 7 ] && - '[' === $html[ $at + 8 ] - ) { - $closer_at = strpos( $html, ']]>', $at + 9 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE; - - return false; - } - - $at = $closer_at + 3; - continue; - } - - /* - * + * ` * These are ASCII-case-insensitive. * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state */ @@ -1457,13 +1712,17 @@ private function parse_next_tag() { ) { $closer_at = strpos( $html, '>', $at + 9 ); if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } - $at = $closer_at + 1; - continue; + $this->parser_state = self::STATE_DOCTYPE; + $this->token_length = $closer_at + 1 - $this->token_starts_at; + $this->text_starts_at = $this->token_starts_at + 9; + $this->text_length = $closer_at - $this->text_starts_at; + $this->bytes_already_parsed = $closer_at + 1; + return true; } /* @@ -1471,14 +1730,54 @@ private function parse_next_tag() { * to the bogus comment state - skip to the nearest >. If no closer is * found then the HTML was truncated inside the markup declaration. */ - $at = strpos( $html, '>', $at + 1 ); - if ( false === $at ) { - $this->parser_state = self::STATE_INCOMPLETE; + $closer_at = strpos( $html, '>', $at + 1 ); + if ( false === $closer_at ) { + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } - continue; + $this->parser_state = self::STATE_COMMENT; + $this->comment_type = self::COMMENT_AS_INVALID_HTML; + $this->token_length = $closer_at + 1 - $this->token_starts_at; + $this->text_starts_at = $this->token_starts_at + 2; + $this->text_length = $closer_at - $this->text_starts_at; + $this->bytes_already_parsed = $closer_at + 1; + + /* + * Identify nodes that would be CDATA if HTML had CDATA sections. + * + * This section must occur after identifying the bogus comment end + * because in an HTML parser it will span to the nearest `>`, even + * if there's no `]]>` as would be required in an XML document. It + * is therefore not possible to parse a CDATA section containing + * a `>` in the HTML syntax. + * + * Inside foreign elements there is a discrepancy between browsers + * and the specification on this. + * + * @todo Track whether the Tag Processor is inside a foreign element + * and require the proper closing `]]>` in those cases. + */ + if ( + $this->token_length >= 10 && + '[' === $html[ $this->token_starts_at + 2 ] && + 'C' === $html[ $this->token_starts_at + 3 ] && + 'D' === $html[ $this->token_starts_at + 4 ] && + 'A' === $html[ $this->token_starts_at + 5 ] && + 'T' === $html[ $this->token_starts_at + 6 ] && + 'A' === $html[ $this->token_starts_at + 7 ] && + '[' === $html[ $this->token_starts_at + 8 ] && + ']' === $html[ $closer_at - 1 ] && + ']' === $html[ $closer_at - 2 ] + ) { + $this->parser_state = self::STATE_COMMENT; + $this->comment_type = self::COMMENT_AS_CDATA_LOOKALIKE; + $this->text_starts_at += 7; + $this->text_length -= 9; + } + + return true; } /* @@ -1491,30 +1790,80 @@ private function parse_next_tag() { * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name */ if ( '>' === $html[ $at + 1 ] ) { - ++$at; - continue; + $this->parser_state = self::STATE_PRESUMPTUOUS_TAG; + $this->token_length = $at + 2 - $this->token_starts_at; + $this->bytes_already_parsed = $at + 2; + return true; } /* - * + * ` * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state */ if ( '?' === $html[ $at + 1 ] ) { $closer_at = strpos( $html, '>', $at + 2 ); if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } - $at = $closer_at + 1; - continue; + $this->parser_state = self::STATE_COMMENT; + $this->comment_type = self::COMMENT_AS_INVALID_HTML; + $this->token_length = $closer_at + 1 - $this->token_starts_at; + $this->text_starts_at = $this->token_starts_at + 2; + $this->text_length = $closer_at - $this->text_starts_at; + $this->bytes_already_parsed = $closer_at + 1; + + /* + * Identify a Processing Instruction node were HTML to have them. + * + * This section must occur after identifying the bogus comment end + * because in an HTML parser it will span to the nearest `>`, even + * if there's no `?>` as would be required in an XML document. It + * is therefore not possible to parse a Processing Instruction node + * containing a `>` in the HTML syntax. + * + * XML allows for more target names, but this code only identifies + * those with ASCII-representable target names. This means that it + * may identify some Processing Instruction nodes as bogus comments, + * but it will not misinterpret the HTML structure. By limiting the + * identification to these target names the Tag Processor can avoid + * the need to start parsing UTF-8 sequences. + * + * > NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | + * [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | + * [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | + * [#x10000-#xEFFFF] + * > NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] + * + * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-PITarget + */ + if ( $this->token_length >= 5 && '?' === $html[ $closer_at - 1 ] ) { + $comment_text = substr( $html, $this->token_starts_at + 2, $this->token_length - 4 ); + $pi_target_length = strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_' ); + + if ( 0 < $pi_target_length ) { + $pi_target_length += strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:_-.', $pi_target_length ); + + $this->comment_type = self::COMMENT_AS_PI_NODE_LOOKALIKE; + $this->tag_name_starts_at = $this->token_starts_at + 2; + $this->tag_name_length = $pi_target_length; + $this->text_starts_at += $pi_target_length; + $this->text_length -= $pi_target_length + 1; + } + } + + return true; } /* * If a non-alpha starts the tag name in a tag closer it's a comment. * Find the first `>`, which closes the comment. * + * This parser classifies these particular comments as special "funky comments" + * which are made available for further processing. + * * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name */ if ( $this->is_closing_tag ) { @@ -1525,13 +1874,17 @@ private function parse_next_tag() { $closer_at = strpos( $html, '>', $at + 3 ); if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } - $at = $closer_at + 1; - continue; + $this->parser_state = self::STATE_FUNKY_COMMENT; + $this->token_length = $closer_at + 1 - $this->token_starts_at; + $this->text_starts_at = $this->token_starts_at + 2; + $this->text_length = $closer_at - $this->text_starts_at; + $this->bytes_already_parsed = $closer_at + 1; + return true; } ++$at; @@ -1551,7 +1904,7 @@ private function parse_next_attribute() { // Skip whitespace and slashes. $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n/", $this->bytes_already_parsed ); if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } @@ -1575,14 +1928,14 @@ private function parse_next_attribute() { $attribute_name = substr( $this->html, $attribute_start, $name_length ); $this->bytes_already_parsed += $name_length; if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } $this->skip_whitespace(); if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } @@ -1592,7 +1945,7 @@ private function parse_next_attribute() { ++$this->bytes_already_parsed; $this->skip_whitespace(); if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } @@ -1620,7 +1973,7 @@ private function parse_next_attribute() { } if ( $attribute_end >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE; + $this->parser_state = self::STATE_INCOMPLETE_INPUT; return false; } @@ -1692,8 +2045,11 @@ private function after_tag() { $this->token_length = null; $this->tag_name_starts_at = null; $this->tag_name_length = null; + $this->text_starts_at = 0; + $this->text_length = 0; $this->is_closing_tag = null; $this->attributes = array(); + $this->comment_type = null; $this->duplicate_attributes = null; } @@ -1985,7 +2341,8 @@ public function seek( $bookmark_name ) { // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - return $this->next_tag( array( 'tag_closers' => 'visit' ) ); + $this->parser_state = self::STATE_READY; + return $this->next_token(); } /** @@ -2216,13 +2573,24 @@ public function get_attribute_names_with_prefix( $prefix ) { * @return string|null Name of currently matched tag in input HTML, or `null` if none found. */ public function get_tag() { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { + if ( null === $this->tag_name_starts_at ) { return null; } $tag_name = substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length ); - return strtoupper( $tag_name ); + if ( self::STATE_MATCHED_TAG === $this->parser_state ) { + return strtoupper( $tag_name ); + } + + if ( + self::STATE_COMMENT === $this->parser_state && + self::COMMENT_AS_PI_NODE_LOOKALIKE === $this->get_comment_type() + ) { + return $tag_name; + } + + return null; } /** @@ -2281,6 +2649,191 @@ public function is_tag_closer() { ); } + /** + * Indicates the kind of matched token, if any. + * + * This differs from `get_token_name()` in that it always + * returns a static string indicating the type, whereas + * `get_token_name()` may return values derived from the + * token itself, such as a tag name or processing + * instruction tag. + * + * Possible values: + * - `#tag` when matched on a tag. + * - `#text` when matched on a text node. + * - `#cdata-section` when matched on a CDATA node. + * - `#comment` when matched on a comment. + * - `#doctype` when matched on a DOCTYPE declaration. + * - `#presumptuous-tag` when matched on an empty tag closer. + * - `#funky-comment` when matched on a funky comment. + * + * @since 6.5.0 + * + * @return string|null What kind of token is matched, or null. + */ + public function get_token_type() { + switch ( $this->parser_state ) { + case self::STATE_MATCHED_TAG: + return '#tag'; + + case self::STATE_DOCTYPE: + return '#doctype'; + + default: + return $this->get_token_name(); + } + } + + /** + * Returns the node name represented by the token. + * + * This matches the DOM API value `nodeName`. Some values + * are static, such as `#text` for a text node, while others + * are dynamically generated from the token itself. + * + * Dynamic names: + * - Uppercase tag name for tag matches. + * - `html` for DOCTYPE declarations. + * + * Note that if the Tag Processor is not matched on a token + * then this function will return `null`, either because it + * hasn't yet found a token or because it reached the end + * of the document without matching a token. + * + * @since 6.5.0 + * + * @return string|null Name of the matched token. + */ + public function get_token_name() { + switch ( $this->parser_state ) { + case self::STATE_MATCHED_TAG: + return $this->get_tag(); + + case self::STATE_TEXT_NODE: + return '#text'; + + case self::STATE_CDATA_NODE: + return '#cdata-section'; + + case self::STATE_COMMENT: + return '#comment'; + + case self::STATE_DOCTYPE: + return 'html'; + + case self::STATE_PRESUMPTUOUS_TAG: + return '#presumptuous-tag'; + + case self::STATE_FUNKY_COMMENT: + return '#funky-comment'; + } + } + + /** + * 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.5.0 + * + * @return string|null + */ + public function get_comment_type() { + if ( self::STATE_COMMENT !== $this->parser_state ) { + return null; + } + + return $this->comment_type; + } + + /** + * Returns the modifiable text for a matched token, or an empty string. + * + * Modifiable text is text content that may be read and changed without + * changing the HTML structure of the document around it. This includes + * the contents of `#text` nodes in the HTML as well as the inner + * contents of HTML comments, Processing Instructions, and others, even + * though these nodes aren't part of a parsed DOM tree. They also contain + * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any + * other section in an HTML document which cannot contain HTML markup (DATA). + * + * If a token has no modifiable text then an empty string is returned to + * avoid needless crashing or type errors. An empty string does not mean + * that a token has modifiable text, and a token with modifiable text may + * have an empty string (e.g. a comment with no contents). + * + * @since 6.5.0 + * + * @return string + */ + public function get_modifiable_text() { + if ( null === $this->text_starts_at ) { + return ''; + } + + $text = substr( $this->html, $this->text_starts_at, $this->text_length ); + + // Comment data is not decoded. + if ( + self::STATE_CDATA_NODE === $this->parser_state || + self::STATE_COMMENT === $this->parser_state || + self::STATE_DOCTYPE === $this->parser_state || + self::STATE_FUNKY_COMMENT === $this->parser_state + ) { + return $text; + } + + $tag_name = $this->get_tag(); + if ( + // Script data is not decoded. + 'SCRIPT' === $tag_name || + + // RAWTEXT data is not decoded. + 'IFRAME' === $tag_name || + 'NOEMBED' === $tag_name || + 'NOFRAMES' === $tag_name || + 'STYLE' === $tag_name || + 'XMP' === $tag_name + ) { + return $text; + } + + $decoded = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE ); + + if ( empty( $decoded ) ) { + return ''; + } + + /* + * TEXTAREA skips a leading newline, but this newline may appear not only as the + * literal character `\n`, but also as a character reference, such as in the + * following markup: ``. + * + * For these cases it's important to first decode the text content before checking + * for a leading newline and removing it. + */ + if ( + self::STATE_MATCHED_TAG === $this->parser_state && + 'TEXTAREA' === $tag_name && + strlen( $decoded ) > 0 && + "\n" === $decoded[0] + ) { + return substr( $decoded, 1 ); + } + + return $decoded; + } + /** * Updates or creates a new attribute on the currently matched tag with the passed value. * @@ -2746,7 +3299,7 @@ private function matches() { } /** - * Parser Ready State + * Parser Ready State. * * Indicates that the parser is ready to run and waiting for a state transition. * It may not have started yet, or it may have just finished parsing a token and @@ -2759,7 +3312,7 @@ private function matches() { const STATE_READY = 'STATE_READY'; /** - * Parser Complete State + * Parser Complete State. * * Indicates that the parser has reached the end of the document and there is * nothing left to scan. It finished parsing the last token completely. @@ -2771,7 +3324,7 @@ private function matches() { const STATE_COMPLETE = 'STATE_COMPLETE'; /** - * Parser Incomplete State + * Parser Incomplete Input State. * * Indicates that the parser has reached the end of the document before finishing * a token. It started parsing a token but there is a possibility that the input @@ -2784,10 +3337,10 @@ private function matches() { * * @access private */ - const STATE_INCOMPLETE = 'STATE_INCOMPLETE'; + const STATE_INCOMPLETE_INPUT = 'STATE_INCOMPLETE_INPUT'; /** - * Parser Matched Tag State + * Parser Matched Tag State. * * Indicates that the parser has found an HTML tag and it's possible to get * the tag name and read or modify its attributes (if it's not a closing tag). @@ -2797,4 +3350,153 @@ private function matches() { * @access private */ const STATE_MATCHED_TAG = 'STATE_MATCHED_TAG'; + + /** + * Parser Text Node State. + * + * Indicates that the parser has found a text node and it's possible + * to read and modify that text. + * + * @since 6.5.0 + * + * @access private + */ + const STATE_TEXT_NODE = 'STATE_TEXT_NODE'; + + /** + * Parser CDATA Node State. + * + * Indicates that the parser has found a CDATA node and it's possible + * to read and modify its modifiable text. Note that in HTML there are + * no CDATA nodes outside of foreign content (SVG and MathML). Outside + * of foreign content, they are treated as HTML comments. + * + * @since 6.5.0 + * + * @access private + */ + const STATE_CDATA_NODE = 'STATE_CDATA_NODE'; + + /** + * Indicates that the parser has found an HTML comment and it's + * possible to read and modify its modifiable text. + * + * @since 6.5.0 + * + * @access private + */ + const STATE_COMMENT = 'STATE_COMMENT'; + + /** + * Indicates that the parser has found a DOCTYPE node and it's + * possible to read and modify its modifiable text. + * + * @since 6.5.0 + * + * @access private + */ + const STATE_DOCTYPE = 'STATE_DOCTYPE'; + + /** + * Indicates that the parser has found an empty tag closer ``. + * + * Note that in HTML there are no empty tag closers, and they + * are ignored. Nonetheless, the Tag Processor still + * recognizes them as they appear in the HTML stream. + * + * These were historically discussed as a "presumptuous tag + * closer," which would close the nearest open tag, but were + * dismissed in favor of explicitly-closing tags. + * + * @since 6.5.0 + * + * @access private + */ + const STATE_PRESUMPTUOUS_TAG = 'STATE_PRESUMPTUOUS_TAG'; + + /** + * Indicates that the parser has found a "funky comment" + * and it's possible to read and modify its modifiable text. + * + * Example: + * + * + * + * + * + * Funky comments are tag closers with invalid tag names. Note + * that in HTML these are turn into bogus comments. Nonetheless, + * the Tag Processor recognizes them in a stream of HTML and + * exposes them for inspection and modification. + * + * @since 6.5.0 + * + * @access private + */ + const STATE_FUNKY_COMMENT = 'STATE_WP_FUNKY'; + + /** + * Indicates that a comment was created when encountering abruptly-closed HTML comment. + * + * Example: + * + * + * + * + * @since 6.5.0 + */ + const COMMENT_AS_ABRUPTLY_CLOSED_COMMENT = 'COMMENT_AS_ABRUPTLY_CLOSED_COMMENT'; + + /** + * Indicates that a comment would be parsed as a CDATA node, + * were HTML to allow CDATA nodes outside of foreign content. + * + * Example: + * + * + * + * This is an HTML comment, but it looks like a CDATA node. + * + * @since 6.5.0 + */ + const COMMENT_AS_CDATA_LOOKALIKE = 'COMMENT_AS_CDATA_LOOKALIKE'; + + /** + * Indicates that a comment was created when encountering + * normative HTML comment syntax. + * + * Example: + * + * + * + * @since 6.5.0 + */ + const COMMENT_AS_HTML_COMMENT = 'COMMENT_AS_HTML_COMMENT'; + + /** + * Indicates that a comment would be parsed as a Processing + * Instruction node, were they to exist within HTML. + * + * Example: + * + * + * + * This is an HTML comment, but it looks like a CDATA node. + * + * @since 6.5.0 + */ + const COMMENT_AS_PI_NODE_LOOKALIKE = 'COMMENT_AS_PI_NODE_LOOKALIKE'; + + /** + * Indicates that a comment was created when encountering invalid + * HTML input, a so-called "bogus comment." + * + * Example: + * + * + * + * + * @since 6.5.0 + */ + const COMMENT_AS_INVALID_HTML = 'COMMENT_AS_INVALID_HTML'; } diff --git a/src/wp-includes/images/smilies/icon_cry.gif b/src/wp-includes/images/smilies/icon_cry.gif index b78b2b33fc5a3..15a571ff7b0b1 100644 Binary files a/src/wp-includes/images/smilies/icon_cry.gif and b/src/wp-includes/images/smilies/icon_cry.gif differ diff --git a/src/wp-includes/images/smilies/icon_lol.gif b/src/wp-includes/images/smilies/icon_lol.gif index 83bcad32b5c97..4ad3baa3a7fa8 100644 Binary files a/src/wp-includes/images/smilies/icon_lol.gif and b/src/wp-includes/images/smilies/icon_lol.gif differ diff --git a/src/wp-includes/images/smilies/icon_redface.gif b/src/wp-includes/images/smilies/icon_redface.gif index c837b6276bfc3..6dd3a617fed9b 100644 Binary files a/src/wp-includes/images/smilies/icon_redface.gif and b/src/wp-includes/images/smilies/icon_redface.gif differ diff --git a/src/wp-includes/images/smilies/icon_rolleyes.gif b/src/wp-includes/images/smilies/icon_rolleyes.gif index 20fbc902abd5c..f038278b88a07 100644 Binary files a/src/wp-includes/images/smilies/icon_rolleyes.gif and b/src/wp-includes/images/smilies/icon_rolleyes.gif differ diff --git a/src/wp-includes/images/wpspin-2x.gif b/src/wp-includes/images/wpspin-2x.gif index 08e47e8211c4d..978f585b983b2 100644 Binary files a/src/wp-includes/images/wpspin-2x.gif and b/src/wp-includes/images/wpspin-2x.gif differ diff --git a/src/wp-includes/images/wpspin.gif b/src/wp-includes/images/wpspin.gif index fbf9be46c1094..b9b7ae4875168 100644 Binary files a/src/wp-includes/images/wpspin.gif and b/src/wp-includes/images/wpspin.gif differ diff --git a/src/wp-includes/images/xit.gif b/src/wp-includes/images/xit.gif index b11c5d43e9bce..9e62856adb47e 100644 Binary files a/src/wp-includes/images/xit.gif and b/src/wp-includes/images/xit.gif differ diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php new file mode 100644 index 0000000000000..24201e82fdd0a --- /dev/null +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php @@ -0,0 +1,244 @@ +get_tag() ) { + return null; + } + + $positions = $this->get_after_opener_tag_and_before_closer_tag_positions(); + if ( ! $positions ) { + return null; + } + list( $after_opener_tag, $before_closer_tag ) = $positions; + + return substr( $this->html, $after_opener_tag, $before_closer_tag - $after_opener_tag ); + } + + /** + * Sets the content between two balanced tags. + * + * @since 6.5.0 + * + * @access private + * + * @param string $new_content The string to replace the content between the matching tags. + * @return bool Whether the content was successfully replaced. + */ + public function set_content_between_balanced_tags( string $new_content ): bool { + $positions = $this->get_after_opener_tag_and_before_closer_tag_positions( true ); + if ( ! $positions ) { + return false; + } + list( $after_opener_tag, $before_closer_tag ) = $positions; + + $this->lexical_updates[] = new WP_HTML_Text_Replacement( + $after_opener_tag, + $before_closer_tag - $after_opener_tag, + esc_html( $new_content ) + ); + + return true; + } + + /** + * Appends content after the closing tag of a template tag. + * + * It positions the cursor in the closer tag of the balanced template tag, + * if it exists. + * + * @access private + * + * @param string $new_content The string to append after the closing template tag. + * @return bool Whether the content was successfully appended. + */ + public function append_content_after_template_tag_closer( string $new_content ): bool { + if ( empty( $new_content ) || 'TEMPLATE' !== $this->get_tag() || ! $this->is_tag_closer() ) { + return false; + } + + // Flushes any changes. + $this->get_updated_html(); + + $bookmark = 'append_content_after_template_tag_closer'; + $this->set_bookmark( $bookmark ); + $after_closing_tag = $this->bookmarks[ $bookmark ]->start + $this->bookmarks[ $bookmark ]->length + 1; + $this->release_bookmark( $bookmark ); + + // Appends the new content. + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $after_closing_tag, 0, $new_content ); + + return true; + } + + /** + * Gets the positions right after the opener tag and right before the closer + * tag in a balanced tag. + * + * By default, it positions the cursor in the closer tag of the balanced tag. + * If $rewind is true, it seeks back to the opener tag. + * + * @since 6.5.0 + * + * @access private + * + * @param bool $rewind Optional. Whether to seek back to the opener tag after finding the positions. Defaults to false. + * @return array|null Start and end byte position, or null when no balanced tag bookmarks. + */ + private function get_after_opener_tag_and_before_closer_tag_positions( bool $rewind = false ) { + // Flushes any changes. + $this->get_updated_html(); + + $bookmarks = $this->get_balanced_tag_bookmarks(); + if ( ! $bookmarks ) { + return null; + } + list( $opener_tag, $closer_tag ) = $bookmarks; + + $after_opener_tag = $this->bookmarks[ $opener_tag ]->start + $this->bookmarks[ $opener_tag ]->length + 1; + $before_closer_tag = $this->bookmarks[ $closer_tag ]->start; + + if ( $rewind ) { + $this->seek( $opener_tag ); + } + + $this->release_bookmark( $opener_tag ); + $this->release_bookmark( $closer_tag ); + + return array( $after_opener_tag, $before_closer_tag ); + } + + /** + * Returns a pair of bookmarks for the current opener tag and the matching + * closer tag. + * + * It positions the cursor in the closer tag of the balanced tag, if it + * exists. + * + * @since 6.5.0 + * + * @return array|null A pair of bookmarks, or null if there's no matching closing tag. + */ + private function get_balanced_tag_bookmarks() { + static $i = 0; + $opener_tag = 'opener_tag_of_balanced_tag_' . ++$i; + + $this->set_bookmark( $opener_tag ); + if ( ! $this->next_balanced_tag_closer_tag() ) { + $this->release_bookmark( $opener_tag ); + return null; + } + + $closer_tag = 'closer_tag_of_balanced_tag_' . ++$i; + $this->set_bookmark( $closer_tag ); + + return array( $opener_tag, $closer_tag ); + } + + /** + * Finds the matching closing tag for an opening tag. + * + * When called while the processor is on an open tag, it traverses the HTML + * until it finds the matching closer tag, respecting any in-between content, + * including nested tags of the same name. Returns false when called on a + * closer tag, a tag that doesn't have a closer tag (void), a tag that + * doesn't visit the closer tag, or if no matching closing tag was found. + * + * @since 6.5.0 + * + * @access private + * + * @return bool Whether a matching closing tag was found. + */ + public function next_balanced_tag_closer_tag(): bool { + $depth = 0; + $tag_name = $this->get_tag(); + + if ( ! $this->has_and_visits_its_closer_tag() ) { + return false; + } + + while ( $this->next_tag( + array( + 'tag_name' => $tag_name, + 'tag_closers' => 'visit', + ) + ) ) { + if ( ! $this->is_tag_closer() ) { + ++$depth; + continue; + } + + if ( 0 === $depth ) { + return true; + } + + --$depth; + } + + return false; + } + + /** + * Checks whether the current tag has and will visit its matching closer tag. + * + * @since 6.5.0 + * + * @access private + * + * @return bool Whether the current tag has a closer tag. + */ + public function has_and_visits_its_closer_tag(): bool { + $tag_name = $this->get_tag(); + + return null !== $tag_name && ( + ! WP_HTML_Processor::is_void( $tag_name ) && + ! in_array( $tag_name, self::TAGS_THAT_DONT_VISIT_CLOSER_TAG, true ) + ); + } +} diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php new file mode 100644 index 0000000000000..bb491a54f4ee2 --- /dev/null +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -0,0 +1,950 @@ + 'data_wp_interactive_processor', + 'data-wp-router-region' => 'data_wp_router_region_processor', + 'data-wp-context' => 'data_wp_context_processor', + 'data-wp-bind' => 'data_wp_bind_processor', + 'data-wp-class' => 'data_wp_class_processor', + 'data-wp-style' => 'data_wp_style_processor', + 'data-wp-text' => 'data_wp_text_processor', + /* + * `data-wp-each` needs to be processed in the last place because it moves + * the cursor to the end of the processed items to prevent them to be + * processed twice. + */ + 'data-wp-each' => 'data_wp_each_processor', + ); + + /** + * Holds the initial state of the different Interactivity API stores. + * + * This state is used during the server directive processing. Then, it is + * serialized and sent to the client as part of the interactivity data to be + * recovered during the hydration of the client interactivity stores. + * + * @since 6.5.0 + * @var array + */ + private $state_data = array(); + + /** + * Holds the configuration required by the different Interactivity API stores. + * + * This configuration is serialized and sent to the client as part of the + * interactivity data and can be accessed by the client interactivity stores. + * + * @since 6.5.0 + * @var array + */ + private $config_data = array(); + + /** + * Flag that indicates whether the `data-wp-router-region` directive has + * been found in the HTML and processed. + * + * The value is saved in a private property of the WP_Interactivity_API + * instance instead of using a static variable inside the processor + * function, which would hold the same value for all instances + * independently of whether they have processed any + * `data-wp-router-region` directive or not. + * + * @since 6.5.0 + * @var bool + */ + private $has_processed_router_region = false; + + /** + * Gets and/or sets the initial state of an Interactivity API store for a + * given namespace. + * + * If state for that store namespace already exists, it merges the new + * provided state with the existing one. + * + * @since 6.5.0 + * + * @param string $store_namespace The unique store namespace identifier. + * @param array $state Optional. The array that will be merged with the existing state for the specified + * store namespace. + * @return array The current state for the specified store namespace. This will be the updated state if a $state + * argument was provided. + */ + public function state( string $store_namespace, array $state = array() ): array { + if ( ! isset( $this->state_data[ $store_namespace ] ) ) { + $this->state_data[ $store_namespace ] = array(); + } + if ( is_array( $state ) ) { + $this->state_data[ $store_namespace ] = array_replace_recursive( + $this->state_data[ $store_namespace ], + $state + ); + } + return $this->state_data[ $store_namespace ]; + } + + /** + * Gets and/or sets the configuration of the Interactivity API for a given + * store namespace. + * + * If configuration for that store namespace exists, it merges the new + * provided configuration with the existing one. + * + * @since 6.5.0 + * + * @param string $store_namespace The unique store namespace identifier. + * @param array $config Optional. The array that will be merged with the existing configuration for the + * specified store namespace. + * @return array The configuration for the specified store namespace. This will be the updated configuration if a + * $config argument was provided. + */ + public function config( string $store_namespace, array $config = array() ): array { + if ( ! isset( $this->config_data[ $store_namespace ] ) ) { + $this->config_data[ $store_namespace ] = array(); + } + if ( is_array( $config ) ) { + $this->config_data[ $store_namespace ] = array_replace_recursive( + $this->config_data[ $store_namespace ], + $config + ); + } + return $this->config_data[ $store_namespace ]; + } + + /** + * Prints the serialized client-side interactivity data. + * + * Encodes the config and initial state into JSON and prints them inside a + * script tag of type "application/json". Once in the browser, the state will + * be parsed and used to hydrate the client-side interactivity stores and the + * configuration will be available using a `getConfig` utility. + * + * @since 6.5.0 + */ + public function print_client_interactivity_data() { + $store = array(); + $has_state = ! empty( $this->state_data ); + $has_config = ! empty( $this->config_data ); + + if ( $has_state || $has_config ) { + if ( $has_config ) { + $store['config'] = $this->config_data; + } + if ( $has_state ) { + $store['state'] = $this->state_data; + } + wp_print_inline_script_tag( + wp_json_encode( + $store, + JSON_HEX_TAG | JSON_HEX_AMP + ), + array( + 'type' => 'application/json', + 'id' => 'wp-interactivity-data', + ) + ); + } + } + + /** + * Registers the `@wordpress/interactivity` script modules. + * + * @since 6.5.0 + */ + public function register_script_modules() { + $suffix = wp_scripts_get_suffix(); + + wp_register_script_module( + '@wordpress/interactivity', + includes_url( "js/dist/interactivity$suffix.js" ) + ); + + wp_register_script_module( + '@wordpress/interactivity-router', + includes_url( "js/dist/interactivity-router$suffix.js" ), + array( '@wordpress/interactivity' ) + ); + } + + /** + * Adds the necessary hooks for the Interactivity API. + * + * @since 6.5.0 + */ + public function add_hooks() { + add_action( 'wp_enqueue_scripts', array( $this, 'register_script_modules' ) ); + add_action( 'wp_footer', array( $this, 'print_client_interactivity_data' ) ); + } + + /** + * Processes the interactivity directives contained within the HTML content + * and updates the markup accordingly. + * + * @since 6.5.0 + * + * @param string $html The HTML content to process. + * @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags. + */ + public function process_directives( string $html ): string { + $context_stack = array(); + $namespace_stack = array(); + $result = $this->process_directives_args( $html, $context_stack, $namespace_stack ); + return null === $result ? $html : $result; + } + + /** + * Processes the interactivity directives contained within the HTML content + * and updates the markup accordingly. + * + * It needs the context and namespace stacks to be passed by reference, and + * it returns null if the HTML contains unbalanced tags. + * + * @since 6.5.0 + * + * @param string $html The HTML content to process. + * @param array $context_stack The reference to the array used to keep track of contexts during processing. + * @param array $namespace_stack The reference to the array used to manage namespaces during processing. + * @return string|null The processed HTML content. It returns null when the HTML contains unbalanced tags. + */ + private function process_directives_args( string $html, array &$context_stack, array &$namespace_stack ) { + $p = new WP_Interactivity_API_Directives_Processor( $html ); + $tag_stack = array(); + $unbalanced = false; + + $directive_processor_prefixes = array_keys( self::$directive_processors ); + $directive_processor_prefixes_reversed = array_reverse( $directive_processor_prefixes ); + + while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + $tag_name = $p->get_tag(); + + if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) { + $unbalanced = true; + break; + } + + if ( $p->is_tag_closer() ) { + list( $opening_tag_name, $directives_prefixes ) = end( $tag_stack ); + + if ( 0 === count( $tag_stack ) || $opening_tag_name !== $tag_name ) { + + /* + * If the tag stack is empty or the matching opening tag is not the + * same than the closing tag, it means the HTML is unbalanced and it + * stops processing it. + */ + $unbalanced = true; + break; + } else { + // Remove the last tag from the stack. + array_pop( $tag_stack ); + } + } else { + if ( 0 !== count( $p->get_attribute_names_with_prefix( 'data-wp-each-child' ) ) ) { + /* + * If the tag has a `data-wp-each-child` directive, jump to its closer + * tag because those tags have already been processed. + */ + $p->next_balanced_tag_closer_tag(); + continue; + } else { + $directives_prefixes = array(); + + // Checks if there is a server directive processor registered for each directive. + foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) as $attribute_name ) { + list( $directive_prefix ) = $this->extract_prefix_and_suffix( $attribute_name ); + if ( array_key_exists( $directive_prefix, self::$directive_processors ) ) { + $directives_prefixes[] = $directive_prefix; + } + } + + /* + * If this tag will visit its closer tag, it adds it to the tag stack + * so it can process its closing tag and check for unbalanced tags. + */ + if ( $p->has_and_visits_its_closer_tag() ) { + $tag_stack[] = array( $tag_name, $directives_prefixes ); + } + } + } + /* + * If the matching opener tag didn't have any directives, it can skip the + * processing. + */ + if ( 0 === count( $directives_prefixes ) ) { + continue; + } + + /* + * Sorts the attributes by the order of the `directives_processor` array + * and checks what directives are present in this element. The processing + * order is reversed for tag closers. + */ + $directives_prefixes = array_intersect( + $p->is_tag_closer() + ? $directive_processor_prefixes_reversed + : $directive_processor_prefixes, + $directives_prefixes + ); + + // Executes the directive processors present in this element. + foreach ( $directives_prefixes as $directive_prefix ) { + $func = is_array( self::$directive_processors[ $directive_prefix ] ) + ? self::$directive_processors[ $directive_prefix ] + : array( $this, self::$directive_processors[ $directive_prefix ] ); + call_user_func_array( + $func, + array( $p, &$context_stack, &$namespace_stack, &$tag_stack ) + ); + } + } + + /* + * It returns null if the HTML is unbalanced because unbalanced HTML is + * not safe to process. In that case, the Interactivity API runtime will + * update the HTML on the client side during the hydration. + */ + return $unbalanced || 0 < count( $tag_stack ) ? null : $p->get_updated_html(); + } + + /** + * Evaluates the reference path passed to a directive based on the current + * store namespace, state and context. + * + * @since 6.5.0 + * + * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute. + * @param string $default_namespace The default namespace to use if none is explicitly defined in the directive + * value. + * @param array|false $context The current context for evaluating the directive or false if there is no + * context. + * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist. + */ + private function evaluate( $directive_value, string $default_namespace, $context = false ) { + list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace ); + if ( empty( $path ) ) { + return null; + } + + $store = array( + 'state' => $this->state_data[ $ns ] ?? array(), + 'context' => $context[ $ns ] ?? array(), + ); + + // Checks if the reference path is preceded by a negation operator (!). + $should_negate_value = '!' === $path[0]; + $path = $should_negate_value ? substr( $path, 1 ) : $path; + + // Extracts the value from the store using the reference path. + $path_segments = explode( '.', $path ); + $current = $store; + foreach ( $path_segments as $path_segment ) { + if ( isset( $current[ $path_segment ] ) ) { + $current = $current[ $path_segment ]; + } else { + return null; + } + } + + // Returns the opposite if it contains a negation operator (!). + return $should_negate_value ? ! $current : $current; + } + + /** + * Extracts the directive attribute name to separate and return the directive + * prefix and an optional suffix. + * + * The suffix is the string after the first double hyphen and the prefix is + * everything that comes before the suffix. + * + * Example: + * + * extract_prefix_and_suffix( 'data-wp-interactive' ) => array( 'data-wp-interactive', null ) + * extract_prefix_and_suffix( 'data-wp-bind--src' ) => array( 'data-wp-bind', 'src' ) + * extract_prefix_and_suffix( 'data-wp-foo--and--bar' ) => array( 'data-wp-foo', 'and--bar' ) + * + * @since 6.5.0 + * + * @param string $directive_name The directive attribute name. + * @return array An array containing the directive prefix and optional suffix. + */ + private function extract_prefix_and_suffix( string $directive_name ): array { + return explode( '--', $directive_name, 2 ); + } + + /** + * Parses and extracts the namespace and reference path from the given + * directive attribute value. + * + * If the value doesn't contain an explicit namespace, it returns the + * default one. If the value contains a JSON object instead of a reference + * path, the function tries to parse it and return the resulting array. If + * the value contains strings that represent booleans ("true" and "false"), + * numbers ("1" and "1.2") or "null", the function also transform them to + * regular booleans, numbers and `null`. + * + * Example: + * + * extract_directive_value( 'actions.foo', 'myPlugin' ) => array( 'myPlugin', 'actions.foo' ) + * extract_directive_value( 'otherPlugin::actions.foo', 'myPlugin' ) => array( 'otherPlugin', 'actions.foo' ) + * extract_directive_value( '{ "isOpen": false }', 'myPlugin' ) => array( 'myPlugin', array( 'isOpen' => false ) ) + * extract_directive_value( 'otherPlugin::{ "isOpen": false }', 'myPlugin' ) => array( 'otherPlugin', array( 'isOpen' => false ) ) + * + * @since 6.5.0 + * + * @param string|true $directive_value The directive attribute value. It can be `true` when it's a boolean + * attribute. + * @param string|null $default_namespace Optional. The default namespace if none is explicitly defined. + * @return array An array containing the namespace in the first item and the JSON, the reference path, or null on the + * second item. + */ + private function extract_directive_value( $directive_value, $default_namespace = null ): array { + if ( empty( $directive_value ) || is_bool( $directive_value ) ) { + return array( $default_namespace, null ); + } + + // Replaces the value and namespace if there is a namespace in the value. + if ( 1 === preg_match( '/^([\w\-_\/]+)::./', $directive_value ) ) { + list($default_namespace, $directive_value) = explode( '::', $directive_value, 2 ); + } + + /* + * Tries to decode the value as a JSON object. If it fails and the value + * isn't `null`, it returns the value as it is. Otherwise, it returns the + * decoded JSON or null for the string `null`. + */ + $decoded_json = json_decode( $directive_value, true ); + if ( null !== $decoded_json || 'null' === $directive_value ) { + $directive_value = $decoded_json; + } + + return array( $default_namespace, $directive_value ); + } + + /** + * Transforms a kebab-case string to camelCase. + * + * @param string $str The kebab-case string to transform to camelCase. + * @return string The transformed camelCase string. + */ + private function kebab_to_camel_case( string $str ): string { + return lcfirst( + preg_replace_callback( + '/(-)([a-z])/', + function ( $matches ) { + return strtoupper( $matches[2] ); + }, + strtolower( rtrim( $str, '-' ) ) + ) + ); + } + + /** + * Processes the `data-wp-interactive` directive. + * + * It adds the default store namespace defined in the directive value to the + * stack so that it's available for the nested interactivity elements. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + */ + private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) { + // In closing tags, it removes the last namespace from the stack. + if ( $p->is_tag_closer() ) { + array_pop( $namespace_stack ); + return; + } + + // Tries to decode the `data-wp-interactive` attribute value. + $attribute_value = $p->get_attribute( 'data-wp-interactive' ); + + /* + * Pushes the newly defined namespace or the current one if the + * `data-wp-interactive` definition was invalid or does not contain a + * namespace. It does so because the function pops out the current namespace + * from the stack whenever it finds a `data-wp-interactive`'s closing tag, + * independently of whether the previous `data-wp-interactive` definition + * contained a valid namespace. + */ + $new_namespace = null; + if ( is_string( $attribute_value ) && ! empty( $attribute_value ) ) { + $decoded_json = json_decode( $attribute_value, true ); + if ( is_array( $decoded_json ) ) { + $new_namespace = $decoded_json['namespace'] ?? null; + } else { + $new_namespace = $attribute_value; + } + } + $namespace_stack[] = ( $new_namespace && 1 === preg_match( '/^([\w\-_\/]+)/', $new_namespace ) ) + ? $new_namespace + : end( $namespace_stack ); + } + + /** + * Processes the `data-wp-context` directive. + * + * It adds the context defined in the directive value to the stack so that + * it's available for the nested interactivity elements. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + */ + private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) { + // In closing tags, it removes the last context from the stack. + if ( $p->is_tag_closer() ) { + array_pop( $context_stack ); + return; + } + + $attribute_value = $p->get_attribute( 'data-wp-context' ); + $namespace_value = end( $namespace_stack ); + + // Separates the namespace from the context JSON object. + list( $namespace_value, $decoded_json ) = is_string( $attribute_value ) && ! empty( $attribute_value ) + ? $this->extract_directive_value( $attribute_value, $namespace_value ) + : array( $namespace_value, null ); + + /* + * If there is a namespace, it adds a new context to the stack merging the + * previous context with the new one. + */ + if ( is_string( $namespace_value ) ) { + $context_stack[] = array_replace_recursive( + end( $context_stack ) !== false ? end( $context_stack ) : array(), + array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() ) + ); + } else { + /* + * If there is no namespace, it pushes the current context to the stack. + * It needs to do so because the function pops out the current context + * from the stack whenever it finds a `data-wp-context`'s closing tag. + */ + $context_stack[] = end( $context_stack ); + } + } + + /** + * Processes the `data-wp-bind` directive. + * + * It updates or removes the bound attributes based on the evaluation of its + * associated reference. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + */ + private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) { + if ( ! $p->is_tag_closer() ) { + $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' ); + + foreach ( $all_bind_directives as $attribute_name ) { + list( , $bound_attribute ) = $this->extract_prefix_and_suffix( $attribute_name ); + if ( empty( $bound_attribute ) ) { + return; + } + + $attribute_value = $p->get_attribute( $attribute_name ); + $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) ); + + if ( null !== $result && ( false !== $result || '-' === $bound_attribute[4] ) ) { + /* + * If the result of the evaluation is a boolean and the attribute is + * `aria-` or `data-, convert it to a string "true" or "false". It + * follows the exact same logic as Preact because it needs to + * replicate what Preact will later do in the client: + * https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L131C24-L136 + */ + if ( is_bool( $result ) && '-' === $bound_attribute[4] ) { + $result = $result ? 'true' : 'false'; + } + $p->set_attribute( $bound_attribute, $result ); + } else { + $p->remove_attribute( $bound_attribute ); + } + } + } + } + + /** + * Processes the `data-wp-class` directive. + * + * It adds or removes CSS classes in the current HTML element based on the + * evaluation of its associated references. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + */ + private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) { + if ( ! $p->is_tag_closer() ) { + $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' ); + + foreach ( $all_class_directives as $attribute_name ) { + list( , $class_name ) = $this->extract_prefix_and_suffix( $attribute_name ); + if ( empty( $class_name ) ) { + return; + } + + $attribute_value = $p->get_attribute( $attribute_name ); + $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) ); + + if ( $result ) { + $p->add_class( $class_name ); + } else { + $p->remove_class( $class_name ); + } + } + } + } + + /** + * Processes the `data-wp-style` directive. + * + * It updates the style attribute value of the current HTML element based on + * the evaluation of its associated references. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + */ + private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) { + if ( ! $p->is_tag_closer() ) { + $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' ); + + foreach ( $all_style_attributes as $attribute_name ) { + list( , $style_property ) = $this->extract_prefix_and_suffix( $attribute_name ); + if ( empty( $style_property ) ) { + continue; + } + + $directive_attribute_value = $p->get_attribute( $attribute_name ); + $style_property_value = $this->evaluate( $directive_attribute_value, end( $namespace_stack ), end( $context_stack ) ); + $style_attribute_value = $p->get_attribute( 'style' ); + $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : ''; + + /* + * Checks first if the style property is not falsy and the style + * attribute value is not empty because if it is, it doesn't need to + * update the attribute value. + */ + if ( $style_property_value || $style_attribute_value ) { + $style_attribute_value = $this->merge_style_property( $style_attribute_value, $style_property, $style_property_value ); + /* + * If the style attribute value is not empty, it sets it. Otherwise, + * it removes it. + */ + if ( ! empty( $style_attribute_value ) ) { + $p->set_attribute( 'style', $style_attribute_value ); + } else { + $p->remove_attribute( 'style' ); + } + } + } + } + } + + /** + * Merges an individual style property in the `style` attribute of an HTML + * element, updating or removing the property when necessary. + * + * If a property is modified, the old one is removed and the new one is added + * at the end of the list. + * + * @since 6.5.0 + * + * Example: + * + * merge_style_property( 'color:green;', 'color', 'red' ) => 'color:red;' + * merge_style_property( 'background:green;', 'color', 'red' ) => 'background:green;color:red;' + * merge_style_property( 'color:green;', 'color', null ) => '' + * + * @param string $style_attribute_value The current style attribute value. + * @param string $style_property_name The style property name to set. + * @param string|false|null $style_property_value The value to set for the style property. With false, null or an + * empty string, it removes the style property. + * @return string The new style attribute value after the specified property has been added, updated or removed. + */ + private function merge_style_property( string $style_attribute_value, string $style_property_name, $style_property_value ): string { + $style_assignments = explode( ';', $style_attribute_value ); + $result = array(); + $style_property_value = ! empty( $style_property_value ) ? rtrim( trim( $style_property_value ), ';' ) : null; + $new_style_property = $style_property_value ? $style_property_name . ':' . $style_property_value . ';' : ''; + + // Generates an array with all the properties but the modified one. + foreach ( $style_assignments as $style_assignment ) { + if ( empty( trim( $style_assignment ) ) ) { + continue; + } + list( $name, $value ) = explode( ':', $style_assignment ); + if ( trim( $name ) !== $style_property_name ) { + $result[] = trim( $name ) . ':' . trim( $value ) . ';'; + } + } + + // Adds the new/modified property at the end of the list. + $result[] = $new_style_property; + + return implode( '', $result ); + } + + /** + * Processes the `data-wp-text` directive. + * + * It updates the inner content of the current HTML element based on the + * evaluation of its associated reference. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + */ + private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) { + if ( ! $p->is_tag_closer() ) { + $attribute_value = $p->get_attribute( 'data-wp-text' ); + $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) ); + + /* + * Follows the same logic as Preact in the client and only changes the + * content if the value is a string or a number. Otherwise, it removes the + * content. + */ + if ( is_string( $result ) || is_numeric( $result ) ) { + $p->set_content_between_balanced_tags( esc_html( $result ) ); + } else { + $p->set_content_between_balanced_tags( '' ); + } + } + } + + /** + * Returns the CSS styles for animating the top loading bar in the router. + * + * @since 6.5.0 + * + * @return string The CSS styles for the router's top loading bar animation. + */ + private function get_router_animation_styles(): string { + return <<

    +
    +HTML; + } + + /** + * Processes the `data-wp-router-region` directive. + * + * It renders in the footer a set of HTML elements to notify users about + * client-side navigations. More concretely, the elements added are 1) a + * top loading bar to visually inform that a navigation is in progress + * and 2) an `aria-live` region for accessible navigation announcements. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + */ + private function data_wp_router_region_processor( WP_Interactivity_API_Directives_Processor $p ) { + if ( ! $p->is_tag_closer() && ! $this->has_processed_router_region ) { + $this->has_processed_router_region = true; + + // Initialize the `core/router` store. + $this->state( + 'core/router', + array( + 'navigation' => array( + 'texts' => array( + 'loading' => __( 'Loading page, please wait.' ), + 'loaded' => __( 'Page Loaded.' ), + ), + ), + ) + ); + + // Enqueues as an inline style. + wp_register_style( 'wp-interactivity-router-animations', false ); + wp_add_inline_style( 'wp-interactivity-router-animations', $this->get_router_animation_styles() ); + wp_enqueue_style( 'wp-interactivity-router-animations' ); + + // Adds the necessary markup to the footer. + add_action( 'wp_footer', array( $this, 'print_router_loading_and_screen_reader_markup' ) ); + } + } + + /** + * Processes the `data-wp-each` directive. + * + * This directive gets an array passed as reference and iterates over it + * generating new content for each item based on the inner markup of the + * `template` tag. + * + * @since 6.5.0 + * + * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. + * @param array $context_stack The reference to the context stack. + * @param array $namespace_stack The reference to the store namespace stack. + * @param array $tag_stack The reference to the tag stack. + */ + private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack, array &$tag_stack ) { + if ( ! $p->is_tag_closer() && 'TEMPLATE' === $p->get_tag() ) { + $attribute_name = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0]; + $extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name ); + $item_name = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item'; + $attribute_value = $p->get_attribute( $attribute_name ); + $result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) ); + + // Gets the content between the template tags and leaves the cursor in the closer tag. + $inner_content = $p->get_content_between_balanced_template_tags(); + + // Checks if there is a manual server-side directive processing. + $template_end = 'data-wp-each: template end'; + $p->set_bookmark( $template_end ); + $p->next_tag(); + $manual_sdp = $p->get_attribute( 'data-wp-each-child' ); + $p->seek( $template_end ); // Rewinds to the template closer tag. + $p->release_bookmark( $template_end ); + + /* + * It doesn't process in these situations: + * - Manual server-side directive processing. + * - Empty or non-array values. + * - Associative arrays because those are deserialized as objects in JS. + * - Templates that contain top-level texts because those texts can't be + * identified and removed in the client. + */ + if ( + $manual_sdp || + empty( $result ) || + ! is_array( $result ) || + ! array_is_list( $result ) || + ! str_starts_with( trim( $inner_content ), '<' ) || + ! str_ends_with( trim( $inner_content ), '>' ) + ) { + array_pop( $tag_stack ); + return; + } + + // Extracts the namespace from the directive attribute value. + $namespace_value = end( $namespace_stack ); + list( $namespace_value ) = is_string( $attribute_value ) && ! empty( $attribute_value ) + ? $this->extract_directive_value( $attribute_value, $namespace_value ) + : array( $namespace_value, null ); + + // Processes the inner content for each item of the array. + $processed_content = ''; + foreach ( $result as $item ) { + // Creates a new context that includes the current item of the array. + $context_stack[] = array_replace_recursive( + end( $context_stack ) !== false ? end( $context_stack ) : array(), + array( $namespace_value => array( $item_name => $item ) ) + ); + + // Processes the inner content with the new context. + $processed_item = $this->process_directives_args( $inner_content, $context_stack, $namespace_stack ); + + if ( null === $processed_item ) { + // If the HTML is unbalanced, stop processing it. + array_pop( $context_stack ); + return; + } + + // Adds the `data-wp-each-child` to each top-level tag. + $i = new WP_Interactivity_API_Directives_Processor( $processed_item ); + while ( $i->next_tag() ) { + $i->set_attribute( 'data-wp-each-child', true ); + $i->next_balanced_tag_closer_tag(); + } + $processed_content .= $i->get_updated_html(); + + // Removes the current context from the stack. + array_pop( $context_stack ); + } + + // Appends the processed content after the tag closer of the template. + $p->append_content_after_template_tag_closer( $processed_content ); + + // Pops the last tag because it skipped the closing tag of the template tag. + array_pop( $tag_stack ); + } + } +} diff --git a/src/wp-includes/interactivity-api/interactivity-api.php b/src/wp-includes/interactivity-api/interactivity-api.php new file mode 100644 index 0000000000000..548fcc3638fb7 --- /dev/null +++ b/src/wp-includes/interactivity-api/interactivity-api.php @@ -0,0 +1,166 @@ +get_registered( $block_name ); + + if ( + isset( $block_name ) && + ( ( isset( $block_type->supports['interactivity'] ) && true === $block_type->supports['interactivity'] ) || + ( isset( $block_type->supports['interactivity']['interactive'] ) && true === $block_type->supports['interactivity']['interactive'] ) ) + ) { + // Annotates the root interactive block for processing. + $root_interactive_block = array( $block_name, $parsed_block ); + + /* + * Adds a filter to process the root interactive block once it has + * finished rendering. + */ + $process_interactive_blocks = static function ( string $content, array $parsed_block ) use ( &$root_interactive_block, &$process_interactive_blocks ): string { + // Checks whether the current block is the root interactive block. + list($root_block_name, $root_parsed_block) = $root_interactive_block; + if ( $root_block_name === $parsed_block['blockName'] && $parsed_block === $root_parsed_block ) { + // The root interactive blocks has finished rendering, process it. + $content = wp_interactivity_process_directives( $content ); + // Removes the filter and reset the root interactive block. + remove_filter( 'render_block_' . $parsed_block['blockName'], $process_interactive_blocks ); + $root_interactive_block = null; + } + return $content; + }; + + /* + * Uses a priority of 20 to ensure that other filters can add additional + * directives before the processing starts. + */ + add_filter( 'render_block_' . $block_name, $process_interactive_blocks, 20, 2 ); + } + } + + return $parsed_block; +} +add_filter( 'render_block_data', 'wp_interactivity_process_directives_of_interactive_blocks' ); + +/** + * Retrieves the main WP_Interactivity_API instance. + * + * It provides access to the WP_Interactivity_API instance, creating one if it + * doesn't exist yet. + * + * @global WP_Interactivity_API $wp_interactivity + * + * @since 6.5.0 + * + * @return WP_Interactivity_API The main WP_Interactivity_API instance. + */ +function wp_interactivity(): WP_Interactivity_API { + global $wp_interactivity; + if ( ! ( $wp_interactivity instanceof WP_Interactivity_API ) ) { + $wp_interactivity = new WP_Interactivity_API(); + } + return $wp_interactivity; +} + +/** + * Processes the interactivity directives contained within the HTML content + * and updates the markup accordingly. + * + * @since 6.5.0 + * + * @param string $html The HTML content to process. + * @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags. + */ +function wp_interactivity_process_directives( string $html ): string { + return wp_interactivity()->process_directives( $html ); +} + +/** + * Gets and/or sets the initial state of an Interactivity API store for a + * given namespace. + * + * If state for that store namespace already exists, it merges the new + * provided state with the existing one. + * + * @since 6.5.0 + * + * @param string $store_namespace The unique store namespace identifier. + * @param array $state Optional. The array that will be merged with the existing state for the specified + * store namespace. + * @return array The state for the specified store namespace. This will be the updated state if a $state argument was + * provided. + */ +function wp_interactivity_state( string $store_namespace, array $state = array() ): array { + return wp_interactivity()->state( $store_namespace, $state ); +} + +/** + * Gets and/or sets the configuration of the Interactivity API for a given + * store namespace. + * + * If configuration for that store namespace exists, it merges the new + * provided configuration with the existing one. + * + * @since 6.5.0 + * + * @param string $store_namespace The unique store namespace identifier. + * @param array $config Optional. The array that will be merged with the existing configuration for the + * specified store namespace. + * @return array The configuration for the specified store namespace. This will be the updated configuration if a + * $config argument was provided. + */ +function wp_interactivity_config( string $store_namespace, array $config = array() ): array { + return wp_interactivity()->config( $store_namespace, $config ); +} + +/** + * Generates a `data-wp-context` directive attribute by encoding a context + * array. + * + * This helper function simplifies the creation of `data-wp-context` directives + * by providing a way to pass an array of data, which encodes into a JSON string + * safe for direct use as a HTML attribute value. + * + * Example: + * + *
    true, 'count' => 0 ) ); ?>> + * + * @since 6.5.0 + * + * @param array $context The array of context data to encode. + * @param string $store_namespace Optional. The unique store namespace identifier. + * @return string A complete `data-wp-context` directive with a JSON encoded value representing the context array and + * the store namespace if specified. + */ +function data_wp_context( array $context, string $store_namespace = '' ): string { + return 'data-wp-context=\'' . + ( $store_namespace ? $store_namespace . '::' : '' ) . + ( empty( $context ) ? '{}' : wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) ) . + '\''; +} diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 726e3da1a5a56..8191b88cee840 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -789,32 +789,71 @@ function load_textdomain( $domain, $mofile, $locale = null ) { */ $mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain ); - if ( ! is_readable( $mofile ) ) { - return false; - } - if ( ! $locale ) { $locale = determine_locale(); } - $mo = new MO(); - if ( ! $mo->import_from_file( $mofile ) ) { - $wp_textdomain_registry->set( $domain, $locale, false ); + $i18n_controller = WP_Translation_Controller::get_instance(); - return false; + // Ensures the correct locale is set as the current one, in case it was filtered. + $i18n_controller->set_locale( $locale ); + + /** + * Filters the preferred file format for translation files. + * + * Can be used to disable the use of PHP files for translations. + * + * @since 6.5.0 + * + * @param string $preferred_format Preferred file format. Possible values: 'php', 'mo'. Default: 'php'. + * @param string $domain The text domain. + */ + $preferred_format = apply_filters( 'translation_file_format', 'php', $domain ); + if ( ! in_array( $preferred_format, array( 'php', 'mo' ), true ) ) { + $preferred_format = 'php'; } - if ( isset( $l10n[ $domain ] ) ) { - $mo->merge_with( $l10n[ $domain ] ); + $translation_files = array(); + + if ( 'mo' !== $preferred_format ) { + $translation_files[] = substr_replace( $mofile, ".l10n.$preferred_format", - strlen( '.mo' ) ); } - unset( $l10n_unloaded[ $domain ] ); + $translation_files[] = $mofile; - $l10n[ $domain ] = &$mo; + foreach ( $translation_files as $file ) { + /** + * Filters the file path for loading translations for the given text domain. + * + * Similar to the {@see 'load_textdomain_mofile'} filter with the difference that + * the file path could be for an MO or PHP file. + * + * @since 6.5.0 + * + * @param string $file Path to the translation file to load. + * @param string $domain The text domain. + */ + $file = (string) apply_filters( 'load_translation_file', $file, $domain ); - $wp_textdomain_registry->set( $domain, $locale, dirname( $mofile ) ); + $success = $i18n_controller->load_file( $file, $domain, $locale ); - return true; + if ( $success ) { + if ( isset( $l10n[ $domain ] ) && $l10n[ $domain ] instanceof MO ) { + $i18n_controller->load_file( $l10n[ $domain ]->get_filename(), $domain, $locale ); + } + + // Unset NOOP_Translations reference in get_translations_for_domain(). + unset( $l10n[ $domain ] ); + + $l10n[ $domain ] = new WP_Translations( $i18n_controller, $domain ); + + $wp_textdomain_registry->set( $domain, $locale, dirname( $file ) ); + + return true; + } + } + + return false; } /** @@ -866,6 +905,11 @@ function unload_textdomain( $domain, $reloadable = false ) { */ do_action( 'unload_textdomain', $domain, $reloadable ); + // Since multiple locales are supported, reloadable text domains don't actually need to be unloaded. + if ( ! $reloadable ) { + WP_Translation_Controller::get_instance()->unload_textdomain( $domain ); + } + if ( isset( $l10n[ $domain ] ) ) { if ( $l10n[ $domain ] instanceof NOOP_Translations ) { unset( $l10n[ $domain ] ); @@ -904,7 +948,7 @@ function load_default_textdomain( $locale = null ) { } // Unload previously loaded strings so we can switch translations. - unload_textdomain( 'default' ); + unload_textdomain( 'default', true ); $return = load_textdomain( 'default', WP_LANG_DIR . "/$locale.mo", $locale ); diff --git a/src/wp-includes/l10n/class-wp-translation-controller.php b/src/wp-includes/l10n/class-wp-translation-controller.php new file mode 100644 index 0000000000000..b44384c013dcd --- /dev/null +++ b/src/wp-includes/l10n/class-wp-translation-controller.php @@ -0,0 +1,437 @@ + [ Textdomain => [ ..., ... ] ] ] + * + * @since 6.5.0 + * @var array> + */ + protected $loaded_translations = array(); + + /** + * List of loaded translation files. + * + * [ Filename => [ Locale => [ Textdomain => WP_Translation_File ] ] ] + * + * @since 6.5.0 + * @var array>> + */ + protected $loaded_files = array(); + + /** + * Container for the main instance of the class. + * + * @since 6.5.0 + * @var WP_Translation_Controller|null + */ + private static $instance = null; + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.5.0 + * + * @return WP_Translation_Controller + */ + public static function get_instance(): WP_Translation_Controller { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Returns the current locale. + * + * @since 6.5.0 + * + * @return string Locale. + */ + public function get_locale(): string { + return $this->current_locale; + } + + /** + * Sets the current locale. + * + * @since 6.5.0 + * + * @param string $locale Locale. + */ + public function set_locale( string $locale ) { + $this->current_locale = $locale; + } + + /** + * Loads a translation file for a given text domain. + * + * @since 6.5.0 + * + * @param string $translation_file Translation file. + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Default current locale. + * @return bool True on success, false otherwise. + */ + public function load_file( string $translation_file, string $textdomain = 'default', string $locale = null ): bool { + if ( null === $locale ) { + $locale = $this->current_locale; + } + + $translation_file = realpath( $translation_file ); + + if ( false === $translation_file ) { + return false; + } + + if ( + isset( $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ] ) && + false !== $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ] + ) { + return null === $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ]->error(); + } + + if ( + isset( $this->loaded_files[ $translation_file ][ $locale ] ) && + array() !== $this->loaded_files[ $translation_file ][ $locale ] + ) { + $moe = reset( $this->loaded_files[ $translation_file ][ $locale ] ); + } else { + $moe = WP_Translation_File::create( $translation_file ); + if ( false === $moe || null !== $moe->error() ) { + $moe = false; + } + } + + $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ] = $moe; + + if ( ! $moe instanceof WP_Translation_File ) { + return false; + } + + if ( ! isset( $this->loaded_translations[ $locale ][ $textdomain ] ) ) { + $this->loaded_translations[ $locale ][ $textdomain ] = array(); + } + + $this->loaded_translations[ $locale ][ $textdomain ][] = $moe; + + return true; + } + + /** + * Unloads a translation file for a given text domain. + * + * @since 6.5.0 + * + * @param WP_Translation_File|string $file Translation file instance or file name. + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Defaults to all locales. + * @return bool True on success, false otherwise. + */ + public function unload_file( $file, string $textdomain = 'default', string $locale = null ): bool { + if ( is_string( $file ) ) { + $file = realpath( $file ); + } + + if ( null !== $locale ) { + if ( isset( $this->loaded_translations[ $locale ][ $textdomain ] ) ) { + foreach ( $this->loaded_translations[ $locale ][ $textdomain ] as $i => $moe ) { + if ( $file === $moe || $file === $moe->get_file() ) { + unset( $this->loaded_translations[ $locale ][ $textdomain ][ $i ] ); + unset( $this->loaded_files[ $moe->get_file() ][ $locale ][ $textdomain ] ); + return true; + } + } + } + + return true; + } + + foreach ( $this->loaded_translations as $l => $domains ) { + if ( ! isset( $domains[ $textdomain ] ) ) { + continue; + } + + foreach ( $domains[ $textdomain ] as $i => $moe ) { + if ( $file === $moe || $file === $moe->get_file() ) { + unset( $this->loaded_translations[ $l ][ $textdomain ][ $i ] ); + unset( $this->loaded_files[ $moe->get_file() ][ $l ][ $textdomain ] ); + return true; + } + } + } + + return false; + } + + /** + * Unloads all translation files for a given text domain. + * + * @since 6.5.0 + * + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Defaults to all locales. + * @return bool True on success, false otherwise. + */ + public function unload_textdomain( string $textdomain = 'default', string $locale = null ): bool { + $unloaded = false; + + if ( null !== $locale ) { + if ( isset( $this->loaded_translations[ $locale ][ $textdomain ] ) ) { + $unloaded = true; + foreach ( $this->loaded_translations[ $locale ][ $textdomain ] as $moe ) { + unset( $this->loaded_files[ $moe->get_file() ][ $locale ][ $textdomain ] ); + } + } + + unset( $this->loaded_translations[ $locale ][ $textdomain ] ); + + return $unloaded; + } + + foreach ( $this->loaded_translations as $l => $domains ) { + if ( ! isset( $domains[ $textdomain ] ) ) { + continue; + } + + $unloaded = true; + + foreach ( $domains[ $textdomain ] as $moe ) { + unset( $this->loaded_files[ $moe->get_file() ][ $l ][ $textdomain ] ); + } + + unset( $this->loaded_translations[ $l ][ $textdomain ] ); + } + + return $unloaded; + } + + /** + * Determines whether translations are loaded for a given text domain. + * + * @since 6.5.0 + * + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Default current locale. + * @return bool True if there are any loaded translations, false otherwise. + */ + public function is_textdomain_loaded( string $textdomain = 'default', string $locale = null ): bool { + if ( null === $locale ) { + $locale = $this->current_locale; + } + + return isset( $this->loaded_translations[ $locale ][ $textdomain ] ) && + array() !== $this->loaded_translations[ $locale ][ $textdomain ]; + } + + /** + * Translates a singular string. + * + * @since 6.5.0 + * + * @param string $text Text to translate. + * @param string $context Optional. Context for the string. Default empty string. + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Default current locale. + * @return string|false Translation on success, false otherwise. + */ + public function translate( string $text, string $context = '', string $textdomain = 'default', string $locale = null ) { + if ( '' !== $context ) { + $context .= "\4"; + } + + $translation = $this->locate_translation( "{$context}{$text}", $textdomain, $locale ); + + if ( false === $translation ) { + return false; + } + + return $translation['entries'][0]; + } + + /** + * Translates plurals. + * + * Checks both singular+plural combinations as well as just singulars, + * in case the translation file does not store the plural. + * + * @since 6.5.0 + * + * @param array{0: string, 1: string} $plurals { + * Pair of singular and plural translations. + * + * @type string $0 Singular translation. + * @type string $1 Plural translation. + * } + * @param int $number Number of items. + * @param string $context Optional. Context for the string. Default empty string. + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Default current locale. + * @return string|false Translation on success, false otherwise. + */ + public function translate_plural( array $plurals, int $number, string $context = '', string $textdomain = 'default', string $locale = null ) { + if ( '' !== $context ) { + $context .= "\4"; + } + + $text = implode( "\0", $plurals ); + $translation = $this->locate_translation( "{$context}{$text}", $textdomain, $locale ); + + if ( false === $translation ) { + $text = $plurals[0]; + $translation = $this->locate_translation( "{$context}{$text}", $textdomain, $locale ); + + if ( false === $translation ) { + return false; + } + } + + /** @var WP_Translation_File $source */ + $source = $translation['source']; + $num = $source->get_plural_form( $number ); + + // See \Translations::translate_plural(). + return $translation['entries'][ $num ] ?? $translation['entries'][0]; + } + + /** + * Returns all existing headers for a given text domain. + * + * @since 6.5.0 + * + * @param string $textdomain Optional. Text domain. Default 'default'. + * @return array Headers. + */ + public function get_headers( string $textdomain = 'default' ): array { + if ( array() === $this->loaded_translations ) { + return array(); + } + + $headers = array(); + + foreach ( $this->get_files( $textdomain ) as $moe ) { + foreach ( $moe->headers() as $header => $value ) { + $headers[ $this->normalize_header( $header ) ] = $value; + } + } + + return $headers; + } + + /** + * Normalizes header names to be capitalized. + * + * @since 6.5.0 + * + * @param string $header Header name. + * @return string Normalized header name. + */ + protected function normalize_header( string $header ): string { + $parts = explode( '-', $header ); + $parts = array_map( 'ucfirst', $parts ); + return implode( '-', $parts ); + } + + /** + * Returns all entries for a given text domain. + * + * @since 6.5.0 + * + * @param string $textdomain Optional. Text domain. Default 'default'. + * @return array Entries. + */ + public function get_entries( string $textdomain = 'default' ): array { + if ( array() === $this->loaded_translations ) { + return array(); + } + + $entries = array(); + + foreach ( $this->get_files( $textdomain ) as $moe ) { + $entries = array_merge( $entries, $moe->entries() ); + } + + return $entries; + } + + /** + * Locates translation for a given string and text domain. + * + * @since 6.5.0 + * + * @param string $singular Singular translation. + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Default current locale. + * @return array{source: WP_Translation_File, entries: string[]}|false { + * Translations on success, false otherwise. + * + * @type WP_Translation_File $source Translation file instance. + * @type string[] $entries Array of translation entries. + * } + */ + protected function locate_translation( string $singular, string $textdomain = 'default', string $locale = null ) { + if ( array() === $this->loaded_translations ) { + return false; + } + + // Find the translation in all loaded files for this text domain. + foreach ( $this->get_files( $textdomain, $locale ) as $moe ) { + $translation = $moe->translate( $singular ); + if ( false !== $translation ) { + return array( + 'entries' => explode( "\0", $translation ), + 'source' => $moe, + ); + } + if ( null !== $moe->error() ) { + // Unload this file, something is wrong. + $this->unload_file( $moe, $textdomain, $locale ); + } + } + + // Nothing could be found. + return false; + } + + /** + * Returns all translation files for a given text domain. + * + * @since 6.5.0 + * + * @param string $textdomain Optional. Text domain. Default 'default'. + * @param string $locale Optional. Locale. Default current locale. + * @return WP_Translation_File[] List of translation files. + */ + protected function get_files( string $textdomain = 'default', string $locale = null ): array { + if ( null === $locale ) { + $locale = $this->current_locale; + } + + return $this->loaded_translations[ $locale ][ $textdomain ] ?? array(); + } +} diff --git a/src/wp-includes/l10n/class-wp-translation-file-mo.php b/src/wp-includes/l10n/class-wp-translation-file-mo.php new file mode 100644 index 0000000000000..bf39cc70ec38b --- /dev/null +++ b/src/wp-includes/l10n/class-wp-translation-file-mo.php @@ -0,0 +1,227 @@ +error = 'Magic marker does not exist'; + return false; + } + + /** + * Parses the file. + * + * @since 6.5.0 + * + * @return bool True on success, false otherwise. + */ + protected function parse_file(): bool { + $this->parsed = true; + + $file_contents = file_get_contents( $this->file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + + if ( false === $file_contents ) { + return false; + } + + $file_length = strlen( $file_contents ); + + if ( $file_length < 24 ) { + $this->error = 'Invalid data'; + return false; + } + + $this->uint32 = $this->detect_endian_and_validate_file( substr( $file_contents, 0, 4 ) ); + + if ( false === $this->uint32 ) { + return false; + } + + $offsets = substr( $file_contents, 4, 24 ); + + if ( false === $offsets ) { + return false; + } + + $offsets = unpack( "{$this->uint32}rev/{$this->uint32}total/{$this->uint32}originals_addr/{$this->uint32}translations_addr/{$this->uint32}hash_length/{$this->uint32}hash_addr", $offsets ); + + if ( false === $offsets ) { + return false; + } + + $offsets['originals_length'] = $offsets['translations_addr'] - $offsets['originals_addr']; + $offsets['translations_length'] = $offsets['hash_addr'] - $offsets['translations_addr']; + + if ( $offsets['rev'] > 0 ) { + $this->error = 'Unsupported revision'; + return false; + } + + if ( $offsets['translations_addr'] > $file_length || $offsets['originals_addr'] > $file_length ) { + $this->error = 'Invalid data'; + return false; + } + + // Load the Originals. + $original_data = str_split( substr( $file_contents, $offsets['originals_addr'], $offsets['originals_length'] ), 8 ); + $translations_data = str_split( substr( $file_contents, $offsets['translations_addr'], $offsets['translations_length'] ), 8 ); + + foreach ( array_keys( $original_data ) as $i ) { + $o = unpack( "{$this->uint32}length/{$this->uint32}pos", $original_data[ $i ] ); + $t = unpack( "{$this->uint32}length/{$this->uint32}pos", $translations_data[ $i ] ); + + if ( false === $o || false === $t ) { + continue; + } + + $original = substr( $file_contents, $o['pos'], $o['length'] ); + $translation = substr( $file_contents, $t['pos'], $t['length'] ); + // GlotPress bug. + $translation = rtrim( $translation, "\0" ); + + // Metadata about the MO file is stored in the first translation entry. + if ( '' === $original ) { + foreach ( explode( "\n", $translation ) as $meta_line ) { + if ( '' === $meta_line ) { + continue; + } + + list( $name, $value ) = array_map( 'trim', explode( ':', $meta_line, 2 ) ); + + $this->headers[ strtolower( $name ) ] = $value; + } + } else { + /* + * In MO files, the key normally contains both singular and plural versions. + * However, this just adds the singular string for lookup, + * which caters for cases where both __( 'Product' ) and _n( 'Product', 'Products' ) + * are used and the translation is expected to be the same for both. + */ + $parts = explode( "\0", (string) $original ); + + $this->entries[ $parts[0] ] = $translation; + } + } + + return true; + } + + /** + * Exports translation contents as a string. + * + * @since 6.5.0 + * + * @return string Translation file contents. + */ + public function export(): string { + // Prefix the headers as the first key. + $headers_string = ''; + foreach ( $this->headers as $header => $value ) { + $headers_string .= "{$header}: $value\n"; + } + $entries = array_merge( array( '' => $headers_string ), $this->entries ); + $entry_count = count( $entries ); + + if ( false === $this->uint32 ) { + $this->uint32 = 'V'; + } + + $bytes_for_entries = $entry_count * 4 * 2; + // Pair of 32bit ints per entry. + $originals_addr = 28; /* header */ + $translations_addr = $originals_addr + $bytes_for_entries; + $hash_addr = $translations_addr + $bytes_for_entries; + $entry_offsets = $hash_addr; + + $file_header = pack( $this->uint32 . '*', self::MAGIC_MARKER, 0 /* rev */, $entry_count, $originals_addr, $translations_addr, 0 /* hash_length */, $hash_addr ); + + $o_entries = ''; + $t_entries = ''; + $o_addr = ''; + $t_addr = ''; + + foreach ( array_keys( $entries ) as $original ) { + $o_addr .= pack( $this->uint32 . '*', strlen( $original ), $entry_offsets ); + $entry_offsets += strlen( $original ) + 1; + $o_entries .= $original . "\0"; + } + + foreach ( $entries as $translations ) { + $t_addr .= pack( $this->uint32 . '*', strlen( $translations ), $entry_offsets ); + $entry_offsets += strlen( $translations ) + 1; + $t_entries .= $translations . "\0"; + } + + return $file_header . $o_addr . $t_addr . $o_entries . $t_entries; + } +} diff --git a/src/wp-includes/l10n/class-wp-translation-file-php.php b/src/wp-includes/l10n/class-wp-translation-file-php.php new file mode 100644 index 0000000000000..f93dd0163fddc --- /dev/null +++ b/src/wp-includes/l10n/class-wp-translation-file-php.php @@ -0,0 +1,79 @@ +parsed = true; + + $result = include $this->file; + if ( ! $result || ! is_array( $result ) ) { + $this->error = 'Invalid data'; + return; + } + + if ( isset( $result['messages'] ) && is_array( $result['messages'] ) ) { + foreach ( $result['messages'] as $original => $translation ) { + $this->entries[ (string) $original ] = $translation; + } + unset( $result['messages'] ); + } + + $this->headers = array_change_key_case( $result ); + } + + /** + * Exports translation contents as a string. + * + * @since 6.5.0 + * + * @return string Translation file contents. + */ + public function export(): string { + $data = array_merge( $this->headers, array( 'messages' => $this->entries ) ); + + return 'var_export( $data ) . ';' . PHP_EOL; + } + + /** + * Outputs or returns a parsable string representation of a variable. + * + * Like {@see var_export()} but "minified", using short array syntax + * and no newlines. + * + * @since 6.5.0 + * + * @param mixed $value The variable you want to export. + * @return string The variable representation. + */ + private function var_export( $value ): string { + if ( ! is_array( $value ) ) { + return var_export( $value, true ); + } + + $entries = array(); + + $is_list = array_is_list( $value ); + + foreach ( $value as $key => $val ) { + $entries[] = $is_list ? $this->var_export( $val ) : var_export( $key, true ) . '=>' . $this->var_export( $val ); + } + + return '[' . implode( ',', $entries ) . ']'; + } +} diff --git a/src/wp-includes/l10n/class-wp-translation-file.php b/src/wp-includes/l10n/class-wp-translation-file.php new file mode 100644 index 0000000000000..e550f9c2f042a --- /dev/null +++ b/src/wp-includes/l10n/class-wp-translation-file.php @@ -0,0 +1,312 @@ + + */ + protected $headers = array(); + + /** + * Whether file has been parsed. + * + * @since 6.5.0 + * @var bool + */ + protected $parsed = false; + + /** + * Error information. + * + * @since 6.5.0 + * @var string|null Error message or null if no error. + */ + protected $error; + + /** + * File name. + * + * @since 6.5.0 + * @var string + */ + protected $file = ''; + + /** + * Translation entries. + * + * @since 6.5.0 + * @var array + */ + protected $entries = array(); + + /** + * Plural forms function. + * + * @since 6.5.0 + * @var callable|null Plural forms. + */ + protected $plural_forms = null; + + /** + * Constructor. + * + * @since 6.5.0 + * + * @param string $file File to load. + */ + protected function __construct( string $file ) { + $this->file = $file; + } + + /** + * Creates a new WP_Translation_File instance for a given file. + * + * @since 6.5.0 + * + * @param string $file File name. + * @param string|null $filetype Optional. File type. Default inferred from file name. + * @return false|WP_Translation_File + */ + public static function create( string $file, string $filetype = null ) { + if ( ! is_readable( $file ) ) { + return false; + } + + if ( null === $filetype ) { + $pos = strrpos( $file, '.' ); + if ( false !== $pos ) { + $filetype = substr( $file, $pos + 1 ); + } + } + + switch ( $filetype ) { + case 'mo': + return new WP_Translation_File_MO( $file ); + case 'php': + return new WP_Translation_File_PHP( $file ); + default: + return false; + } + } + + /** + * Creates a new WP_Translation_File instance for a given file. + * + * @since 6.5.0 + * + * @param string $file Source file name. + * @param string $filetype Desired target file type. + * @return string|false Transformed translation file contents on success, false otherwise. + */ + public static function transform( string $file, string $filetype ) { + $source = self::create( $file ); + + if ( false === $source ) { + return false; + } + + switch ( $filetype ) { + case 'mo': + $destination = new WP_Translation_File_MO( '' ); + break; + case 'php': + $destination = new WP_Translation_File_PHP( '' ); + break; + default: + return false; + } + + $success = $destination->import( $source ); + + if ( ! $success ) { + return false; + } + + return $destination->export(); + } + + /** + * Returns all headers. + * + * @since 6.5.0 + * + * @return array Headers. + */ + public function headers(): array { + if ( ! $this->parsed ) { + $this->parse_file(); + } + return $this->headers; + } + + /** + * Returns all entries. + * + * @since 6.5.0 + * + * @return array Entries. + */ + public function entries(): array { + if ( ! $this->parsed ) { + $this->parse_file(); + } + + return $this->entries; + } + + /** + * Returns the current error information. + * + * @since 6.5.0 + * + * @return string|null Error message or null if no error. + */ + public function error() { + return $this->error; + } + + /** + * Returns the file name. + * + * @since 6.5.0 + * + * @return string File name. + */ + public function get_file(): string { + return $this->file; + } + + /** + * Translates a given string. + * + * @since 6.5.0 + * + * @param string $text String to translate. + * @return false|string Translation(s) on success, false otherwise. + */ + public function translate( string $text ) { + if ( ! $this->parsed ) { + $this->parse_file(); + } + + return $this->entries[ $text ] ?? false; + } + + /** + * Returns the plural form for a given number. + * + * @since 6.5.0 + * + * @param int $number Count. + * @return int Plural form. + */ + public function get_plural_form( int $number ): int { + if ( ! $this->parsed ) { + $this->parse_file(); + } + + if ( null === $this->plural_forms && isset( $this->headers['plural-forms'] ) ) { + $expression = $this->get_plural_expression_from_header( $this->headers['plural-forms'] ); + $this->plural_forms = $this->make_plural_form_function( $expression ); + } + + if ( is_callable( $this->plural_forms ) ) { + /** + * Plural form. + * + * @var int $result Plural form. + */ + $result = call_user_func( $this->plural_forms, $number ); + + return $result; + } + + // Default plural form matches English, only "One" is considered singular. + return ( 1 === $number ? 0 : 1 ); + } + + /** + * Returns the plural forms expression as a tuple. + * + * @since 6.5.0 + * + * @param string $header Plural-Forms header string. + * @return string Plural forms expression. + */ + protected function get_plural_expression_from_header( string $header ): string { + if ( preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches ) ) { + return trim( $matches[2] ); + } + + return 'n != 1'; + } + + /** + * Makes a function, which will return the right translation index, according to the + * plural forms header. + * + * @since 6.5.0 + * + * @param string $expression Plural form expression. + * @return callable(int $num): int Plural forms function. + */ + protected function make_plural_form_function( string $expression ): callable { + try { + $handler = new Plural_Forms( rtrim( $expression, ';' ) ); + return array( $handler, 'get' ); + } catch ( Exception $e ) { + // Fall back to default plural-form function. + return $this->make_plural_form_function( 'n != 1' ); + } + } + + /** + * Imports translations from another file. + * + * @since 6.5.0 + * + * @param WP_Translation_File $source Source file. + * @return bool True on success, false otherwise. + */ + protected function import( WP_Translation_File $source ): bool { + if ( null !== $source->error() ) { + return false; + } + + $this->headers = $source->headers(); + $this->entries = $source->entries(); + $this->error = $source->error(); + + return null === $this->error; + } + + /** + * Parses the file. + * + * @since 6.5.0 + */ + abstract protected function parse_file(); + + /** + * Exports translation contents as a string. + * + * @since 6.5.0 + * + * @return string Translation file contents. + */ + abstract public function export(); +} diff --git a/src/wp-includes/l10n/class-wp-translations.php b/src/wp-includes/l10n/class-wp-translations.php new file mode 100644 index 0000000000000..e177e1d8c536b --- /dev/null +++ b/src/wp-includes/l10n/class-wp-translations.php @@ -0,0 +1,152 @@ + $headers + * @property-read array $entries + */ +class WP_Translations { + /** + * Text domain. + * + * @since 6.5.0 + * @var string + */ + protected $textdomain = 'default'; + + /** + * Translation controller instance. + * + * @since 6.5.0 + * @var WP_Translation_Controller + */ + protected $controller; + + /** + * Constructor. + * + * @since 6.5.0 + * + * @param WP_Translation_Controller $controller I18N controller. + * @param string $textdomain Optional. Text domain. Default 'default'. + */ + public function __construct( WP_Translation_Controller $controller, string $textdomain = 'default' ) { + $this->controller = $controller; + $this->textdomain = $textdomain; + } + + /** + * Magic getter for backward compatibility. + * + * @since 6.5.0 + * + * @param string $name Property name. + * @return mixed + */ + public function __get( string $name ) { + if ( 'entries' === $name ) { + $entries = $this->controller->get_entries( $this->textdomain ); + + $result = array(); + + foreach ( $entries as $original => $translations ) { + $result[] = $this->make_entry( $original, $translations ); + } + + return $result; + } + + if ( 'headers' === $name ) { + return $this->controller->get_headers( $this->textdomain ); + } + + return null; + } + + /** + * Builds a Translation_Entry from original string and translation strings. + * + * @see MO::make_entry() + * + * @since 6.5.0 + * + * @param string $original Original string to translate from MO file. Might contain + * 0x04 as context separator or 0x00 as singular/plural separator. + * @param string $translations Translation strings from MO file. + * @return Translation_Entry Entry instance. + */ + private function make_entry( $original, $translations ): Translation_Entry { + $entry = new Translation_Entry(); + + // Look for context, separated by \4. + $parts = explode( "\4", $original ); + if ( isset( $parts[1] ) ) { + $original = $parts[1]; + $entry->context = $parts[0]; + } + + $entry->singular = $original; + $entry->translations = explode( "\0", $translations ); + $entry->is_plural = count( $entry->translations ) > 1; + + return $entry; + } + + /** + * Translates a plural string. + * + * @since 6.5.0 + * + * @param string|null $singular Singular string. + * @param string|null $plural Plural string. + * @param int|float $count Count. Should be an integer, but some plugins pass floats. + * @param string|null $context Context. + * @return string|null Translation if it exists, or the unchanged singular string. + */ + public function translate_plural( $singular, $plural, $count = 1, $context = '' ) { + if ( null === $singular || null === $plural ) { + return $singular; + } + + $translation = $this->controller->translate_plural( array( $singular, $plural ), (int) $count, (string) $context, $this->textdomain ); + if ( false !== $translation ) { + return $translation; + } + + // Fall back to the original with English grammar rules. + return ( 1 === $count ? $singular : $plural ); + } + + /** + * Translates a singular string. + * + * @since 6.5.0 + * + * @param string|null $singular Singular string. + * @param string|null $context Context. + * @return string|null Translation if it exists, or the unchanged singular string + */ + public function translate( $singular, $context = '' ) { + if ( null === $singular ) { + return null; + } + + $translation = $this->controller->translate( $singular, (string) $context, $this->textdomain ); + if ( false !== $translation ) { + return $translation; + } + + // Fall back to the original. + return $singular; + } +} diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index 9f3d9a97ee440..a782edfb85548 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -4768,7 +4768,7 @@ function get_the_privacy_policy_link( $before = '', $after = '' ) { * By default the list of internal hosts is comprised of the host name of * the site's home_url() (as parsed by wp_parse_url()). * - * This list is used when determining if a specificed URL is a link to a page on + * This list is used when determining if a specified URL is a link to a page on * the site itself or a link offsite (to an external host). This is used, for * example, when determining if the "nofollow" attribute should be applied to a * link. diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 520902cdd64ba..78961bccec9e2 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -598,6 +598,10 @@ function wp_debug_mode() { error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR ); } + /* + * The 'REST_REQUEST' check here is optimistic as the constant is most + * likely not set at this point even if it is in fact a REST request. + */ if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || defined( 'MS_FILES_REQUEST' ) || ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) || wp_doing_ajax() || wp_is_json_request() @@ -982,6 +986,7 @@ function wp_get_active_and_valid_plugins() { $network_plugins = is_multisite() ? wp_get_active_network_plugins() : false; + $invalid_plugins = array(); foreach ( $active_plugins as $plugin ) { if ( ! validate_file( $plugin ) // $plugin must validate as file. && str_ends_with( $plugin, '.php' ) // $plugin must end with '.php'. @@ -990,6 +995,20 @@ function wp_get_active_and_valid_plugins() { && ( ! $network_plugins || ! in_array( WP_PLUGIN_DIR . '/' . $plugin, $network_plugins, true ) ) ) { $plugins[] = WP_PLUGIN_DIR . '/' . $plugin; + } else { + $invalid_plugins[] = $plugin; + } + } + + if ( ! empty( $invalid_plugins ) ) { + $all_plugin_data = get_option( 'plugin_data', array() ); + + if ( ! empty( $all_plugin_data ) ) { + foreach ( $invalid_plugins as $invalid_plugin ) { + unset( $all_plugin_data[ $invalid_plugin ] ); + } + + update_option( 'plugin_data', $all_plugin_data ); } } @@ -1186,6 +1205,7 @@ function is_protected_ajax_action() { 'search-install-plugins', // Searching for a plugin in the plugin install screen. 'update-plugin', // Update an existing plugin. 'update-theme', // Update an existing theme. + 'activate-plugin', // Activating an existing plugin. ); /** @@ -1480,6 +1500,11 @@ function wp_load_translations_early() { // Translation and localization. require_once ABSPATH . WPINC . '/pomo/mo.php'; + require_once ABSPATH . WPINC . '/l10n/class-wp-translation-controller.php'; + require_once ABSPATH . WPINC . '/l10n/class-wp-translations.php'; + require_once ABSPATH . WPINC . '/l10n/class-wp-translation-file.php'; + require_once ABSPATH . WPINC . '/l10n/class-wp-translation-file-mo.php'; + require_once ABSPATH . WPINC . '/l10n/class-wp-translation-file-php.php'; require_once ABSPATH . WPINC . '/l10n.php'; require_once ABSPATH . WPINC . '/class-wp-textdomain-registry.php'; require_once ABSPATH . WPINC . '/class-wp-locale.php'; diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 38ec2213b7506..69a6d53299cab 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4100,6 +4100,7 @@ function _wp_image_editor_choose( $args = array() ) { require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php'; require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; + require_once ABSPATH . WPINC . '/class-avif-info.php'; /** * Filters the list of image editing library classes. * @@ -4204,6 +4205,11 @@ function wp_plupload_default_settings() { $defaults['webp_upload_error'] = true; } + // Check if AVIF images can be edited. + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) { + $defaults['avif_upload_error'] = true; + } + /** * Filters the Plupload default settings. * @@ -5480,6 +5486,7 @@ function wp_show_heic_upload_error( $plupload_settings ) { * * @since 5.7.0 * @since 5.8.0 Added support for WebP images. + * @since 6.5.0 Added support for AVIF images. * * @param string $filename The file path. * @param array $image_info Optional. Extended image information (passed by reference). @@ -5512,7 +5519,11 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } } - if ( false !== $info ) { + if ( + ! empty( $info ) && + // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs. + ! ( empty( $info[0] ) && empty( $info[1] ) ) + ) { return $info; } @@ -5541,10 +5552,75 @@ function wp_getimagesize( $filename, array &$image_info = null ) { } } + // For PHP versions that don't support AVIF images, extract the image size info from the file headers. + if ( 'image/avif' === wp_get_image_mime( $filename ) ) { + $avif_info = wp_get_avif_info( $filename ); + + $width = $avif_info['width']; + $height = $avif_info['height']; + + // Mimic the native return format. + if ( $width && $height ) { + return array( + $width, + $height, + IMAGETYPE_AVIF, + sprintf( + 'width="%d" height="%d"', + $width, + $height + ), + 'mime' => 'image/avif', + ); + } + } + // The image could not be parsed. return false; } +/** + * Extracts meta information about an AVIF file: width, height, bit depth, and number of channels. + * + * @since 6.5.0 + * + * @param string $filename Path to an AVIF file. + * @return array { + * An array of AVIF image information. + * + * @type int|false $width Image width on success, false on failure. + * @type int|false $height Image height on success, false on failure. + * @type int|false $bit_depth Image bit depth on success, false on failure. + * @type int|false $num_channels Image number of channels on success, false on failure. + * } + */ +function wp_get_avif_info( $filename ) { + $results = array( + 'width' => false, + 'height' => false, + 'bit_depth' => false, + 'num_channels' => false, + ); + + if ( 'image/avif' !== wp_get_image_mime( $filename ) ) { + return $results; + } + + // Parse the file using libavifinfo's PHP implementation. + require_once ABSPATH . WPINC . '/class-avif-info.php'; + + $handle = fopen( $filename, 'rb' ); + if ( $handle ) { + $parser = new Avifinfo\Parser( $handle ); + $success = $parser->parse_ftyp() && $parser->parse_file(); + fclose( $handle ); + if ( $success ) { + $results = $parser->features->primary_item_features; + } + } + return $results; +} + /** * Extracts meta information about a WebP file: width, height, and type. * diff --git a/src/wp-includes/pomo/po.php b/src/wp-includes/pomo/po.php index 7b9ec0b88bb2c..a4e3cab4ef17a 100644 --- a/src/wp-includes/pomo/po.php +++ b/src/wp-includes/pomo/po.php @@ -53,7 +53,7 @@ public function export_headers() { /** * Exports all entries to PO format * - * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end + * @return string sequence of msgid/msgstr PO strings, doesn't contain a newline at the end */ public function export_entries() { // TODO: Sorting. @@ -64,7 +64,7 @@ public function export_entries() { * Exports the whole PO file as a string * * @param bool $include_headers whether to include the headers in the export - * @return string ready for inclusion in PO file string for headers and all the enrtries + * @return string ready for inclusion in PO file string for headers and all the entries */ public function export( $include_headers = true ) { $res = ''; @@ -127,7 +127,7 @@ public static function poify( $input_string ) { $input_string = str_replace( array_keys( $replaces ), array_values( $replaces ), $input_string ); $po = $quote . implode( "{$slash}n{$quote}{$newline}{$quote}", explode( $newline, $input_string ) ) . $quote; - // Add empty string on first line for readbility. + // Add empty string on first line for readability. if ( str_contains( $input_string, $newline ) && ( substr_count( $input_string, $newline ) > 1 || substr( $input_string, -strlen( $newline ) ) !== $newline ) ) { $po = "$quote$quote$newline$po"; @@ -141,7 +141,7 @@ public static function poify( $input_string ) { * Gives back the original string from a PO-formatted string * * @param string $input_string PO-formatted string - * @return string enascaped string + * @return string unescaped string */ public static function unpoify( $input_string ) { $escapes = array( diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 69a63625524d1..5fa058363af9b 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -564,6 +564,72 @@ function create_initial_post_types() { ) ); + register_post_type( + 'wp_font_family', + array( + 'labels' => array( + 'name' => __( 'Font Families' ), + 'singular_name' => __( 'Font Family' ), + ), + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'hierarchical' => false, + 'capabilities' => array( + 'read' => 'edit_theme_options', + 'read_private_posts' => 'edit_theme_options', + 'create_posts' => 'edit_theme_options', + 'publish_posts' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'edit_published_posts' => 'edit_theme_options', + 'delete_posts' => 'edit_theme_options', + 'delete_others_posts' => 'edit_theme_options', + 'delete_published_posts' => 'edit_theme_options', + ), + 'map_meta_cap' => true, + 'query_var' => false, + 'rewrite' => false, + 'show_in_rest' => true, + 'rest_base' => 'font-families', + 'rest_controller_class' => 'WP_REST_Font_Families_Controller', + // Disable autosave endpoints for font families. + 'autosave_rest_controller_class' => 'stdClass', + ) + ); + + register_post_type( + 'wp_font_face', + array( + 'labels' => array( + 'name' => __( 'Font Faces' ), + 'singular_name' => __( 'Font Face' ), + ), + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'hierarchical' => false, + 'capabilities' => array( + 'read' => 'edit_theme_options', + 'read_private_posts' => 'edit_theme_options', + 'create_posts' => 'edit_theme_options', + 'publish_posts' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'edit_published_posts' => 'edit_theme_options', + 'delete_posts' => 'edit_theme_options', + 'delete_others_posts' => 'edit_theme_options', + 'delete_published_posts' => 'edit_theme_options', + ), + 'map_meta_cap' => true, + 'query_var' => false, + 'rewrite' => false, + 'show_in_rest' => true, + 'rest_base' => 'font-families/(?P[\d]+)/font-faces', + 'rest_controller_class' => 'WP_REST_Font_Faces_Controller', + // Disable autosave endpoints for font faces. + 'autosave_rest_controller_class' => 'stdClass', + ) + ); + register_post_status( 'publish', array( @@ -6700,7 +6766,7 @@ function wp_attachment_is( $type, $post = null ) { switch ( $type ) { case 'image': - $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' ); + $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); return in_array( $ext, $image_exts, true ); case 'audio': diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 61e324e801445..b44b205afb6ef 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -209,7 +209,7 @@ function rest_api_register_rewrites() { * @since 4.4.0 */ function rest_api_default_filters() { - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { // Deprecated reporting. add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); add_filter( 'deprecated_function_trigger_error', '__return_false' ); @@ -391,6 +391,10 @@ function create_initial_rest_routes() { // Navigation Fallback. $controller = new WP_REST_Navigation_Fallback_Controller(); $controller->register_routes(); + + // Font Collections. + $font_collections_controller = new WP_REST_Font_Collections_Controller(); + $font_collections_controller->register_routes(); } /** @@ -3389,3 +3393,38 @@ static function ( $status, $error_data ) { return new WP_REST_Response( $data, $status ); } + +/** + * Checks whether a REST API endpoint request is currently being handled. + * + * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. + * + * @since 6.5.0 + * + * @global WP_REST_Server $wp_rest_server REST server instance. + * + * @return bool True if a REST endpoint request is currently being handled, false otherwise. + */ +function wp_is_rest_endpoint() { + /* @var WP_REST_Server $wp_rest_server */ + global $wp_rest_server; + + // Check whether this is a standalone REST request. + $is_rest_endpoint = wp_is_serving_rest_request(); + if ( ! $is_rest_endpoint ) { + // Otherwise, check whether an internal REST request is currently being handled. + $is_rest_endpoint = isset( $wp_rest_server ) + && $wp_rest_server->is_dispatching(); + } + + /** + * Filters whether a REST endpoint request is currently being handled. + * + * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. + * + * @since 6.5.0 + * + * @param bool $is_request_endpoint Whether a REST endpoint request is currently being handled. + */ + return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint ); +} 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 6838579ce8609..861f5115e6ae9 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -87,6 +87,14 @@ class WP_REST_Server { */ protected $embed_cache = array(); + /** + * Stores request objects that are currently being handled. + * + * @since 6.5.0 + * @var array + */ + protected $dispatching_requests = array(); + /** * Instantiates the REST server. * @@ -983,6 +991,8 @@ public function get_route_options( $route ) { * @return WP_REST_Response Response returned by the callback. */ public function dispatch( $request ) { + $this->dispatching_requests[] = $request; + /** * Filters the pre-calculated result of a REST API dispatch request. * @@ -1008,6 +1018,7 @@ public function dispatch( $request ) { $result = $this->error_to_response( $result ); } + array_pop( $this->dispatching_requests ); return $result; } @@ -1015,7 +1026,9 @@ public function dispatch( $request ) { $matched = $this->match_request_to_handler( $request ); if ( is_wp_error( $matched ) ) { - return $this->error_to_response( $matched ); + $response = $this->error_to_response( $matched ); + array_pop( $this->dispatching_requests ); + return $response; } list( $route, $handler ) = $matched; @@ -1040,7 +1053,22 @@ public function dispatch( $request ) { } } - return $this->respond_to_request( $request, $route, $handler, $error ); + $response = $this->respond_to_request( $request, $route, $handler, $error ); + array_pop( $this->dispatching_requests ); + return $response; + } + + /** + * Returns whether the REST server is currently dispatching / responding to a request. + * + * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. + * + * @since 6.5.0 + * + * @return bool Whether the REST server is currently handling a request. + */ + public function is_dispatching() { + return (bool) $this->dispatching_requests; } /** 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 7367c0fc57a07..8a26a79a67932 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 @@ -186,6 +186,12 @@ public function create_item( $request ) { return $fields_update; } + $terms_update = $this->handle_terms( $attachment_id, $request ); + + if ( is_wp_error( $terms_update ) ) { + return $terms_update; + } + $request->set_param( 'context', 'edit' ); /** @@ -201,7 +207,7 @@ public function create_item( $request ) { wp_after_insert_post( $attachment, false, null ); - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. @@ -450,7 +456,7 @@ public function edit_media_item( $request ) { ); } - $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' ); + $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ); $mime_type = get_post_mime_type( $attachment_id ); if ( ! in_array( $mime_type, $supported_types, true ) ) { return new WP_Error( @@ -630,7 +636,7 @@ public function edit_media_item( $request ) { update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) ); } - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. 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 852143d96ec35..7bf2f6af4af2e 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 @@ -280,6 +280,7 @@ public function prepare_item_for_response( $item, $request ) { 'keywords', 'parent', 'ancestor', + 'allowed_blocks', 'provides_context', 'uses_context', 'selectors', @@ -292,6 +293,7 @@ public function prepare_item_for_response( $item, $request ) { 'view_script_handles', 'editor_style_handles', 'style_handles', + 'view_style_handles', 'variations', 'block_hooks', ), @@ -602,6 +604,16 @@ public function get_item_schema() { 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), + 'view_style_handles' => array( + 'description' => __( 'Public facing style handles.' ), + 'type' => array( 'array' ), + 'default' => array(), + 'items' => array( + 'type' => 'string', + ), + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), 'styles' => array( 'description' => __( 'Block style variations.' ), 'type' => 'array', @@ -712,6 +724,17 @@ public function get_item_schema() { 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), + 'allowed_blocks' => array( + 'description' => __( 'Allowed child block types.' ), + 'type' => array( 'array', 'null' ), + 'items' => array( + 'type' => 'string', + 'pattern' => self::NAME_PATTERN, + ), + 'default' => null, + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), 'keywords' => $keywords_definition, 'example' => $example_definition, 'block_hooks' => array( diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php new file mode 100644 index 0000000000000..05ef0c7b11b64 --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php @@ -0,0 +1,322 @@ +rest_base = 'font-collections'; + $this->namespace = 'wp/v2'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.5.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\/\w-]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Gets the font collections available. + * + * @since 6.5.0 + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $collections_all = WP_Font_Library::get_instance()->get_font_collections(); + + $page = $request['page']; + $per_page = $request['per_page']; + $total_items = count( $collections_all ); + $max_pages = ceil( $total_items / $per_page ); + + if ( $page > $max_pages && $total_items > 0 ) { + return new WP_Error( + 'rest_post_invalid_page_number', + __( 'The page number requested is larger than the number of pages available.' ), + array( 'status' => 400 ) + ); + } + + $collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page ); + + $items = array(); + foreach ( $collections_page as $collection ) { + $item = $this->prepare_item_for_response( $collection, $request ); + + // If there's an error loading a collection, skip it and continue loading valid collections. + if ( is_wp_error( $item ) ) { + continue; + } + $item = $this->prepare_response_for_collection( $item ); + $items[] = $item; + } + + $response = rest_ensure_response( $items ); + + $response->header( 'X-WP-Total', (int) $total_items ); + $response->header( 'X-WP-TotalPages', (int) $max_pages ); + + $request_params = $request->get_query_params(); + $collection_url = rest_url( $this->namespace . '/' . $this->rest_base ); + $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); + + if ( $page > 1 ) { + $prev_page = $page - 1; + + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Gets a font collection. + * + * @since 6.5.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. + */ + public function get_item( $request ) { + $slug = $request->get_param( 'slug' ); + $collection = WP_Font_Library::get_instance()->get_font_collection( $slug ); + + if ( ! $collection ) { + return new WP_Error( 'rest_font_collection_not_found', __( 'Font collection not found.' ), array( 'status' => 404 ) ); + } + + return $this->prepare_item_for_response( $collection, $request ); + } + + /** + * Prepare a single collection output for response. + * + * @since 6.5.0 + * + * @param WP_Font_Collection $item Font collection object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = array(); + + if ( rest_is_field_included( 'slug', $fields ) ) { + $data['slug'] = $item->slug; + } + + // If any data fields are requested, get the collection data. + $data_fields = array( 'name', 'description', 'font_families', 'categories' ); + if ( ! empty( array_intersect( $fields, $data_fields ) ) ) { + $collection_data = $item->get_data(); + if ( is_wp_error( $collection_data ) ) { + $collection_data->add_data( array( 'status' => 500 ) ); + return $collection_data; + } + + foreach ( $data_fields as $field ) { + if ( rest_is_field_included( $field, $fields ) ) { + $data[ $field ] = $collection_data[ $field ]; + } + } + } + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) ) { + $links = $this->prepare_links( $item ); + $response->add_links( $links ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $response->data = $this->add_additional_fields_to_object( $response->data, $request ); + $response->data = $this->filter_response_by_context( $response->data, $context ); + + /** + * Filters the font collection data for a REST API response. + * + * @since 6.5.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Font_Collection $item The font collection object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'rest_prepare_font_collection', $response, $item, $request ); + } + + /** + * Retrieves the font collection's schema, conforming to JSON Schema. + * + * @since 6.5.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'font-collection', + 'type' => 'object', + 'properties' => array( + 'slug' => array( + 'description' => __( 'Unique identifier for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'The name for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'The description for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'font_families' => array( + 'description' => __( 'The font families for the font collection.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'categories' => array( + 'description' => __( 'The categories for the font collection.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Prepares links for the request. + * + * @since 6.5.0 + * + * @param WP_Font_Collection $collection Font collection data + * @return array Links for the given font collection. + */ + protected function prepare_links( $collection ) { + return array( + 'self' => array( + 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $collection->slug ) ), + ), + 'collection' => array( + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), + ), + ); + } + + /** + * Retrieves the search params for the font collections. + * + * @since 6.5.0 + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + $query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + + unset( $query_params['search'] ); + + /** + * Filters REST API collection parameters for the font collections controller. + * + * @since 6.5.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + */ + return apply_filters( 'rest_font_collections_collection_params', $query_params ); + } + + /** + * Checks whether the user has permissions to use the Fonts Collections. + * + * @since 6.5.0 + * + * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( current_user_can( 'edit_theme_options' ) ) { + return true; + } + + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access font collections.' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php new file mode 100644 index 0000000000000..0a870b8c705ce --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -0,0 +1,950 @@ +namespace, + '/' . $this->rest_base, + array( + 'args' => array( + 'font_family_id' => array( + 'description' => __( 'The ID for the parent font family of the font face.' ), + 'type' => 'integer', + 'required' => true, + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_create_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'font_family_id' => array( + 'description' => __( 'The ID for the parent font family of the font face.' ), + 'type' => 'integer', + 'required' => true, + ), + 'id' => array( + 'description' => __( 'Unique identifier for the font face.' ), + 'type' => 'integer', + 'required' => true, + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'type' => 'boolean', + 'default' => false, + 'description' => __( 'Whether to bypass Trash and force deletion.', 'default' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Checks if a given request has access to font faces. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { + $post_type = get_post_type_object( $this->post_type ); + + if ( ! current_user_can( $post_type->cap->read ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access font faces.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Checks if a given request has access to a font face. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + if ( ! current_user_can( 'read_post', $post->ID ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access this font face.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Validates settings when creating a font face. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font face settings. + * @param WP_REST_Request $request Request object. + * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. + */ + public function validate_create_font_face_settings( $value, $request ) { + $settings = json_decode( $value, true ); + + // Check settings string is valid JSON. + if ( null === $settings ) { + return new WP_Error( + 'rest_invalid_param', + __( 'font_face_settings parameter must be a valid JSON string.' ), + array( 'status' => 400 ) + ); + } + + // Check that the font face settings match the theme.json schema. + $schema = $this->get_item_schema()['properties']['font_face_settings']; + $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_face_settings' ); + + if ( is_wp_error( $has_valid_settings ) ) { + $has_valid_settings->add_data( array( 'status' => 400 ) ); + return $has_valid_settings; + } + + // Check that none of the required settings are empty values. + $required = $schema['required']; + foreach ( $required as $key ) { + if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Name of the missing font face settings parameter, e.g. "font_face_settings[src]". */ + sprintf( __( '%s cannot be empty.' ), "font_face_setting[ $key ]" ), + array( 'status' => 400 ) + ); + } + } + + $srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] ); + $files = $request->get_file_params(); + + foreach ( $srcs as $src ) { + // Check that each src is a non-empty string. + $src = ltrim( $src ); + if ( empty( $src ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Font face source parameter name: "font_face_settings[src]". */ + sprintf( __( '%s values must be non-empty strings.' ), 'font_face_settings[src]' ), + array( 'status' => 400 ) + ); + } + + // Check that srcs are valid URLs or file references. + if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: 1: Font face source parameter name: "font_face_settings[src]", 2: The invalid src value. */ + sprintf( __( '%1$s value "%2$s" must be a valid URL or file reference.' ), 'font_face_settings[src]', $src ), + array( 'status' => 400 ) + ); + } + } + + // Check that each file in the request references a src in the settings. + foreach ( array_keys( $files ) as $file ) { + if ( ! in_array( $file, $srcs, true ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: 1: File key (e.g. "file-0") in the request data, 2: Font face source parameter name: "font_face_settings[src]". */ + sprintf( __( 'File %1$s must be used in %2$s.' ), $file, 'font_face_settings[src]' ), + array( 'status' => 400 ) + ); + } + } + + return true; + } + + /** + * Sanitizes the font face settings when creating a font face. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font face settings. + * @return array Decoded and sanitized array of font face settings. + */ + public function sanitize_font_face_settings( $value ) { + // Settings arrive as stringified JSON, since this is a multipart/form-data request. + $settings = json_decode( $value, true ); + $schema = $this->get_item_schema()['properties']['font_face_settings']['properties']; + + // Sanitize settings based on callbacks in the schema. + foreach ( $settings as $key => $value ) { + $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; + $settings[ $key ] = call_user_func( $sanitize_callback, $value ); + } + + return $settings; + } + + /** + * Retrieves a collection of font faces within the parent font family. + * + * @since 6.5.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. + */ + public function get_items( $request ) { + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + return parent::get_items( $request ); + } + + /** + * Retrieves a single font face within the parent font family. + * + * @since 6.5.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. + */ + public function get_item( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + // Check that the font face has a valid parent font family. + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + if ( (int) $font_family->ID !== (int) $post->post_parent ) { + return new WP_Error( + 'rest_font_face_parent_id_mismatch', + /* translators: %d: A post id. */ + sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), + array( 'status' => 404 ) + ); + } + + return parent::get_item( $request ); + } + + /** + * Creates a font face for the parent font family. + * + * @since 6.5.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. + */ + public function create_item( $request ) { + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + // Settings have already been decoded by ::sanitize_font_face_settings(). + $settings = $request->get_param( 'font_face_settings' ); + $file_params = $request->get_file_params(); + + // Check that the necessary font face properties are unique. + $query = new WP_Query( + array( + 'post_type' => $this->post_type, + 'posts_per_page' => 1, + 'title' => WP_Font_Utils::get_font_face_slug( $settings ), + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + if ( ! empty( $query->posts ) ) { + return new WP_Error( + 'rest_duplicate_font_face', + __( 'A font face matching those settings already exists.' ), + array( 'status' => 400 ) + ); + } + + // Move the uploaded font asset from the temp folder to the fonts directory. + if ( ! function_exists( 'wp_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + $srcs = is_string( $settings['src'] ) ? array( $settings['src'] ) : $settings['src']; + $processed_srcs = array(); + $font_file_meta = array(); + + foreach ( $srcs as $src ) { + // If src not a file reference, use it as is. + if ( ! isset( $file_params[ $src ] ) ) { + $processed_srcs[] = $src; + continue; + } + + $file = $file_params[ $src ]; + $font_file = $this->handle_font_file_upload( $file ); + if ( is_wp_error( $font_file ) ) { + return $font_file; + } + + $processed_srcs[] = $font_file['url']; + $font_file_meta[] = $this->relative_fonts_path( $font_file['file'] ); + } + + // Store the updated settings for prepare_item_for_database to use. + $settings['src'] = count( $processed_srcs ) === 1 ? $processed_srcs[0] : $processed_srcs; + $request->set_param( 'font_face_settings', $settings ); + + // Ensure that $settings data is slashed, so values with quotes are escaped. + // WP_REST_Posts_Controller::create_item uses wp_slash() on the post_content. + $font_face_post = parent::create_item( $request ); + + if ( is_wp_error( $font_face_post ) ) { + return $font_face_post; + } + + $font_face_id = $font_face_post->data['id']; + + foreach ( $font_file_meta as $font_file_path ) { + add_post_meta( $font_face_id, '_wp_font_face_file', $font_file_path ); + } + + return $font_face_post; + } + + /** + * Deletes a single font face. + * + * @since 6.5.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. + */ + public function delete_item( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + if ( (int) $font_family->ID !== (int) $post->post_parent ) { + return new WP_Error( + 'rest_font_face_parent_id_mismatch', + /* translators: %d: A post id. */ + sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), + array( 'status' => 404 ) + ); + } + + $force = isset( $request['force'] ) ? (bool) $request['force'] : false; + + // We don't support trashing for font faces. + if ( ! $force ) { + return new WP_Error( + 'rest_trash_not_supported', + /* translators: %s: force=true */ + sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), + array( 'status' => 501 ) + ); + } + + return parent::delete_item( $request ); + } + + /** + * Prepares a single font face output for response. + * + * @since 6.5.0 + * + * @param WP_Post $item Post object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = array(); + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = $item->ID; + } + if ( rest_is_field_included( 'theme_json_version', $fields ) ) { + $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; + } + + if ( rest_is_field_included( 'parent', $fields ) ) { + $data['parent'] = $item->post_parent; + } + + if ( rest_is_field_included( 'font_face_settings', $fields ) ) { + $data['font_face_settings'] = $this->get_settings_from_post( $item ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $item ); + $response->add_links( $links ); + } + + /** + * Filters the font face data for a REST API response. + * + * @since 6.5.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Font face post object. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'rest_prepare_wp_font_face', $response, $item, $request ); + } + + /** + * Retrieves the post's schema, conforming to JSON Schema. + * + * @since 6.5.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => $this->post_type, + 'type' => 'object', + // Base properties for every Post. + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the post.', 'default' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'theme_json_version' => array( + 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), + 'type' => 'integer', + 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, + 'minimum' => 2, + 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'parent' => array( + 'description' => __( 'The ID for the parent font family of the font face.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + // Font face settings come directly from theme.json schema + // See https://schemas.wp.org/trunk/theme.json + 'font_face_settings' => array( + 'description' => __( 'font-face declaration in theme.json format.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'properties' => array( + 'fontFamily' => array( + 'description' => __( 'CSS font-family value.' ), + 'type' => 'string', + 'default' => '', + 'arg_options' => array( + 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), + ), + ), + 'fontStyle' => array( + 'description' => __( 'CSS font-style value.' ), + 'type' => 'string', + 'default' => 'normal', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontWeight' => array( + 'description' => __( 'List of available font weights, separated by a space.' ), + 'default' => '400', + // Changed from `oneOf` to avoid errors from loose type checking. + // e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check. + 'type' => array( 'string', 'integer' ), + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontDisplay' => array( + 'description' => __( 'CSS font-display value.' ), + 'type' => 'string', + 'default' => 'fallback', + 'enum' => array( + 'auto', + 'block', + 'fallback', + 'swap', + 'optional', + ), + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'src' => array( + 'description' => __( 'Paths or URLs to the font files.' ), + // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array, + // and causing a "matches more than one of the expected formats" error. + 'anyOf' => array( + array( + 'type' => 'string', + ), + array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + 'default' => array(), + 'arg_options' => array( + 'sanitize_callback' => function ( $value ) { + return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value ); + }, + ), + ), + 'fontStretch' => array( + 'description' => __( 'CSS font-stretch value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'ascentOverride' => array( + 'description' => __( 'CSS ascent-override value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'descentOverride' => array( + 'description' => __( 'CSS descent-override value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontVariant' => array( + 'description' => __( 'CSS font-variant value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontFeatureSettings' => array( + 'description' => __( 'CSS font-feature-settings value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontVariationSettings' => array( + 'description' => __( 'CSS font-variation-settings value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'lineGapOverride' => array( + 'description' => __( 'CSS line-gap-override value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'sizeAdjust' => array( + 'description' => __( 'CSS size-adjust value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'unicodeRange' => array( + 'description' => __( 'CSS unicode-range value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'preview' => array( + 'description' => __( 'URL to a preview image of the font face.' ), + 'type' => 'string', + 'format' => 'uri', + 'default' => '', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_url', + ), + ), + ), + 'required' => array( 'fontFamily', 'src' ), + 'additionalProperties' => false, + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the item's schema for display / public consumption purposes. + * + * @since 6.5.0 + * + * @return array Public item schema data. + */ + public function get_public_item_schema() { + + $schema = parent::get_public_item_schema(); + + // Also remove `arg_options' from child font_family_settings properties, since the parent + // controller only handles the top level properties. + foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) { + unset( $property['arg_options'] ); + } + + return $schema; + } + + /** + * Retrieves the query params for the font face collection. + * + * @since 6.5.0 + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + // Remove unneeded params. + unset( + $query_params['after'], + $query_params['modified_after'], + $query_params['before'], + $query_params['modified_before'], + $query_params['search'], + $query_params['search_columns'], + $query_params['slug'], + $query_params['status'] + ); + + $query_params['orderby']['default'] = 'id'; + $query_params['orderby']['enum'] = array( 'id', 'include' ); + + /** + * Filters collection parameters for the font face controller. + * + * @since 6.5.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + */ + return apply_filters( 'rest_wp_font_face_collection_params', $query_params ); + } + + /** + * Get the params used when creating a new font face. + * + * @since 6.5.0 + * + * @return array Font face create arguments. + */ + public function get_create_params() { + $properties = $this->get_item_schema()['properties']; + return array( + 'theme_json_version' => $properties['theme_json_version'], + // When creating, font_face_settings is stringified JSON, to work with multipart/form-data used + // when uploading font files. + 'font_face_settings' => array( + 'description' => __( 'font-face declaration in theme.json format, encoded as a string.' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'validate_create_font_face_settings' ), + 'sanitize_callback' => array( $this, 'sanitize_font_face_settings' ), + ), + ); + } + + /** + * Get the parent font family, if the ID is valid. + * + * @since 6.5.0 + * + * @param int $font_family_id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent_font_family_post( $font_family_id ) { + $error = new WP_Error( + 'rest_post_invalid_parent', + __( 'Invalid post parent ID.', 'default' ), + array( 'status' => 404 ) + ); + + if ( (int) $font_family_id <= 0 ) { + return $error; + } + + $font_family_post = get_post( (int) $font_family_id ); + + if ( empty( $font_family_post ) || empty( $font_family_post->ID ) + || 'wp_font_family' !== $font_family_post->post_type + ) { + return $error; + } + + return $font_family_post; + } + + /** + * Prepares links for the request. + * + * @since 6.5.0 + * + * @param WP_Post $post Post object. + * @return array Links for the given post. + */ + protected function prepare_links( $post ) { + // Entity meta. + return array( + 'self' => array( + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), + ), + 'collection' => array( + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ), + ), + 'parent' => array( + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ), + ), + ); + } + + /** + * Prepares a single font face post for creation. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Request object. + * @return stdClass Post object. + */ + protected function prepare_item_for_database( $request ) { + $prepared_post = new stdClass(); + + // Settings have already been decoded by ::sanitize_font_face_settings(). + $settings = $request->get_param( 'font_face_settings' ); + + // Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting, + // which may contain multibyte characters. + $title = WP_Font_Utils::get_font_face_slug( $settings ); + + $prepared_post->post_type = $this->post_type; + $prepared_post->post_parent = $request['font_family_id']; + $prepared_post->post_status = 'publish'; + $prepared_post->post_title = $title; + $prepared_post->post_name = sanitize_title( $title ); + $prepared_post->post_content = wp_json_encode( $settings ); + + return $prepared_post; + } + + /** + * Sanitizes a single src value for a font face. + * + * @since 6.5.0 + * + * @param string $value Font face src that is a URL or the key for a $_FILES array item. + * @return string Sanitized value. + */ + protected function sanitize_src( $value ) { + $value = ltrim( $value ); + return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value ); + } + + /** + * Handles the upload of a font file using wp_handle_upload(). + * + * @since 6.5.0 + * + * @param array $file Single file item from $_FILES. + * @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure. + */ + protected function handle_font_file_upload( $file ) { + add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); + add_filter( 'upload_dir', 'wp_get_font_dir' ); + + $overrides = array( + 'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ), + // Arbitrary string to avoid the is_uploaded_file() check applied + // when using 'wp_handle_upload'. + 'action' => 'wp_handle_font_upload', + // Not testing a form submission. + 'test_form' => false, + // Seems mime type for files that are not images cannot be tested. + // See wp_check_filetype_and_ext(). + 'test_type' => true, + // Only allow uploading font files for this request. + 'mimes' => WP_Font_Utils::get_allowed_font_mime_types(), + ); + + $uploaded_file = wp_handle_upload( $file, $overrides ); + + remove_filter( 'upload_dir', 'wp_get_font_dir' ); + remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); + + return $uploaded_file; + } + + /** + * Handles file upload error. + * + * @since 6.5.0 + * + * @param array $file File upload data. + * @param string $message Error message from wp_handle_upload(). + * @return WP_Error WP_Error object. + */ + public function handle_font_file_upload_error( $file, $message ) { + $status = 500; + $code = 'rest_font_upload_unknown_error'; + + if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) { + $status = 400; + $code = 'rest_font_upload_invalid_file_type'; + } + + return new WP_Error( $code, $message, array( 'status' => $status ) ); + } + + /** + * Returns relative path to an uploaded font file. + * + * The path is relative to the current fonts directory. + * + * @since 6.5.0 + * @access private + * + * @param string $path Full path to the file. + * @return string Relative path on success, unchanged path on failure. + */ + protected function relative_fonts_path( $path ) { + $new_path = $path; + + $fonts_dir = wp_get_font_dir(); + if ( str_starts_with( $new_path, $fonts_dir['path'] ) ) { + $new_path = str_replace( $fonts_dir, '', $new_path ); + $new_path = ltrim( $new_path, '/' ); + } + + return $new_path; + } + + /** + * Gets the font face's settings from the post. + * + * @since 6.5.0 + * + * @param WP_Post $post Font face post object. + * @return array Font face settings array. + */ + protected function get_settings_from_post( $post ) { + $settings = json_decode( $post->post_content, true ); + $properties = $this->get_item_schema()['properties']['font_face_settings']['properties']; + + // Provide required, empty settings if needed. + if ( null === $settings ) { + $settings = array( + 'fontFamily' => '', + 'src' => array(), + ); + } + + // Only return the properties defined in the schema. + return array_intersect_key( $settings, $properties ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php new file mode 100644 index 0000000000000..184b42d141dd3 --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php @@ -0,0 +1,564 @@ +post_type ); + + if ( ! current_user_can( $post_type->cap->read ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access font families.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Checks if a given request has access to a font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + if ( ! current_user_can( 'read_post', $post->ID ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access this font family.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Validates settings when creating or updating a font family. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font family settings. + * @param WP_REST_Request $request Request object. + * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. + */ + public function validate_font_family_settings( $value, $request ) { + $settings = json_decode( $value, true ); + + // Check settings string is valid JSON. + if ( null === $settings ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Parameter name: "font_family_settings". */ + sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ), + array( 'status' => 400 ) + ); + } + + $schema = $this->get_item_schema()['properties']['font_family_settings']; + $required = $schema['required']; + + if ( isset( $request['id'] ) ) { + // Allow sending individual properties if we are updating an existing font family. + unset( $schema['required'] ); + + // But don't allow updating the slug, since it is used as a unique identifier. + if ( isset( $settings['slug'] ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Name of parameter being updated: font_family_settings[slug]". */ + sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ), + array( 'status' => 400 ) + ); + } + } + + // Check that the font face settings match the theme.json schema. + $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' ); + + if ( is_wp_error( $has_valid_settings ) ) { + $has_valid_settings->add_data( array( 'status' => 400 ) ); + return $has_valid_settings; + } + + // Check that none of the required settings are empty values. + foreach ( $required as $key ) { + if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */ + sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ), + array( 'status' => 400 ) + ); + } + } + + return true; + } + + /** + * Sanitizes the font family settings when creating or updating a font family. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font family settings. + * @return array Decoded array of font family settings. + */ + public function sanitize_font_family_settings( $value ) { + // Settings arrive as stringified JSON, since this is a multipart/form-data request. + $settings = json_decode( $value, true ); + $schema = $this->get_item_schema()['properties']['font_family_settings']['properties']; + + // Sanitize settings based on callbacks in the schema. + foreach ( $settings as $key => $value ) { + $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; + $settings[ $key ] = call_user_func( $sanitize_callback, $value ); + } + + return $settings; + } + + /** + * Creates a single font family. + * + * @since 6.5.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. + */ + public function create_item( $request ) { + $settings = $request->get_param( 'font_family_settings' ); + + // Check that the font family slug is unique. + $query = new WP_Query( + array( + 'post_type' => $this->post_type, + 'posts_per_page' => 1, + 'name' => $settings['slug'], + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + if ( ! empty( $query->posts ) ) { + return new WP_Error( + 'rest_duplicate_font_family', + /* translators: %s: Font family slug. */ + sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ), + array( 'status' => 400 ) + ); + } + + return parent::create_item( $request ); + } + + /** + * Deletes a single font family. + * + * @since 6.5.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. + */ + public function delete_item( $request ) { + $force = isset( $request['force'] ) ? (bool) $request['force'] : false; + + // We don't support trashing for font families. + if ( ! $force ) { + return new WP_Error( + 'rest_trash_not_supported', + /* translators: %s: force=true */ + sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), + array( 'status' => 501 ) + ); + } + + return parent::delete_item( $request ); + } + + /** + * Prepares a single font family output for response. + * + * @since 6.5.0 + * + * @param WP_Post $item Post object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = array(); + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = $item->ID; + } + + if ( rest_is_field_included( 'theme_json_version', $fields ) ) { + $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; + } + + if ( rest_is_field_included( 'font_faces', $fields ) ) { + $data['font_faces'] = $this->get_font_face_ids( $item->ID ); + } + + if ( rest_is_field_included( 'font_family_settings', $fields ) ) { + $data['font_family_settings'] = $this->get_settings_from_post( $item ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) ) { + $links = $this->prepare_links( $item ); + $response->add_links( $links ); + } + + /** + * Filters the font family data for a REST API response. + * + * @since 6.5.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Font family post object. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request ); + } + + /** + * Retrieves the post's schema, conforming to JSON Schema. + * + * @since 6.5.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => $this->post_type, + 'type' => 'object', + // Base properties for every Post. + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the post.', 'default' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'theme_json_version' => array( + 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), + 'type' => 'integer', + 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, + 'minimum' => 2, + 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'font_faces' => array( + 'description' => __( 'The IDs of the child font faces in the font family.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit', 'embed' ), + 'items' => array( + 'type' => 'integer', + ), + ), + // Font family settings come directly from theme.json schema + // See https://schemas.wp.org/trunk/theme.json + 'font_family_settings' => array( + 'description' => __( 'font-face definition in theme.json format.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'properties' => array( + 'name' => array( + 'description' => __( 'Name of the font family preset, translatable.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'slug' => array( + 'description' => __( 'Kebab-case unique identifier for the font family preset.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_title', + ), + ), + 'fontFamily' => array( + 'description' => __( 'CSS font-family value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), + ), + ), + 'preview' => array( + 'description' => __( 'URL to a preview image of the font family.' ), + 'type' => 'string', + 'format' => 'uri', + 'default' => '', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_url', + ), + ), + ), + 'required' => array( 'name', 'slug', 'fontFamily' ), + 'additionalProperties' => false, + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the item's schema for display / public consumption purposes. + * + * @since 6.5.0 + * + * @return array Public item schema data. + */ + public function get_public_item_schema() { + + $schema = parent::get_public_item_schema(); + + // Also remove `arg_options' from child font_family_settings properties, since the parent + // controller only handles the top level properties. + foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) { + unset( $property['arg_options'] ); + } + + return $schema; + } + + /** + * Retrieves the query params for the font family collection. + * + * @since 6.5.0 + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + // Remove unneeded params. + unset( + $query_params['after'], + $query_params['modified_after'], + $query_params['before'], + $query_params['modified_before'], + $query_params['search'], + $query_params['search_columns'], + $query_params['status'] + ); + + $query_params['orderby']['default'] = 'id'; + $query_params['orderby']['enum'] = array( 'id', 'include' ); + + /** + * Filters collection parameters for the font family controller. + * + * @since 6.5.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + */ + return apply_filters( 'rest_wp_font_family_collection_params', $query_params ); + } + + /** + * Get the arguments used when creating or updating a font family. + * + * @since 6.5.0 + * + * @return array Font family create/edit arguments. + */ + public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { + if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) { + $properties = $this->get_item_schema()['properties']; + return array( + 'theme_json_version' => $properties['theme_json_version'], + // When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data. + // Font families don't currently support file uploads, but may accept preview files in the future. + 'font_family_settings' => array( + 'description' => __( 'font-family declaration in theme.json format, encoded as a string.' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'validate_font_family_settings' ), + 'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ), + ), + ); + } + + return parent::get_endpoint_args_for_item_schema( $method ); + } + + /** + * Get the child font face post IDs. + * + * @since 6.5.0 + * + * @param int $font_family_id Font family post ID. + * @return int[] Array of child font face post IDs. + */ + protected function get_font_face_ids( $font_family_id ) { + $query = new WP_Query( + array( + 'fields' => 'ids', + 'post_parent' => $font_family_id, + 'post_type' => 'wp_font_face', + 'posts_per_page' => 99, + 'order' => 'ASC', + 'orderby' => 'id', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + + return $query->posts; + } + + /** + * Prepares font family links for the request. + * + * @since 6.5.0 + * + * @param WP_Post $post Post object. + * @return array Links for the given post. + */ + protected function prepare_links( $post ) { + // Entity meta. + $links = parent::prepare_links( $post ); + + return array( + 'self' => $links['self'], + 'collection' => $links['collection'], + 'font_faces' => $this->prepare_font_face_links( $post->ID ), + ); + } + + /** + * Prepares child font face links for the request. + * + * @param int $font_family_id Font family post ID. + * @return array Links for the child font face posts. + */ + protected function prepare_font_face_links( $font_family_id ) { + $font_face_ids = $this->get_font_face_ids( $font_family_id ); + $links = array(); + foreach ( $font_face_ids as $font_face_id ) { + $links[] = array( + 'embeddable' => true, + 'href' => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ), + ); + } + return $links; + } + + /** + * Prepares a single font family post for create or update. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Request object. + * @return stdClass|WP_Error Post object or WP_Error. + */ + protected function prepare_item_for_database( $request ) { + $prepared_post = new stdClass(); + // Settings have already been decoded by ::sanitize_font_family_settings(). + $settings = $request->get_param( 'font_family_settings' ); + + // This is an update and we merge with the existing font family. + if ( isset( $request['id'] ) ) { + $existing_post = $this->get_post( $request['id'] ); + if ( is_wp_error( $existing_post ) ) { + return $existing_post; + } + + $prepared_post->ID = $existing_post->ID; + $existing_settings = $this->get_settings_from_post( $existing_post ); + $settings = array_merge( $existing_settings, $settings ); + } + + $prepared_post->post_type = $this->post_type; + $prepared_post->post_status = 'publish'; + $prepared_post->post_title = $settings['name']; + $prepared_post->post_name = sanitize_title( $settings['slug'] ); + + // Remove duplicate information from settings. + unset( $settings['name'] ); + unset( $settings['slug'] ); + + $prepared_post->post_content = wp_json_encode( $settings ); + + return $prepared_post; + } + + /** + * Gets the font family's settings from the post. + * + * @since 6.5.0 + * + * @param WP_Post $post Font family post object. + * @return array Font family settings array. + */ + protected function get_settings_from_post( $post ) { + $settings_json = json_decode( $post->post_content, true ); + + // Default to empty strings if the settings are missing. + return array( + 'name' => isset( $post->post_title ) && $post->post_title ? $post->post_title : '', + 'slug' => isset( $post->post_name ) && $post->post_name ? $post->post_name : '', + 'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '', + 'preview' => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '', + ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php index 9ce7e2b3df66a..db1be197e5ab4 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php @@ -47,6 +47,7 @@ public function __construct() { * Registers the controller's routes. * * @since 6.3.0 + * @since 6.5.0 Added route to fetch individual global styles revisions. */ public function register_routes() { register_rest_route( @@ -68,6 +69,32 @@ public function register_routes() { 'schema' => array( $this, 'get_public_item_schema' ), ) ); + + register_rest_route( + $this->namespace, + '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The ID for the parent of the global styles revision.' ), + 'type' => 'integer', + ), + 'id' => array( + 'description' => __( 'Unique identifier for the global styles revision.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); } /** @@ -241,6 +268,56 @@ public function get_items( $request ) { return $response; } + /** + * Retrieves one global styles revision from the collection. + * + * @since 6.5.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. + */ + public function get_item( $request ) { + $parent = $this->get_parent( $request['parent'] ); + if ( is_wp_error( $parent ) ) { + return $parent; + } + + $revision = $this->get_revision( $request['id'] ); + if ( is_wp_error( $revision ) ) { + return $revision; + } + + $response = $this->prepare_item_for_response( $revision, $request ); + return rest_ensure_response( $response ); + } + + /** + * Gets the global styles revision, if the ID is valid. + * + * @since 6.5.0 + * + * @param int $id Supplied ID. + * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. + */ + protected function get_revision( $id ) { + $error = new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid global styles revision ID.' ), + array( 'status' => 404 ) + ); + + if ( (int) $id <= 0 ) { + return $error; + } + + $revision = get_post( (int) $id ); + if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) { + return $error; + } + + return $revision; + } + /** * Checks the post_date_gmt or modified_gmt and prepare any post or * modified date for single post output. 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 53f8faa75595b..1549fd4295561 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 @@ -726,6 +726,14 @@ public function prepare_item_for_response( $item, $request ) { $data['modified'] = mysql_to_rfc3339( $template->modified ); } + if ( rest_is_field_included( 'author_text', $fields ) ) { + $data['author_text'] = self::get_wp_templates_author_text_field( $template ); + } + + if ( rest_is_field_included( 'original_source', $fields ) ) { + $data['original_source'] = self::get_wp_templates_original_source_field( $template ); + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -748,6 +756,83 @@ public function prepare_item_for_response( $item, $request ) { return $response; } + /** + * Returns the source from where the template originally comes from. + * + * @since 6.5.0 + * + * @param WP_Block_Template $template_object Template instance. + * @return string Original source of the template one of theme, plugin, site, or user. + */ + 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. + if ( $template_object->has_theme_file && + ( 'theme' === $template_object->origin || ( + empty( $template_object->origin ) && in_array( + $template_object->source, + array( + 'theme', + 'custom', + ), + true + ) ) + ) + ) { + return 'theme'; + } + + // Added by plugin. + if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) { + 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. + if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { + return 'site'; + } + } + + // Added by user. + return 'user'; + } + + /** + * Returns a human readable text for the author of the template. + * + * @since 6.5.0 + * + * @param WP_Block_Template $template_object Template instance. + * @return string Human readable text for the author. + */ + private static function get_wp_templates_author_text_field( $template_object ) { + $original_source = self::get_wp_templates_original_source_field( $template_object ); + switch ( $original_source ) { + case 'theme': + $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); + return empty( $theme_name ) ? $template_object->theme : $theme_name; + case 'plugin': + $plugins = get_plugins(); + $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ]; + return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; + case 'site': + return get_bloginfo( 'name' ); + case 'user': + $author = get_user_by( 'id', $template_object->author ); + if ( ! $author ) { + return __( 'Unknown author' ); + } + return $author->get( 'display_name' ); + } + } + /** * Prepares links for the request. @@ -861,13 +946,13 @@ public function get_item_schema() { 'title' => $this->post_type, 'type' => 'object', 'properties' => array( - 'id' => array( + 'id' => array( 'description' => __( 'ID of template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'slug' => array( + 'slug' => array( 'description' => __( 'Unique slug identifying the template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), @@ -875,29 +960,29 @@ public function get_item_schema() { 'minLength' => 1, 'pattern' => '[a-zA-Z0-9_\%-]+', ), - 'theme' => array( + 'theme' => array( 'description' => __( 'Theme identifier for the template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), - 'type' => array( + 'type' => array( 'description' => __( 'Type of template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), - 'source' => array( + 'source' => array( 'description' => __( 'Source of template' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'origin' => array( + 'origin' => array( 'description' => __( 'Source of a customized template' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'content' => array( + 'content' => array( 'description' => __( 'Content of template.' ), 'type' => array( 'object', 'string' ), 'default' => '', @@ -916,7 +1001,7 @@ public function get_item_schema() { ), ), ), - 'title' => array( + 'title' => array( 'description' => __( 'Title of template.' ), 'type' => array( 'object', 'string' ), 'default' => '', @@ -935,43 +1020,61 @@ public function get_item_schema() { ), ), ), - 'description' => array( + 'description' => array( 'description' => __( 'Description of template.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), ), - 'status' => array( + 'status' => array( 'description' => __( 'Status of template.' ), 'type' => 'string', 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 'default' => 'publish', 'context' => array( 'embed', 'view', 'edit' ), ), - 'wp_id' => array( + 'wp_id' => array( 'description' => __( 'Post ID.' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'has_theme_file' => array( + 'has_theme_file' => array( 'description' => __( 'Theme file exists.' ), 'type' => 'bool', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'author' => array( + 'author' => array( 'description' => __( 'The ID for the author of the template.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), - 'modified' => array( + 'modified' => array( 'description' => __( "The date the template was last modified, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'author_text' => array( + 'type' => 'string', + 'description' => __( 'Human readable text for the author.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'original_source' => array( + 'description' => __( 'Where the template originally comes from e.g. \'theme\'' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + 'enum' => array( + 'theme', + 'plugin', + 'site', + 'user', + ), + ), ), ); 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 c9ac6675d093f..7dcc4d79236a1 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 @@ -128,7 +128,7 @@ public function get_item_schema() { * * @since 5.9.0 * - * @param WP_REST_REQUEST $request Full details about the request. + * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error The parsed details as a response object. WP_Error if there are errors. */ public function parse_url_details( $request ) { diff --git a/src/wp-includes/rss.php b/src/wp-includes/rss.php index 6d8941ab3856d..4d3f35e5cb547 100644 --- a/src/wp-includes/rss.php +++ b/src/wp-includes/rss.php @@ -631,6 +631,9 @@ function _response_to_rss ($resp) { * Set up constants with default values, unless user overrides. * * @since 1.5.0 + * + * @global string $wp_version The WordPress version string. + * * @package External * @subpackage MagpieRSS */ diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 8886cb587b170..932fcfc06c6c0 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -96,6 +96,7 @@ 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', @@ -120,6 +121,7 @@ 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 ) { @@ -417,6 +419,8 @@ function wp_default_packages_inline_scripts( $scripts ) { $timezone_abbr = $timezone_date->format( 'T' ); } + $gmt_offset = get_option( 'gmt_offset', 0 ); + $scripts->add_inline_script( 'wp-date', sprintf( @@ -437,27 +441,27 @@ function wp_default_packages_inline_scripts( $scripts ) { 'past' => __( '%s ago' ), /* translators: One second from or to a particular datetime, e.g., "a second ago" or "a second from now". */ 's' => __( 'a second' ), - /* translators: %s: Duration in seconds from or to a particular datetime, e.g., "4 seconds ago" or "4 seconds from now". */ + /* translators: %d: Duration in seconds from or to a particular datetime, e.g., "4 seconds ago" or "4 seconds from now". */ 'ss' => __( '%d seconds' ), /* translators: One minute from or to a particular datetime, e.g., "a minute ago" or "a minute from now". */ 'm' => __( 'a minute' ), - /* translators: %s: Duration in minutes from or to a particular datetime, e.g., "4 minutes ago" or "4 minutes from now". */ + /* translators: %d: Duration in minutes from or to a particular datetime, e.g., "4 minutes ago" or "4 minutes from now". */ 'mm' => __( '%d minutes' ), - /* translators: %s: One hour from or to a particular datetime, e.g., "an hour ago" or "an hour from now". */ + /* translators: One hour from or to a particular datetime, e.g., "an hour ago" or "an hour from now". */ 'h' => __( 'an hour' ), - /* translators: %s: Duration in hours from or to a particular datetime, e.g., "4 hours ago" or "4 hours from now". */ + /* translators: %d: Duration in hours from or to a particular datetime, e.g., "4 hours ago" or "4 hours from now". */ 'hh' => __( '%d hours' ), - /* translators: %s: One day from or to a particular datetime, e.g., "a day ago" or "a day from now". */ + /* translators: One day from or to a particular datetime, e.g., "a day ago" or "a day from now". */ 'd' => __( 'a day' ), - /* translators: %s: Duration in days from or to a particular datetime, e.g., "4 days ago" or "4 days from now". */ + /* translators: %d: Duration in days from or to a particular datetime, e.g., "4 days ago" or "4 days from now". */ 'dd' => __( '%d days' ), - /* translators: %s: One month from or to a particular datetime, e.g., "a month ago" or "a month from now". */ + /* translators: One month from or to a particular datetime, e.g., "a month ago" or "a month from now". */ 'M' => __( 'a month' ), - /* translators: %s: Duration in months from or to a particular datetime, e.g., "4 months ago" or "4 months from now". */ + /* translators: %d: Duration in months from or to a particular datetime, e.g., "4 months ago" or "4 months from now". */ 'MM' => __( '%d months' ), - /* translators: %s: One year from or to a particular datetime, e.g., "a year ago" or "a year from now". */ + /* translators: One year from or to a particular datetime, e.g., "a year ago" or "a year from now". */ 'y' => __( 'a year' ), - /* translators: %s: Duration in years from or to a particular datetime, e.g., "4 years ago" or "4 years from now". */ + /* translators: %d: Duration in years from or to a particular datetime, e.g., "4 years ago" or "4 years from now". */ 'yy' => __( '%d years' ), ), 'startOfWeek' => (int) get_option( 'start_of_week', 0 ), @@ -473,9 +477,10 @@ function wp_default_packages_inline_scripts( $scripts ) { 'datetimeAbbreviated' => __( 'M j, Y g:i a' ), ), 'timezone' => array( - 'offset' => (float) get_option( 'gmt_offset', 0 ), - 'string' => $timezone_string, - 'abbr' => $timezone_abbr, + 'offset' => (float) $gmt_offset, + 'offsetFormatted' => str_replace( array( '.25', '.5', '.75' ), array( ':15', ':30', ':45' ), (string) $gmt_offset ), + 'string' => $timezone_string, + 'abbr' => $timezone_abbr, ), ) ) @@ -1714,7 +1719,7 @@ function wp_default_styles( $styles ) { ); $package_styles = array( - 'block-editor' => array( 'wp-components' ), + 'block-editor' => array( 'wp-components', 'wp-preferences' ), 'block-library' => array(), 'block-directory' => array(), 'components' => array(), @@ -1726,17 +1731,20 @@ function wp_default_styles( $styles ) { 'wp-edit-blocks', 'wp-block-library', 'wp-commands', + 'wp-preferences', ), 'editor' => array( 'wp-components', 'wp-block-editor', 'wp-reusable-blocks', 'wp-patterns', + 'wp-preferences', ), 'format-library' => array(), 'list-reusable-blocks' => array( 'wp-components' ), 'reusable-blocks' => array( 'wp-components' ), 'patterns' => array( 'wp-components' ), + 'preferences' => array( 'wp-components' ), 'nux' => array( 'wp-components' ), 'widgets' => array( 'wp-components', @@ -1748,6 +1756,7 @@ function wp_default_styles( $styles ) { 'wp-block-library', 'wp-reusable-blocks', 'wp-patterns', + 'wp-preferences', ), 'customize-widgets' => array( 'wp-widgets', @@ -1756,12 +1765,14 @@ function wp_default_styles( $styles ) { 'wp-block-library', 'wp-reusable-blocks', 'wp-patterns', + 'wp-preferences', ), 'edit-site' => array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks', 'wp-commands', + 'wp-preferences', ), ); @@ -2586,7 +2597,7 @@ function wp_should_load_block_editor_scripts_and_styles() { * @return bool Whether separate assets will be loaded. */ function wp_should_load_separate_core_block_assets() { - if ( is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + if ( is_admin() || is_feed() || wp_is_rest_endpoint() ) { return false; } @@ -2834,18 +2845,18 @@ function wp_print_script_tag( $attributes ) { } /** - * Wraps inline JavaScript in `\n", wp_sanitize_script_attributes( $attributes ), $javascript ); + return sprintf( "%s\n", wp_sanitize_script_attributes( $attributes ), $data ); } /** - * Prints inline JavaScript wrapped in `', + 'description' => 'My collection description', + 'font_families' => array( + array( + 'font_family_settings' => array( + 'fontFamily' => 'Open Sans, sans-serif', + 'slug' => 'open-sans', + 'name' => 'Open Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'https://example.com/src-as-string.ttf?a=', + ), + array( + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => array( + 'https://example.com/src-as-array.woff2?a=', + 'https://example.com/src-as-array.ttf', + ), + ), + ), + 'unwanted_property' => 'potentially evil value', + ), + 'categories' => array( 'sans-serif' ), + ), + ), + 'categories' => array( + array( + 'name' => 'Mock col', + 'slug' => 'mock-col', + 'unwanted_property' => 'potentially evil value', + ), + ), + 'unwanted_property' => 'potentially evil value', + ), + 'expected_data' => array( + 'description' => 'My collection description', + 'categories' => array( + array( + 'name' => 'Mock col', + 'slug' => 'mock-colalertxss', + ), + ), + 'name' => 'My Collection', + 'font_families' => array( + array( + 'font_family_settings' => array( + 'fontFamily' => 'Open Sans, sans-serif', + 'slug' => 'open-sans', + 'name' => 'Open Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'https://example.com/src-as-string.ttf?a=', + ), + array( + 'fontFamily' => 'Open Sans', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => array( + 'https://example.com/src-as-array.woff2?a=', + 'https://example.com/src-as-array.ttf', + ), + ), + ), + ), + 'categories' => array( 'sans-serifalertxss' ), + ), + ), + ), + ), + + ); + } + + /** + * @dataProvider data_should_error_when_missing_properties + * + * @param array $config Font collection config. + */ + public function test_should_error_when_missing_properties( $config ) { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::sanitize_and_validate_data' ); + + $collection = new WP_Font_Collection( 'my-collection', $config ); + $data = $collection->get_data(); + + $this->assertWPError( $data, 'Error is not returned when property is missing or invalid.' ); + $this->assertSame( + $data->get_error_code(), + 'font_collection_missing_property', + 'Incorrect error code when property is missing or invalid.' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_error_when_missing_properties() { + return array( + 'missing name' => array( + 'config' => array( + 'font_families' => array( 'mock' ), + ), + ), + 'empty name' => array( + 'config' => array( + 'name' => '', + 'font_families' => array( 'mock' ), + ), + ), + 'missing font_families' => array( + 'config' => array( + 'name' => 'My Collection', + ), + ), + 'empty font_families' => array( + 'config' => array( + 'name' => 'My Collection', + 'font_families' => array(), + ), + ), + ); + } + + public function test_should_error_with_invalid_json_file_path() { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); + + $collection = new WP_Font_Collection( 'my-collection', 'non-existing.json' ); + $data = $collection->get_data(); + + $this->assertWPError( $data, 'Error is not returned when invalid file path is provided.' ); + $this->assertSame( + $data->get_error_code(), + 'font_collection_json_missing', + 'Incorrect error code when invalid file path is provided.' + ); + } + + public function test_should_error_with_invalid_json_from_file() { + $mock_file = wp_tempnam( 'my-collection-data-' ); + file_put_contents( $mock_file, 'invalid-json' ); + + $collection = new WP_Font_Collection( 'my-collection', $mock_file ); + + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Testing error response returned by `load_from_json`, not the underlying error from `wp_json_file_decode`. + $data = @$collection->get_data(); + + $this->assertWPError( $data, 'Error is not returned with invalid json file contents.' ); + $this->assertSame( + $data->get_error_code(), + 'font_collection_decode_error', + 'Incorrect error code with invalid json file contents.' + ); + } + + public function test_should_error_with_invalid_url() { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); + + $collection = new WP_Font_Collection( 'my-collection', 'not-a-url' ); + $data = $collection->get_data(); + + $this->assertWPError( $data, 'Error is not returned when invalid url is provided.' ); + $this->assertSame( + $data->get_error_code(), + 'font_collection_json_missing', + 'Incorrect error code when invalid url is provided.' + ); + } + + public function test_should_error_with_unsuccessful_response_status() { + add_filter( 'pre_http_request', array( $this, 'mock_request_unsuccessful_response' ), 10, 3 ); + + $collection = new WP_Font_Collection( 'my-collection', 'https://example.com/fonts/missing-collection.json' ); + $data = $collection->get_data(); + + remove_filter( 'pre_http_request', array( $this, 'mock_request_unsuccessful_response' ) ); + + $this->assertWPError( $data, 'Error is not returned when response is unsuccessful.' ); + $this->assertSame( + $data->get_error_code(), + 'font_collection_request_error', + 'Incorrect error code when response is unsuccussful.' + ); + } + + public function test_should_error_with_invalid_json_from_url() { + add_filter( 'pre_http_request', array( $this, 'mock_request_invalid_json' ), 10, 3 ); + + $collection = new WP_Font_Collection( 'my-collection', 'https://example.com/fonts/invalid-collection.json' ); + $data = $collection->get_data(); + + remove_filter( 'pre_http_request', array( $this, 'mock_request_invalid_json' ) ); + + $this->assertWPError( $data, 'Error is not returned when response is invalid json.' ); + $this->assertSame( + $data->get_error_code(), + 'font_collection_decode_error', + 'Incorrect error code when response is invalid json.' + ); + } + + public function mock_request( $preempt, $args, $url ) { + if ( 'https://example.com/fonts/mock-font-collection.json' !== $url ) { + return false; + } + + return array( + 'body' => wp_json_encode( self::$mock_collection_data ), + 'response' => array( + 'code' => 200, + ), + ); + } + + public function mock_request_unsuccessful_response( $preempt, $args, $url ) { + if ( 'https://example.com/fonts/missing-collection.json' !== $url ) { + return false; + } + + return array( + 'body' => '', + 'response' => array( + 'code' => 404, + ), + ); + } + + public function mock_request_invalid_json( $preempt, $args, $url ) { + if ( 'https://example.com/fonts/invalid-collection.json' !== $url ) { + return false; + } + + return array( + 'body' => 'invalid', + 'response' => array( + 'code' => 200, + ), + ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/base.php b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/base.php new file mode 100644 index 0000000000000..135329e5add73 --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/base.php @@ -0,0 +1,25 @@ +get_font_collections(); + foreach ( $collections as $slug => $collection ) { + WP_Font_Library::get_instance()->unregister_font_collection( $slug ); + } + } + + public function set_up() { + parent::set_up(); + $this->reset_font_collections(); + } + + public function tear_down() { + parent::tear_down(); + $this->reset_font_collections(); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php new file mode 100644 index 0000000000000..e01cc2c1a105a --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php @@ -0,0 +1,30 @@ + 'Test Collection', + 'font_families' => array( 'mock' ), + ); + + wp_register_font_collection( 'my-font-collection', $mock_collection_data ); + $font_collection = WP_Font_Library::get_instance()->get_font_collection( 'my-font-collection' ); + $this->assertInstanceOf( 'WP_Font_Collection', $font_collection ); + } + + public function test_should_get_no_font_collection_if_the_slug_is_not_registered() { + $font_collection = WP_Font_Library::get_instance()->get_font_collection( 'not-registered-font-collection' ); + $this->assertNull( $font_collection ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php new file mode 100644 index 0000000000000..f5ca6389b8ff5 --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php @@ -0,0 +1,34 @@ +get_font_collections(); + $this->assertEmpty( $font_collections, 'Should return an empty array.' ); + } + + public function test_should_get_mock_font_collection() { + $my_font_collection_config = array( + 'name' => 'My Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'font_families' => array( 'mock' ), + ); + + WP_Font_Library::get_instance()->register_font_collection( 'my-font-collection', $my_font_collection_config ); + + $font_collections = WP_Font_Library::get_instance()->get_font_collections(); + $this->assertNotEmpty( $font_collections, 'Should return an array of font collections.' ); + $this->assertCount( 1, $font_collections, 'Should return an array with one font collection.' ); + $this->assertArrayHasKey( 'my-font-collection', $font_collections, 'The array should have the key of the registered font collection id.' ); + $this->assertInstanceOf( 'WP_Font_Collection', $font_collections['my-font-collection'], 'The value of the array $font_collections[id] should be an instance of WP_Font_Collection class.' ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php new file mode 100644 index 0000000000000..d3b0f126e2e7e --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php @@ -0,0 +1,40 @@ + 'My Collection', + 'font_families' => array( 'mock' ), + ); + + $collection = WP_Font_Library::get_instance()->register_font_collection( 'my-collection', $config ); + $this->assertInstanceOf( 'WP_Font_Collection', $collection ); + } + + public function test_should_return_error_if_slug_is_repeated() { + $mock_collection_data = array( + 'name' => 'Test Collection', + 'font_families' => array( 'mock' ), + ); + + // Register first collection. + $collection1 = WP_Font_Library::get_instance()->register_font_collection( 'my-collection-1', $mock_collection_data ); + $this->assertInstanceOf( 'WP_Font_Collection', $collection1, 'A collection should be registered.' ); + + // Expects a _doing_it_wrong notice. + $this->setExpectedIncorrectUsage( 'WP_Font_Library::register_font_collection' ); + + // Try to register a second collection with same slug. + WP_Font_Library::get_instance()->register_font_collection( 'my-collection-1', $mock_collection_data ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php new file mode 100644 index 0000000000000..ddb0fa91c1d60 --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php @@ -0,0 +1,46 @@ + 'Test Collection', + 'font_families' => array( 'mock' ), + ); + + // Registers two mock font collections. + WP_Font_Library::get_instance()->register_font_collection( 'mock-font-collection-1', $mock_collection_data ); + WP_Font_Library::get_instance()->register_font_collection( 'mock-font-collection-2', $mock_collection_data ); + + // Unregister mock font collection. + WP_Font_Library::get_instance()->unregister_font_collection( 'mock-font-collection-1' ); + $collections = WP_Font_Library::get_instance()->get_font_collections(); + $this->assertArrayNotHasKey( 'mock-font-collection-1', $collections, 'Font collection was not unregistered.' ); + $this->assertArrayHasKey( 'mock-font-collection-2', $collections, 'Font collection was unregistered by mistake.' ); + + // Unregisters remaining mock font collection. + WP_Font_Library::get_instance()->unregister_font_collection( 'mock-font-collection-2' ); + $collections = WP_Font_Library::get_instance()->get_font_collections(); + $this->assertArrayNotHasKey( 'mock-font-collection-2', $collections, 'Mock font collection was not unregistered.' ); + + // Checks that all font collections were unregistered. + $this->assertEmpty( $collections, 'Font collections were not unregistered.' ); + } + + public function unregister_non_existing_collection() { + // Unregisters non-existing font collection. + WP_Font_Library::get_instance()->unregister_font_collection( 'non-existing-collection' ); + $collections = WP_Font_Library::get_instance()->get_font_collections(); + $this->assertEmpty( $collections, 'No collections should be registered.' ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontUtils/getFontFaceSlug.php b/tests/phpunit/tests/fonts/font-library/wpFontUtils/getFontFaceSlug.php new file mode 100644 index 0000000000000..de0b02e63185e --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontUtils/getFontFaceSlug.php @@ -0,0 +1,92 @@ +assertSame( $expected_slug, $slug ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_font_face_slug_normalizes_values() { + return array( + 'Sets defaults' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans', + ), + 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF', + ), + 'Converts normal weight to 400' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans', + 'fontWeight' => 'normal', + ), + 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF', + ), + 'Converts bold weight to 700' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans', + 'fontWeight' => 'bold', + ), + 'expected_slug' => 'open sans;normal;700;100%;U+0-10FFFF', + ), + 'Converts normal font-stretch to 100%' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans', + 'fontStretch' => 'normal', + ), + 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF', + ), + 'Removes double quotes from fontFamilies' => array( + 'settings' => array( + 'fontFamily' => '"Open Sans"', + ), + 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF', + ), + 'Removes single quotes from fontFamilies' => array( + 'settings' => array( + 'fontFamily' => "'Open Sans'", + ), + 'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF', + ), + 'Removes spaces between comma separated font families' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans, serif', + ), + 'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF', + ), + 'Removes tabs between comma separated font families' => array( + 'settings' => array( + 'fontFamily' => "Open Sans,\tserif", + ), + 'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF', + ), + 'Removes new lines between comma separated font families' => array( + 'settings' => array( + 'fontFamily' => "Open Sans,\nserif", + ), + 'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF', + ), + ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php b/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php new file mode 100644 index 0000000000000..71511331c65dc --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php @@ -0,0 +1,63 @@ +assertSame( + $expected, + WP_Font_Utils::sanitize_font_family( + $font_family + ) + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_sanitize_font_family() { + return array( + 'data_families_with_spaces_and_numbers' => array( + 'font_family' => 'Rock 3D , Open Sans,serif', + 'expected' => '"Rock 3D", "Open Sans", serif', + ), + 'data_single_font_family' => array( + 'font_family' => 'Rock 3D', + 'expected' => '"Rock 3D"', + ), + 'data_no_spaces' => array( + 'font_family' => 'Rock3D', + 'expected' => 'Rock3D', + ), + 'data_many_spaces_and_existing_quotes' => array( + 'font_family' => 'Rock 3D serif, serif,sans-serif, "Open Sans"', + 'expected' => '"Rock 3D serif", serif, sans-serif, "Open Sans"', + ), + 'data_empty_family' => array( + 'font_family' => ' ', + 'expected' => '', + ), + 'data_font_family_with_whitespace_tags_new_lines' => array( + 'font_family' => " Rock 3D\n ", + 'expected' => '"Rock 3D"', + ), + ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFromSchema.php b/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFromSchema.php new file mode 100644 index 0000000000000..88983fe15a14e --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFromSchema.php @@ -0,0 +1,310 @@ +assertSame( $result, $expected ); + } + + public function data_sanitize_from_schema() { + return array( + 'One level associative array' => array( + 'data' => array( + 'slug' => 'open - sans', + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json', + ), + 'schema' => array( + 'slug' => 'sanitize_title', + 'fontFamily' => 'sanitize_text_field', + 'src' => 'sanitize_url', + ), + 'expected' => array( + 'slug' => 'open-sansalertxss', + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json/stylescriptalert(xss)/script', + ), + ), + + 'Nested associative arrays' => array( + 'data' => array( + 'slug' => 'open - sans', + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json', + 'nested' => array( + 'key1' => 'value1', + 'key2' => 'value2', + 'nested2' => array( + 'key3' => 'value3', + 'key4' => 'value4', + ), + ), + ), + 'schema' => array( + 'slug' => 'sanitize_title', + 'fontFamily' => 'sanitize_text_field', + 'src' => 'sanitize_url', + 'nested' => array( + 'key1' => 'sanitize_text_field', + 'key2' => 'sanitize_text_field', + 'nested2' => array( + 'key3' => 'sanitize_text_field', + 'key4' => 'sanitize_text_field', + ), + ), + ), + 'expected' => array( + 'slug' => 'open-sansalertxss', + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json/stylescriptalert(xss)/script', + 'nested' => array( + 'key1' => 'value1', + 'key2' => 'value2', + 'nested2' => array( + 'key3' => 'value3', + 'key4' => 'value4', + ), + ), + ), + ), + + 'Indexed arrays' => array( + 'data' => array( + 'slug' => 'oPeN SaNs', + 'enum' => array( + 'value1', + 'value2', + 'value3', + ), + ), + 'schema' => array( + 'slug' => 'sanitize_title', + 'enum' => array( 'sanitize_text_field' ), + ), + 'expected' => array( + 'slug' => 'open-sans', + 'enum' => array( 'value1', 'value2', 'value3' ), + ), + ), + + 'Nested indexed arrays' => array( + 'data' => array( + 'slug' => 'OPEN-SANS', + 'name' => 'Open Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json/stylescriptalert(xss)/script', + ), + array( + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json/stylescriptalert(xss)/script', + ), + ), + ), + 'schema' => array( + 'slug' => 'sanitize_title', + 'name' => 'sanitize_text_field', + 'fontFace' => array( + array( + 'fontFamily' => 'sanitize_text_field', + 'src' => 'sanitize_url', + ), + ), + ), + 'expected' => array( + 'slug' => 'open-sans', + 'name' => 'Open Sans', + 'fontFace' => array( + array( + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json/stylescriptalert(xss)/script', + ), + array( + 'fontFamily' => 'Open Sans, sans-serif', + 'src' => 'https://wordpress.org/example.json/stylescriptalert(xss)/script', + ), + ), + ), + ), + + 'Custom sanitization function' => array( + 'data' => array( + 'key1' => 'abc123edf456ghi789', + 'key2' => 'value2', + ), + 'schema' => array( + 'key1' => function ( $value ) { + // Remove the six first character. + return substr( $value, 6 ); + }, + 'key2' => function ( $value ) { + // Capitalize the value. + return strtoupper( $value ); + }, + ), + 'expected' => array( + 'key1' => 'edf456ghi789', + 'key2' => 'VALUE2', + ), + ), + + 'Null as schema value' => array( + 'data' => array( + 'key1' => 'value1', + 'key2' => 'value2', + 'nested' => array( + 'key3' => 'value3', + 'key4' => 'value4', + ), + ), + 'schema' => array( + 'key1' => null, + 'key2' => 'sanitize_text_field', + 'nested' => null, + ), + 'expected' => array( + 'key1' => 'value1', + 'key2' => 'value2', + 'nested' => array( + 'key3' => 'value3', + 'key4' => 'value4', + ), + ), + ), + + 'Keys to remove' => array( + 'data' => array( + 'key1' => 'value1', + 'key2' => 'value2', + 'unwanted1' => 'value', + 'unwanted2' => 'value', + 'nestedAssociative' => array( + 'key5' => 'value5', + 'unwanted3' => 'value', + ), + 'nestedIndexed' => array( + array( + 'key6' => 'value7', + 'unwanted4' => 'value', + ), + array( + 'key6' => 'value7', + 'unwanted5' => 'value', + ), + ), + + ), + 'schema' => array( + 'key1' => 'sanitize_text_field', + 'key2' => 'sanitize_text_field', + 'nestedAssociative' => array( + 'key5' => 'sanitize_text_field', + ), + 'nestedIndexed' => array( + array( + 'key6' => 'sanitize_text_field', + ), + ), + ), + 'expected' => array( + 'key1' => 'value1', + 'key2' => 'value2', + 'nestedAssociative' => array( + 'key5' => 'value5', + ), + 'nestedIndexed' => array( + array( + 'key6' => 'value7', + ), + array( + 'key6' => 'value7', + ), + ), + ), + ), + + 'With empty structure' => array( + 'data' => array( + 'slug' => 'open-sans', + 'nested' => array( + 'key1' => 'value', + 'nested2' => array( + 'key2' => 'value', + 'nested3' => array( + 'nested4' => array(), + ), + ), + ), + ), + 'schema' => array( + 'slug' => 'sanitize_title', + 'nested' => array( + 'key1' => 'sanitize_text_field', + 'nested2' => array( + 'key2' => 'sanitize_text_field', + 'nested3' => array( + 'key3' => 'sanitize_text_field', + 'nested4' => array( + 'key4' => 'sanitize_text_field', + ), + ), + ), + ), + ), + 'expected' => array( + 'slug' => 'open-sans', + 'nested' => array( + 'key1' => 'value', + 'nested2' => array( + 'key2' => 'value', + ), + ), + ), + ), + ); + } + + public function test_sanitize_from_schema_with_invalid_data() { + $data = 'invalid data'; + $schema = array( + 'key1' => 'sanitize_text_field', + 'key2' => 'sanitize_text_field', + ); + + $result = WP_Font_Utils::sanitize_from_schema( $data, $schema ); + + $this->assertSame( $result, array() ); + } + + + public function test_sanitize_from_schema_with_invalid_schema() { + $data = array( + 'key1' => 'value1', + 'key2' => 'value2', + ); + $schema = 'invalid schema'; + + $result = WP_Font_Utils::sanitize_from_schema( $data, $schema ); + + $this->assertSame( $result, array() ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpFontsDir.php b/tests/phpunit/tests/fonts/font-library/wpFontsDir.php new file mode 100644 index 0000000000000..a8f79888315bd --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpFontsDir.php @@ -0,0 +1,72 @@ + path_join( WP_CONTENT_DIR, 'fonts' ), + 'url' => content_url( 'fonts' ), + 'subdir' => '', + 'basedir' => path_join( WP_CONTENT_DIR, 'fonts' ), + 'baseurl' => content_url( 'fonts' ), + 'error' => false, + ); + } + + public function test_fonts_dir() { + $font_dir = wp_get_font_dir(); + + $this->assertSame( $font_dir, static::$dir_defaults ); + } + + public function test_fonts_dir_with_filter() { + // Define a callback function to pass to the filter. + function set_new_values( $defaults ) { + $defaults['path'] = '/custom-path/fonts/my-custom-subdir'; + $defaults['url'] = 'http://example.com/custom-path/fonts/my-custom-subdir'; + $defaults['subdir'] = 'my-custom-subdir'; + $defaults['basedir'] = '/custom-path/fonts'; + $defaults['baseurl'] = 'http://example.com/custom-path/fonts'; + $defaults['error'] = false; + return $defaults; + } + + // Add the filter. + add_filter( 'font_dir', 'set_new_values' ); + + // Gets the fonts dir. + $font_dir = wp_get_font_dir(); + + $expected = array( + 'path' => '/custom-path/fonts/my-custom-subdir', + 'url' => 'http://example.com/custom-path/fonts/my-custom-subdir', + 'subdir' => 'my-custom-subdir', + 'basedir' => '/custom-path/fonts', + 'baseurl' => 'http://example.com/custom-path/fonts', + 'error' => false, + ); + + // Remove the filter. + remove_filter( 'font_dir', 'set_new_values' ); + + $this->assertSame( $expected, $font_dir, 'The wp_get_font_dir() method should return the expected values.' ); + + // Gets the fonts dir. + $font_dir = wp_get_font_dir(); + + $this->assertSame( static::$dir_defaults, $font_dir, 'The wp_get_font_dir() method should return the default values.' ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php new file mode 100644 index 0000000000000..0f7fe8f9c662b --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php @@ -0,0 +1,209 @@ +get_font_collections(); + foreach ( $collections as $slug => $collection ) { + WP_Font_Library::get_instance()->unregister_font_collection( $slug ); + } + + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + $mock_file = wp_tempnam( 'my-collection-data-' ); + file_put_contents( $mock_file, '{"name": "Mock Collection", "font_families": [ "mock" ], "categories": [ "mock" ] }' ); + + wp_register_font_collection( 'mock-col-slug', $mock_file ); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); + wp_unregister_font_collection( 'mock-col-slug' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertCount( 1, $routes['/wp/v2/font-collections'], 'Rest server has not the collections path initialized.' ); + $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' ); + + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections'][0]['methods'], 'Rest server has not the GET method for collections initialized.' ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection initialized.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_items + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $content = $response->get_data(); + $this->assertIsArray( $content ); + $this->assertSame( 200, $response->get_status() ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_items + */ + public function test_get_items_should_only_return_valid_collections() { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); + + wp_set_current_user( self::$admin_id ); + wp_register_font_collection( 'invalid-collection', 'invalid-collection-file' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $content = $response->get_data(); + + wp_unregister_font_collection( 'invalid-collection' ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertCount( 1, $content, 'The response should only contain valid collections.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + + $response_data = $response->get_data(); + $this->assertArrayHasKey( 'name', $response_data, 'Response data does not have the name key.' ); + $this->assertArrayHasKey( 'slug', $response_data, 'Response data does not have the slug key.' ); + $this->assertArrayHasKey( 'description', $response_data, 'Response data does not have the description key.' ); + $this->assertArrayHasKey( 'font_families', $response_data, 'Response data does not have the font_families key.' ); + $this->assertArrayHasKey( 'categories', $response_data, 'Response data does not have the categories key.' ); + + $this->assertIsString( $response_data['name'], 'name is not a string.' ); + $this->assertIsString( $response_data['slug'], 'slug is not a string.' ); + $this->assertIsString( $response_data['description'], 'description is not a string.' ); + + $this->assertIsArray( $response_data['font_families'], 'font_families is not an array.' ); + $this->assertIsArray( $response_data['categories'], 'categories is not an array.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_invalid_slug() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_font_collection_not_found', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_invalid_collection() { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); + + wp_set_current_user( self::$admin_id ); + $slug = 'invalid-collection'; + wp_register_font_collection( $slug, 'invalid-collection-file' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/' . $slug ); + $response = rest_get_server()->dispatch( $request ); + + wp_unregister_font_collection( $slug ); + + $this->assertErrorResponse( 'font_collection_json_missing', $response, 500, 'When the collection json file is invalid, the response should return an error for "font_collection_json_missing" with 500 status.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_invalid_id_permission() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' ); + + wp_set_current_user( 0 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response status should be 401 for non-authenticated users.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response status should be 403 for users without the right permissions.' ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // Controller does not use get_context_param(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_create_item() { + // Controller does not use test_create_item(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_update_item() { + // Controller does not use test_update_item(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_delete_item() { + // Controller does not use test_delete_item(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_prepare_item() { + // Controller does not use test_prepare_item(). + } + + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $properties = $data['schema']['properties']; + $this->assertCount( 5, $properties, 'There should be 5 properties in the response data schema.' ); + $this->assertArrayHasKey( 'slug', $properties, 'The slug property should exist in the response data schema.' ); + $this->assertArrayHasKey( 'name', $properties, 'The name property should exist in the response data schema.' ); + $this->assertArrayHasKey( 'description', $properties, 'The description property should exist in the response data schema.' ); + $this->assertArrayHasKey( 'font_families', $properties, 'The slug font_families should exist in the response data schema.' ); + $this->assertArrayHasKey( 'categories', $properties, 'The categories property should exist in the response data schema.' ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php new file mode 100644 index 0000000000000..3e74b23b7cb20 --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php @@ -0,0 +1,1076 @@ + '"Open Sans"', + 'fontWeight' => '400', + 'fontStyle' => 'normal', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ); + + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$font_family_id = Tests_REST_WpRestFontFamiliesController::create_font_family_post(); + self::$other_font_family_id = Tests_REST_WpRestFontFamiliesController::create_font_family_post(); + + self::$font_face_id1 = self::create_font_face_post( + self::$font_family_id, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '400', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ), + ) + ); + self::$font_face_id2 = self::create_font_face_post( + self::$font_family_id, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '900', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ), + ) + ); + + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + + self::$post_ids_for_cleanup = array(); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); + + wp_delete_post( self::$font_family_id, true ); + wp_delete_post( self::$other_font_family_id, true ); + wp_delete_post( self::$font_face_id1, true ); + wp_delete_post( self::$font_face_id2, true ); + } + + public function tear_down() { + foreach ( self::$post_ids_for_cleanup as $post_id ) { + wp_delete_post( $post_id, true ); + } + self::$post_ids_for_cleanup = array(); + parent::tear_down(); + } + + public static function create_font_face_post( $parent_id, $settings = array() ) { + $settings = array_merge( self::$default_settings, $settings ); + $title = WP_Font_Utils::get_font_face_slug( $settings ); + $post_id = self::factory()->post->create( + wp_slash( + array( + 'post_type' => 'wp_font_face', + 'post_status' => 'publish', + 'post_title' => $title, + 'post_name' => sanitize_title( $title ), + 'post_content' => wp_json_encode( $settings ), + 'post_parent' => $parent_id, + ) + ) + ); + + self::$post_ids_for_cleanup[] = $post_id; + + return $post_id; + } + + /** + * @covers WP_REST_Font_Faces_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces', + $routes, + 'Font faces collection for the given font family does not exist' + ); + $this->assertCount( + 2, + $routes['/wp/v2/font-families/(?P[\d]+)/font-faces'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + $this->assertArrayHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)', + $routes, + 'Single font face route for the given font family does not exist' + ); + $this->assertCount( + 2, + $routes['/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + } + + public function test_font_faces_no_autosave_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)/autosaves', + $routes, + 'Font faces autosaves route exists.' + ); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)/autosaves/(?P[\d]+)', + $routes, + 'Font faces autosaves by id route exists.' + ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // See test_get_context_param(). + } + + /** + * @dataProvider data_get_context_param + * + * @covers WP_REST_Font_Faces_Controller::get_context_param + * + * @param bool $single_route Whether to test a single route. + */ + public function test_get_context_param( $single_route ) { + $route = '/wp/v2/font-families/' . self::$font_family_id . '/font-faces'; + if ( $single_route ) { + $route .= '/' . self::$font_face_id1; + } + + $request = new WP_REST_Request( 'OPTIONS', $route ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $endpoint_data = $data['endpoints'][0]; + $this->assertArrayNotHasKey( 'allow_batch', $endpoint_data, 'The allow_batch property should not exist in the endpoint data.' ); + $this->assertSame( 'view', $endpoint_data['args']['context']['default'], 'The endpoint\'s args::context::default should be set to view.' ); + $this->assertSame( array( 'view', 'embed', 'edit' ), $endpoint_data['args']['context']['enum'], 'The endpoint\'s args::context::enum should be set to [ view, embed, edit ].' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_context_param() { + return array( + 'Collection' => array( false ), + 'Single' => array( true ), + ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_items + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200' ); + $this->assertCount( 2, $data, 'There should be 2 properties in the response data.' ); + $this->assertArrayHasKey( '_links', $data[0], 'The _links property should exist in the response data 0.' ); + $this->check_font_face_data( $data[0], self::$font_face_id2, $data[0]['_links'] ); + $this->assertArrayHasKey( '_links', $data[1], 'The _links property should exist in the response data 1.' ); + $this->check_font_face_data( $data[1], self::$font_face_id1, $data[1]['_links'] ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_items + */ + public function test_get_items_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_items + */ + public function test_get_items_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_face_data( $data, self::$font_face_id1, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response + */ + public function test_get_item_removes_extra_settings() { + $font_face_id = self::create_font_face_post( self::$font_family_id, array( 'extra' => array() ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in the response data.' ); + $this->assertArrayNotHasKey( 'extra', $data['font_face_settings'], 'The extra property should exist in the font_face_settings data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response + */ + public function test_get_item_malformed_post_content_returns_empty_settings() { + $font_face_id = wp_insert_post( + array( + 'post_type' => 'wp_font_face', + 'post_parent' => self::$font_family_id, + 'post_status' => 'publish', + 'post_content' => 'invalid', + ) + ); + + self::$post_ids_for_cleanup[] = $font_face_id; + + $empty_settings = array( + 'fontFamily' => '', + 'src' => array(), + ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in the response data.' ); + $this->assertSame( $empty_settings, $data['font_face_settings'], 'The empty settings should exist in the font_face_settings data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_invalid_font_face_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_valid_parent_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertSame( self::$font_family_id, $data['parent'], 'The returned parent id should match the font family id.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_invalid_parent_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$other_font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404 ); + + $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '".'; + $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item() { + wp_set_current_user( self::$admin_id ); + $files = $this->setup_font_file_upload( array( 'woff2' ) ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + 'src' => array_keys( $files )[0], + ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_face_data( $data, $data['id'], $response->get_links() ); + $this->check_file_meta( $data['id'], array( $data['font_face_settings']['src'] ) ); + + $settings = $data['font_face_settings']; + unset( $settings['src'] ); + $this->assertSame( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + ), + $settings, + 'The font_face_settings data should match the expected data.' + ); + + $this->assertSame( self::$font_family_id, $data['parent'], 'The returned parent id should match the font family id.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_multiple_font_files() { + wp_set_current_user( self::$admin_id ); + $files = $this->setup_font_file_upload( array( 'ttf', 'otf', 'woff', 'woff2' ) ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + 'src' => array_keys( $files ), + ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_face_data( $data, $data['id'], $response->get_links() ); + $this->check_file_meta( $data['id'], $data['font_face_settings']['src'] ); + + $settings = $data['font_face_settings']; + $this->assertCount( 4, $settings['src'], 'There should be 4 items in the font_face_settings::src data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_invalid_file_type() { + $image_file = DIR_TESTDATA . '/images/canola.jpg'; + $image_path = wp_tempnam( 'canola.jpg' ); + copy( $image_file, $image_path ); + + $files = array( + 'file-0' => array( + 'name' => 'canola.jpg', + 'full_path' => 'canola.jpg', + 'type' => 'font/woff2', + 'tmp_name' => $image_path, + 'error' => 0, + 'size' => filesize( $image_path ), + ), + ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array_merge( + self::$default_settings, + array( + 'fontWeight' => '200', + 'src' => array_keys( $files )[0], + ) + ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_font_upload_invalid_file_type', $response, 400 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_url_src() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_face_data( $data, $data['id'], $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_all_properties() { + wp_set_current_user( self::$admin_id ); + + $properties = array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '300 500', + 'fontStyle' => 'oblique 30deg 50deg', + 'fontDisplay' => 'swap', + 'fontStretch' => 'expanded', + 'ascentOverride' => '70%', + 'descentOverride' => '30%', + 'fontVariant' => 'normal', + 'fontFeatureSettings' => '"swsh" 2', + 'fontVariationSettings' => '"xhgt" 0.7', + 'lineGapOverride' => '10%', + 'sizeAdjust' => '90%', + 'unicodeRange' => 'U+0025-00FF, U+4??', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_face_settings', wp_json_encode( $properties ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + wp_delete_post( $data['id'], true ); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in the response data.' ); + $this->assertSame( $properties, $data['font_face_settings'], 'The font_face_settings should match the expected properties.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces' ); + $request->set_param( + 'font_face_settings', + wp_json_encode( array_merge( self::$default_settings, array( 'fontWeight' => '100' ) ) ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_duplicate_properties() { + $settings = array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'italic', + 'src' => home_url( '/wp-content/fonts/open-sans-italic-light.ttf' ), + ); + self::create_font_face_post( self::$font_family_id, $settings ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_duplicate_font_face', $response, 400, 'The response should return an error for "rest_duplicate_font_face" with 400 status.' ); + $expected_message = 'A font face matching those settings already exists.'; + $message = $response->as_error()->get_error_messages()[0]; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_request + */ + public function test_create_item_default_theme_json_version() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + wp_delete_post( $data['id'], true ); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in the response data.' ); + $this->assertSame( WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The default theme.json version should match the latest version supported by the controller.' ); + } + + /** + * @dataProvider data_create_item_invalid_theme_json_version + * + * @covers WP_REST_Font_Faces_Controller::create_item + * + * @param int $theme_json_version Version input to test. + */ + public function test_create_item_invalid_theme_json_version( $theme_json_version ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', $theme_json_version ); + $request->set_param( 'font_face_settings', '' ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_theme_json_version() { + return array( + array( 1 ), + array( 3 ), + ); + } + + /** + * @dataProvider data_create_item_invalid_settings + * + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + * + * @param mixed $settings Settings to test. + */ + public function test_create_item_invalid_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_settings() { + return array( + 'Missing fontFamily' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Empty fontFamily' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Wrong fontFamily type' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => 1234 ) ), + ), + 'Invalid fontDisplay' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontDisplay' => 'invalid' ) ), + ), + 'Missing src' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'src' => '' ) ), + ), + 'Empty src string' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => '' ) ), + ), + 'Empty src array' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => array() ) ), + ), + 'Empty src array values' => array( + 'settings' => array_merge( self::$default_settings, array( '', '' ) ), + ), + 'Wrong src type' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => 1234 ) ), + ), + 'Wrong src array types' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => array( 1234, 5678 ) ) ), + ), + ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + */ + public function test_create_item_invalid_settings_json() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_face_settings', 'invalid' ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_face_settings parameter must be a valid JSON string.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + */ + public function test_create_item_invalid_file_src() { + $files = $this->setup_font_file_upload( array( 'woff2' ) ); + + wp_set_current_user( self::$admin_id ); + $src = 'invalid'; + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array_merge( self::$default_settings, array( 'src' => $src ) ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_face_settings[src] value "' . $src . '" must be a valid URL or file reference.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + */ + public function test_create_item_missing_file_src() { + $files = $this->setup_font_file_upload( array( 'woff2', 'woff' ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array_merge( self::$default_settings, array( 'src' => array( array_keys( $files )[0] ) ) ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'File ' . array_keys( $files )[1] . ' must be used in font_face_settings[src].'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @dataProvider data_sanitize_font_face_settings + * + * @covers WP_REST_Font_Face_Controller::sanitize_font_face_settings + * + * @param string $settings Settings to test. + * @param string $expected Expected settings result. + */ + public function test_create_item_sanitize_font_face_settings( $settings, $expected ) { + $settings = array_merge( self::$default_settings, $settings ); + $expected = array_merge( self::$default_settings, $expected ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + wp_delete_post( $data['id'], true ); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertSame( $expected, $data['font_face_settings'], 'The response font_face_settings should match.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_sanitize_font_face_settings() { + return array( + 'settings with tags, extra whitespace, new lines' => array( + 'settings' => array( + 'fontFamily' => " Open Sans\n ", + 'fontStyle' => " oblique 20deg 50deg\n ", + 'fontWeight' => " 200\n ", + 'src' => " https://example.com/ ", + 'fontStretch' => " expanded\n ", + 'ascentOverride' => " 70%\n ", + 'descentOverride' => " 30%\n ", + 'fontVariant' => " normal\n ", + 'fontFeatureSettings' => " \"swsh\" 2\n ", + 'fontVariationSettings' => " \"xhgt\" 0.7\n ", + 'lineGapOverride' => " 10%\n ", + 'sizeAdjust' => " 90%\n ", + 'unicodeRange' => " U+0025-00FF, U+4??\n ", + 'preview' => " https://example.com/ ", + ), + 'expected' => array( + 'fontFamily' => '"Open Sans"', + 'fontStyle' => 'oblique 20deg 50deg', + 'fontWeight' => '200', + 'src' => 'https://example.com//stylescriptalert(\'XSS\');/script%20%20%20%20%20%20', + 'fontStretch' => 'expanded', + 'ascentOverride' => '70%', + 'descentOverride' => '30%', + 'fontVariant' => 'normal', + 'fontFeatureSettings' => '"swsh" 2', + 'fontVariationSettings' => '"xhgt" 0.7', + 'lineGapOverride' => '10%', + 'sizeAdjust' => '90%', + 'unicodeRange' => 'U+0025-00FF, U+4??', + 'preview' => 'https://example.com//stylescriptalert(\'XSS\');/script%20%20%20%20%20%20', + ), + ), + 'multiword font family name with integer' => array( + 'settings' => array( + 'fontFamily' => 'Libre Barcode 128 Text', + ), + 'expected' => array( + 'fontFamily' => '"Libre Barcode 128 Text"', + ), + ), + 'multiword font family name' => array( + 'settings' => array( + 'fontFamily' => 'B612 Mono', + ), + 'expected' => array( + 'fontFamily' => '"B612 Mono"', + ), + ), + 'comma-separated font family names' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans, Noto Sans, sans-serif', + ), + 'expected' => array( + 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif', + ), + ), + ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + // public function test_create_item_no_permission() {} + + /** + * @covers WP_REST_Font_Faces_Controller::update_item + */ + public function test_update_item() { + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_no_route', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item() { + wp_set_current_user( self::$admin_id ); + $font_face_id = self::create_font_face_post( self::$font_family_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 201.' ); + $this->assertNull( get_post( $font_face_id ), 'The deleted post should not exist.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item_no_trash() { + wp_set_current_user( self::$admin_id ); + $font_face_id = self::create_font_face_post( self::$font_family_id ); + + // Attempt trashing. + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'The response should return an error for "rest_trash_not_supported" with 501 status.' ); + + $request->set_param( 'force', 'false' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'When "force" is false, the response should return an error for "rest_trash_not_supported" with 501 status.' ); + + // Ensure the post still exists. + $post = get_post( $font_face_id ); + $this->assertNotEmpty( $post, 'The post should still exists.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item_invalid_font_face_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete + */ + public function test_delete_item_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces/' . self::$font_face_id1 ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_delete_item_invalid_parent_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$other_font_family_id . '/font-faces/' . self::$font_face_id1 ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404, 'The response should return an error for "rest_font_face_parent_id_mismatch" with 404 status.' ); + + $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '".'; + $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item_no_permissions() { + $font_face_id = $this->create_font_face_post( self::$font_family_id ); + + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 401, 'The response should return an error for "rest_cannot_delete" with 401 status for an invalid user.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 403, 'The response should return an error for "rest_cannot_delete" with 403 status for a user without permission.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response + */ + public function test_prepare_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id2 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_face_data( $data, self::$font_face_id2, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item_schema + */ + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $properties = $data['schema']['properties']; + $this->assertCount( 4, $properties, 'There should be 4 properties in the schema::properties data.' ); + $this->assertArrayHasKey( 'id', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'theme_json_version', $properties, 'The theme_json_version property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'parent', $properties, 'The parent property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_face_settings', $properties, 'The font_face_settings property should exist in the schema::properties data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item_schema + */ + public function test_get_item_schema_font_face_settings_should_all_have_sanitize_callbacks() { + $schema = ( new WP_REST_Font_Faces_Controller( 'wp_font_face' ) )->get_item_schema(); + $font_face_settings_schema = $schema['properties']['font_face_settings']; + + $this->assertArrayHasKey( 'properties', $font_face_settings_schema, 'font_face_settings schema is missing properties.' ); + $this->assertIsArray( $font_face_settings_schema['properties'], 'font_face_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_face_settings_schema['properties'] as $property ) { + $this->assertArrayHasKey( 'arg_options', $property, 'Setting schema should have arg_options.' ); + $this->assertArrayHasKey( 'sanitize_callback', $property['arg_options'], 'Setting schema should have a sanitize_callback.' ); + $this->assertIsCallable( $property['arg_options']['sanitize_callback'], 'The sanitize_callback value should be callable.' ); + } + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_public_item_schema + */ + public function test_get_public_item_schema_should_not_have_arg_options() { + $schema = ( new WP_REST_Font_Faces_Controller( 'wp_font_face' ) )->get_public_item_schema(); + $font_face_settings_schema = $schema['properties']['font_face_settings']; + + $this->assertArrayHasKey( 'properties', $font_face_settings_schema, 'font_face_settings schema is missing properties.' ); + $this->assertIsArray( $font_face_settings_schema['properties'], 'font_face_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_face_settings_schema['properties'] as $property ) { + $this->assertArrayNotHasKey( 'arg_options', $property, 'arg_options should be removed from the schema for each setting.' ); + } + } + + /** + * If WP_Theme_JSON::LATEST_SCHEMA is changed, the controller should be updated to handle any differences + * in `fontFace` structure to ensure support for the latest theme.json schema, and backwards compatibility + * for existing wp_font_face posts. + */ + public function test_controller_supports_latest_theme_json_version() { + $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + } + + protected function check_font_face_data( $data, $post_id, $links ) { + self::$post_ids_for_cleanup[] = $post_id; + $post = get_post( $post_id ); + + $this->assertArrayHasKey( 'id', $data, 'The id property should exist in response data.' ); + $this->assertSame( $post->ID, $data['id'], 'The "id" from the response data should match the post ID.' ); + + $this->assertArrayHasKey( 'parent', $data, 'The parent property should exist in response data.' ); + $this->assertSame( $post->post_parent, $data['parent'], 'The "parent" from the response data should match the post parent.' ); + + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in response data.' ); + $this->assertSame( WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The "theme_json_version" from the response data should match the latest version supported by the controller.' ); + + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in response data.' ); + $this->assertSame( $post->post_content, wp_json_encode( $data['font_face_settings'] ), 'The encoded "font_face_settings" from the response data should match the post content.' ); + + $this->assertNotEmpty( $links, 'The links should not be empty in the response data.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ); + $this->assertSame( $expected, $links['self'][0]['href'], 'The links URL from the response data should match the post\'s REST endpoint.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces' ); + $this->assertSame( $expected, $links['collection'][0]['href'], 'The links collection URL from the response data should match the REST endpoint.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->post_parent ); + $this->assertSame( $expected, $links['parent'][0]['href'], 'The links for a parent URL from the response data should match the parent\'s REST endpoint.' ); + } + + protected function check_file_meta( $font_face_id, $src_attributes ) { + $file_meta = get_post_meta( $font_face_id, '_wp_font_face_file' ); + + foreach ( $src_attributes as $src_attribute ) { + $file_name = basename( $src_attribute ); + $this->assertContains( $file_name, $file_meta, 'The uploaded font file path should be saved in the post meta.' ); + } + } + + protected function setup_font_file_upload( $formats ) { + $files = array(); + foreach ( $formats as $format ) { + $font_file = DIR_TESTDATA . '/fonts/OpenSans-Regular.' . $format; + $font_path = wp_tempnam( 'OpenSans-Regular.' . $format ); + copy( $font_file, $font_path ); + + $files[ 'file-' . count( $files ) ] = array( + 'name' => 'OpenSans-Regular.' . $format, + 'full_path' => 'OpenSans-Regular.' . $format, + 'type' => 'font/' . $format, + 'tmp_name' => $font_path, + 'error' => 0, + 'size' => filesize( $font_path ), + ); + } + + return $files; + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php new file mode 100644 index 0000000000000..0676c3ce4b88d --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php @@ -0,0 +1,1057 @@ + 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + ); + + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + + self::$font_family_id1 = self::create_font_family_post( + array( + 'name' => 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + ) + ); + self::$font_family_id2 = self::create_font_family_post( + array( + 'name' => 'Helvetica', + 'slug' => 'helvetica', + 'fontFamily' => 'Helvetica, Arial, sans-serif', + ) + ); + self::$font_face_id1 = Tests_REST_WpRestFontFacesController::create_font_face_post( + self::$font_family_id1, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '400', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ), + ) + ); + self::$font_face_id2 = Tests_REST_WpRestFontFacesController::create_font_face_post( + self::$font_family_id1, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '900', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ), + ) + ); + + static::$post_ids_to_cleanup = array(); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); + + wp_delete_post( self::$font_family_id1 ); + wp_delete_post( self::$font_family_id2 ); + wp_delete_post( self::$font_face_id1 ); + wp_delete_post( self::$font_face_id2 ); + } + + public function tear_down() { + foreach ( static::$post_ids_to_cleanup as $post_id ) { + wp_delete_post( $post_id, true ); + } + static::$post_ids_to_cleanup = array(); + + parent::tear_down(); + } + + public static function create_font_family_post( $settings = array() ) { + $settings = array_merge( self::$default_settings, $settings ); + $post_id = self::factory()->post->create( + wp_slash( + array( + 'post_type' => 'wp_font_family', + 'post_status' => 'publish', + 'post_title' => $settings['name'], + 'post_name' => $settings['slug'], + 'post_content' => wp_json_encode( + array( + 'fontFamily' => $settings['fontFamily'], + 'preview' => $settings['preview'], + ) + ), + ) + ) + ); + + static::$post_ids_to_cleanup[] = $post_id; + + return $post_id; + } + + /** + * @covers WP_REST_Font_Families_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/font-families', + $routes, + 'Font faces collection for the given font family does not exist' + ); + $this->assertCount( + 2, + $routes['/wp/v2/font-families'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + $this->assertArrayHasKey( + '/wp/v2/font-families/(?P[\d]+)', + $routes, + 'Single font face route for the given font family does not exist' + ); + $this->assertCount( + 3, + $routes['/wp/v2/font-families/(?P[\d]+)'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + } + + public function test_font_families_no_autosave_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/autosaves', + $routes, + 'Font families autosaves route exists.' + ); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/autosaves/(?P[\d]+)', + $routes, + 'Font families autosaves by id route exists.' + ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // See test_get_context_param(). + } + + /** + * @dataProvider data_get_context_param + * + * @covers WP_REST_Font_Families_Controller::get_context_param + * + * @param bool $single_route Whether to test a single route. + */ + public function test_get_context_param( $single_route ) { + $route = '/wp/v2/font-families'; + if ( $single_route ) { + $route .= '/' . self::$font_family_id1; + } + + $request = new WP_REST_Request( 'OPTIONS', $route ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $endpoint_data = $data['endpoints'][0]; + $this->assertArrayNotHasKey( 'allow_batch', $endpoint_data, 'The allow_batch property should not exist in the endpoint data.' ); + $this->assertSame( 'view', $endpoint_data['args']['context']['default'], 'The endpoint\'s args::context::default should be set to view.' ); + $this->assertSame( array( 'view', 'embed', 'edit' ), $endpoint_data['args']['context']['enum'], 'The endpoint\'s args::context::enum should be set to [ view, embed, edit ].' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_context_param() { + return array( + 'Collection' => array( false ), + 'Single' => array( true ), + ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_items + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertCount( 2, $data, 'There should be 2 properties in the response data.' ); + $this->assertArrayHasKey( '_links', $data[0], 'The _links property should exist in the response data 0.' ); + $this->check_font_family_data( $data[0], self::$font_family_id2, $data[0]['_links'] ); + $this->assertArrayHasKey( '_links', $data[1], 'The _links property should exist in the response data 1.' ); + $this->check_font_family_data( $data[1], self::$font_family_id1, $data[1]['_links'] ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_items + */ + public function test_get_items_by_slug() { + $font_family = get_post( self::$font_family_id2 ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $request->set_param( 'slug', $font_family->post_name ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertCount( 1, $data, 'There should be 2 properties in the response data.' ); + $this->assertArrayHasKey( 'id', $data[0], 'The id property should exist in the response data.' ); + $this->assertSame( $font_family->ID, $data[0]['id'], 'The id should match the expected ID in the response data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_items + */ + public function test_get_items_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_family_data( $data, self::$font_family_id1, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Families_Controller::prepare_item_for_response + */ + public function test_get_item_embedded_font_faces() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( '_embed', true ); + $response = rest_get_server()->dispatch( $request ); + $data = rest_get_server()->response_to_data( $response, true ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayHasKey( '_embedded', $data, 'The _embedded property should exist in the response data.' ); + $this->assertArrayHasKey( 'font_faces', $data['_embedded'], 'The font_faces property should exist in _embedded data.' ); + $this->assertCount( 2, $data['_embedded']['font_faces'], 'There should be 2 font_faces in the _embedded data.' ); + + foreach ( $data['_embedded']['font_faces'] as $font_face ) { + $this->assertArrayHasKey( 'id', $font_face, 'The id property should exist in the _embedded font_face data.' ); + + $font_face_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 . '/font-faces/' . $font_face['id'] ); + $font_face_response = rest_get_server()->dispatch( $font_face_request ); + $font_face_data = rest_get_server()->response_to_data( $font_face_response, true ); + + $this->assertSame( $font_face_data, $font_face, 'The embedded font_face data should match when the data from a single request.' ); + } + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item_removes_extra_settings() { + $font_family_id = self::create_font_family_post( array( 'fontFace' => array() ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayNotHasKey( 'fontFace', $data['font_family_settings'], 'The fontFace property should not exist in the font_family_settings data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::prepare_item_for_response + */ + public function test_get_item_malformed_post_content_returns_empty_settings() { + $font_family_id = wp_insert_post( + array( + 'post_type' => 'wp_font_family', + 'post_status' => 'publish', + 'post_content' => 'invalid', + ) + ); + + static::$post_ids_to_cleanup[] = $font_family_id; + + $empty_settings = array( + 'name' => '', + // Slug will default to the post id. + 'slug' => (string) $font_family_id, + 'fontFamily' => '', + 'preview' => '', + ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertSame( $empty_settings, $data['font_family_settings'], 'The empty settings should exist in the font_family_settings data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item_invalid_font_family_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::create_item + */ + public function test_create_item() { + $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_family_data( $data, $data['id'], $response->get_links() ); + + $reponse_settings = $data['font_family_settings']; + $this->assertSame( $settings, $reponse_settings, 'The expected settings should exist in the font_family_settings data.' ); + $this->assertEmpty( $data['font_faces'], 'The font_faces should be empty or not exist in the response data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::validate_create_font_face_request + */ + public function test_create_item_default_theme_json_version() { + $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) ); + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + static::$post_ids_to_cleanup[] = $data['id']; + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in the response data.' ); + $this->assertSame( WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The default theme.json version should match the latest version supported by the controller.' ); + } + + /** + * @dataProvider data_create_item_invalid_theme_json_version + * + * @covers WP_REST_Font_Families_Controller::create_item + * + * @param int $theme_json_version Version to test. + */ + public function test_create_item_invalid_theme_json_version( $theme_json_version ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', $theme_json_version ); + $request->set_param( 'font_family_settings', wp_json_encode( self::$default_settings ) ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_theme_json_version() { + return array( + array( 1 ), + array( 3 ), + ); + } + + /** + * @dataProvider data_create_item_with_default_preview + * + * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings + * + * @param array $settings Settings to test. + */ + public function test_create_item_with_default_preview( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + static::$post_ids_to_cleanup[] = $data['id']; + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $response_settings = $data['font_family_settings']; + $this->assertArrayHasKey( 'preview', $response_settings, 'The preview property should exist in the font_family_settings data.' ); + $this->assertSame( '', $response_settings['preview'], 'The preview data should be an empty string.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_with_default_preview() { + $default_settings = array( + 'name' => 'Open Sans', + 'slug' => 'open-sans-2', + 'fontFamily' => '"Open Sans", sans-serif', + ); + return array( + 'No preview param' => array( + 'settings' => $default_settings, + ), + 'Empty preview' => array( + 'settings' => array_merge( $default_settings, array( 'preview' => '' ) ), + ), + ); + } + + /** + * @dataProvider data_sanitize_font_family_settings + * + * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings + * + * @param string $settings Font family settings to test. + * @param string $expected Expected settings result. + */ + public function test_create_item_santize_font_family_settings( $settings, $expected ) { + $settings = array_merge( self::$default_settings, $settings ); + $expected = array_merge( self::$default_settings, $expected ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + static::$post_ids_to_cleanup[] = $data['id']; + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertSame( $expected, $data['font_family_settings'], 'The response font_family_settings should match.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_sanitize_font_family_settings() { + return array( + 'settings with tags, extra whitespace, new lines' => array( + 'settings' => array( + 'name' => " Opening Sans\n ", + 'slug' => " OPENing SanS \n ", + 'fontFamily' => " Opening Sans\n ", + 'preview' => " https://example.com/ ", + ), + 'expected' => array( + 'name' => 'Opening Sans', + 'slug' => 'opening-sans-alertxss', + 'fontFamily' => '"Opening Sans"', + 'preview' => "https://example.com//stylescriptalert('XSS');/script%20%20%20%20%20%20", + ), + ), + 'multiword font family name with integer' => array( + 'settings' => array( + 'slug' => 'libre-barcode-128-text', + 'fontFamily' => 'Libre Barcode 128 Text', + ), + 'expected' => array( + 'slug' => 'libre-barcode-128-text', + 'fontFamily' => '"Libre Barcode 128 Text"', + ), + ), + 'multiword font family name' => array( + 'settings' => array( + 'slug' => 'b612-mono', + 'fontFamily' => 'B612 Mono', + ), + 'expected' => array( + 'slug' => 'b612-mono', + 'fontFamily' => '"B612 Mono"', + ), + ), + 'comma-separated font family names' => array( + 'settings' => array( + 'slug' => 'open-sans-noto-sans', + 'fontFamily' => 'Open Sans, Noto Sans, sans-serif', + ), + 'expected' => array( + 'slug' => 'open-sans-noto-sans', + 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif', + ), + ), + ); + } + + /** + * @dataProvider data_create_item_invalid_settings + * + * @covers WP_REST_Font_Families_Controller::validate_create_font_face_settings + * + * @param array $settings Settings to test. + */ + public function test_create_item_invalid_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_settings() { + return array( + 'Missing name' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'name' => '' ) ), + ), + 'Empty name' => array( + 'settings' => array_merge( self::$default_settings, array( 'name' => '' ) ), + ), + 'Wrong name type' => array( + 'settings' => array_merge( self::$default_settings, array( 'name' => 1234 ) ), + ), + 'Missing slug' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'slug' => '' ) ), + ), + 'Empty slug' => array( + 'settings' => array_merge( self::$default_settings, array( 'slug' => '' ) ), + ), + 'Wrong slug type' => array( + 'settings' => array_merge( self::$default_settings, array( 'slug' => 1234 ) ), + ), + 'Missing fontFamily' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Empty fontFamily' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Wrong fontFamily type' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => 1234 ) ), + ), + ); + } + + /** + * @covers WP_REST_Font_Family_Controller::validate_font_family_settings + */ + public function test_create_item_invalid_settings_json() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_family_settings', 'invalid' ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_family_settings parameter must be a valid JSON string.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_family_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Family_Controller::create_item + */ + public function test_create_item_with_duplicate_slug() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + $request->set_param( 'font_family_settings', wp_json_encode( array_merge( self::$default_settings, array( 'slug' => 'helvetica' ) ) ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_duplicate_font_family', $response, 400, 'The response should return an error for "rest_duplicate_font_family" with 400 status.' ); + $expected_message = 'A font family with slug "helvetica" already exists.'; + $message = $response->as_error()->get_error_messages()[0]; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::create_item + */ + public function test_create_item_no_permission() { + $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) ); + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_create', $response, 401, 'The response should return an error for "rest_cannot_create" with 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( + 'font_family_settings', + wp_json_encode( + array( + 'name' => 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + ) + ) + ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_create', $response, 403, 'The response should return an error for "rest_cannot_create" with 403 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item() { + wp_set_current_user( self::$admin_id ); + + $settings = array( + 'name' => 'Open Sans', + 'fontFamily' => '"Open Sans, "Noto Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.9/previews/open-sans/open-sans-400-normal.svg', + ); + + $font_family_id = self::create_font_family_post( array( 'slug' => 'open-sans-2' ) ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id ); + $request->set_param( + 'font_family_settings', + wp_json_encode( $settings ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_family_data( $data, $font_family_id, $response->get_links() ); + + $expected_settings = array( + 'name' => $settings['name'], + 'slug' => 'open-sans-2', + 'fontFamily' => $settings['fontFamily'], + 'preview' => $settings['preview'], + ); + $this->assertSame( $expected_settings, $data['font_family_settings'], 'The response font_family_settings should match expected settings.' ); + } + + /** + * @dataProvider data_update_item_individual_settings + * + * @covers WP_REST_Font_Families_Controller::update_item + * + * @param array $settings Settings to test. + */ + public function test_update_item_individual_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + + $font_family_id = self::create_font_family_post(); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $key = key( $settings ); + $value = current( $settings ); + $this->assertArrayHasKey( $key, $data['font_family_settings'], 'The expected key should exist in the font_family_settings data.' ); + $this->assertSame( $value, $data['font_family_settings'][ $key ], 'The font_family_settings data should match.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_update_item_individual_settings() { + return array( + array( array( 'name' => 'Opened Sans' ) ), + array( array( 'fontFamily' => '"Opened Sans", sans-serif' ) ), + array( array( 'preview' => 'https://s.w.org/images/fonts/16.7/previews/opened-sans/opened-sans-400-normal.svg' ) ), + // Empty preview is allowed. + array( array( 'preview' => '' ) ), + ); + } + + /** + * @dataProvider data_sanitize_font_family_settings + * + * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings + * + * @param string $settings Font family settings to test. + * @param string $expected Expected settings result. + */ + public function test_update_item_santize_font_family_settings( $settings, $expected ) { + // Unset/modify slug from the data provider, since we're updating rather than creating. + unset( $settings['slug'] ); + $initial_settings = array( 'slug' => 'open-sans-update' ); + $expected = array_merge( self::$default_settings, $expected, $initial_settings ); + + wp_set_current_user( self::$admin_id ); + $font_family_id = self::create_font_family_post( $initial_settings ); + static::$post_ids_to_cleanup[] = $font_family_id; + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertSame( $expected, $data['font_family_settings'], 'The response font_family_settings should match.' ); + } + + /** + * @dataProvider data_update_item_invalid_settings + * + * @covers WP_REST_Font_Families_Controller::update_item + * + * @param array $settings Settings to test. + */ + public function test_update_item_empty_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( + 'font_family_settings', + wp_json_encode( $settings ) + ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_update_item_invalid_settings() { + return array( + 'Empty name' => array( + array( 'name' => '' ), + ), + 'Wrong name type' => array( + array( 'name' => 1234 ), + ), + 'Empty fontFamily' => array( + array( 'fontFamily' => '' ), + ), + 'Wrong fontFamily type' => array( + array( 'fontFamily' => 1234 ), + ), + ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item_update_slug_not_allowed() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( + 'font_family_settings', + wp_json_encode( array( 'slug' => 'new-slug' ) ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_family_settings[slug] cannot be updated.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_family_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item_invalid_font_family_id() { + $settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404, 'The response should return an error for "rest_post_invalid_id" with 404 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item_no_permission() { + $settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) ); + + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_edit', $response, 401, 'The response should return an error for "rest_cannot_edit" with 401 status for an invalid user.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_edit', $response, 403, 'The response should return an error for "rest_cannot_edit" with 403 status for a user without permission.' ); + } + + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item() { + wp_set_current_user( self::$admin_id ); + $font_family_id = self::create_font_family_post(); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $request['force'] = true; + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertNull( get_post( $font_family_id ), 'The post should not exist after deleting.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item_no_trash() { + wp_set_current_user( self::$admin_id ); + $font_family_id = self::create_font_family_post(); + + // Attempt trashing. + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'The response should return an error for "rest_trash_not_supported" with 501 status.' ); + + $request->set_param( 'force', 'false' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'When "force" is false, the response should return an error for "rest_trash_not_supported" with 501 status.' ); + + // Ensure the post still exists. + $post = get_post( $font_family_id ); + $this->assertNotEmpty( $post, 'The post should still exist.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item_invalid_font_family_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item_no_permissions() { + $font_family_id = self::create_font_family_post(); + + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 401, 'The response should return an error for "rest_cannot_delete" with 401 status for an invalid user.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 403, 'The response should return an error for "rest_cannot_delete" with 403 status for a user without permission.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::prepare_item_for_response + */ + public function test_prepare_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id2 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_family_data( $data, self::$font_family_id2, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item_schema + */ + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $properties = $data['schema']['properties']; + $this->assertCount( 4, $properties, 'There should be 4 properties in the schema::properties data.' ); + $this->assertArrayHasKey( 'id', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'theme_json_version', $properties, 'The theme_json_version property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_faces', $properties, 'The font_faces property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_family_settings', $properties, 'The font_family_settings property should exist in the schema::properties data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item_schema + */ + public function test_get_item_schema_font_family_settings_should_all_have_sanitize_callbacks() { + $schema = ( new WP_REST_Font_Families_Controller( 'wp_font_family' ) )->get_item_schema(); + $font_family_settings_schema = $schema['properties']['font_family_settings']; + + $this->assertArrayHasKey( 'properties', $font_family_settings_schema, 'font_family_settings schema is missing properties.' ); + $this->assertIsArray( $font_family_settings_schema['properties'], 'font_family_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_family_settings_schema['properties'] as $property ) { + $this->assertArrayHasKey( 'arg_options', $property, 'Setting schema should have arg_options.' ); + $this->assertArrayHasKey( 'sanitize_callback', $property['arg_options'], 'Setting schema should have a sanitize_callback.' ); + $this->assertIsCallable( $property['arg_options']['sanitize_callback'], 'That sanitize_callback value should be callable.' ); + } + } + + /** + * @covers WP_REST_Font_Families_Controller::get_public_item_schema + */ + public function test_get_public_item_schema_should_not_have_arg_options() { + $schema = ( new WP_REST_Font_Families_Controller( 'wp_font_family' ) )->get_public_item_schema(); + $font_family_settings_schema = $schema['properties']['font_family_settings']; + + $this->assertArrayHasKey( 'properties', $font_family_settings_schema, 'font_family_settings schema is missing properties.' ); + $this->assertIsArray( $font_family_settings_schema['properties'], 'font_family_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_family_settings_schema['properties'] as $property ) { + $this->assertArrayNotHasKey( 'arg_options', $property, 'arg_options should be removed from the schema for each setting.' ); + } + } + + /** + * If WP_Theme_JSON::LATEST_SCHEMA is changed, the controller should be updated to handle any differences + * in `fontFamilies` structure to ensure support for the latest theme.json schema, and backwards compatibility + * for existing wp_font_family posts. + */ + public function test_controller_supports_latest_theme_json_version() { + $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + } + + protected function check_font_family_data( $data, $post_id, $links ) { + static::$post_ids_to_cleanup[] = $post_id; + $post = get_post( $post_id ); + + $this->assertArrayHasKey( 'id', $data, 'The id property should exist in response data.' ); + $this->assertSame( $post->ID, $data['id'], 'The "id" from the response data should match the post ID.' ); + + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in response data.' ); + $this->assertSame( WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The "theme_json_version" from the response data should match the latest version supported by the controller.' ); + + $font_face_ids = get_children( + array( + 'fields' => 'ids', + 'post_parent' => $post_id, + 'post_type' => 'wp_font_face', + 'order' => 'ASC', + 'orderby' => 'ID', + ) + ); + $this->assertArrayHasKey( 'font_faces', $data, 'The font_faces property should exist in the response data.' ); + + foreach ( $font_face_ids as $font_face_id ) { + $this->assertContains( $font_face_id, $data['font_faces'], 'The ID is in the font_faces data.' ); + } + + $this->assertArrayHasKey( 'font_family_settings', $data, 'The font_family_settings property should exist in the response data.' ); + $settings = $data['font_family_settings']; + $expected_settings = array( + 'name' => $post->post_title, + 'slug' => $post->post_name, + 'fontFamily' => $settings['fontFamily'], + 'preview' => $settings['preview'], + ); + $this->assertSame( $expected_settings, $settings, 'The font_family_settings should match.' ); + + $this->assertNotEmpty( $links, 'The links should not be empty in the response data.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->ID ); + $this->assertSame( $expected, $links['self'][0]['href'], 'The links URL from the response data should match the post\'s REST endpoint.' ); + $expected = rest_url( 'wp/v2/font-families' ); + $this->assertSame( $expected, $links['collection'][0]['href'], 'The links collection URL from the response data should match the REST endpoint.' ); + + if ( ! $font_face_ids ) { + return; + } + + // Check font_face links, if present. + $this->assertArrayHasKey( 'font_faces', $links ); + foreach ( $links['font_faces'] as $index => $link ) { + $expected = rest_url( 'wp/v2/font-families/' . $post->ID . '/font-faces/' . $font_face_ids[ $index ] ); + $this->assertSame( $expected, $link['href'], 'The links for a font faces URL from the response data should match the REST endpoint.' ); + + $embeddable = isset( $link['attributes']['embeddable'] ) + ? $link['attributes']['embeddable'] + : $link['embeddable']; + $this->assertTrue( $embeddable, 'The embeddable should be true.' ); + } + } +} diff --git a/tests/phpunit/tests/formatting/sanitizeTextField.php b/tests/phpunit/tests/formatting/sanitizeTextField.php index d7a58486d1ba5..82cef34a382d5 100644 --- a/tests/phpunit/tests/formatting/sanitizeTextField.php +++ b/tests/phpunit/tests/formatting/sanitizeTextField.php @@ -143,4 +143,26 @@ public function data_sanitize_text_field() { ), ); } + + /** + * @ticket 60357 + */ + public function test_sanitize_text_field_filter() { + $filter = new MockAction(); + add_filter( 'sanitize_text_field', array( $filter, 'filter' ) ); + + $this->assertSame( 'example', sanitize_text_field( 'example' ) ); + $this->assertSame( 1, $filter->get_call_count(), 'The sanitize_text_field filter was not called.' ); + } + + /** + * @ticket 60357 + */ + public function test_sanitize_textarea_field_filter() { + $filter = new MockAction(); + add_filter( 'sanitize_textarea_field', array( $filter, 'filter' ) ); + + $this->assertSame( 'example', sanitize_textarea_field( 'example' ) ); + $this->assertSame( 1, $filter->get_call_count(), 'The sanitize_textarea_field filter was not called.' ); + } } diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 3c2e1101ad69d..abef68f75b085 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1370,6 +1370,26 @@ public function data_wp_get_image_mime() { DIR_TESTDATA . '/uploads/dashicons.woff', false, ), + // Animated AVIF. + array( + DIR_TESTDATA . '/images/avif-animated.avif', + 'image/avif', + ), + // Lossless AVIF. + array( + DIR_TESTDATA . '/images/avif-lossless.avif', + 'image/avif', + ), + // Lossy AVIF. + array( + DIR_TESTDATA . '/images/avif-lossy.avif', + 'image/avif', + ), + // Transparent AVIF. + array( + DIR_TESTDATA . '/images/avif-transparent.avif', + 'image/avif', + ), ); return $data; @@ -1496,6 +1516,50 @@ public function data_wp_getimagesize() { DIR_TESTDATA . '/uploads/dashicons.woff', false, ), + // Animated AVIF. + array( + DIR_TESTDATA . '/images/avif-animated.avif', + array( + 150, + 150, + IMAGETYPE_AVIF, + 'width="150" height="150"', + 'mime' => 'image/avif', + ), + ), + // Lossless AVIF. + array( + DIR_TESTDATA . '/images/avif-lossless.avif', + array( + 400, + 400, + IMAGETYPE_AVIF, + 'width="400" height="400"', + 'mime' => 'image/avif', + ), + ), + // Lossy AVIF. + array( + DIR_TESTDATA . '/images/avif-lossy.avif', + array( + 400, + 400, + IMAGETYPE_AVIF, + 'width="400" height="400"', + 'mime' => 'image/avif', + ), + ), + // Transparent AVIF. + array( + DIR_TESTDATA . '/images/avif-transparent.avif', + array( + 128, + 128, + IMAGETYPE_AVIF, + 'width="128" height="128"', + 'mime' => 'image/avif', + ), + ), ); return $data; diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlProcessor.php index d9f1357b5c66f..26dc7b0bdb826 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor.php @@ -52,12 +52,12 @@ public function test_warns_that_the_static_creator_methods_should_be_called_inst * @covers WP_HTML_Processor::get_tag */ public function test_get_tag_is_null_once_document_is_finished() { - $p = WP_HTML_Processor::create_fragment( '
    Test
    ' ); - $p->next_tag(); - $this->assertSame( 'DIV', $p->get_tag() ); + $processor = WP_HTML_Processor::create_fragment( '
    Test
    ' ); + $processor->next_tag(); + $this->assertSame( 'DIV', $processor->get_tag() ); - $this->assertFalse( $p->next_tag() ); - $this->assertNull( $p->get_tag() ); + $this->assertFalse( $processor->next_tag() ); + $this->assertNull( $processor->get_tag() ); } /** @@ -77,44 +77,44 @@ public function test_get_tag_is_null_once_document_is_finished() { * @covers WP_HTML_Processor::seek */ public function test_clear_to_navigate_after_seeking() { - $p = WP_HTML_Processor::create_fragment( '

    ' ); + $processor = WP_HTML_Processor::create_fragment( '

    ' ); - while ( $p->next_tag() ) { + while ( $processor->next_tag() ) { // Create a bookmark before entering a stack of elements and formatting elements. - if ( null !== $p->get_attribute( 'one' ) ) { - $this->assertTrue( $p->set_bookmark( 'one' ) ); + if ( null !== $processor->get_attribute( 'one' ) ) { + $this->assertTrue( $processor->set_bookmark( 'one' ) ); continue; } // Create a bookmark inside of that stack. - if ( null !== $p->get_attribute( 'two' ) ) { - $p->set_bookmark( 'two' ); + if ( null !== $processor->get_attribute( 'two' ) ) { + $processor->set_bookmark( 'two' ); break; } } // Ensure that it's possible to seek back to the outside location. - $this->assertTrue( $p->seek( 'one' ), 'Could not seek to earlier-seen location.' ); - $this->assertSame( 'DIV', $p->get_tag(), "Should have jumped back to DIV but found {$p->get_tag()} instead." ); + $this->assertTrue( $processor->seek( 'one' ), 'Could not seek to earlier-seen location.' ); + $this->assertSame( 'DIV', $processor->get_tag(), "Should have jumped back to DIV but found {$processor->get_tag()} instead." ); /* * Ensure that the P element from the inner location isn't still on the stack of open elements. * If it were, then the first STRONG element, inside the outer DIV would match the next call. */ - $this->assertTrue( $p->next_tag( array( 'breadcrumbs' => array( 'P', 'STRONG' ) ) ), 'Failed to find given location after seeking.' ); + $this->assertTrue( $processor->next_tag( array( 'breadcrumbs' => array( 'P', 'STRONG' ) ) ), 'Failed to find given location after seeking.' ); // Only if the stack is properly managed will the processor advance to the inner STRONG element. - $this->assertTrue( $p->get_attribute( 'two' ), "Found the wrong location given the breadcrumbs, at {$p->get_tag()}." ); + $this->assertTrue( $processor->get_attribute( 'two' ), "Found the wrong location given the breadcrumbs, at {$processor->get_tag()}." ); // Ensure that in seeking backwards the processor reports the correct full set of breadcrumbs. - $this->assertTrue( $p->seek( 'one' ), 'Failed to jump back to first bookmark.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $p->get_breadcrumbs(), 'Found wrong set of breadcrumbs navigating to node "one".' ); + $this->assertTrue( $processor->seek( 'one' ), 'Failed to jump back to first bookmark.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $processor->get_breadcrumbs(), 'Found wrong set of breadcrumbs navigating to node "one".' ); // Ensure that in seeking forwards the processor reports the correct full set of breadcrumbs. - $this->assertTrue( $p->seek( 'two' ), 'Failed to jump forward to second bookmark.' ); - $this->assertTrue( $p->get_attribute( 'two' ), "Found the wrong location given the bookmark, at {$p->get_tag()}." ); + $this->assertTrue( $processor->seek( 'two' ), 'Failed to jump forward to second bookmark.' ); + $this->assertTrue( $processor->get_attribute( 'two' ), "Found the wrong location given the bookmark, at {$processor->get_tag()}." ); - $this->assertSame( array( 'HTML', 'BODY', 'P', 'STRONG' ), $p->get_breadcrumbs(), 'Found wrong set of bookmarks navigating to node "two".' ); + $this->assertSame( array( 'HTML', 'BODY', 'P', 'STRONG' ), $processor->get_breadcrumbs(), 'Found wrong set of bookmarks navigating to node "two".' ); } /** @@ -126,10 +126,144 @@ public function test_clear_to_navigate_after_seeking() { * @covers WP_HTML_Processor::reconstruct_active_formatting_elements */ public function test_fails_to_reconstruct_formatting_elements() { - $p = WP_HTML_Processor::create_fragment( '

    One

    Two

    Three

    Four' ); + $processor = WP_HTML_Processor::create_fragment( '

    One

    Two

    Three

    Four' ); - $this->assertTrue( $p->next_tag( 'EM' ), 'Could not find first EM.' ); - $this->assertFalse( $p->next_tag( 'EM' ), 'Should have aborted before finding second EM as it required reconstructing the first EM.' ); + $this->assertTrue( $processor->next_tag( 'EM' ), 'Could not find first EM.' ); + $this->assertFalse( $processor->next_tag( 'EM' ), 'Should have aborted before finding second EM as it required reconstructing the first EM.' ); + } + + /** + * Ensure non-nesting tags do not nest. + * + * @ticket 60283 + * + * @covers WP_HTML_Processor::step_in_body + * @covers WP_HTML_Processor::is_void + * + * @dataProvider data_void_tags + * + * @param string $tag_name Name of void tag under test. + */ + public function test_cannot_nest_void_tags( $tag_name ) { + $processor = WP_HTML_Processor::create_fragment( "<{$tag_name}>

    " ); + + /* + * This HTML represents the same as the following HTML, + * assuming that it were provided `` as the tag: + * + * + * + * + *
    + * + * + */ + + $found_tag = $processor->next_tag(); + + if ( WP_HTML_Processor::ERROR_UNSUPPORTED === $processor->get_last_error() ) { + $this->markTestSkipped( "Tag {$tag_name} is not supported." ); + } + + $this->assertTrue( + $found_tag, + "Could not find first {$tag_name}." + ); + + $this->assertSame( + array( 'HTML', 'BODY', $tag_name ), + $processor->get_breadcrumbs(), + 'Found incorrect nesting of first element.' + ); + + $this->assertTrue( + $processor->next_tag(), + 'Should have found the DIV as the second tag.' + ); + + $this->assertSame( + array( 'HTML', 'BODY', 'DIV' ), + $processor->get_breadcrumbs(), + "DIV should have been a sibling of the {$tag_name}." + ); + } + + /** + * Ensure non-nesting tags do not nest when processing tokens. + * + * @ticket 60382 + * + * @dataProvider data_void_tags + * + * @param string $tag_name Name of void tag under test. + */ + public function test_cannot_nest_void_tags_next_token( $tag_name ) { + $processor = WP_HTML_Processor::create_fragment( "<{$tag_name}>
    " ); + + /* + * This HTML represents the same as the following HTML, + * assuming that it were provided `` as the tag: + * + * + * + * + *
    + * + * + */ + + $found_tag = $processor->next_token(); + + if ( WP_HTML_Processor::ERROR_UNSUPPORTED === $processor->get_last_error() ) { + $this->markTestSkipped( "Tag {$tag_name} is not supported." ); + } + + $this->assertTrue( + $found_tag, + "Could not find first {$tag_name}." + ); + + $this->assertSame( + array( 'HTML', 'BODY', $tag_name ), + $processor->get_breadcrumbs(), + 'Found incorrect nesting of first element.' + ); + + $this->assertTrue( + $processor->next_token(), + 'Should have found the DIV as the second tag.' + ); + + $this->assertSame( + array( 'HTML', 'BODY', 'DIV' ), + $processor->get_breadcrumbs(), + "DIV should have been a sibling of the {$tag_name}." + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_void_tags() { + return array( + 'AREA' => array( 'AREA' ), + 'BASE' => array( 'BASE' ), + 'BR' => array( 'BR' ), + 'COL' => array( 'COL' ), + 'EMBED' => array( 'EMBED' ), + 'HR' => array( 'HR' ), + 'IMG' => array( 'IMG' ), + 'INPUT' => array( 'INPUT' ), + 'KEYGEN' => array( 'KEYGEN' ), + 'LINK' => array( 'LINK' ), + 'META' => array( 'META' ), + 'PARAM' => array( 'PARAM' ), + 'SOURCE' => array( 'SOURCE' ), + 'TRACK' => array( 'TRACK' ), + 'WBR' => array( 'WBR' ), + ); } /** @@ -156,30 +290,23 @@ public function test_step_in_body_fails_on_unsupported_tags( $tag_name ) { * * @return array[] */ - public function data_unsupported_special_in_body_tags() { + public static function data_unsupported_special_in_body_tags() { return array( 'APPLET' => array( 'APPLET' ), - 'AREA' => array( 'AREA' ), 'BASE' => array( 'BASE' ), 'BASEFONT' => array( 'BASEFONT' ), 'BGSOUND' => array( 'BGSOUND' ), 'BODY' => array( 'BODY' ), - 'BR' => array( 'BR' ), 'CAPTION' => array( 'CAPTION' ), 'COL' => array( 'COL' ), 'COLGROUP' => array( 'COLGROUP' ), - 'EMBED' => array( 'EMBED' ), 'FORM' => array( 'FORM' ), 'FRAME' => array( 'FRAME' ), 'FRAMESET' => array( 'FRAMESET' ), 'HEAD' => array( 'HEAD' ), - 'HR' => array( 'HR' ), 'HTML' => array( 'HTML' ), 'IFRAME' => array( 'IFRAME' ), - 'INPUT' => array( 'INPUT' ), - 'KEYGEN' => array( 'KEYGEN' ), 'LINK' => array( 'LINK' ), - 'LISTING' => array( 'LISTING' ), 'MARQUEE' => array( 'MARQUEE' ), 'MATH' => array( 'MATH' ), 'META' => array( 'META' ), @@ -190,9 +317,7 @@ public function data_unsupported_special_in_body_tags() { 'OBJECT' => array( 'OBJECT' ), 'OPTGROUP' => array( 'OPTGROUP' ), 'OPTION' => array( 'OPTION' ), - 'PARAM' => array( 'PARAM' ), 'PLAINTEXT' => array( 'PLAINTEXT' ), - 'PRE' => array( 'PRE' ), 'RB' => array( 'RB' ), 'RP' => array( 'RP' ), 'RT' => array( 'RT' ), @@ -200,7 +325,6 @@ public function data_unsupported_special_in_body_tags() { 'SARCASM' => array( 'SARCASM' ), 'SCRIPT' => array( 'SCRIPT' ), 'SELECT' => array( 'SELECT' ), - 'SOURCE' => array( 'SOURCE' ), 'STYLE' => array( 'STYLE' ), 'SVG' => array( 'SVG' ), 'TABLE' => array( 'TABLE' ), @@ -213,8 +337,6 @@ public function data_unsupported_special_in_body_tags() { 'THEAD' => array( 'THEAD' ), 'TITLE' => array( 'TITLE' ), 'TR' => array( 'TR' ), - 'TRACK' => array( 'TRACK' ), - 'WBR' => array( 'WBR' ), 'XMP' => array( 'XMP' ), ); } diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php b/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php index 15d38d6f70c6c..1488be91654a7 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php @@ -23,10 +23,10 @@ class Tests_HtmlApi_WpHtmlProcessorBreadcrumbs extends WP_UnitTestCase { * @param string $tag_name Name of first tag in HTML (because HTML treats IMAGE as IMG this may not match the HTML). */ public function test_navigates_into_normative_html_for_supported_elements( $html, $tag_name ) { - $p = WP_HTML_Processor::create_fragment( $html ); + $processor = WP_HTML_Processor::create_fragment( $html ); - $this->assertTrue( $p->step(), "Failed to step into supported {$tag_name} element." ); - $this->assertSame( $tag_name, $p->get_tag(), "Misread {$tag_name} as a {$p->get_tag()} element." ); + $this->assertTrue( $processor->step(), "Failed to step into supported {$tag_name} element." ); + $this->assertSame( $tag_name, $processor->get_tag(), "Misread {$tag_name} as a {$processor->get_tag()} element." ); } /** @@ -34,12 +34,13 @@ public function test_navigates_into_normative_html_for_supported_elements( $html * * @return array[] */ - public function data_single_tag_of_supported_elements() { + public static function data_single_tag_of_supported_elements() { $supported_elements = array( 'A', 'ABBR', 'ACRONYM', // Neutralized. 'ADDRESS', + 'AREA', 'ARTICLE', 'ASIDE', 'AUDIO', @@ -48,6 +49,7 @@ public function data_single_tag_of_supported_elements() { 'BDO', 'BIG', 'BLINK', // Deprecated. + 'BR', 'BUTTON', 'CANVAS', 'CENTER', // Neutralized. @@ -65,6 +67,7 @@ public function data_single_tag_of_supported_elements() { 'DL', 'DT', 'EM', + 'EMBED', 'FIELDSET', 'FIGCAPTION', 'FIGURE', @@ -78,22 +81,25 @@ public function data_single_tag_of_supported_elements() { 'H6', 'HEADER', 'HGROUP', + 'HR', 'I', 'IMG', 'INS', 'LI', - 'ISINDEX', // Deprecated + 'ISINDEX', // Deprecated. 'KBD', + 'KEYGEN', // Deprecated. 'LABEL', 'LEGEND', + 'LISTING', // Deprecated. 'MAIN', 'MAP', 'MARK', 'MENU', 'METER', - 'MULTICOL', // Deprecated + 'MULTICOL', // Deprecated. 'NAV', - 'NEXTID', // Deprecated + 'NEXTID', // Deprecated. 'OL', 'OUTPUT', 'P', @@ -106,7 +112,7 @@ public function data_single_tag_of_supported_elements() { 'SECTION', 'SLOT', 'SMALL', - 'SPACER', // Deprecated + 'SPACER', // Deprecated. 'SPAN', 'STRIKE', 'STRONG', @@ -149,9 +155,9 @@ public function data_single_tag_of_supported_elements() { * @param string $html HTML string containing unsupported elements. */ public function test_fails_when_encountering_unsupported_tag( $html ) { - $p = WP_HTML_Processor::create_fragment( $html ); + $processor = WP_HTML_Processor::create_fragment( $html ); - $this->assertFalse( $p->step(), "Should not have stepped into unsupported {$p->get_tag()} element." ); + $this->assertFalse( $processor->step(), "Should not have stepped into unsupported {$processor->get_tag()} element." ); } /** @@ -159,29 +165,22 @@ public function test_fails_when_encountering_unsupported_tag( $html ) { * * @return array[] */ - public function data_unsupported_elements() { + public static function data_unsupported_elements() { $unsupported_elements = array( 'APPLET', // Deprecated. - 'AREA', 'BASE', 'BGSOUND', // Deprecated; self-closing if self-closing flag provided, otherwise normal. 'BODY', - 'BR', 'CAPTION', 'COL', 'COLGROUP', - 'EMBED', 'FORM', 'FRAME', 'FRAMESET', 'HEAD', - 'HR', 'HTML', 'IFRAME', - 'INPUT', - 'KEYGEN', // Deprecated; void. 'LINK', - 'LISTING', // Deprecated, use PRE instead. 'MARQUEE', // Deprecated. 'MATH', 'META', @@ -193,14 +192,12 @@ public function data_unsupported_elements() { 'OPTGROUP', 'OPTION', 'PLAINTEXT', // Neutralized. - 'PRE', 'RB', // Neutralized. 'RP', 'RT', 'RTC', // Neutralized. 'SCRIPT', 'SELECT', - 'SOURCE', 'STYLE', 'SVG', 'TABLE', @@ -213,8 +210,6 @@ public function data_unsupported_elements() { 'THEAD', 'TITLE', 'TR', - 'TRACK', - 'WBR', 'XMP', // Deprecated, use PRE instead. ); @@ -234,14 +229,14 @@ public function data_unsupported_elements() { * @param string $html HTML containing unsupported markup. */ public function test_fails_when_encountering_unsupported_markup( $html, $description ) { - $p = WP_HTML_Processor::create_fragment( $html ); + $processor = WP_HTML_Processor::create_fragment( $html ); - while ( $p->step() && null === $p->get_attribute( 'supported' ) ) { + while ( $processor->step() && null === $processor->get_attribute( 'supported' ) ) { continue; } - $this->assertTrue( $p->get_attribute( 'supported' ), 'Did not find required supported element.' ); - $this->assertFalse( $p->step(), "Didn't properly reject unsupported markup: {$description}" ); + $this->assertTrue( $processor->get_attribute( 'supported' ), 'Did not find required supported element.' ); + $this->assertFalse( $processor->step(), "Didn't properly reject unsupported markup: {$description}" ); } /** @@ -249,7 +244,7 @@ public function test_fails_when_encountering_unsupported_markup( $html, $descrip * * @return array[] */ - public function data_unsupported_markup() { + public static function data_unsupported_markup() { return array( 'A with formatting following unclosed A' => array( 'Click Here', @@ -275,17 +270,17 @@ public function data_unsupported_markup() { * @param int $n How many breadcrumb matches to scan through in order to find "target" element. */ public function test_finds_correct_tag_given_breadcrumbs( $html, $breadcrumbs, $n ) { - $p = WP_HTML_Processor::create_fragment( $html ); + $processor = WP_HTML_Processor::create_fragment( $html ); - $p->next_tag( + $processor->next_tag( array( 'breadcrumbs' => $breadcrumbs, 'match_offset' => $n, ) ); - $this->assertNotNull( $p->get_tag(), 'Failed to find target node.' ); - $this->assertTrue( $p->get_attribute( 'target' ), "Found {$p->get_tag()} element didn't contain the necessary 'target' attribute." ); + $this->assertNotNull( $processor->get_tag(), 'Failed to find target node.' ); + $this->assertTrue( $processor->get_attribute( 'target' ), "Found {$processor->get_tag()} element didn't contain the necessary 'target' attribute." ); } /** @@ -300,14 +295,14 @@ public function test_finds_correct_tag_given_breadcrumbs( $html, $breadcrumbs, $ * @param int $ignored_n Not used in this test but provided in the dataset for other tests. */ public function test_reports_correct_breadcrumbs_for_html( $html, $breadcrumbs, $ignored_n ) { - $p = WP_HTML_Processor::create_fragment( $html ); + $processor = WP_HTML_Processor::create_fragment( $html ); - while ( $p->next_tag() && null === $p->get_attribute( 'target' ) ) { + while ( $processor->next_tag() && null === $processor->get_attribute( 'target' ) ) { continue; } - $this->assertNotNull( $p->get_tag(), 'Failed to find the target node.' ); - $this->assertSame( $breadcrumbs, $p->get_breadcrumbs(), 'Found the wrong path from the root of the HTML document to the target node.' ); + $this->assertNotNull( $processor->get_tag(), 'Failed to find the target node.' ); + $this->assertSame( $breadcrumbs, $processor->get_breadcrumbs(), 'Found the wrong path from the root of the HTML document to the target node.' ); } /** @@ -315,7 +310,7 @@ public function test_reports_correct_breadcrumbs_for_html( $html, $breadcrumbs, * * @return array[] */ - public function data_html_target_with_breadcrumbs() { + public static function data_html_target_with_breadcrumbs() { return array( 'Simple IMG tag' => array( '', array( 'HTML', 'BODY', 'IMG' ), 1 ), 'Two sibling IMG tags' => array( '', array( 'HTML', 'BODY', 'IMG' ), 2 ), @@ -398,7 +393,7 @@ public function test_reports_if_tag_matches_breadcrumbs_of_various_specificity( * * @return array[]. */ - public function data_html_with_breadcrumbs_of_various_specificity() { + public static function data_html_with_breadcrumbs_of_various_specificity() { return array( // Test with void elements. 'Inner IMG' => array( '
    ', array( 'span', 'figure', 'img' ), true ), @@ -438,38 +433,38 @@ public function data_html_with_breadcrumbs_of_various_specificity() { * @covers WP_HTML_Tag_Processor::get_updated_html */ public function test_remains_stable_when_editing_attributes() { - $p = WP_HTML_Processor::create_fragment( '
    ' ); + $processor = WP_HTML_Processor::create_fragment( '
    Test
    ' ); - $p->step(); - $this->assertSame( 'DIV', $p->get_tag(), 'Did not stop at initial DIV tag.' ); - $this->assertFalse( $p->is_tag_closer(), 'Did not find that initial DIV tag is an opener.' ); + $processor->step(); + $this->assertSame( 'DIV', $processor->get_tag(), 'Did not stop at initial DIV tag.' ); + $this->assertFalse( $processor->is_tag_closer(), 'Did not find that initial DIV tag is an opener.' ); /* * When encountering the BUTTON closing tag, there is no BUTTON in the stack of open elements. * It should be ignored as there's no BUTTON to close. */ - $this->assertTrue( $p->step(), 'Found no further tags when it should have found the closing DIV' ); - $this->assertSame( 'DIV', $p->get_tag(), "Did not skip unexpected BUTTON; stopped at {$p->get_tag()}." ); - $this->assertTrue( $p->is_tag_closer(), 'Did not find that the terminal DIV tag is a closer.' ); + $this->assertTrue( $processor->step(), 'Found no further tags when it should have found the closing DIV' ); + $this->assertSame( 'DIV', $processor->get_tag(), "Did not skip unexpected BUTTON; stopped at {$processor->get_tag()}." ); + $this->assertTrue( $processor->is_tag_closer(), 'Did not find that the terminal DIV tag is a closer.' ); } /** @@ -143,20 +143,20 @@ public function test_in_body_skips_unexpected_button_closer() { * @ticket 58961 */ public function test_in_body_button_with_no_button_in_scope() { - $p = WP_HTML_Processor::create_fragment( '

    Click the button !

    ' ); + $processor = WP_HTML_Processor::create_fragment( '

    Click the button !

    ' ); - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected first button.' ); - $this->assertTrue( $p->get_attribute( 'one' ), 'Failed to match expected attribute on first button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'P', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for first button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected first button.' ); + $this->assertTrue( $processor->get_attribute( 'one' ), 'Failed to match expected attribute on first button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'P', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for first button.' ); /* * There's nothing special about this HTML construction, but it's important to verify that * the HTML Processor can find a BUTTON under normal and normative scenarios, not just the * malformed and unexpected ones. */ - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected second button.' ); - $this->assertTrue( $p->get_attribute( 'two' ), 'Failed to match expected attribute on second button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for second button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected second button.' ); + $this->assertTrue( $processor->get_attribute( 'two' ), 'Failed to match expected attribute on second button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for second button.' ); } /** @@ -168,27 +168,27 @@ public function test_in_body_button_with_no_button_in_scope() { * @since 6.4.0 */ public function test_in_body_button_with_button_in_scope_as_parent() { - $p = WP_HTML_Processor::create_fragment( '

    Click the button !

    ' ); + $processor = WP_HTML_Processor::create_fragment( '

    Click the button !

    ' ); - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected first button.' ); - $this->assertTrue( $p->get_attribute( 'one' ), 'Failed to match expected attribute on first button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'P', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for first button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected first button.' ); + $this->assertTrue( $processor->get_attribute( 'one' ), 'Failed to match expected attribute on first button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'P', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for first button.' ); /* * A naive parser might skip the second BUTTON because it's looking for the close of the first one, * or it may place it as a child of the first one, but it implicitly closes the open BUTTON. */ - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected second button.' ); - $this->assertTrue( $p->get_attribute( 'two' ), 'Failed to match expected attribute on second button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'P', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for second button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected second button.' ); + $this->assertTrue( $processor->get_attribute( 'two' ), 'Failed to match expected attribute on second button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'P', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for second button.' ); /* * This is another form of the test for the second button, but from a different side. The test is * looking for proper handling of the open and close sequence for the BUTTON tags. */ - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected third button.' ); - $this->assertTrue( $p->get_attribute( 'three' ), 'Failed to match expected attribute on third button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for third button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected third button.' ); + $this->assertTrue( $processor->get_attribute( 'three' ), 'Failed to match expected attribute on third button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for third button.' ); } /** @@ -201,12 +201,12 @@ public function test_in_body_button_with_button_in_scope_as_parent() { * @since 6.4.0 */ public function test_in_body_button_with_button_in_scope_as_ancestor() { - $p = WP_HTML_Processor::create_fragment( '
    !

    ' ); + $processor = WP_HTML_Processor::create_fragment( '
    !

    ' ); // This button finds itself normally nesting inside the DIV. - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected first button.' ); - $this->assertTrue( $p->get_attribute( 'one' ), 'Failed to match expected attribute on first button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for first button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected first button.' ); + $this->assertTrue( $processor->get_attribute( 'one' ), 'Failed to match expected attribute on first button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for first button.' ); /* * Because the second button appears while a BUTTON is in scope, it generates implied end tags and closes @@ -214,14 +214,30 @@ public function test_in_body_button_with_button_in_scope_as_ancestor() { * of an unexpected closing SPAN tag because the SPAN was closed by the second BUTTON. This element finds * itself a child of the most-recent open element above the most-recent BUTTON, or the DIV. */ - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected second button.' ); - $this->assertTrue( $p->get_attribute( 'two' ), 'Failed to match expected attribute on second button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for second button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected second button.' ); + $this->assertTrue( $processor->get_attribute( 'two' ), 'Failed to match expected attribute on second button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for second button.' ); // The third button is back to normal, because everything has been implicitly or explicitly closed by now. - $this->assertTrue( $p->next_tag( 'BUTTON' ), 'Could not find expected third button.' ); - $this->assertTrue( $p->get_attribute( 'three' ), 'Failed to match expected attribute on third button.' ); - $this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for third button.' ); + $this->assertTrue( $processor->next_tag( 'BUTTON' ), 'Could not find expected third button.' ); + $this->assertTrue( $processor->get_attribute( 'three' ), 'Failed to match expected attribute on third button.' ); + $this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting for third button.' ); + } + + /** + * Verifies that HR closes an open p tag + * + * @ticket 60283 + */ + public function test_in_body_hr_element_closes_open_p_tag() { + $processor = WP_HTML_Processor::create_fragment( '


    ' ); + + $processor->next_tag( 'HR' ); + $this->assertSame( + array( 'HTML', 'BODY', 'HR' ), + $processor->get_breadcrumbs(), + 'Expected HR to be a direct child of the BODY, having closed the open P element.' + ); } /** @@ -258,7 +274,7 @@ public function test_in_body_heading_element_closes_open_p_tag( $tag_name ) { * * @return array[]. */ - public function data_heading_elements() { + public static function data_heading_elements() { return array( 'H1' => array( 'H1' ), 'H2' => array( 'H2' ), @@ -312,7 +328,7 @@ public function test_in_body_heading_element_closes_other_heading_elements( $fir * * @return array[] */ - public function data_heading_combinations() { + public static function data_heading_combinations() { $headings = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ); $combinations = array(); @@ -339,15 +355,15 @@ public function data_heading_combinations() { * @since 6.4.0 */ public function test_in_body_any_other_end_tag_with_unclosed_special_element() { - $p = WP_HTML_Processor::create_fragment( '

    ' ); + $processor = WP_HTML_Processor::create_fragment( '

    ' ); - $p->next_tag( 'P' ); - $this->assertSame( 'P', $p->get_tag(), "Expected to start test on P element but found {$p->get_tag()} instead." ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'P' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting.' ); + $processor->next_tag( 'P' ); + $this->assertSame( 'P', $processor->get_tag(), "Expected to start test on P element but found {$processor->get_tag()} instead." ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'P' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting.' ); - $this->assertTrue( $p->next_tag(), 'Failed to advance past P tag to expected DIV opener.' ); - $this->assertSame( 'DIV', $p->get_tag(), "Expected to find DIV element, but found {$p->get_tag()} instead." ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'DIV' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting: SPAN should still be open and DIV should be its child.' ); + $this->assertTrue( $processor->next_tag(), 'Failed to advance past P tag to expected DIV opener.' ); + $this->assertSame( 'DIV', $processor->get_tag(), "Expected to find DIV element, but found {$processor->get_tag()} instead." ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'DIV' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting: SPAN should still be open and DIV should be its child.' ); } /** @@ -362,18 +378,43 @@ public function test_in_body_any_other_end_tag_with_unclosed_special_element() { * @since 6.4.0 */ public function test_in_body_any_other_end_tag_with_unclosed_non_special_element() { - $p = WP_HTML_Processor::create_fragment( '
    ' ); + $processor = WP_HTML_Processor::create_fragment( '
    ' ); + + $processor->next_tag( 'CODE' ); + $this->assertSame( 'CODE', $processor->get_tag(), "Expected to start test on CODE element but found {$processor->get_tag()} instead." ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'CODE' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting.' ); - $p->next_tag( 'CODE' ); - $this->assertSame( 'CODE', $p->get_tag(), "Expected to start test on CODE element but found {$p->get_tag()} instead." ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'SPAN', 'CODE' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting.' ); + $this->assertTrue( $processor->step(), 'Failed to advance past CODE tag to expected SPAN closer.' ); + $this->assertTrue( $processor->is_tag_closer(), 'Expected to find closing SPAN, but found opener instead.' ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $processor->get_breadcrumbs(), 'Failed to advance past CODE tag to expected DIV opener.' ); - $this->assertTrue( $p->step(), 'Failed to advance past CODE tag to expected SPAN closer.' ); - $this->assertTrue( $p->is_tag_closer(), 'Expected to find closing SPAN, but found opener instead.' ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV' ), $p->get_breadcrumbs(), 'Failed to advance past CODE tag to expected DIV opener.' ); + $this->assertTrue( $processor->next_tag(), 'Failed to advance past SPAN closer to expected DIV opener.' ); + $this->assertSame( 'DIV', $processor->get_tag(), "Expected to find DIV element, but found {$processor->get_tag()} instead." ); + $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'DIV' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting: SPAN should be closed and DIV should be its sibling.' ); + } + + /** + * Ensures that support isn't accidentally partially added for the closing BR tag `
    `. + * + * This tag closer has special rules and support shouldn't be added without implementing full support. + * + * > 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. + * + * When this handling is implemented, this test should be removed. It's not incorporated + * into the existing unsupported tag behavior test because the opening tag is supported; + * only the closing tag isn't. + * + * @covers WP_HTML_Processor::step_in_body + * + * @ticket 60283 + */ + public function test_br_end_tag_unsupported() { + $processor = WP_HTML_Processor::create_fragment( '
    ' ); - $this->assertTrue( $p->next_tag(), 'Failed to advance past SPAN closer to expected DIV opener.' ); - $this->assertSame( 'DIV', $p->get_tag(), "Expected to find DIV element, but found {$p->get_tag()} instead." ); - $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'DIV' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting: SPAN should be closed and DIV should be its sibling.' ); + $this->assertFalse( $processor->next_tag(), 'Found a BR tag that should not be handled.' ); + $this->assertSame( WP_HTML_Processor::ERROR_UNSUPPORTED, $processor->get_last_error() ); } } diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesHeadingElements.php b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesHeadingElements.php index d8d70acb61e76..b33c6e072dfb1 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesHeadingElements.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesHeadingElements.php @@ -53,7 +53,7 @@ public function test_in_body_heading_element_closes_open_p_tag( $tag_name ) { * * @return array[]. */ - public function data_heading_elements() { + public static function data_heading_elements() { return array( 'H1' => array( 'H1' ), 'H2' => array( 'H2' ), @@ -109,7 +109,7 @@ public function test_in_body_heading_element_closes_other_heading_elements( $fir * * @return array[] */ - public function data_heading_combinations() { + public static function data_heading_combinations() { $headings = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ); $combinations = array(); diff --git a/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php b/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php index 2770acb7360df..2d3cd21ce461b 100644 --- a/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlSupportRequiredHtmlProcessor.php @@ -42,9 +42,9 @@ class Tests_HtmlApi_WpHtmlSupportRequiredHtmlProcessor extends WP_UnitTestCase { * @param string $tag_name the HTML Processor should abort when encountering this tag, e.g. "BUTTON". */ private function ensure_support_is_added_everywhere( $tag_name ) { - $p = WP_HTML_Processor::create_fragment( "<$tag_name>" ); + $processor = WP_HTML_Processor::create_fragment( "<$tag_name>" ); - $this->assertFalse( $p->step(), "Must support terminating elements in specific scope check before adding support for the {$tag_name} element." ); + $this->assertFalse( $processor->step(), "Must support terminating elements in specific scope check before adding support for the {$tag_name} element." ); } /** diff --git a/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php b/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php index 0a05629e024bd..c2e8c697e8156 100644 --- a/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php +++ b/tests/phpunit/tests/html-api/wpHtmlSupportRequiredOpenElements.php @@ -44,9 +44,9 @@ class Tests_HtmlApi_WpHtmlSupportRequiredOpenElements extends WP_UnitTestCase { * @param string $tag_name the HTML Processor should abort when encountering this tag, e.g. "BUTTON". */ private function ensure_support_is_added_everywhere( $tag_name ) { - $p = WP_HTML_Processor::create_fragment( "<$tag_name>" ); + $processor = WP_HTML_Processor::create_fragment( "<$tag_name>" ); - $this->assertFalse( $p->step(), "Must support terminating elements in specific scope check before adding support for the {$tag_name} element." ); + $this->assertFalse( $processor->step(), "Must support terminating elements in specific scope check before adding support for the {$tag_name} element." ); } /** diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessor-bookmark.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessor-bookmark.php index 90adfb20be955..a0a3b2aa44b4b 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessor-bookmark.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessor-bookmark.php @@ -19,12 +19,12 @@ class Tests_HtmlApi_WpHtmlTagProcessor_Bookmark extends WP_UnitTestCase { * @covers WP_HTML_Tag_Processor::set_bookmark */ public function test_set_bookmark() { - $p = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); - $p->next_tag( 'li' ); - $this->assertTrue( $p->set_bookmark( 'first li' ), 'Could not allocate a "first li" bookmark' ); - $p->next_tag( 'li' ); - $this->assertTrue( $p->set_bookmark( 'second li' ), 'Could not allocate a "second li" bookmark' ); - $this->assertTrue( $p->set_bookmark( 'first li' ), 'Could not move the "first li" bookmark' ); + $processor = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); + $processor->next_tag( 'li' ); + $this->assertTrue( $processor->set_bookmark( 'first li' ), 'Could not allocate a "first li" bookmark' ); + $processor->next_tag( 'li' ); + $this->assertTrue( $processor->set_bookmark( 'second li' ), 'Could not allocate a "second li" bookmark' ); + $this->assertTrue( $processor->set_bookmark( 'first li' ), 'Could not move the "first li" bookmark' ); } /** @@ -33,11 +33,11 @@ public function test_set_bookmark() { * @covers WP_HTML_Tag_Processor::release_bookmark */ public function test_release_bookmark() { - $p = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); - $p->next_tag( 'li' ); - $this->assertFalse( $p->release_bookmark( 'first li' ), 'Released a non-existing bookmark' ); - $p->set_bookmark( 'first li' ); - $this->assertTrue( $p->release_bookmark( 'first li' ), 'Could not release a bookmark' ); + $processor = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); + $processor->next_tag( 'li' ); + $this->assertFalse( $processor->release_bookmark( 'first li' ), 'Released a non-existing bookmark' ); + $processor->set_bookmark( 'first li' ); + $this->assertTrue( $processor->release_bookmark( 'first li' ), 'Could not release a bookmark' ); } /** @@ -46,8 +46,8 @@ public function test_release_bookmark() { * @covers WP_HTML_Tag_Processor::has_bookmark */ public function test_has_bookmark_returns_false_if_bookmark_does_not_exist() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertFalse( $p->has_bookmark( 'my-bookmark' ) ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $this->assertFalse( $processor->has_bookmark( 'my-bookmark' ) ); } /** @@ -56,10 +56,10 @@ public function test_has_bookmark_returns_false_if_bookmark_does_not_exist() { * @covers WP_HTML_Tag_Processor::has_bookmark */ public function test_has_bookmark_returns_true_if_bookmark_exists() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->set_bookmark( 'my-bookmark' ); - $this->assertTrue( $p->has_bookmark( 'my-bookmark' ) ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->set_bookmark( 'my-bookmark' ); + $this->assertTrue( $processor->has_bookmark( 'my-bookmark' ) ); } /** @@ -68,11 +68,11 @@ public function test_has_bookmark_returns_true_if_bookmark_exists() { * @covers WP_HTML_Tag_Processor::has_bookmark */ public function test_has_bookmark_returns_false_if_bookmark_has_been_released() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->set_bookmark( 'my-bookmark' ); - $p->release_bookmark( 'my-bookmark' ); - $this->assertFalse( $p->has_bookmark( 'my-bookmark' ) ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->set_bookmark( 'my-bookmark' ); + $processor->release_bookmark( 'my-bookmark' ); + $this->assertFalse( $processor->has_bookmark( 'my-bookmark' ) ); } /** @@ -81,19 +81,19 @@ public function test_has_bookmark_returns_false_if_bookmark_has_been_released() * @covers WP_HTML_Tag_Processor::seek */ public function test_seek() { - $p = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); - $p->next_tag( 'li' ); - $p->set_bookmark( 'first li' ); + $processor = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); + $processor->next_tag( 'li' ); + $processor->set_bookmark( 'first li' ); - $p->next_tag( 'li' ); - $p->set_attribute( 'foo-2', 'bar-2' ); + $processor->next_tag( 'li' ); + $processor->set_attribute( 'foo-2', 'bar-2' ); - $p->seek( 'first li' ); - $p->set_attribute( 'foo-1', 'bar-1' ); + $processor->seek( 'first li' ); + $processor->set_attribute( 'foo-1', 'bar-1' ); $this->assertSame( '
    • One
    • Two
    • Three
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not seek to the intended bookmark locations' ); } @@ -104,18 +104,18 @@ public function test_seek() { * @covers WP_HTML_Tag_Processor::seek */ public function test_seeks_to_tag_closer_bookmark() { - $p = new WP_HTML_Tag_Processor( '
    First
    Second' ); - $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $p->set_bookmark( 'first' ); - $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $p->set_bookmark( 'second' ); + $processor = new WP_HTML_Tag_Processor( '
    First
    Second' ); + $processor->next_tag( array( 'tag_closers' => 'visit' ) ); + $processor->set_bookmark( 'first' ); + $processor->next_tag( array( 'tag_closers' => 'visit' ) ); + $processor->set_bookmark( 'second' ); - $p->seek( 'first' ); - $p->seek( 'second' ); + $processor->seek( 'first' ); + $processor->seek( 'second' ); $this->assertSame( 'DIV', - $p->get_tag(), + $processor->get_tag(), 'Did not seek to the intended bookmark location' ); } @@ -159,24 +159,24 @@ public function test_seeks_to_tag_closer_bookmark() { * @covers WP_HTML_Tag_Processor::set_bookmark */ public function test_removing_long_attributes_doesnt_break_seek() { - $input = << HTML; - $p = new WP_HTML_Tag_Processor( $input ); - $p->next_tag( 'button' ); - $p->set_bookmark( 'first' ); - $p->next_tag( 'button' ); - $p->set_bookmark( 'second' ); + $processor = new WP_HTML_Tag_Processor( $input ); + $processor->next_tag( 'button' ); + $processor->set_bookmark( 'first' ); + $processor->next_tag( 'button' ); + $processor->set_bookmark( 'second' ); $this->assertTrue( - $p->seek( 'first' ), + $processor->seek( 'first' ), 'Seek() to the first button has failed' ); - $p->remove_attribute( 'twenty_one_characters' ); - $p->remove_attribute( '7_chars' ); + $processor->remove_attribute( 'twenty_one_characters' ); + $processor->remove_attribute( '7_chars' ); $this->assertTrue( - $p->seek( 'second' ), + $processor->seek( 'second' ), 'Seek() to the second button has failed' ); } @@ -232,61 +232,61 @@ public function test_bookmarks_complex_use_case() {
    HTML; - $p = new WP_HTML_Tag_Processor( $input ); - $p->next_tag( 'div' ); - $p->next_tag( 'div' ); - $p->next_tag( 'div' ); - $p->set_bookmark( 'first div' ); - $p->next_tag( 'button' ); - $p->set_bookmark( 'first button' ); - $p->next_tag( 'button' ); - $p->set_bookmark( 'second button' ); - $p->next_tag( 'button' ); - $p->set_bookmark( 'third button' ); - $p->next_tag( 'button' ); - $p->set_bookmark( 'fourth button' ); - - $p->seek( 'first button' ); - $p->set_attribute( 'type', 'submit' ); + $processor = new WP_HTML_Tag_Processor( $input ); + $processor->next_tag( 'div' ); + $processor->next_tag( 'div' ); + $processor->next_tag( 'div' ); + $processor->set_bookmark( 'first div' ); + $processor->next_tag( 'button' ); + $processor->set_bookmark( 'first button' ); + $processor->next_tag( 'button' ); + $processor->set_bookmark( 'second button' ); + $processor->next_tag( 'button' ); + $processor->set_bookmark( 'third button' ); + $processor->next_tag( 'button' ); + $processor->set_bookmark( 'fourth button' ); + + $processor->seek( 'first button' ); + $processor->set_attribute( 'type', 'submit' ); $this->assertTrue( - $p->seek( 'third button' ), + $processor->seek( 'third button' ), 'Seek() to the third button failed' ); - $p->remove_attribute( 'class' ); - $p->remove_attribute( 'type' ); - $p->remove_attribute( 'aria-expanded' ); - $p->set_attribute( 'id', 'rebase-and-merge' ); - $p->remove_attribute( 'data-details-container' ); + $processor->remove_attribute( 'class' ); + $processor->remove_attribute( 'type' ); + $processor->remove_attribute( 'aria-expanded' ); + $processor->set_attribute( 'id', 'rebase-and-merge' ); + $processor->remove_attribute( 'data-details-container' ); $this->assertTrue( - $p->seek( 'first div' ), + $processor->seek( 'first div' ), 'Seek() to the first div failed' ); - $p->set_attribute( 'checked', false ); + $processor->set_attribute( 'checked', false ); $this->assertTrue( - $p->seek( 'fourth button' ), + $processor->seek( 'fourth button' ), 'Seek() to the fourth button failed' ); - $p->set_attribute( 'id', 'last-button' ); - $p->remove_attribute( 'class' ); - $p->remove_attribute( 'type' ); - $p->remove_attribute( 'checked' ); - $p->remove_attribute( 'aria-label' ); - $p->remove_attribute( 'disabled' ); - $p->remove_attribute( 'data-view-component' ); + $processor->set_attribute( 'id', 'last-button' ); + $processor->remove_attribute( 'class' ); + $processor->remove_attribute( 'type' ); + $processor->remove_attribute( 'checked' ); + $processor->remove_attribute( 'aria-label' ); + $processor->remove_attribute( 'disabled' ); + $processor->remove_attribute( 'data-view-component' ); $this->assertTrue( - $p->seek( 'second button' ), + $processor->seek( 'second button' ), 'Seek() to the second button failed' ); - $p->remove_attribute( 'type' ); - $p->set_attribute( 'class', 'hx_create-pr-button' ); + $processor->remove_attribute( 'type' ); + $processor->set_attribute( 'class', 'hx_create-pr-button' ); $this->assertSame( $expected_output, - $p->get_updated_html(), + $processor->get_updated_html(), 'Performing several attribute updates on different tags does not produce the expected HTML snippet' ); } @@ -297,18 +297,18 @@ public function test_bookmarks_complex_use_case() { * @covers WP_HTML_Tag_Processor::seek */ public function test_updates_bookmark_for_additions_after_both_sides() { - $p = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); - $p->next_tag(); - $p->set_bookmark( 'first' ); - $p->next_tag(); - $p->add_class( 'second' ); + $processor = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); + $processor->next_tag(); + $processor->set_bookmark( 'first' ); + $processor->next_tag(); + $processor->add_class( 'second' ); - $p->seek( 'first' ); - $p->add_class( 'first' ); + $processor->seek( 'first' ); + $processor->add_class( 'first' ); $this->assertSame( '
    First
    Second
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'The bookmark was updated incorrectly in response to HTML markup updates' ); } @@ -319,21 +319,21 @@ public function test_updates_bookmark_for_additions_after_both_sides() { * @covers WP_HTML_Tag_Processor::seek */ public function test_updates_bookmark_for_additions_before_both_sides() { - $p = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); - $p->next_tag(); - $p->set_bookmark( 'first' ); - $p->next_tag(); - $p->set_bookmark( 'second' ); + $processor = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); + $processor->next_tag(); + $processor->set_bookmark( 'first' ); + $processor->next_tag(); + $processor->set_bookmark( 'second' ); - $p->seek( 'first' ); - $p->add_class( 'first' ); + $processor->seek( 'first' ); + $processor->add_class( 'first' ); - $p->seek( 'second' ); - $p->add_class( 'second' ); + $processor->seek( 'second' ); + $processor->add_class( 'second' ); $this->assertSame( '
    First
    Second
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'The bookmark was updated incorrectly in response to HTML markup updates' ); } @@ -344,14 +344,14 @@ public function test_updates_bookmark_for_additions_before_both_sides() { * @covers WP_HTML_Tag_Processor::seek */ public function test_updates_bookmark_for_deletions_after_both_sides() { - $p = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); - $p->next_tag(); - $p->set_bookmark( 'first' ); - $p->next_tag(); - $p->remove_attribute( 'disabled' ); + $processor = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); + $processor->next_tag(); + $processor->set_bookmark( 'first' ); + $processor->next_tag(); + $processor->remove_attribute( 'disabled' ); - $p->seek( 'first' ); - $p->set_attribute( 'untouched', true ); + $processor->seek( 'first' ); + $processor->set_attribute( 'untouched', true ); $this->assertSame( /* @@ -363,7 +363,7 @@ public function test_updates_bookmark_for_deletions_after_both_sides() { * is not required. */ '
    First
    Second
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'The bookmark was incorrectly in response to HTML markup updates' ); } @@ -374,17 +374,17 @@ public function test_updates_bookmark_for_deletions_after_both_sides() { * @covers WP_HTML_Tag_Processor::seek */ public function test_updates_bookmark_for_deletions_before_both_sides() { - $p = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); - $p->next_tag(); - $p->set_bookmark( 'first' ); - $p->next_tag(); - $p->set_bookmark( 'second' ); + $processor = new WP_HTML_Tag_Processor( '
    First
    Second
    ' ); + $processor->next_tag(); + $processor->set_bookmark( 'first' ); + $processor->next_tag(); + $processor->set_bookmark( 'second' ); - $p->seek( 'first' ); - $p->remove_attribute( 'disabled' ); + $processor->seek( 'first' ); + $processor->remove_attribute( 'disabled' ); - $p->seek( 'second' ); - $p->set_attribute( 'safe', true ); + $processor->seek( 'second' ); + $processor->set_attribute( 'safe', true ); $this->assertSame( /* @@ -396,7 +396,7 @@ public function test_updates_bookmark_for_deletions_before_both_sides() { * is not required. */ '
    First
    Second
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'The bookmark was updated incorrectly in response to HTML markup updates' ); } @@ -407,15 +407,15 @@ public function test_updates_bookmark_for_deletions_before_both_sides() { * @covers WP_HTML_Tag_Processor::set_bookmark */ public function test_limits_the_number_of_bookmarks() { - $p = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); - $p->next_tag( 'li' ); + $processor = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); + $processor->next_tag( 'li' ); for ( $i = 0; $i < WP_HTML_Tag_Processor::MAX_BOOKMARKS; $i++ ) { - $this->assertTrue( $p->set_bookmark( "bookmark $i" ), "Could not allocate the bookmark #$i" ); + $this->assertTrue( $processor->set_bookmark( "bookmark $i" ), "Could not allocate the bookmark #$i" ); } $this->setExpectedIncorrectUsage( 'WP_HTML_Tag_Processor::set_bookmark' ); - $this->assertFalse( $p->set_bookmark( 'final bookmark' ), "Allocated $i bookmarks, which is one above the limit" ); + $this->assertFalse( $processor->set_bookmark( 'final bookmark' ), "Allocated $i bookmarks, which is one above the limit" ); } /** @@ -424,15 +424,60 @@ public function test_limits_the_number_of_bookmarks() { * @covers WP_HTML_Tag_Processor::seek */ public function test_limits_the_number_of_seek_calls() { - $p = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); - $p->next_tag( 'li' ); - $p->set_bookmark( 'bookmark' ); + $processor = new WP_HTML_Tag_Processor( '
    • One
    • Two
    • Three
    ' ); + $processor->next_tag( 'li' ); + $processor->set_bookmark( 'bookmark' ); for ( $i = 0; $i < WP_HTML_Tag_Processor::MAX_SEEK_OPS; $i++ ) { - $this->assertTrue( $p->seek( 'bookmark' ), 'Could not seek to the "bookmark"' ); + $this->assertTrue( $processor->seek( 'bookmark' ), 'Could not seek to the "bookmark"' ); } $this->setExpectedIncorrectUsage( 'WP_HTML_Tag_Processor::seek' ); - $this->assertFalse( $p->seek( 'bookmark' ), "$i-th seek() to the bookmark succeeded, even though it should exceed the allowed limit" ); + $this->assertFalse( $processor->seek( 'bookmark' ), "$i-th seek() to the bookmark succeeded, even though it should exceed the allowed limit" ); + } + + /** + * Ensures that it's possible to seek to an earlier location in a document even + * after reaching the end of a document, when most functionality shuts down. + * + * @ticket 60428 + * + * @dataProvider data_incomplete_html_with_target_nodes_for_seeking + * + * @param string $html_with_target_element HTML string containing a tag with a `target` attribute. + */ + public function test_can_seek_after_document_ends( $html_with_target_element ) { + $processor = new WP_HTML_Tag_Processor( $html_with_target_element ); + + $sought_tag_name = null; + while ( $processor->next_tag() ) { + if ( null !== $processor->get_attribute( 'target' ) ) { + $processor->set_bookmark( 'target' ); + $sought_tag_name = $processor->get_tag(); + } + } + + $this->assertTrue( + $processor->seek( 'target' ), + 'Should have been able to seek to the target bookmark after reaching the end of the document.' + ); + + $this->assertSame( + $sought_tag_name, + $processor->get_tag(), + "Should have found original target node instead of {$processor->get_tag()}." + ); + } + + /** + * Data provider. + * + * @return array[]. + */ + public static function data_incomplete_html_with_target_nodes_for_seeking() { + return array( + 'Compete document' => array( '
    ' ), + 'Incomplete document' => array( '
    assertFalse( + $processor->next_token(), + "Should not have found any tokens but found {$processor->get_token_type()}." + ); + } + + /** + * Ensures that normative text nodes are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_text_node() { + $processor = new WP_HTML_Tag_Processor( 'Hello, World!' ); + $processor->next_token(); + + $this->assertSame( + '#text', + $processor->get_token_type(), + "Should have found #text token type but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + 'Hello, World!', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative Elements are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_element() { + $processor = new WP_HTML_Tag_Processor( '
    Hello, World!
    ' ); + $processor->next_token(); + + $this->assertSame( + 'DIV', + $processor->get_token_name(), + "Should have found DIV tag name but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + 'test', + $processor->get_attribute( 'id' ), + "Should have found id attribute value 'test' but found {$processor->get_attribute( 'id' )} instead." + ); + + $this->assertTrue( + $processor->get_attribute( 'inert' ), + "Should have found boolean attribute 'inert' but didn't." + ); + + $attributes = $processor->get_attribute_names_with_prefix( '' ); + $attribute_list = array_map( 'Tests_HtmlApi_WpHtmlProcessor_Token_Scanning::quoted', $attributes ); + $this->assertSame( + array( 'id', 'inert' ), + $attributes, + 'Should have found only two attributes but found ' . implode( ', ', $attribute_list ) . ' instead.' + ); + + $this->assertSame( + '', + $processor->get_modifiable_text(), + "Should have found empty modifiable text but found '{$processor->get_modifiable_text()}' instead." + ); + } + + /** + * Ensures that normative SCRIPT elements are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_script_element() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + 'SCRIPT', + $processor->get_token_name(), + "Should have found SCRIPT tag name but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + 'module', + $processor->get_attribute( 'type' ), + "Should have found type attribute value 'module' but found {$processor->get_attribute( 'type' )} instead." + ); + + $attributes = $processor->get_attribute_names_with_prefix( '' ); + $attribute_list = array_map( 'Tests_HtmlApi_WpHtmlProcessor_Token_Scanning::quoted', $attributes ); + $this->assertSame( + array( 'type' ), + $attributes, + "Should have found single 'type' attribute but found " . implode( ', ', $attribute_list ) . ' instead.' + ); + + $this->assertSame( + 'console.log( "Hello, World!" );', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative TEXTAREA elements are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_textarea_element() { + $processor = new WP_HTML_Tag_Processor( + << +Is > XHTML? + +HTML + ); + $processor->next_token(); + + $this->assertSame( + 'TEXTAREA', + $processor->get_token_name(), + "Should have found TEXTAREA tag name but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + '30', + $processor->get_attribute( 'rows' ), + "Should have found rows attribute value 'module' but found {$processor->get_attribute( 'rows' )} instead." + ); + + $this->assertSame( + '80', + $processor->get_attribute( 'cols' ), + "Should have found cols attribute value 'module' but found {$processor->get_attribute( 'cols' )} instead." + ); + + $attributes = $processor->get_attribute_names_with_prefix( '' ); + $attribute_list = array_map( 'Tests_HtmlApi_WpHtmlProcessor_Token_Scanning::quoted', $attributes ); + $this->assertSame( + array( 'rows', 'cols' ), + $attributes, + 'Should have found only two attributes but found ' . implode( ', ', $attribute_list ) . ' instead.' + ); + + // Note that the leading newline should be removed from the TEXTAREA contents. + $this->assertSame( + "Is > XHTML?\n", + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative TITLE elements are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_title_element() { + $processor = new WP_HTML_Tag_Processor( + << +Is > XHTML? + +HTML + ); + $processor->next_token(); + + $this->assertSame( + 'TITLE', + $processor->get_token_name(), + "Should have found TITLE tag name but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + 'multi-line-title', + $processor->get_attribute( 'class' ), + "Should have found class attribute value 'multi-line-title' but found {$processor->get_attribute( 'rows' )} instead." + ); + + $attributes = $processor->get_attribute_names_with_prefix( '' ); + $attribute_list = array_map( 'Tests_HtmlApi_WpHtmlProcessor_Token_Scanning::quoted', $attributes ); + $this->assertSame( + array( 'class' ), + $attributes, + 'Should have found only one attribute but found ' . implode( ', ', $attribute_list ) . ' instead.' + ); + + $this->assertSame( + "\nIs > XHTML?\n", + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative RAWTEXT elements are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + * + * @dataProvider data_rawtext_elements + * + * @param string $tag_name The name of the RAWTEXT tag to test. + */ + public function test_basic_assertion_rawtext_elements( $tag_name ) { + $processor = new WP_HTML_Tag_Processor( + << +Is > XHTML? + +HTML + ); + $processor->next_token(); + + $this->assertSame( + $tag_name, + $processor->get_token_name(), + "Should have found {$tag_name} tag name but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + 'multi-line-title', + $processor->get_attribute( 'class' ), + "Should have found class attribute value 'multi-line-title' but found {$processor->get_attribute( 'rows' )} instead." + ); + + $attributes = $processor->get_attribute_names_with_prefix( '' ); + $attribute_list = array_map( 'Tests_HtmlApi_WpHtmlProcessor_Token_Scanning::quoted', $attributes ); + $this->assertSame( + array( 'class' ), + $attributes, + 'Should have found only one attribute but found ' . implode( ', ', $attribute_list ) . ' instead.' + ); + + $this->assertSame( + "\nIs > XHTML?\n", + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Data provider. + * + * @return array[]. + */ + public static function data_rawtext_elements() { + return array( + 'IFRAME' => array( 'IFRAME' ), + 'NOEMBED' => array( 'NOEMBED' ), + 'NOFRAMES' => array( 'NOFRAMES' ), + 'STYLE' => array( 'STYLE' ), + 'XMP' => array( 'XMP' ), + ); + } + + /** + * Ensures that normative CDATA sections are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_cdata_section() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found comment token but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + WP_HTML_Processor::COMMENT_AS_CDATA_LOOKALIKE, + $processor->get_comment_type(), + 'Should have detected a CDATA-like invalid comment.' + ); + + $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 a comment', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative CDATA sections are properly parsed. + * + * @ticket 60406 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_cdata_comment_with_incorrect_closer() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found comment token but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + WP_HTML_Processor::COMMENT_AS_INVALID_HTML, + $processor->get_comment_type(), + 'Should have detected invalid HTML comment.' + ); + + $this->assertSame( + '[CDATA[this is missing a closing square bracket]', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that abruptly-closed CDATA sections are properly parsed as comments. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_abruptly_closed_cdata_section() { + $processor = new WP_HTML_Tag_Processor( ' a comment]]>' ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found a bogus comment but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + WP_HTML_Processor::COMMENT_AS_INVALID_HTML, + $processor->get_comment_type(), + 'Should have detected invalid HTML comment.' + ); + + $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( + '[CDATA[this is ', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + + $processor->next_token(); + + $this->assertSame( + '#text', + $processor->get_token_name(), + "Should have found text node but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + ' a comment]]>', + $processor->get_modifiable_text(), + 'Should have found remaining syntax from abruptly-closed CDATA section.' + ); + } + + /** + * Ensures that normative Processing Instruction nodes are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_processing_instruction() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found comment token but found {$processor->get_token_name()} instead." + ); + + $this->assertSame( + WP_HTML_Processor::COMMENT_AS_PI_NODE_LOOKALIKE, + $processor->get_comment_type(), + 'Should have detected a Processing Instruction-like invalid comment.' + ); + + $this->assertSame( + 'wp-bit', + $processor->get_tag(), + "Should have found PI target as tag name but found {$processor->get_tag()} instead." + ); + + $this->assertNull( + $processor->get_attribute( 'type' ), + 'Should not have been able to query attributes on non-element token.' + ); + + $this->assertSame( + ' {"just": "kidding"}', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that abruptly-closed Processing Instruction nodes are properly parsed as comments. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_abruptly_closed_processing_instruction() { + $processor = new WP_HTML_Tag_Processor( '=5.3.6"?>' ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_type(), + "Should have found bogus comment but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found #comment as name 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( + 'version="', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + + $processor->next_token(); + + $this->assertSame( + '=5.3.6"?>', + $processor->get_modifiable_text(), + 'Should have found remaining syntax from abruptly-closed Processing Instruction.' + ); + } + + /** + * Ensures that common comments are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @dataProvider data_common_comments + * + * @covers WP_HTML_Tag_Processor::next_token + * + * @param string $html Contains the comment in full. + * @param string $text Contains the appropriate modifiable text. + */ + public function test_basic_assertion_common_comments( $html, $text ) { + $processor = new WP_HTML_Tag_Processor( $html ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_type(), + "Should have found comment but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found #comment as name 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( + $text, + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Data provider. + * + * @return array[]. + */ + public static function data_common_comments() { + return array( + 'Shortest comment' => array( '', '' ), + 'Short comment' => array( '', '' ), + 'Short comment w/o text' => array( '', '' ), + 'Short comment with text' => array( '', '-' ), + 'PI node without target' => array( '', ' missing?' ), + 'Invalid PI node' => array( '', '/missing/' ), + 'Invalid ! directive' => array( '', 'something else' ), + ); + } + + /** + * Ensures that normative HTML comments are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_html_comment() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#comment', + $processor->get_token_type(), + "Should have found comment but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + '#comment', + $processor->get_token_name(), + "Should have found #comment as name 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( + ' wp:paragraph ', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative DOCTYPE elements are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_doctype() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#doctype', + $processor->get_token_type(), + "Should have found DOCTYPE but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + 'html', + $processor->get_token_name(), + "Should have found 'html' as name 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( + ' html', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative presumptuous tag closers (empty closers) are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_presumptuous_tag() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#presumptuous-tag', + $processor->get_token_type(), + "Should have found presumptuous tag but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + '#presumptuous-tag', + $processor->get_token_name(), + "Should have found #presumptuous-tag as name 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( + '', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that normative funky comments are properly parsed. + * + * @ticket 60170 + * + * @since 6.5.0 + * + * @covers WP_HTML_Tag_Processor::next_token + */ + public function test_basic_assertion_funky_comment() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + '#funky-comment', + $processor->get_token_type(), + "Should have found funky comment but found {$processor->get_token_type()} instead." + ); + + $this->assertSame( + '#funky-comment', + $processor->get_token_name(), + "Should have found #funky-comment as name 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( + '%url', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Test helper that wraps a string in double quotes. + * + * @param string $s The string to wrap in double-quotes. + * @return string The string wrapped in double-quotes. + */ + private static function quoted( $s ) { + return "\"$s\""; + } +} diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php index 8a681d2cb0042..5375a2fca0ebf 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php @@ -22,9 +22,9 @@ class Tests_HtmlApi_WpHtmlTagProcessor extends WP_UnitTestCase { * @covers WP_HTML_Tag_Processor::get_tag */ public function test_get_tag_returns_null_before_finding_tags() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertNull( $p->get_tag(), 'Calling get_tag() without selecting a tag did not return null' ); + $this->assertNull( $processor->get_tag(), 'Calling get_tag() without selecting a tag did not return null' ); } /** @@ -33,10 +33,10 @@ public function test_get_tag_returns_null_before_finding_tags() { * @covers WP_HTML_Tag_Processor::get_tag */ public function test_get_tag_returns_null_when_not_in_open_tag() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - $this->assertNull( $p->get_tag(), 'Accessing a non-existing tag did not return null' ); + $this->assertFalse( $processor->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); + $this->assertNull( $processor->get_tag(), 'Accessing a non-existing tag did not return null' ); } /** @@ -45,10 +45,10 @@ public function test_get_tag_returns_null_when_not_in_open_tag() { * @covers WP_HTML_Tag_Processor::get_tag */ public function test_get_tag_returns_open_tag_name() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertSame( 'DIV', $p->get_tag(), 'Accessing an existing tag name did not return "div"' ); + $this->assertTrue( $processor->next_tag( 'div' ), 'Querying an existing tag did not return true' ); + $this->assertSame( 'DIV', $processor->get_tag(), 'Accessing an existing tag name did not return "div"' ); } /** @@ -62,13 +62,13 @@ public function test_get_tag_returns_open_tag_name() { * @param bool $flag_is_set Whether the input HTML's first tag contains the self-closing flag. */ public function test_has_self_closing_flag_matches_input_html( $html, $flag_is_set ) { - $p = new WP_HTML_Tag_Processor( $html ); - $p->next_tag( array( 'tag_closers' => 'visit' ) ); + $processor = new WP_HTML_Tag_Processor( $html ); + $processor->next_tag( array( 'tag_closers' => 'visit' ) ); if ( $flag_is_set ) { - $this->assertTrue( $p->has_self_closing_flag(), 'Did not find the self-closing tag when it was present.' ); + $this->assertTrue( $processor->has_self_closing_flag(), 'Did not find the self-closing tag when it was present.' ); } else { - $this->assertFalse( $p->has_self_closing_flag(), 'Found the self-closing tag when it was absent.' ); + $this->assertFalse( $processor->has_self_closing_flag(), 'Found the self-closing tag when it was absent.' ); } } @@ -77,7 +77,7 @@ public function test_has_self_closing_flag_matches_input_html( $html, $flag_is_s * * @return array[] */ - public function data_has_self_closing_flag() { + public static function data_has_self_closing_flag() { return array( // These should not have a self-closer, and will leave an element un-closed if it's assumed they are self-closing. 'Self-closing flag on non-void HTML element' => array( '
    ', true ), @@ -107,9 +107,9 @@ public function data_has_self_closing_flag() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_null_before_finding_tags() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertNull( $p->get_attribute( 'class' ), 'Accessing an attribute without selecting a tag did not return null' ); + $this->assertNull( $processor->get_attribute( 'class' ), 'Accessing an attribute without selecting a tag did not return null' ); } /** @@ -118,10 +118,10 @@ public function test_get_attribute_returns_null_before_finding_tags() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_null_when_not_in_open_tag() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - $this->assertNull( $p->get_attribute( 'class' ), 'Accessing an attribute of a non-existing tag did not return null' ); + $this->assertFalse( $processor->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); + $this->assertNull( $processor->get_attribute( 'class' ), 'Accessing an attribute of a non-existing tag did not return null' ); } /** @@ -130,11 +130,11 @@ public function test_get_attribute_returns_null_when_not_in_open_tag() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_null_when_in_closing_tag() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Querying an existing closing tag did not return true' ); - $this->assertNull( $p->get_attribute( 'class' ), 'Accessing an attribute of a closing tag did not return null' ); + $this->assertTrue( $processor->next_tag( 'div' ), 'Querying an existing tag did not return true' ); + $this->assertTrue( $processor->next_tag( array( 'tag_closers' => 'visit' ) ), 'Querying an existing closing tag did not return true' ); + $this->assertNull( $processor->get_attribute( 'class' ), 'Accessing an attribute of a closing tag did not return null' ); } /** @@ -143,10 +143,10 @@ public function test_get_attribute_returns_null_when_in_closing_tag() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_null_when_attribute_missing() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertNull( $p->get_attribute( 'test-id' ), 'Accessing a non-existing attribute did not return null' ); + $this->assertTrue( $processor->next_tag( 'div' ), 'Querying an existing tag did not return true' ); + $this->assertNull( $processor->get_attribute( 'test-id' ), 'Accessing a non-existing attribute did not return null' ); } /** @@ -155,10 +155,10 @@ public function test_get_attribute_returns_null_when_attribute_missing() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_attribute_value() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' ); - $this->assertSame( 'test', $p->get_attribute( 'class' ), 'Accessing a class="test" attribute value did not return "test"' ); + $this->assertTrue( $processor->next_tag( 'div' ), 'Querying an existing tag did not return true' ); + $this->assertSame( 'test', $processor->get_attribute( 'class' ), 'Accessing a class="test" attribute value did not return "test"' ); } /** @@ -167,10 +167,10 @@ public function test_get_attribute_returns_attribute_value() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_true_for_boolean_attribute() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertTrue( $p->next_tag( array( 'class_name' => 'test' ) ), 'Querying an existing tag did not return true' ); - $this->assertTrue( $p->get_attribute( 'enabled' ), 'Accessing a boolean "enabled" attribute value did not return true' ); + $this->assertTrue( $processor->next_tag( array( 'class_name' => 'test' ) ), 'Querying an existing tag did not return true' ); + $this->assertTrue( $processor->get_attribute( 'enabled' ), 'Accessing a boolean "enabled" attribute value did not return true' ); } /** @@ -179,12 +179,12 @@ public function test_get_attribute_returns_true_for_boolean_attribute() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_string_for_truthy_attributes() { - $p = new WP_HTML_Tag_Processor( '' ); + $processor = new WP_HTML_Tag_Processor( '' ); - $this->assertTrue( $p->next_tag(), 'Querying an existing tag did not return true' ); - $this->assertSame( 'enabled', $p->get_attribute( 'enabled' ), 'Accessing a boolean "enabled" attribute value did not return true' ); - $this->assertSame( '1', $p->get_attribute( 'checked' ), 'Accessing a checked=1 attribute value did not return "1"' ); - $this->assertSame( 'true', $p->get_attribute( 'hidden' ), 'Accessing a hidden="true" attribute value did not return "true"' ); + $this->assertTrue( $processor->next_tag(), 'Querying an existing tag did not return true' ); + $this->assertSame( 'enabled', $processor->get_attribute( 'enabled' ), 'Accessing a boolean "enabled" attribute value did not return true' ); + $this->assertSame( '1', $processor->get_attribute( 'checked' ), 'Accessing a checked=1 attribute value did not return "1"' ); + $this->assertSame( 'true', $processor->get_attribute( 'hidden' ), 'Accessing a hidden="true" attribute value did not return "true"' ); } /** @@ -193,10 +193,10 @@ public function test_get_attribute_returns_string_for_truthy_attributes() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_decodes_html_character_references() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); - $this->assertSame( 'the "grande" is < 32oz†', $p->get_attribute( 'id' ), 'HTML Attribute value was returned without decoding character references' ); + $this->assertSame( 'the "grande" is < 32oz†', $processor->get_attribute( 'id' ), 'HTML Attribute value was returned without decoding character references' ); } /** @@ -205,14 +205,14 @@ public function test_get_attribute_decodes_html_character_references() { * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_attributes_parser_treats_slash_as_attribute_separator() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $this->assertTrue( $p->next_tag(), 'Querying an existing tag did not return true' ); - $this->assertTrue( $p->get_attribute( 'a' ), 'Accessing an existing attribute did not return true' ); - $this->assertTrue( $p->get_attribute( 'b' ), 'Accessing an existing attribute did not return true' ); - $this->assertTrue( $p->get_attribute( 'c' ), 'Accessing an existing attribute did not return true' ); - $this->assertTrue( $p->get_attribute( 'd' ), 'Accessing an existing attribute did not return true' ); - $this->assertSame( 'test', $p->get_attribute( 'e' ), 'Accessing an existing e="test" did not return "test"' ); + $this->assertTrue( $processor->next_tag(), 'Querying an existing tag did not return true' ); + $this->assertTrue( $processor->get_attribute( 'a' ), 'Accessing an existing attribute did not return true' ); + $this->assertTrue( $processor->get_attribute( 'b' ), 'Accessing an existing attribute did not return true' ); + $this->assertTrue( $processor->get_attribute( 'c' ), 'Accessing an existing attribute did not return true' ); + $this->assertTrue( $processor->get_attribute( 'd' ), 'Accessing an existing attribute did not return true' ); + $this->assertSame( 'test', $processor->get_attribute( 'e' ), 'Accessing an existing e="test" did not return "test"' ); } /** @@ -225,12 +225,12 @@ public function test_attributes_parser_treats_slash_as_attribute_separator() { * @param string $attribute_name Name of data-enabled attribute with case variations. */ public function test_get_attribute_is_case_insensitive_for_attributes_with_values( $attribute_name ) { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); $this->assertSame( 'true', - $p->get_attribute( $attribute_name ), + $processor->get_attribute( $attribute_name ), 'Accessing an attribute by a differently-cased name did not return its value' ); } @@ -245,11 +245,11 @@ public function test_get_attribute_is_case_insensitive_for_attributes_with_value * @param string $attribute_name Name of data-enabled attribute with case variations. */ public function test_attributes_parser_is_case_insensitive_for_attributes_without_values( $attribute_name ) { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); $this->assertTrue( - $p->get_attribute( $attribute_name ), + $processor->get_attribute( $attribute_name ), 'Accessing an attribute by a differently-cased name did not return its value' ); } @@ -259,7 +259,7 @@ public function test_attributes_parser_is_case_insensitive_for_attributes_withou * * @return array[]. */ - public function data_attribute_name_case_variants() { + public static function data_attribute_name_case_variants() { return array( array( 'DATA-enabled' ), array( 'data-enabled' ), @@ -274,11 +274,11 @@ public function data_attribute_name_case_variants() { * @covers WP_HTML_Tag_Processor::remove_attribute */ public function test_remove_attribute_is_case_insensitive() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->remove_attribute( 'data-enabled' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->remove_attribute( 'data-enabled' ); - $this->assertSame( '
    Test
    ', $p->get_updated_html(), 'A case-insensitive remove_attribute call did not remove the attribute' ); + $this->assertSame( '
    Test
    ', $processor->get_updated_html(), 'A case-insensitive remove_attribute call did not remove the attribute' ); } /** @@ -287,11 +287,11 @@ public function test_remove_attribute_is_case_insensitive() { * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_is_case_insensitive() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->set_attribute( 'data-enabled', 'abc' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->set_attribute( 'data-enabled', 'abc' ); - $this->assertSame( '
    Test
    ', $p->get_updated_html(), 'A case-insensitive set_attribute call did not update the existing attribute' ); + $this->assertSame( '
    Test
    ', $processor->get_updated_html(), 'A case-insensitive set_attribute call did not update the existing attribute' ); } /** @@ -300,9 +300,9 @@ public function test_set_attribute_is_case_insensitive() { * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix */ public function test_get_attribute_names_with_prefix_returns_null_before_finding_tags() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); $this->assertNull( - $p->get_attribute_names_with_prefix( 'data-' ), + $processor->get_attribute_names_with_prefix( 'data-' ), 'Accessing attributes by their prefix did not return null when no tag was selected' ); } @@ -313,9 +313,9 @@ public function test_get_attribute_names_with_prefix_returns_null_before_finding * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix */ public function test_get_attribute_names_with_prefix_returns_null_when_not_in_open_tag() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag( 'p' ); - $this->assertNull( $p->get_attribute_names_with_prefix( 'data-' ), 'Accessing attributes of a non-existing tag did not return null' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag( 'p' ); + $this->assertNull( $processor->get_attribute_names_with_prefix( 'data-' ), 'Accessing attributes of a non-existing tag did not return null' ); } /** @@ -324,11 +324,11 @@ public function test_get_attribute_names_with_prefix_returns_null_when_not_in_op * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix */ public function test_get_attribute_names_with_prefix_returns_null_when_in_closing_tag() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag( 'div' ); - $p->next_tag( array( 'tag_closers' => 'visit' ) ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag( 'div' ); + $processor->next_tag( array( 'tag_closers' => 'visit' ) ); - $this->assertNull( $p->get_attribute_names_with_prefix( 'data-' ), 'Accessing attributes of a closing tag did not return null' ); + $this->assertNull( $processor->get_attribute_names_with_prefix( 'data-' ), 'Accessing attributes of a closing tag did not return null' ); } /** @@ -337,10 +337,10 @@ public function test_get_attribute_names_with_prefix_returns_null_when_in_closin * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix */ public function test_get_attribute_names_with_prefix_returns_empty_array_when_no_attributes_present() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag( 'div' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag( 'div' ); - $this->assertSame( array(), $p->get_attribute_names_with_prefix( 'data-' ), 'Accessing the attributes on a tag without any did not return an empty array' ); + $this->assertSame( array(), $processor->get_attribute_names_with_prefix( 'data-' ), 'Accessing the attributes on a tag without any did not return an empty array' ); } /** @@ -349,12 +349,12 @@ public function test_get_attribute_names_with_prefix_returns_empty_array_when_no * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix */ public function test_get_attribute_names_with_prefix_returns_matching_attribute_names_in_lowercase() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); $this->assertSame( array( 'data-enabled', 'data-test-id' ), - $p->get_attribute_names_with_prefix( 'data-' ), + $processor->get_attribute_names_with_prefix( 'data-' ), 'Accessing attributes by their prefix did not return their lowercase names' ); } @@ -365,18 +365,18 @@ public function test_get_attribute_names_with_prefix_returns_matching_attribute_ * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix */ public function test_get_attribute_names_with_prefix_returns_attribute_added_by_set_attribute() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->set_attribute( 'data-test-id', '14' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->set_attribute( 'data-test-id', '14' ); $this->assertSame( '
    Test
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Updated HTML doesn't include attribute added via set_attribute" ); $this->assertSame( array( 'data-test-id', 'data-foo' ), - $p->get_attribute_names_with_prefix( 'data-' ), + $processor->get_attribute_names_with_prefix( 'data-' ), "Accessing attribute names doesn't find attribute added via set_attribute" ); } @@ -387,17 +387,17 @@ public function test_get_attribute_names_with_prefix_returns_attribute_added_by_ * @covers WP_HTML_Tag_Processor::__toString */ public function test_to_string_returns_updated_html() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); - $p->next_tag(); - $p->set_attribute( 'id', 'div-id-1' ); - $p->add_class( 'new_class_1' ); + $processor->next_tag(); + $processor->set_attribute( 'id', 'div-id-1' ); + $processor->add_class( 'new_class_1' ); $this->assertSame( - $p->get_updated_html(), - (string) $p, + $processor->get_updated_html(), + (string) $processor, 'get_updated_html() returned a different value than __toString()' ); } @@ -408,35 +408,35 @@ public function test_to_string_returns_updated_html() { * @covers WP_HTML_Tag_Processor::get_updated_html */ public function test_get_updated_html_applies_the_updates_so_far_and_keeps_the_processor_on_the_current_tag() { - $p = new WP_HTML_Tag_Processor( '
    Test
    ' ); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( '
    Test
    ' ); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); - $p->next_tag(); - $p->set_attribute( 'id', 'div-id-1' ); - $p->add_class( 'new_class_1' ); + $processor->next_tag(); + $processor->set_attribute( 'id', 'div-id-1' ); + $processor->add_class( 'new_class_1' ); $this->assertSame( '
    Test
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Calling get_updated_html after updating the attributes of the second tag returned different HTML than expected' ); - $p->set_attribute( 'id', 'div-id-2' ); - $p->add_class( 'new_class_2' ); + $processor->set_attribute( 'id', 'div-id-2' ); + $processor->add_class( 'new_class_2' ); $this->assertSame( '
    Test
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Calling get_updated_html after updating the attributes of the second tag for the second time returned different HTML than expected' ); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); $this->assertSame( '
    Test
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Calling get_updated_html after removing the id attribute of the third tag returned different HTML than expected' ); } @@ -447,11 +447,11 @@ public function test_get_updated_html_applies_the_updates_so_far_and_keeps_the_p * @covers WP_HTML_Tag_Processor::get_updated_html */ public function test_get_updated_html_without_updating_any_attributes_returns_the_original_html() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); $this->assertSame( self::HTML_SIMPLE, - $p->get_updated_html(), + $processor->get_updated_html(), 'Casting WP_HTML_Tag_Processor to a string without performing any updates did not return the initial HTML snippet' ); } @@ -463,17 +463,17 @@ public function test_get_updated_html_without_updating_any_attributes_returns_th * @ticket 58160 */ public function test_get_updated_html_applies_updates_to_content_after_seeking_to_before_parsed_bytes() { - $p = new WP_HTML_Tag_Processor( '
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); - $p->set_attribute( 'wonky', true ); - $p->next_tag(); - $p->set_bookmark( 'here' ); + $processor->next_tag(); + $processor->set_attribute( 'wonky', true ); + $processor->next_tag(); + $processor->set_bookmark( 'here' ); - $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $p->seek( 'here' ); + $processor->next_tag( array( 'tag_closers' => 'visit' ) ); + $processor->seek( 'here' ); - $this->assertSame( '
    ', $p->get_updated_html() ); + $this->assertSame( '
    ', $processor->get_updated_html() ); } /** @@ -482,9 +482,9 @@ public function test_get_updated_html_applies_updates_to_content_after_seeking_t * @covers WP_HTML_Tag_Processor::next_tag */ public function test_next_tag_with_no_arguments_should_find_the_next_existing_tag() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertTrue( $p->next_tag(), 'Querying an existing tag did not return true' ); + $this->assertTrue( $processor->next_tag(), 'Querying an existing tag did not return true' ); } /** @@ -493,9 +493,9 @@ public function test_next_tag_with_no_arguments_should_find_the_next_existing_ta * @covers WP_HTML_Tag_Processor::next_tag */ public function test_next_tag_should_return_false_for_a_non_existing_tag() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); + $this->assertFalse( $processor->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); } /** @@ -504,9 +504,9 @@ public function test_next_tag_should_return_false_for_a_non_existing_tag() { * @covers WP_HTML_Tag_Processor::next_tag */ public function test_next_tag_matches_decoded_class_names() { - $p = new WP_HTML_Tag_Processor( '
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $this->assertTrue( $p->next_tag( array( 'class_name' => '' ) ), 'Failed to find tag with HTML-encoded class name.' ); + $this->assertTrue( $processor->next_tag( array( 'class_name' => '' ) ), 'Failed to find tag with HTML-encoded class name.' ); } /** @@ -517,22 +517,22 @@ public function test_next_tag_matches_decoded_class_names() { * @covers WP_HTML_Tag_Processor::is_tag_closer */ public function test_next_tag_should_stop_on_closers_only_when_requested() { - $p = new WP_HTML_Tag_Processor( '
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $this->assertTrue( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Did not find desired tag opener' ); - $this->assertFalse( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Visited an unwanted tag, a tag closer' ); + $this->assertTrue( $processor->next_tag( array( 'tag_name' => 'div' ) ), 'Did not find desired tag opener' ); + $this->assertFalse( $processor->next_tag( array( 'tag_name' => 'div' ) ), 'Visited an unwanted tag, a tag closer' ); - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag( + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit', ) ); - $this->assertFalse( $p->is_tag_closer(), 'Indicated a tag opener is a tag closer' ); + $this->assertFalse( $processor->is_tag_closer(), 'Indicated a tag opener is a tag closer' ); $this->assertTrue( - $p->next_tag( + $processor->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit', @@ -540,11 +540,11 @@ public function test_next_tag_should_stop_on_closers_only_when_requested() { ), 'Did not stop at desired tag closer' ); - $this->assertTrue( $p->is_tag_closer(), 'Indicated a tag closer is a tag opener' ); + $this->assertTrue( $processor->is_tag_closer(), 'Indicated a tag closer is a tag opener' ); - $p = new WP_HTML_Tag_Processor( '
    ' ); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), "Did not find a tag opener when tag_closers was set to 'visit'" ); - $this->assertFalse( $p->next_tag( array( 'tag_closers' => 'visit' ) ), "Found a closer where there wasn't one" ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $this->assertTrue( $processor->next_tag( array( 'tag_closers' => 'visit' ) ), "Did not find a tag opener when tag_closers was set to 'visit'" ); + $this->assertFalse( $processor->next_tag( array( 'tag_closers' => 'visit' ) ), "Found a closer where there wasn't one" ); } /** @@ -554,32 +554,38 @@ public function test_next_tag_should_stop_on_closers_only_when_requested() { * @covers WP_HTML_Tag_Processor::is_tag_closer */ public function test_next_tag_should_stop_on_rcdata_and_script_tag_closers_when_requested() { - $p = new WP_HTML_Tag_Processor( '' ); + $processor = new WP_HTML_Tag_Processor( '' ); - $p->next_tag(); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer' ); - $this->assertTrue( $p->is_tag_closer(), 'Indicated a ' ); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer when there was no tag opener' ); + $processor = new WP_HTML_Tag_Processor( 'abc' ); + $this->assertTrue( $processor->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer when there was no tag opener' ); - $p = new WP_HTML_Tag_Processor( '' ); + $processor = new WP_HTML_Tag_Processor( '' ); - $p->next_tag(); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer' ); - $this->assertTrue( $p->is_tag_closer(), 'Indicated a ' ); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer when there was no tag opener' ); + $processor = new WP_HTML_Tag_Processor( 'abc' ); + $this->assertTrue( $processor->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer when there was no tag opener' ); - $p = new WP_HTML_Tag_Processor( 'abc' ); + $processor = new WP_HTML_Tag_Processor( 'abc' ); - $p->next_tag(); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer' ); - $this->assertTrue( $p->is_tag_closer(), 'Indicated a tag opener is a tag closer' ); + $processor->next_tag(); + $this->assertFalse( + $processor->next_tag( array( 'tag_closers' => 'visit' ) ), + 'Should not have found closing TITLE when closing an opener.' + ); - $p = new WP_HTML_Tag_Processor( 'abc' ); - $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer when there was no tag opener' ); + $processor = new WP_HTML_Tag_Processor( 'abc' ); + $this->assertTrue( $processor->next_tag( array( 'tag_closers' => 'visit' ) ), 'Did not find the tag closer when there was no tag opener' ); } /** @@ -614,16 +620,16 @@ public function test_internal_pointer_returns_to_original_spot_after_inserting_c * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_on_a_non_existing_tag_does_not_change_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); - $this->assertFalse( $p->next_tag( 'div' ), 'Querying a non-existing tag did not return false' ); + $this->assertFalse( $processor->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); + $this->assertFalse( $processor->next_tag( 'div' ), 'Querying a non-existing tag did not return false' ); - $p->set_attribute( 'id', 'primary' ); + $processor->set_attribute( 'id', 'primary' ); $this->assertSame( self::HTML_SIMPLE, - $p->get_updated_html(), + $processor->get_updated_html(), 'Calling get_updated_html after updating a non-existing tag returned an HTML that was different from the original HTML' ); } @@ -637,31 +643,31 @@ public function test_set_attribute_on_a_non_existing_tag_does_not_change_the_mar * @covers WP_HTML_Tag_Processor::remove_class */ public function test_attribute_ops_on_tag_closer_do_not_change_the_markup() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag( + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit', ) ); - $this->assertFalse( $p->is_tag_closer(), 'Skipped tag opener' ); + $this->assertFalse( $processor->is_tag_closer(), 'Skipped tag opener' ); - $p->next_tag( + $processor->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit', ) ); - $this->assertTrue( $p->is_tag_closer(), 'Skipped tag closer' ); - $this->assertFalse( $p->set_attribute( 'id', 'test' ), "Allowed setting an attribute on a tag closer when it shouldn't have" ); - $this->assertFalse( $p->remove_attribute( 'invalid-id' ), "Allowed removing an attribute on a tag closer when it shouldn't have" ); - $this->assertFalse( $p->add_class( 'sneaky' ), "Allowed adding a class on a tag closer when it shouldn't have" ); - $this->assertFalse( $p->remove_class( 'not-appearing-in-this-test' ), "Allowed removing a class on a tag closer when it shouldn't have" ); + $this->assertTrue( $processor->is_tag_closer(), 'Skipped tag closer' ); + $this->assertFalse( $processor->set_attribute( 'id', 'test' ), "Allowed setting an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $processor->remove_attribute( 'invalid-id' ), "Allowed removing an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $processor->add_class( 'sneaky' ), "Allowed adding a class on a tag closer when it shouldn't have" ); + $this->assertFalse( $processor->remove_class( 'not-appearing-in-this-test' ), "Allowed removing a class on a tag closer when it shouldn't have" ); $this->assertSame( '
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Calling get_updated_html after updating a non-existing tag returned an HTML that was different from the original HTML' ); } @@ -670,9 +676,9 @@ public function test_attribute_ops_on_tag_closer_do_not_change_the_markup() { * Passing a double quote inside of an attribute value could lead to an XSS attack as follows: * * ```php - * $p = new WP_HTML_Tag_Processor( '
    ' ); - * $p->next_tag(); - * $p->set_attribute('class', '" onclick="alert'); + * $processor = new WP_HTML_Tag_Processor( '
    ' ); + * $processor->next_tag(); + * $processor->set_attribute('class', '" onclick="alert'); * echo $p; * //
    * ``` @@ -691,9 +697,9 @@ public function test_attribute_ops_on_tag_closer_do_not_change_the_markup() { * @param string $attribute_value A value with potential XSS exploit. */ public function test_set_attribute_prevents_xss( $attribute_value ) { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); - $p->set_attribute( 'test', $attribute_value ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); + $processor->set_attribute( 'test', $attribute_value ); /* * Testing the escaping is hard using tools that properly parse @@ -707,7 +713,7 @@ public function test_set_attribute_prevents_xss( $attribute_value ) { * content and (b) looks at the raw values. */ $match = null; - preg_match( '~^
    $~', $p->get_updated_html(), $match ); + preg_match( '~^
    $~', $processor->get_updated_html(), $match ); list( , $actual_value ) = $match; $this->assertSame( '"' . esc_attr( $attribute_value ) . '"', $actual_value, 'Entities were not properly escaped in the attribute value' ); @@ -718,7 +724,7 @@ public function test_set_attribute_prevents_xss( $attribute_value ) { * * @return string[][]. */ - public function data_set_attribute_prevents_xss() { + public static function data_set_attribute_prevents_xss() { return array( array( '"' ), array( '"' ), @@ -738,18 +744,18 @@ public function data_set_attribute_prevents_xss() { * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_with_a_non_existing_attribute_adds_a_new_attribute_to_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'test-attribute', 'test-value' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'test-attribute', 'test-value' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include attribute added via set_attribute()' ); $this->assertSame( 'test-value', - $p->get_attribute( 'test-attribute' ), + $processor->get_attribute( 'test-attribute' ), 'get_attribute() (called after get_updated_html()) did not return attribute added via set_attribute()' ); } @@ -760,18 +766,18 @@ public function test_set_attribute_with_a_non_existing_attribute_adds_a_new_attr * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_updated_values_before_they_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'test-attribute', 'test-value' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'test-attribute', 'test-value' ); $this->assertSame( 'test-value', - $p->get_attribute( 'test-attribute' ), + $processor->get_attribute( 'test-attribute' ), 'get_attribute() (called before get_updated_html()) did not return attribute added via set_attribute()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include attribute added via set_attribute()' ); } @@ -782,18 +788,18 @@ public function test_get_attribute_returns_updated_values_before_they_are_applie * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_returns_updated_values_before_they_are_applied_with_different_name_casing() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'test-ATTribute', 'test-value' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'test-ATTribute', 'test-value' ); $this->assertSame( 'test-value', - $p->get_attribute( 'test-attribute' ), + $processor->get_attribute( 'test-attribute' ), 'get_attribute() (called before get_updated_html()) did not return attribute added via set_attribute()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include attribute added via set_attribute()' ); } @@ -804,18 +810,18 @@ public function test_get_attribute_returns_updated_values_before_they_are_applie * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_added_class_names_before_they_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->add_class( 'my-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->add_class( 'my-class' ); $this->assertSame( 'my-class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), 'get_attribute() (called before get_updated_html()) did not return class name added via add_class()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include class name added via add_class()' ); } @@ -826,26 +832,26 @@ public function test_get_attribute_reflects_added_class_names_before_they_are_ap * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_added_class_names_before_they_are_applied_and_retains_classes_from_previous_add_class_calls() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->add_class( 'my-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->add_class( 'my-class' ); $this->assertSame( 'my-class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), 'get_attribute() (called before get_updated_html()) did not return class name added via add_class()' ); - $p->add_class( 'my-other-class' ); + $processor->add_class( 'my-other-class' ); $this->assertSame( 'my-class my-other-class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), 'get_attribute() (called before get_updated_html()) did not return class names added via subsequent add_class() calls' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include class names added via subsequent add_class() calls' ); } @@ -856,17 +862,17 @@ public function test_get_attribute_reflects_added_class_names_before_they_are_ap * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_removed_attribute_before_it_is_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); $this->assertNull( - $p->get_attribute( 'id' ), + $processor->get_attribute( 'id' ), 'get_attribute() (called before get_updated_html()) returned attribute that was removed by remove_attribute()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes attribute that was removed by remove_attribute()' ); } @@ -877,18 +883,18 @@ public function test_get_attribute_reflects_removed_attribute_before_it_is_appli * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_adding_and_then_removing_an_attribute_before_those_updates_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'test-attribute', 'test-value' ); - $p->remove_attribute( 'test-attribute' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'test-attribute', 'test-value' ); + $processor->remove_attribute( 'test-attribute' ); $this->assertNull( - $p->get_attribute( 'test-attribute' ), + $processor->get_attribute( 'test-attribute' ), 'get_attribute() (called before get_updated_html()) returned attribute that was added via set_attribute() and then removed by remove_attribute()' ); $this->assertSame( self::HTML_SIMPLE, - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes attribute that was added via set_attribute() and then removed by remove_attribute()' ); } @@ -899,18 +905,18 @@ public function test_get_attribute_reflects_adding_and_then_removing_an_attribut * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_setting_and_then_removing_an_existing_attribute_before_those_updates_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'id', 'test-value' ); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'id', 'test-value' ); + $processor->remove_attribute( 'id' ); $this->assertNull( - $p->get_attribute( 'id' ), + $processor->get_attribute( 'id' ), 'get_attribute() (called before get_updated_html()) returned attribute that was overwritten by set_attribute() and then removed by remove_attribute()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes attribute that was overwritten by set_attribute() and then removed by remove_attribute()' ); } @@ -921,18 +927,18 @@ public function test_get_attribute_reflects_setting_and_then_removing_an_existin * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_removed_class_names_before_they_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->remove_class( 'with-border' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->remove_class( 'with-border' ); $this->assertSame( 'main', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), 'get_attribute() (called before get_updated_html()) returned the wrong attribute after calling remove_attribute()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes wrong attribute after calling remove_attribute()' ); } @@ -943,19 +949,19 @@ public function test_get_attribute_reflects_removed_class_names_before_they_are_ * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_setting_and_then_removing_a_class_name_before_those_updates_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $p->remove_class( 'foo-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'foo-class' ); + $processor->remove_class( 'foo-class' ); $this->assertSame( 'main with-border', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), 'get_attribute() (called before get_updated_html()) returned class name that was added via add_class() and then removed by remove_class()' ); $this->assertSame( self::HTML_WITH_CLASSES, - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes class that was added via add_class() and then removed by remove_class()' ); } @@ -966,19 +972,19 @@ public function test_get_attribute_reflects_setting_and_then_removing_a_class_na * @covers WP_HTML_Tag_Processor::get_attribute */ public function test_get_attribute_reflects_duplicating_and_then_removing_an_existing_class_name_before_those_updates_are_applied() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'with-border' ); - $p->remove_class( 'with-border' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'with-border' ); + $processor->remove_class( 'with-border' ); $this->assertSame( 'main', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), 'get_attribute() (called before get_updated_html()) returned class name that was duplicated via add_class() and then removed by remove_class()' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes class that was duplicated via add_class() and then removed by remove_class()' ); } @@ -992,13 +998,13 @@ public function test_get_attribute_reflects_duplicating_and_then_removing_an_exi * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_update_first_attribute_when_duplicated_attributes_exist() { - $p = new WP_HTML_Tag_Processor( '
    Text
    ' ); - $p->next_tag(); - $p->set_attribute( 'id', 'updated-id' ); + $processor = new WP_HTML_Tag_Processor( '
    Text
    ' ); + $processor->next_tag(); + $processor->set_attribute( 'id', 'updated-id' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Proper (first) appearance of attribute was not updated when duplicates exist' ); } @@ -1009,12 +1015,12 @@ public function test_update_first_attribute_when_duplicated_attributes_exist() { * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_with_an_existing_attribute_name_updates_its_value_in_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'id', 'new-id' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'id', 'new-id' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Existing attribute was not updated' ); } @@ -1028,13 +1034,13 @@ public function test_set_attribute_with_an_existing_attribute_name_updates_its_v * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_with_case_variants_updates_only_the_original_first_copy() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); - $p->set_attribute( 'DATA-ENABLED', 'canary' ); - $p->set_attribute( 'Data-Enabled', 'canary' ); - $p->set_attribute( 'dATa-EnABled', 'canary' ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); + $processor->set_attribute( 'DATA-ENABLED', 'canary' ); + $processor->set_attribute( 'Data-Enabled', 'canary' ); + $processor->set_attribute( 'dATa-EnABled', 'canary' ); - $this->assertSame( '
    ', strtolower( $p->get_updated_html() ) ); + $this->assertSame( '
    ', strtolower( $processor->get_updated_html() ) ); } /** @@ -1044,14 +1050,14 @@ public function test_set_attribute_with_case_variants_updates_only_the_original_ * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_next_tag_and_set_attribute_in_a_loop_update_all_tags_in_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - while ( $p->next_tag() ) { - $p->set_attribute( 'data-foo', 'bar' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + while ( $processor->next_tag() ) { + $processor->set_attribute( 'data-foo', 'bar' ); } $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Not all tags were updated when looping with next_tag() and set_attribute()' ); } @@ -1067,13 +1073,13 @@ public function test_next_tag_and_set_attribute_in_a_loop_update_all_tags_in_the * @covers WP_HTML_Tag_Processor::remove_attribute */ public function test_remove_first_when_duplicated_attribute() { - $p = new WP_HTML_Tag_Processor( '
    Text
    ' ); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( '
    Text
    ' ); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); $this->assertStringNotContainsString( 'update-me', - $p->get_updated_html(), + $processor->get_updated_html(), 'First attribute (when duplicates exist) was not removed' ); } @@ -1084,13 +1090,13 @@ public function test_remove_first_when_duplicated_attribute() { * @covers WP_HTML_Tag_Processor::remove_attribute */ public function test_remove_attribute_with_an_existing_attribute_name_removes_it_from_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Attribute was not removed' ); } @@ -1105,16 +1111,16 @@ public function test_remove_attribute_with_an_existing_attribute_name_removes_it * @dataProvider data_html_with_duplicated_attributes */ public function test_remove_attribute_with_duplicated_attributes_removes_all_of_them( $html_with_duplicate_attributes, $attribute_to_remove ) { - $p = new WP_HTML_Tag_Processor( $html_with_duplicate_attributes ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( $html_with_duplicate_attributes ); + $processor->next_tag(); - $p->remove_attribute( $attribute_to_remove ); - $this->assertNull( $p->get_attribute( $attribute_to_remove ), 'Failed to remove all copies of an attribute when duplicated in modified source.' ); + $processor->remove_attribute( $attribute_to_remove ); + $this->assertNull( $processor->get_attribute( $attribute_to_remove ), 'Failed to remove all copies of an attribute when duplicated in modified source.' ); // Recreate a tag processor with the updated HTML after removing the attribute. - $p = new WP_HTML_Tag_Processor( $p->get_updated_html() ); - $p->next_tag(); - $this->assertNull( $p->get_attribute( $attribute_to_remove ), 'Failed to remove all copies of duplicated attributes when getting updated HTML.' ); + $processor = new WP_HTML_Tag_Processor( $processor->get_updated_html() ); + $processor->next_tag(); + $this->assertNull( $processor->get_attribute( $attribute_to_remove ), 'Failed to remove all copies of duplicated attributes when getting updated HTML.' ); } /** @@ -1125,12 +1131,12 @@ public function test_remove_attribute_with_duplicated_attributes_removes_all_of_ * @covers WP_HTML_Tag_Processor::remove_attribute */ public function test_previous_duplicated_attributes_are_not_removed_on_successive_tag_removal() { - $p = new WP_HTML_Tag_Processor( '' ); - $p->next_tag(); - $p->next_tag(); - $p->remove_attribute( 'id' ); + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_tag(); + $processor->next_tag(); + $processor->remove_attribute( 'id' ); - $this->assertSame( '', $p->get_updated_html() ); + $this->assertSame( '', $processor->get_updated_html() ); } /** @@ -1140,7 +1146,7 @@ public function test_previous_duplicated_attributes_are_not_removed_on_successiv * * @return array[]. */ - public function data_html_with_duplicated_attributes() { + public static function data_html_with_duplicated_attributes() { return array( 'Double attributes' => array( '
    ', 'id' ), 'Triple attributes' => array( '
    ', 'id' ), @@ -1156,13 +1162,13 @@ public function data_html_with_duplicated_attributes() { * @covers WP_HTML_Tag_Processor::remove_attribute */ public function test_remove_attribute_with_a_non_existing_attribute_name_does_not_change_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_attribute( 'no-such-attribute' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->remove_attribute( 'no-such-attribute' ); $this->assertSame( self::HTML_SIMPLE, - $p->get_updated_html(), + $processor->get_updated_html(), 'Content was changed when attempting to remove an attribute that did not exist' ); } @@ -1173,18 +1179,18 @@ public function test_remove_attribute_with_a_non_existing_attribute_name_does_no * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_creates_a_class_attribute_when_there_is_none() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->add_class( 'foo-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->add_class( 'foo-class' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include class name added via add_class()' ); $this->assertSame( 'foo-class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) did not return class name added via add_class()" ); } @@ -1195,19 +1201,19 @@ public function test_add_class_creates_a_class_attribute_when_there_is_none() { * @covers WP_HTML_Tag_Processor::add_class */ public function test_calling_add_class_twice_creates_a_class_attribute_with_both_class_names_when_there_is_no_class_attribute() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $p->add_class( 'bar-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->add_class( 'foo-class' ); + $processor->add_class( 'bar-class' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not include class names added via subsequent add_class() calls' ); $this->assertSame( 'foo-class bar-class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) did not return class names added via subsequent add_class() calls" ); } @@ -1218,17 +1224,17 @@ public function test_calling_add_class_twice_creates_a_class_attribute_with_both * @covers WP_HTML_Tag_Processor::remove_class */ public function test_remove_class_does_not_change_the_markup_when_there_is_no_class_attribute() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->remove_class( 'foo-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->remove_class( 'foo-class' ); $this->assertSame( self::HTML_SIMPLE, - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML includes class name that was removed by remove_class()' ); $this->assertNull( - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) did not return null for class name that was removed by remove_class()" ); } @@ -1239,19 +1245,19 @@ public function test_remove_class_does_not_change_the_markup_when_there_is_no_cl * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_appends_class_names_to_the_existing_class_attribute_when_one_already_exists() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'foo-class' ); - $p->add_class( 'bar-class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'foo-class' ); + $processor->add_class( 'bar-class' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect class names added to existing class attribute via subsequent add_class() calls' ); $this->assertSame( 'main with-border foo-class bar-class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect class names added to existing class attribute via subsequent add_class() calls" ); } @@ -1262,18 +1268,18 @@ public function test_add_class_appends_class_names_to_the_existing_class_attribu * @covers WP_HTML_Tag_Processor::remove_class */ public function test_remove_class_removes_a_single_class_from_the_class_attribute_when_one_exists() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->remove_class( 'main' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->remove_class( 'main' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect class name removed from existing class attribute via remove_class()' ); $this->assertSame( ' with-border', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect class name removed from existing class attribute via remove_class()" ); } @@ -1284,18 +1290,18 @@ public function test_remove_class_removes_a_single_class_from_the_class_attribut * @covers WP_HTML_Tag_Processor::remove_class */ public function test_calling_remove_class_with_all_listed_class_names_removes_the_existing_class_attribute_from_the_markup() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->remove_class( 'main' ); - $p->remove_class( 'with-border' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->remove_class( 'main' ); + $processor->remove_class( 'with-border' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect class attribute removed via subesequent remove_class() calls' ); $this->assertNull( - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) did not return null for class attribute removed via subesequent remove_class() calls" ); } @@ -1306,18 +1312,18 @@ public function test_calling_remove_class_with_all_listed_class_names_removes_th * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_does_not_add_duplicate_class_names() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'with-border' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'with-border' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect deduplicated class name added via add_class()' ); $this->assertSame( 'main with-border', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect deduplicated class name added via add_class()" ); } @@ -1328,18 +1334,18 @@ public function test_add_class_does_not_add_duplicate_class_names() { * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_preserves_class_name_order_when_a_duplicate_class_name_is_added() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'main' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'main' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect class name order after adding duplicated class name via add_class()' ); $this->assertSame( 'main with-border', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect class name order after adding duplicated class name added via add_class()" ); } @@ -1350,20 +1356,20 @@ public function test_add_class_preserves_class_name_order_when_a_duplicate_class * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_when_there_is_a_class_attribute_with_excessive_whitespaces() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    Text
    ' ); - $p->next_tag(); - $p->add_class( 'foo-class' ); + $processor->next_tag(); + $processor->add_class( 'foo-class' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $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', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect existing excessive whitespace after adding class name via add_class()" ); } @@ -1374,20 +1380,20 @@ public function test_add_class_when_there_is_a_class_attribute_with_excessive_wh * @covers WP_HTML_Tag_Processor::remove_class */ public function test_remove_class_preserves_whitespaces_when_there_is_a_class_attribute_with_excessive_whitespaces() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    Text
    ' ); - $p->next_tag(); - $p->remove_class( 'with-border' ); + $processor->next_tag(); + $processor->remove_class( 'with-border' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect existing excessive whitespace after removing class name via remove_class()' ); $this->assertSame( ' main', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect existing excessive whitespace after removing class name via removing_class()" ); } @@ -1398,19 +1404,19 @@ public function test_remove_class_preserves_whitespaces_when_there_is_a_class_at * @covers WP_HTML_Tag_Processor::remove_class */ public function test_removing_all_classes_removes_the_existing_class_attribute_from_the_markup_even_when_excessive_whitespaces_are_present() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    Text
    ' ); - $p->next_tag(); - $p->remove_class( 'main' ); - $p->remove_class( 'with-border' ); + $processor->next_tag(); + $processor->remove_class( 'main' ); + $processor->remove_class( 'with-border' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Updated HTML does not reflect removed class attribute after removing all class names via remove_class()' ); $this->assertNull( - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute( 'class' ) did not return null after removing all class names via remove_class()" ); } @@ -1429,33 +1435,33 @@ public function test_removing_all_classes_removes_the_existing_class_attribute_f * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_takes_priority_over_add_class() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'add_class' ); - $p->set_attribute( 'class', 'set_attribute' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'add_class' ); + $processor->set_attribute( 'class', 'set_attribute' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Calling get_updated_html after updating first tag's attributes did not return the expected HTML" ); $this->assertSame( 'set_attribute', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "Calling get_attribute after updating first tag's attributes did not return the expected class name" ); - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->set_attribute( 'class', 'set_attribute' ); - $p->add_class( 'add_class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->set_attribute( 'class', 'set_attribute' ); + $processor->add_class( 'add_class' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Calling get_updated_html after updating first tag's attributes did not return the expected HTML" ); $this->assertSame( 'set_attribute add_class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "Calling get_attribute after updating first tag's attributes did not return the expected class name" ); } @@ -1476,33 +1482,33 @@ public function test_set_attribute_takes_priority_over_add_class() { * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_takes_priority_over_add_class_even_before_updating() { - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->add_class( 'add_class' ); - $p->set_attribute( 'class', 'set_attribute' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->add_class( 'add_class' ); + $processor->set_attribute( 'class', 'set_attribute' ); $this->assertSame( 'set_attribute', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "Calling get_attribute after updating first tag's attributes did not return the expected class name" ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Calling get_updated_html after updating first tag's attributes did not return the expected HTML" ); - $p = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); - $p->next_tag(); - $p->set_attribute( 'class', 'set_attribute' ); - $p->add_class( 'add_class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_WITH_CLASSES ); + $processor->next_tag(); + $processor->set_attribute( 'class', 'set_attribute' ); + $processor->add_class( 'add_class' ); $this->assertSame( 'set_attribute add_class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "Calling get_attribute after updating first tag's attributes did not return the expected class name" ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Calling get_updated_html after updating first tag's attributes did not return the expected HTML" ); } @@ -1513,18 +1519,18 @@ public function test_set_attribute_takes_priority_over_add_class_even_before_upd * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_overrides_boolean_class_attribute() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'class', true ); - $p->add_class( 'add_class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'class', true ); + $processor->add_class( 'add_class' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Updated HTML doesn't reflect class added via add_class that was originally set as boolean attribute" ); $this->assertSame( 'add_class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute (called after get_updated_html()) doesn't reflect class added via add_class that was originally set as boolean attribute" ); } @@ -1535,18 +1541,18 @@ public function test_add_class_overrides_boolean_class_attribute() { * @covers WP_HTML_Tag_Processor::add_class */ public function test_add_class_overrides_boolean_class_attribute_even_before_updating() { - $p = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); - $p->next_tag(); - $p->set_attribute( 'class', true ); - $p->add_class( 'add_class' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_SIMPLE ); + $processor->next_tag(); + $processor->set_attribute( 'class', true ); + $processor->add_class( 'add_class' ); $this->assertSame( 'add_class', - $p->get_attribute( 'class' ), + $processor->get_attribute( 'class' ), "get_attribute (called before get_updated_html()) doesn't reflect class added via add_class that was originally set as boolean attribute" ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), "Updated HTML doesn't reflect class added via add_class that was originally set as boolean attribute" ); } @@ -1607,12 +1613,12 @@ public function test_advanced_use_case() {
    HTML; - $p = new WP_HTML_Tag_Processor( $input ); - $this->assertTrue( $p->next_tag( 'div' ), 'Did not find first DIV tag in input.' ); - $p->set_attribute( 'data-details', '{ "key": "value" }' ); - $p->add_class( 'is-processed' ); + $processor = new WP_HTML_Tag_Processor( $input ); + $this->assertTrue( $processor->next_tag( 'div' ), 'Did not find first DIV tag in input.' ); + $processor->set_attribute( 'data-details', '{ "key": "value" }' ); + $processor->add_class( 'is-processed' ); $this->assertTrue( - $p->next_tag( + $processor->next_tag( array( 'tag_name' => 'div', 'class_name' => 'BtnGroup', @@ -1620,11 +1626,11 @@ public function test_advanced_use_case() { ), 'Did not find the first BtnGroup DIV tag' ); - $p->remove_class( 'BtnGroup' ); - $p->add_class( 'button-group' ); - $p->add_class( 'Another-Mixed-Case' ); + $processor->remove_class( 'BtnGroup' ); + $processor->add_class( 'button-group' ); + $processor->add_class( 'Another-Mixed-Case' ); $this->assertTrue( - $p->next_tag( + $processor->next_tag( array( 'tag_name' => 'div', 'class_name' => 'BtnGroup', @@ -1632,11 +1638,11 @@ public function test_advanced_use_case() { ), 'Did not find the second BtnGroup DIV tag' ); - $p->remove_class( 'BtnGroup' ); - $p->add_class( 'button-group' ); - $p->add_class( 'Another-Mixed-Case' ); + $processor->remove_class( 'BtnGroup' ); + $processor->add_class( 'button-group' ); + $processor->add_class( 'Another-Mixed-Case' ); $this->assertTrue( - $p->next_tag( + $processor->next_tag( array( 'tag_name' => 'button', 'class_name' => 'btn', @@ -1645,10 +1651,10 @@ public function test_advanced_use_case() { ), 'Did not find third BUTTON tag with "btn" CSS class' ); - $p->remove_attribute( 'class' ); - $this->assertFalse( $p->next_tag( 'non-existent' ), "Found a {$p->get_tag()} tag when none should have been found." ); - $p->set_attribute( 'class', 'test' ); - $this->assertSame( $expected_output, $p->get_updated_html(), 'Calling get_updated_html after updating the attributes did not return the expected HTML' ); + $processor->remove_attribute( 'class' ); + $this->assertFalse( $processor->next_tag( 'non-existent' ), "Found a {$processor->get_tag()} tag when none should have been found." ); + $processor->set_attribute( 'class', 'test' ); + $this->assertSame( $expected_output, $processor->get_updated_html(), 'Calling get_updated_html after updating the attributes did not return the expected HTML' ); } /** @@ -1657,26 +1663,26 @@ public function test_advanced_use_case() { * @covers WP_HTML_Tag_Processor::next_tag */ public function test_correctly_parses_html_attributes_wrapped_in_single_quotation_marks() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    Text
    ' ); - $p->next_tag( + $processor->next_tag( array( 'tag_name' => 'div', 'id' => 'first', ) ); - $p->remove_attribute( 'id' ); - $p->next_tag( + $processor->remove_attribute( 'id' ); + $processor->next_tag( array( 'tag_name' => 'span', 'id' => 'second', ) ); - $p->set_attribute( 'id', 'single-quote' ); + $processor->set_attribute( 'id', 'single-quote' ); $this->assertSame( '
    Text
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not remove single-quoted attribute' ); } @@ -1687,14 +1693,14 @@ public function test_correctly_parses_html_attributes_wrapped_in_single_quotatio * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_set_attribute_with_value_equal_to_true_adds_a_boolean_html_attribute_with_implicit_value() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', true ); + $processor->next_tag( 'input' ); + $processor->set_attribute( 'checked', true ); $this->assertSame( '
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not add "checked" as an expected boolean attribute' ); } @@ -1705,14 +1711,14 @@ public function test_set_attribute_with_value_equal_to_true_adds_a_boolean_html_ * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_setting_a_boolean_attribute_to_false_removes_it_from_the_markup() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', false ); + $processor->next_tag( 'input' ); + $processor->set_attribute( 'checked', false ); $this->assertSame( '
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not remove boolean attribute when set to false' ); } @@ -1724,12 +1730,12 @@ public function test_setting_a_boolean_attribute_to_false_removes_it_from_the_ma */ public function test_setting_a_missing_attribute_to_false_does_not_change_the_markup() { $html_input = '
    '; - $p = new WP_HTML_Tag_Processor( $html_input ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', false ); + $processor = new WP_HTML_Tag_Processor( $html_input ); + $processor->next_tag( 'input' ); + $processor->set_attribute( 'checked', false ); $this->assertSame( $html_input, - $p->get_updated_html(), + $processor->get_updated_html(), 'Changed the markup unexpectedly when setting a non-existing attribute to false' ); } @@ -1740,14 +1746,14 @@ public function test_setting_a_missing_attribute_to_false_does_not_change_the_ma * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_setting_a_boolean_attribute_to_a_string_value_adds_explicit_value_to_the_markup() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag( 'input' ); - $p->set_attribute( 'checked', 'checked' ); + $processor->next_tag( 'input' ); + $processor->set_attribute( 'checked', 'checked' ); $this->assertSame( '
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not add string value to existing boolean attribute' ); } @@ -1759,18 +1765,18 @@ public function test_setting_a_boolean_attribute_to_a_string_value_adds_explicit * @covers WP_HTML_Tag_Processor::paused_at_incomplete_token */ public function test_unclosed_script_tag_should_not_cause_an_infinite_loop() { - $p = new WP_HTML_Tag_Processor( '
    ', @@ -1860,11 +1866,11 @@ public function data_next_tag_ignores_script_tag_contents() { * element after contain the "start" and "end" CSS classes. */ public function test_next_tag_ignores_invalid_first_character_of_tag_name_comments( $html_with_markers ) { - $p = new WP_HTML_Tag_Processor( $html_with_markers ); - $p->next_tag( array( 'class_name' => 'start' ) ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( $html_with_markers ); + $processor->next_tag( array( 'class_name' => 'start' ) ); + $processor->next_tag(); - $this->assertSame( 'end', $p->get_attribute( 'class' ) ); + $this->assertSame( 'end', $processor->get_attribute( 'class' ) ); } /** @@ -1872,7 +1878,7 @@ public function test_next_tag_ignores_invalid_first_character_of_tag_name_commen * * @return array[] */ - public function data_next_tag_ignores_invalid_first_character_of_tag_name_comments() { + public static function data_next_tag_ignores_invalid_first_character_of_tag_name_comments() { return array( 'Invalid tag openers as normal text' => array( '
    • I <3 when outflow > inflow
    ', @@ -1899,11 +1905,11 @@ public function data_next_tag_ignores_invalid_first_character_of_tag_name_commen * @param string $rcdata_tag RCDATA tag. */ public function test_next_tag_ignores_contents_of_rcdata_tag( $rcdata_then_div, $rcdata_tag ) { - $p = new WP_HTML_Tag_Processor( $rcdata_then_div ); - $p->next_tag(); - $this->assertSame( $rcdata_tag, $p->get_tag(), "The first found tag was not '$rcdata_tag'" ); - $p->next_tag(); - $this->assertSame( 'DIV', $p->get_tag(), "The second found tag was not 'div'" ); + $processor = new WP_HTML_Tag_Processor( $rcdata_then_div ); + $processor->next_tag(); + $this->assertSame( $rcdata_tag, $processor->get_tag(), "The first found tag was not '$rcdata_tag'" ); + $processor->next_tag(); + $this->assertSame( 'DIV', $processor->get_tag(), "The second found tag was not 'div'" ); } /** @@ -1911,7 +1917,7 @@ public function test_next_tag_ignores_contents_of_rcdata_tag( $rcdata_then_div, * * @return array[] */ - public function data_next_tag_ignores_contents_of_rcdata_tag() { + public static function data_next_tag_ignores_contents_of_rcdata_tag() { return array( 'simple textarea' => array( 'rcdata_then_div' => '
    ', @@ -1958,10 +1964,10 @@ public function data_next_tag_ignores_contents_of_rcdata_tag() { * @covers WP_HTML_Tag_Processor::next_tag */ public function test_processes_inside_of_noscript_elements() { - $p = new WP_HTML_Tag_Processor( '
    ' ); + $processor = new WP_HTML_Tag_Processor( '
    ' ); - $this->assertTrue( $p->next_tag( 'INPUT' ), 'Failed to find INPUT element inside NOSCRIPT element.' ); - $this->assertTrue( $p->next_tag( 'DIV' ), 'Failed to find DIV element after NOSCRIPT element.' ); + $this->assertTrue( $processor->next_tag( 'INPUT' ), 'Failed to find INPUT element inside NOSCRIPT element.' ); + $this->assertTrue( $processor->next_tag( 'DIV' ), 'Failed to find DIV element after NOSCRIPT element.' ); } /** @@ -1990,7 +1996,7 @@ public function test_next_tag_ignores_contents_of_rawtext_tags( $rawtext_element * * @return array[]. */ - public function data_next_tag_ignores_contents_of_rawtext_tags() { + public static function data_next_tag_ignores_contents_of_rawtext_tags() { return array( 'IFRAME' => array( '
    ' ), 'NOEMBED' => array( '<p></p>
    ' ), @@ -2006,11 +2012,11 @@ public function data_next_tag_ignores_contents_of_rawtext_tags() { * @covers WP_HTML_Tag_Processor::class_list */ public function test_class_list_empty_when_missing_class() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); $found_classes = false; - foreach ( $p->class_list() as $class ) { + foreach ( $processor->class_list() as $class ) { $found_classes = true; } @@ -2023,11 +2029,11 @@ public function test_class_list_empty_when_missing_class() { * @covers WP_HTML_Tag_Processor::class_list */ public function test_class_list_empty_when_class_is_boolean() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); $found_classes = false; - foreach ( $p->class_list() as $class ) { + foreach ( $processor->class_list() as $class ) { $found_classes = true; } @@ -2040,11 +2046,11 @@ public function test_class_list_empty_when_class_is_boolean() { * @covers WP_HTML_Tag_Processor::class_list */ public function test_class_list_empty_when_class_is_empty() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); $found_classes = false; - foreach ( $p->class_list() as $class ) { + foreach ( $processor->class_list() as $class ) { $found_classes = true; } @@ -2057,11 +2063,11 @@ public function test_class_list_empty_when_class_is_empty() { * @covers WP_HTML_Tag_Processor::class_list */ public function test_class_list_visits_each_class_in_order() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); $found_classes = array(); - foreach ( $p->class_list() as $class ) { + foreach ( $processor->class_list() as $class ) { $found_classes[] = $class; } @@ -2074,11 +2080,11 @@ public function test_class_list_visits_each_class_in_order() { * @covers WP_HTML_Tag_Processor::class_list */ public function test_class_list_decodes_class_names() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); $found_classes = array(); - foreach ( $p->class_list() as $class ) { + foreach ( $processor->class_list() as $class ) { $found_classes[] = $class; } @@ -2091,11 +2097,11 @@ public function test_class_list_decodes_class_names() { * @covers WP_HTML_Tag_Processor::class_list */ public function test_class_list_visits_unique_class_names_only_once() { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( '
    ' ); + $processor->next_tag(); $found_classes = array(); - foreach ( $p->class_list() as $class ) { + foreach ( $processor->class_list() as $class ) { $found_classes[] = $class; } @@ -2114,13 +2120,13 @@ public function test_class_list_visits_unique_class_names_only_once() { * @param bool $has_class Whether the sought class exists in the given HTML. */ public function test_has_class_handles_expected_class_name_variations( $html, $sought_class, $has_class ) { - $p = new WP_HTML_Tag_Processor( $html ); - $p->next_tag(); + $processor = new WP_HTML_Tag_Processor( $html ); + $processor->next_tag(); if ( $has_class ) { - $this->assertTrue( $p->has_class( $sought_class ), "Failed to find expected class {$sought_class}." ); + $this->assertTrue( $processor->has_class( $sought_class ), "Failed to find expected class {$sought_class}." ); } else { - $this->assertFalse( $p->has_class( $sought_class ), "Found class {$sought_class} when it doesn't exist." ); + $this->assertFalse( $processor->has_class( $sought_class ), "Found class {$sought_class} when it doesn't exist." ); } } @@ -2129,7 +2135,7 @@ public function test_has_class_handles_expected_class_name_variations( $html, $s * * @return array[] */ - public function data_html_with_variations_of_class_values_and_sought_class_names() { + public static function data_html_with_variations_of_class_values_and_sought_class_names() { return array( 'Tag without any classes' => array( '
    ', 'foo', false ), 'Tag with boolean class' => array( '', 'foo', false ), @@ -2153,16 +2159,16 @@ public function data_html_with_variations_of_class_values_and_sought_class_names * */ public function test_allows_incorrectly_closed_comments() { - $p = new WP_HTML_Tag_Processor( '-->' ); + $processor = new WP_HTML_Tag_Processor( '-->' ); - $p->next_tag(); - $this->assertSame( 'before', $p->get_attribute( 'id' ), 'Did not find starting tag.' ); + $processor->next_tag(); + $this->assertSame( 'before', $processor->get_attribute( 'id' ), 'Did not find starting tag.' ); - $p->next_tag(); - $this->assertSame( 'after', $p->get_attribute( 'id' ), 'Did not properly close improperly-closed comment.' ); + $processor->next_tag(); + $this->assertSame( 'after', $processor->get_attribute( 'id' ), 'Did not properly close improperly-closed comment.' ); - $p->next_tag(); - $this->assertSame( 'final', $p->get_attribute( 'id' ), 'Did not skip over unopened comment-closer.' ); + $processor->next_tag(); + $this->assertSame( 'final', $processor->get_attribute( 'id' ), 'Did not skip over unopened comment-closer.' ); } /** @@ -2178,15 +2184,15 @@ public function test_allows_incorrectly_closed_comments() { * @param string $html_ending_before_comment_close HTML with opened comments that aren't closed. */ public function test_documents_may_end_with_unclosed_comment( $html_ending_before_comment_close ) { - $p = new WP_HTML_Tag_Processor( $html_ending_before_comment_close ); + $processor = new WP_HTML_Tag_Processor( $html_ending_before_comment_close ); $this->assertFalse( - $p->next_tag(), - "Should not have found any tag, but found {$p->get_tag()}." + $processor->next_tag(), + "Should not have found any tag, but found {$processor->get_tag()}." ); $this->assertTrue( - $p->paused_at_incomplete_token(), + $processor->paused_at_incomplete_token(), "Should have indicated that the parser found an incomplete token but didn't." ); } @@ -2196,7 +2202,7 @@ public function test_documents_may_end_with_unclosed_comment( $html_ending_befor * * @return array[] */ - public function data_html_with_unclosed_comments() { + public static function data_html_with_unclosed_comments() { return array( 'Shortest open valid comment' => array( '
    ' ), 'Empty comment with two dashes only, improperly closed' => array( '

    ' ), @@ -2256,11 +2262,11 @@ public function data_abruptly_closed_empty_comments() { * @param $input_html HTML with multiple divs, one of which carries the "target" attribute. */ public function test_skips_contents_of_script_and_rcdata_regions( $input_html ) { - $p = new WP_HTML_Tag_Processor( $input_html ); - $p->next_tag( 'div' ); + $processor = new WP_HTML_Tag_Processor( $input_html ); + $processor->next_tag( 'div' ); $this->assertTrue( - $p->get_attribute( 'target' ), + $processor->get_attribute( 'target' ), 'Did not properly skip over script and rcdata regions; incorrectly found tags inside' ); } @@ -2270,7 +2276,7 @@ public function test_skips_contents_of_script_and_rcdata_regions( $input_html ) * * @return array[] */ - public function data_skips_contents_of_script_and_rcdata_regions() { + public static function data_skips_contents_of_script_and_rcdata_regions() { return array( 'Balanced SCRIPT tags' => array( '
    ' ), 'Unexpected SCRIPT closer after DIV' => array( 'console.log("
    ")
    ' ), @@ -2289,16 +2295,16 @@ public function data_skips_contents_of_script_and_rcdata_regions() { * @covers WP_HTML_Tag_Processor::set_attribute */ public function test_can_query_and_update_wrongly_nested_tags() { - $p = new WP_HTML_Tag_Processor( + $processor = new WP_HTML_Tag_Processor( '123

    456789

    ' ); - $p->next_tag( 'span' ); - $p->set_attribute( 'class', 'span-class' ); - $p->next_tag( 'p' ); - $p->set_attribute( 'class', 'p-class' ); + $processor->next_tag( 'span' ); + $processor->set_attribute( 'class', 'span-class' ); + $processor->next_tag( 'p' ); + $processor->set_attribute( 'class', 'p-class' ); $this->assertSame( '123

    456789

    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not find overlapping p tag' ); } @@ -2310,12 +2316,12 @@ public function test_can_query_and_update_wrongly_nested_tags() { * @covers WP_HTML_Tag_Processor::remove_attribute */ public function test_removing_specific_attributes_in_malformed_html() { - $p = new WP_HTML_Tag_Processor( self::HTML_MALFORMED ); - $p->next_tag( 'span' ); - $p->remove_attribute( 'Notifications<' ); + $processor = new WP_HTML_Tag_Processor( self::HTML_MALFORMED ); + $processor->next_tag( 'span' ); + $processor->remove_attribute( 'Notifications<' ); $this->assertSame( '
    Back to notifications
    ', - $p->get_updated_html(), + $processor->get_updated_html(), 'Did not remove "Notifications<" attribute in malformed input' ); } @@ -2351,12 +2357,13 @@ public function test_next_tag_returns_false_when_there_are_no_tags( $html_withou * * @return array[] */ - public function data_html_without_tags() { + public static function data_html_without_tags() { return array( 'DOCTYPE declaration' => array( 'Just some HTML' ), 'No tags' => array( 'this is nothing more than a text node' ), 'Text with comments' => array( 'One comment.' ), 'Empty tag closer' => array( '' ), + 'CDATA as HTML comment' => array( '' ), 'Processing instruction' => array( '' ), 'Combination XML-like' => array( '' ), ); @@ -2375,15 +2382,15 @@ public function data_html_without_tags() { * @param string $incomplete_html HTML text containing some kind of incomplete syntax. */ public function test_next_tag_returns_false_for_incomplete_syntax_elements( $incomplete_html ) { - $p = new WP_HTML_Tag_Processor( $incomplete_html ); + $processor = new WP_HTML_Tag_Processor( $incomplete_html ); $this->assertFalse( - $p->next_tag(), - "Shouldn't have found any tags but found {$p->get_tag()}." + $processor->next_tag(), + "Shouldn't have found any tags but found {$processor->get_tag()}." ); $this->assertTrue( - $p->paused_at_incomplete_token(), + $processor->paused_at_incomplete_token(), "Should have indicated that the parser found an incomplete token but didn't." ); } @@ -2393,7 +2400,7 @@ public function test_next_tag_returns_false_for_incomplete_syntax_elements( $inc * * @return array[] */ - public function data_incomplete_syntax_elements() { + public static function data_incomplete_syntax_elements() { return array( 'Incomplete tag name' => array( ' array( ' array( ' array( ' array( '' => array( '' ), 'Unclosed IFRAME' => array( '