diff --git a/.eslintrc.js b/.eslintrc.js
index a81369b61c5f1..fd7e9183781ea 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -294,7 +294,7 @@ module.exports = {
'packages/e2e-test-utils-playwright/**/*.[tj]s',
],
extends: [
- 'plugin:eslint-plugin-playwright/playwright-test',
+ 'plugin:@wordpress/eslint-plugin/test-playwright',
'plugin:@typescript-eslint/base',
],
parserOptions: {
@@ -308,7 +308,6 @@ module.exports = {
rules: {
'@wordpress/no-global-active-element': 'off',
'@wordpress/no-global-get-selection': 'off',
- 'playwright/no-page-pause': 'error',
'no-restricted-syntax': [
'error',
{
diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml
index 5c4c7b068108f..27a1d7290f2b5 100644
--- a/.github/workflows/performance.yml
+++ b/.github/workflows/performance.yml
@@ -66,13 +66,13 @@ jobs:
- name: Compare performance with base branch
if: github.event_name == 'push'
# The base hash used here need to be a commit that is compatible with the current WP version
- # The current one is 34af5829ac9edb31833167ff6a3b51bea982999c and it needs to be updated every WP major release.
+ # The current one is bd2a881101727b03b0be09382b34841af5a3c03e and it needs to be updated every WP major release.
# It is used as a base comparison point to avoid fluctuation in the performance metrics.
run: |
WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt)
IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION"
WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}"
- ./bin/plugin/cli.js perf $GITHUB_SHA 34af5829ac9edb31833167ff6a3b51bea982999c --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
+ ./bin/plugin/cli.js perf $GITHUB_SHA bd2a881101727b03b0be09382b34841af5a3c03e --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
- name: Compare performance with custom branches
if: github.event_name == 'workflow_dispatch'
@@ -95,7 +95,7 @@ jobs:
CODEHEALTH_PROJECT_TOKEN: ${{ secrets.CODEHEALTH_PROJECT_TOKEN }}
run: |
COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI")
- ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA 34af5829ac9edb31833167ff6a3b51bea982999c $COMMITTED_AT
+ ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA bd2a881101727b03b0be09382b34841af5a3c03e $COMMITTED_AT
- name: Archive debug artifacts (screenshots, HTML snapshots)
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml
index dd1f188e0ae2f..52a6d8f9833f2 100644
--- a/.github/workflows/publish-npm-packages.yml
+++ b/.github/workflows/publish-npm-packages.yml
@@ -30,12 +30,14 @@ jobs:
environment: WordPress packages
steps:
- name: Checkout (for CLI)
+ if: ${{ github.event.inputs.release_type != 'wp' }}
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
path: cli
ref: trunk
- name: Checkout (for publishing)
+ if: ${{ github.event.inputs.release_type != 'wp' }}
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
path: publish
@@ -43,6 +45,14 @@ jobs:
ref: trunk
token: ${{ secrets.GUTENBERG_TOKEN }}
+ - name: Checkout (for publishing WP major version)
+ if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }}
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ with:
+ path: publish
+ ref: wp/${{ github.event.inputs.wp_version }}
+ token: ${{ secrets.GUTENBERG_TOKEN }}
+
- name: Configure git user name and email (for publishing)
run: |
cd publish
@@ -50,11 +60,19 @@ jobs:
git config user.email gutenberg@wordpress.org
- name: Setup Node.js
+ if: ${{ github.event.inputs.release_type != 'wp' }}
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
node-version-file: 'cli/.nvmrc'
registry-url: 'https://registry.npmjs.org'
+ - name: Setup Node.js (for WP major version)
+ if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }}
+ uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+ with:
+ node-version-file: 'publish/.nvmrc'
+ registry-url: 'https://registry.npmjs.org'
+
- name: Publish development packages to npm ("next" dist-tag)
if: ${{ github.event.inputs.release_type == 'development' }}
run: |
@@ -73,7 +91,7 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- - name: Publish packages to npm for WP major ("wp/${{ github.event.inputs.wp_version || 'X.Y' }}" dist-tag)
+ - name: Publish packages to npm for WP major version ("wp/${{ github.event.inputs.wp_version || 'X.Y' }}" dist-tag)
if: ${{ github.event.inputs.release_type == 'wp' && github.event.inputs.wp_version }}
run: |
cd publish
diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs
index baf8d42962f8e..91ded84489e68 100644
--- a/bin/cherry-pick.mjs
+++ b/bin/cherry-pick.mjs
@@ -118,19 +118,10 @@ async function fetchPRs() {
id,
number,
title,
- closed_at,
pull_request,
- } ) ).sort( ( a, b ) => {
- /*
- * `closed_at` and `pull_request.merged_at` are _usually_ the same,
- * but let's prefer the latter if it's available.
- */
- if ( a?.pull_request?.merged_at && b?.pull_request?.merged_at ) {
- return new Date( a?.pull_request?.merged_at ) - new Date( b?.pull_request?.merged_at );
- }
- return new Date( a.closed_at ) - new Date( b.closed_at );
- } );
-
+ } ) )
+ .filter( ( { pull_request } ) => !! pull_request?.merged_at )
+ .sort( ( a, b ) => new Date( a?.pull_request?.merged_at ) - new Date( b?.pull_request?.merged_at ) );
console.log( 'Found the following PRs to cherry-pick (sorted by closed date in ascending order): ' );
PRs.forEach( ( { number, title } ) =>
diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js
index 02cb5f7afdd81..59d77c3fe9610 100644
--- a/bin/plugin/commands/changelog.js
+++ b/bin/plugin/commands/changelog.js
@@ -72,7 +72,7 @@ const LABEL_TYPE_MAPPING = {
'[Type] Project Management': 'Tools',
'[Package] Scripts': 'Tools',
'[Type] Build Tooling': 'Tools',
- 'Automated Testing': 'Tools',
+ '[Type] Automated Testing': 'Tools',
'[Package] Dependency Extraction Webpack Plugin': 'Tools',
'[Type] Code Quality': 'Code Quality',
'[Type] Accessibility (a11y)': 'Accessibility',
@@ -128,7 +128,7 @@ const LABEL_FEATURE_MAPPING = {
'New Block': 'Block Library',
'[Package] E2E Tests': 'Testing',
'[Package] E2E Test Utils': 'Testing',
- 'Automated Testing': 'Testing',
+ '[Type] Automated Testing': 'Testing',
'CSS Styling': 'CSS & Styling',
'developer-docs': 'Documentation',
'[Type] Developer Documentation': 'Documentation',
diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js
index 87ecc6eb89cfc..4cf509764436c 100644
--- a/bin/plugin/commands/packages.js
+++ b/bin/plugin/commands/packages.js
@@ -150,6 +150,7 @@ async function runNpmReleaseBranchSyncStep( pluginReleaseBranch, config ) {
*/
await repo
.raw( 'rm', '-r', '.' )
+ .fetch( 'origin', pluginReleaseBranch, [ '--depth=1' ] )
.raw( 'checkout', `origin/${ pluginReleaseBranch }`, '--', '.' );
const { commit: commitHash } = await repo.commit(
diff --git a/bin/plugin/commands/test/__snapshots__/changelog.js.snap b/bin/plugin/commands/test/__snapshots__/changelog.js.snap
index 571019ea3dca9..9ecb797fa5683 100644
--- a/bin/plugin/commands/test/__snapshots__/changelog.js.snap
+++ b/bin/plugin/commands/test/__snapshots__/changelog.js.snap
@@ -89,6 +89,7 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = `
### Performance
+- Add search performance measure and make other measures more stable. ([33848](https://github.com/WordPress/gutenberg/pull/33848))
- Avoid double parsing the content when loading the editor. ([33727](https://github.com/WordPress/gutenberg/pull/33727))
#### Block Library
@@ -170,7 +171,6 @@ exports[`getChangelog verify that the changelog is properly formatted 1`] = `
- Scripts: Update webpack to v5 (try 2). ([33818](https://github.com/WordPress/gutenberg/pull/33818))
#### Testing
-- Add search performance measure and make other measures more stable. ([33848](https://github.com/WordPress/gutenberg/pull/33848))
- E2E: Block Hierarchy Navigation wait for the column to be highlighted. ([33721](https://github.com/WordPress/gutenberg/pull/33721))
diff --git a/changelog.txt b/changelog.txt
index 0149b4c2b7bf5..0f4f5bcf9bbcf 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,6 +1,277 @@
== Changelog ==
-= 16.5.0-rc.1 =
+= 16.6.0-rc.1 =
+
+## Changelog
+
+### Features
+
+#### Interactivity API
+- Add Slot and Fill directives. ([53958](https://github.com/WordPress/gutenberg/pull/53958))
+- Query block: Client-side pagination. ([53812](https://github.com/WordPress/gutenberg/pull/53812))
+- Update `data-wp-bind` directive logic. ([54003](https://github.com/WordPress/gutenberg/pull/54003))
+
+
+### Enhancements
+
+- Bundle ObserveTyping within the BlockList component. ([53875](https://github.com/WordPress/gutenberg/pull/53875))
+- Default appender: Hide the dashed indicator until ancestor is selected. ([53761](https://github.com/WordPress/gutenberg/pull/53761))
+- Register the block editor keyboard shortcuts automatically when using BlockEditorProvider. ([53910](https://github.com/WordPress/gutenberg/pull/53910))
+- [Commands]: Add toggle list view command in site editor. ([53983](https://github.com/WordPress/gutenberg/pull/53983))
+
+#### Components
+- Bundle SlotFillProvider within BlockEditorProvider. ([53940](https://github.com/WordPress/gutenberg/pull/53940))
+- Make the Popover.Slot optional. ([53889](https://github.com/WordPress/gutenberg/pull/53889))
+- Popover: Update `@floating-ui` to latest version, remove custom fix for iframe positioning and scaling. ([46845](https://github.com/WordPress/gutenberg/pull/46845))
+- `AlignmentMatrixControl`: Replace `act()` with `userEvent`. ([53703](https://github.com/WordPress/gutenberg/pull/53703))
+- `ProgressBar`: Add transition to determinate indicator. ([53877](https://github.com/WordPress/gutenberg/pull/53877))
+
+#### Block Library
+- Blocks: Move bootstrapped block types to Redux state. ([53807](https://github.com/WordPress/gutenberg/pull/53807))
+- Capture toolbars in navigation block. ([53697](https://github.com/WordPress/gutenberg/pull/53697))
+- Content Block: Change placeholder and end-to-end test to refer to Content block. ([53902](https://github.com/WordPress/gutenberg/pull/53902))
+- Make mid size parameter settable for Query Pagination block. ([51216](https://github.com/WordPress/gutenberg/pull/51216))
+
+#### Block Editor
+- Capture toolbars in quote block. ([53699](https://github.com/WordPress/gutenberg/pull/53699))
+- Improve writing flow for lists by capturing list item toolbars. ([53306](https://github.com/WordPress/gutenberg/pull/53306))
+- RichTextValue: Typescript Adjustment. ([54002](https://github.com/WordPress/gutenberg/pull/54002))
+
+#### Typography
+- Font Face: Prepare for merge into Core. ([53858](https://github.com/WordPress/gutenberg/pull/53858))
+- Renames "Fonts Library" to "Font Library". ([53780](https://github.com/WordPress/gutenberg/pull/53780))
+
+#### Post Editor
+- Edit Post: Use hooks instead of HoCs in `TaxonomyPanel`. ([53773](https://github.com/WordPress/gutenberg/pull/53773))
+
+#### List View
+- Add keyboard shortcut for duplicating blocks. ([53559](https://github.com/WordPress/gutenberg/pull/53559))
+
+#### Patterns
+- Add a custom taxonomy for user created patterns. ([53163](https://github.com/WordPress/gutenberg/pull/53163))
+
+
+### New APIs
+
+#### Interactivity API
+- Router with region-based client-side navigation. ([53733](https://github.com/WordPress/gutenberg/pull/53733))
+
+
+### Bug Fixes
+
+- Add missing aria roles for block locking toolbar and menu buttons. ([53734](https://github.com/WordPress/gutenberg/pull/53734))
+- Block Editor: Fix cleanup in the 'useNavModeExit' hook. ([53795](https://github.com/WordPress/gutenberg/pull/53795))
+- Command Palette: Fix crash on block-related commands. ([53923](https://github.com/WordPress/gutenberg/pull/53923))
+- Date: Add relative time translations for moment.js. ([53931](https://github.com/WordPress/gutenberg/pull/53931))
+- Date: Update translation domains for strings to be translatable. ([53995](https://github.com/WordPress/gutenberg/pull/53995))
+- Iframe: Set character encoding to utf-8. ([53519](https://github.com/WordPress/gutenberg/pull/53519))
+- Replace horizontal ellipsis icon with vertical ellipsis icon. ([52731](https://github.com/WordPress/gutenberg/pull/52731))
+- Toggle Distraction free mode mode based on compatibility. ([54030](https://github.com/WordPress/gutenberg/pull/54030))
+- Warning: Introduce `SCRIPT_DEBUG` to make the package compatible with webpack 5. ([50122](https://github.com/WordPress/gutenberg/pull/50122))
+- [Commands]: Fix `move to` command condition for registering. ([54049](https://github.com/WordPress/gutenberg/pull/54049))
+- [Commands]: Fix block editor commands availability. ([53994](https://github.com/WordPress/gutenberg/pull/53994))
+- [Format library]: Fix `language` popover position. ([53841](https://github.com/WordPress/gutenberg/pull/53841))
+
+#### Block Library
+- Add init.js module for the Footnotes block. ([53763](https://github.com/WordPress/gutenberg/pull/53763))
+- Adding center align css for social icon issue. ([43120](https://github.com/WordPress/gutenberg/pull/43120))
+- Cover block: Fix exception when adding video background. ([53961](https://github.com/WordPress/gutenberg/pull/53961))
+- List View: Allow replacing template part when a block isn't selected. ([53757](https://github.com/WordPress/gutenberg/pull/53757))
+- Post Navigation Link: Remove unnecessary space between arrows and label. ([53572](https://github.com/WordPress/gutenberg/pull/53572))
+- Search block: Fix width input field. ([53952](https://github.com/WordPress/gutenberg/pull/53952))
+- Simplify check for no posts in query-no-results block. ([53772](https://github.com/WordPress/gutenberg/pull/53772))
+- Site Logo: Remove line-height for the anchor element. ([53909](https://github.com/WordPress/gutenberg/pull/53909))
+
+#### Components
+- Always render the fallback Popover anchor within the Popover's parent element. ([53982](https://github.com/WordPress/gutenberg/pull/53982))
+- Fix the cleanup method for SandBox. ([53796](https://github.com/WordPress/gutenberg/pull/53796))
+- PaletteEdit: Fix component height. ([54000](https://github.com/WordPress/gutenberg/pull/54000))
+
+#### Post Editor
+- Edit Post: Fix tab border conflicts in the Document Overview panel. ([53711](https://github.com/WordPress/gutenberg/pull/53711))
+- EditPostPreferencesModal: Fix intermittently failing tests. ([53814](https://github.com/WordPress/gutenberg/pull/53814))
+- getInsertionPoint: Fix type check for the state value. ([53793](https://github.com/WordPress/gutenberg/pull/53793))
+
+#### npm Packages
+- Workflow: Run Learn directly from GitHub action when publishing to npm targeting WP core. ([53762](https://github.com/WordPress/gutenberg/pull/53762))
+- Workflows: Fix issues with the npm publishing workflow when using locally. ([53565](https://github.com/WordPress/gutenberg/pull/53565))
+
+#### Themes
+- Command Palette: Proper handling of page/post links in all themes. ([53718](https://github.com/WordPress/gutenberg/pull/53718))
+- Fix query loop bugs by correctly relying on the main query and removing problematic workaround. ([49904](https://github.com/WordPress/gutenberg/pull/49904))
+
+#### Block Editor
+- Fix: Indicator style when block moving mode. ([53972](https://github.com/WordPress/gutenberg/pull/53972))
+
+#### Icons
+- Fix invalid namespaces. ([53955](https://github.com/WordPress/gutenberg/pull/53955))
+
+#### Patterns
+- Disable the preview option button when editing. ([53913](https://github.com/WordPress/gutenberg/pull/53913))
+
+#### Global Styles
+- Gallery: Re-enable block spacing at block level while still hiding in global styles. ([53900](https://github.com/WordPress/gutenberg/pull/53900))
+
+#### Layout
+- BlockList: Ensure element styles (and svg) are always appended at the end of the document. ([53859](https://github.com/WordPress/gutenberg/pull/53859))
+
+#### Interactivity API
+- Add "supports.interactivity" to Image block. ([53850](https://github.com/WordPress/gutenberg/pull/53850))
+
+#### Style Variations
+- Block Styles: Fix misplaced preview popover on RTL site. ([53726](https://github.com/WordPress/gutenberg/pull/53726))
+
+#### List View
+- Recalculate window list when expanded state changes (fix logic for long nested lists). ([53716](https://github.com/WordPress/gutenberg/pull/53716))
+
+#### Widgets Editor
+- Block Widget: Fix content cutoff in the keyboard shortcut modal. ([53638](https://github.com/WordPress/gutenberg/pull/53638))
+
+#### Rich Text
+- Fix cleanup in `useRemoveBrowserShortcuts`. ([52225](https://github.com/WordPress/gutenberg/pull/52225))
+
+
+### Accessibility
+
+- Edit site: Add missing label to post status password protected input field. ([52885](https://github.com/WordPress/gutenberg/pull/52885))
+- [a11y] Fix: Aria-haspop, aria-expanded attributes on the link format button. ([53691](https://github.com/WordPress/gutenberg/pull/53691))
+
+#### Site Editor
+- Add missing aria roles to the 'Create template part' menu item. ([53754](https://github.com/WordPress/gutenberg/pull/53754))
+- Unify the delete button style in the dropdown menu with red. ([52597](https://github.com/WordPress/gutenberg/pull/52597))
+
+#### Block Library
+- Add missing aria roles to the 'Replace template part' menu item. ([53755](https://github.com/WordPress/gutenberg/pull/53755))
+
+#### Patterns
+- Add missing aria roles to the 'Create pattern' menu item. ([53739](https://github.com/WordPress/gutenberg/pull/53739))
+
+#### List View
+- [a11y] Fix: Aria-haspop and aria-expanded attributes on list view button. ([53693](https://github.com/WordPress/gutenberg/pull/53693))
+
+#### Block Editor
+- [a11y] Fix: Aria-haspop and aria-expanded attributes on the inserter button. ([53692](https://github.com/WordPress/gutenberg/pull/53692))
+
+
+### Performance
+
+- Revert "Switch performance tests to Playwright (#52022)". ([53741](https://github.com/WordPress/gutenberg/pull/53741))
+- StartPageOptions: Load and parse patterns only after establishing the need for them. ([53673](https://github.com/WordPress/gutenberg/pull/53673))
+- Switch performance tests to Playwright: Take 2. ([53768](https://github.com/WordPress/gutenberg/pull/53768))
+
+
+### Experiments
+
+#### Block API
+- Auto-inserting blocks: Add block inspector panel. ([52969](https://github.com/WordPress/gutenberg/pull/52969))
+
+
+### Documentation
+
+- Add juanmaguitar as codeowner of /packages/interactivity/docs. ([53845](https://github.com/WordPress/gutenberg/pull/53845))
+- Add new How-to Guide for enqueueing assets in the Editor. ([53828](https://github.com/WordPress/gutenberg/pull/53828))
+- Adds example for useBlockProps hook. ([53646](https://github.com/WordPress/gutenberg/pull/53646))
+- Adds explanatory text to view.js template. ([53870](https://github.com/WordPress/gutenberg/pull/53870))
+- Clarification for `parent` and `ancestor` hierarchical relationships. ([53855](https://github.com/WordPress/gutenberg/pull/53855))
+- Docs: Extend the information about using `render` with `block.json`. ([53973](https://github.com/WordPress/gutenberg/pull/53973))
+- Docs: Remove duplicate sections from FAQ page. ([53830](https://github.com/WordPress/gutenberg/pull/53830))
+- Document the naming convention for `block-library` PHP functions. ([53777](https://github.com/WordPress/gutenberg/pull/53777))
+- Fix 'lerna' links in the release documentation. ([53770](https://github.com/WordPress/gutenberg/pull/53770))
+- Fix typo in code sample for Interactivity API. ([53916](https://github.com/WordPress/gutenberg/pull/53916))
+- MenuItem: Add Storybook stories. ([53613](https://github.com/WordPress/gutenberg/pull/53613))
+- Shortcut: Add Storybook stories. ([53627](https://github.com/WordPress/gutenberg/pull/53627))
+- Storybook: Add back subcomponents to props table. ([53751](https://github.com/WordPress/gutenberg/pull/53751))
+- Storybook: Fix default source visibility. ([53749](https://github.com/WordPress/gutenberg/pull/53749))
+- Storybook: Show main story before description. ([53753](https://github.com/WordPress/gutenberg/pull/53753))
+- Update local instructions on the dev env documentation. ([53924](https://github.com/WordPress/gutenberg/pull/53924))
+- Update the Block Variations API doc. ([53817](https://github.com/WordPress/gutenberg/pull/53817))
+- Update to node 16 and npm 8 in the getting started with code contribution doc. ([53912](https://github.com/WordPress/gutenberg/pull/53912))
+- docs: Fix report-flaky-test link. ([53848](https://github.com/WordPress/gutenberg/pull/53848))
+
+
+### Code Quality
+
+- Components: Update Popover per reviews. ([53907](https://github.com/WordPress/gutenberg/pull/53907))
+- Edit Site: Rename `CanvasSpinner` to `CanvasLoader`. ([53728](https://github.com/WordPress/gutenberg/pull/53728))
+- Enforce valid function names in the packages/block-library/src/*/*.php files. ([53438](https://github.com/WordPress/gutenberg/pull/53438))
+- Fonts Library: Update properties name from snake case to camel case to match the rest of the properties. ([53746](https://github.com/WordPress/gutenberg/pull/53746))
+
+#### Post Editor
+- Editor: Fix the 'useSelect' warning in the 'useIsDirty' hook. ([53759](https://github.com/WordPress/gutenberg/pull/53759))
+- Fix browser console error when changing device preview mode. ([53969](https://github.com/WordPress/gutenberg/pull/53969))
+- Refactor latest content selectors in 'CopyContentMenuItem' components. ([53676](https://github.com/WordPress/gutenberg/pull/53676))
+
+#### Components
+- Remove unnecessary utils. ([53679](https://github.com/WordPress/gutenberg/pull/53679))
+- SlotFill: Refactor ``. ([53272](https://github.com/WordPress/gutenberg/pull/53272))
+- Storybook: Update TypeScript types. ([53748](https://github.com/WordPress/gutenberg/pull/53748))
+
+#### List View
+- Fix warning error when the gallery block has the same image URLs. ([53809](https://github.com/WordPress/gutenberg/pull/53809))
+
+#### Typography
+- Font Face API: Use `gutenberg_get_global_settings` instead of private API. ([53805](https://github.com/WordPress/gutenberg/pull/53805))
+
+
+### Tools
+
+- Try: Change PR label enforcer automation not to work on draft PRs by default. ([53417](https://github.com/WordPress/gutenberg/pull/53417))
+
+#### Testing
+- Attempt to fix intermittent end-to-end test failure. ([53905](https://github.com/WordPress/gutenberg/pull/53905))
+- Fonts Library: Test improvements. ([53702](https://github.com/WordPress/gutenberg/pull/53702))
+- Make fonts test files use Core approach. ([53856](https://github.com/WordPress/gutenberg/pull/53856))
+- Migrate shortcut help end-to-end tests to Playwright. ([53832](https://github.com/WordPress/gutenberg/pull/53832))
+- Relocates Font Face and Fonts Library PHP files into Core's fonts directory. ([53747](https://github.com/WordPress/gutenberg/pull/53747))
+- `ColorPalette`: Refine test query. ([53704](https://github.com/WordPress/gutenberg/pull/53704))
+- end-to-end Playwright Utils: Automatically detect canvas type. ([53744](https://github.com/WordPress/gutenberg/pull/53744))
+- test: Automate mobile editor tests. ([53991](https://github.com/WordPress/gutenberg/pull/53991))
+
+#### Build Tooling
+- Update Jest to latest version, and use optimized JSDOM. ([53736](https://github.com/WordPress/gutenberg/pull/53736))
+
+#### Plugin
+- Backport themes `is_block_theme` collection param from core. ([53846](https://github.com/WordPress/gutenberg/pull/53846))
+
+
+## First time contributors
+
+The following PRs were merged by first time contributors:
+
+- @JEverhart383: Fix typo in code sample for Interactivity API. ([53916](https://github.com/WordPress/gutenberg/pull/53916))
+- @krokodok: Make mid size parameter settable for Query Pagination block. ([51216](https://github.com/WordPress/gutenberg/pull/51216))
+- @mklute101: Update local instructions on the dev env documentation. ([53924](https://github.com/WordPress/gutenberg/pull/53924))
+
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@afercia @andrewserong @anton-vlasenko @bangank36 @brookewp @ciampo @colorful-tones @DAreRodz @dcalhoun @derekblank @ellatrix @felixarntz @geriux @glendaviesnz @gziolo @hellofromtonya @jasmussen @jblz @JEverhart383 @jordesign @jorgefilipecosta @jsnajdr @juanmaguitar @krokodok @luisherranz @Mamaduka @margolisj @matiasbenedetto @mburridge @mirka @mklute101 @mokagio @ndiego @ntsekouras @oandregal @ocean90 @ockham @priethor @ramonjd @richtabor @SiobhyB @Smit2808 @stokesman @t-hamano @torounit @tyxla @walbo @WunderBart @youknowriad
+
+
+= 16.5.1 =
+
+
+
+## Changelog
+
+### Bug Fixes
+
+#### Block Editor
+- Pass entire link value on toggle of setting on Link Preview. ([53949](https://github.com/WordPress/gutenberg/pull/53949))
+
+
+
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@getdave
+
+
+= 16.5.0 =
@@ -13,166 +284,139 @@
### Enhancements
-#### Post Editor
-- Command Palette:
- - Add new block commands. ([52509](https://github.com/WordPress/gutenberg/pull/52509))
- - Add support for registering commands without icons. ([53647](https://github.com/WordPress/gutenberg/pull/53647))
- - Update the `Preview in a new tab` command to reuse the preview target tab when available. ([53242](https://github.com/WordPress/gutenberg/pull/53242))
- - Update command palette styling. ([53117](https://github.com/WordPress/gutenberg/pull/53117))
- - Improve command palette rendering on smaller viewports. ([53661](https://github.com/WordPress/gutenberg/pull/53661))
-- Replace `withPluginContext` in `PluginPostPublishPanel` ([53302](https://github.com/WordPress/gutenberg/pull/53302)) and `PluginPrePublishPanel` ([53304](https://github.com/WordPress/gutenberg/pull/53304)).
-- Replace HoCs with hooks in:
- - `PluginDocumentSettingPanel` ([53290](https://github.com/WordPress/gutenberg/pull/53290))
- - `PostPendingStatusCheck` ([53389](https://github.com/WordPress/gutenberg/pull/53389))
- - `PostPendingStatus` ([53387](https://github.com/WordPress/gutenberg/pull/53387))
-- Top-align Publish row in the post panel. ([53573](https://github.com/WordPress/gutenberg/pull/53573))
-- Dependencies: Bump `remove-accents` to 0.5.0. ([53420](https://github.com/WordPress/gutenberg/pull/53420))
+#### Commands
+- Add block-related commands. ([52509](https://github.com/WordPress/gutenberg/pull/52509))
+- Add support for registering commands without icons. ([53647](https://github.com/WordPress/gutenberg/pull/53647))
+- Update the "Preview in a new tab" command to reuse the preview target tab when available. ([53242](https://github.com/WordPress/gutenberg/pull/53242))
+- Update command palette styling. ([53117](https://github.com/WordPress/gutenberg/pull/53117))
+- Improve command palette rendering on smaller viewports. ([53661](https://github.com/WordPress/gutenberg/pull/53661))
+- Tweak existing commands to establish consistency with command language. ([53496](https://github.com/WordPress/gutenberg/pull/53496))
+- End the command palette description with a period in the keyboard shortcut modal. ([53635](https://github.com/WordPress/gutenberg/pull/53635))
#### Components
- Button: Remove default border from the destructive button. ([53607](https://github.com/WordPress/gutenberg/pull/53607))
- LineHeightControl: Allow for more granular control of decimal places. ([52902](https://github.com/WordPress/gutenberg/pull/52902))
- Snackbar: Design and motion improvements. ([53248](https://github.com/WordPress/gutenberg/pull/53248))
- Modal:
- - Add `headerActions` prop to render buttons in the header. ([53328](https://github.com/WordPress/gutenberg/pull/53328))
- - Nuance outside interactions. ([52994](https://github.com/WordPress/gutenberg/pull/52994))
-- ProgressBar:
- - Use gray 300 for track color. ([53349](https://github.com/WordPress/gutenberg/pull/53349))
- - Use the theme system accent for indicator color. ([53347](https://github.com/WordPress/gutenberg/pull/53347))
- - Use theme accent color variable. ([53632](https://github.com/WordPress/gutenberg/pull/53632))
-- Expose `Theme` via private APIs. ([53262](https://github.com/WordPress/gutenberg/pull/53262))
-- Move accent colors to theme context. ([53631](https://github.com/WordPress/gutenberg/pull/53631))
+ - Add `headerActions` prop to enable buttons or other elements to be injected in the header. ([53328](https://github.com/WordPress/gutenberg/pull/53328))
+ - Enhance overlay interactions, enabling outside interactions without dismissal. ([52994](https://github.com/WordPress/gutenberg/pull/52994))
+- ProgressBar: Update colors, including gray 300 for track color ([53349](https://github.com/WordPress/gutenberg/pull/53349)), theme system accent for indicator color ([53347](https://github.com/WordPress/gutenberg/pull/53347)), and the theme accent color variable. ([53632](https://github.com/WordPress/gutenberg/pull/53632)).
#### Block Library
-- Details block: Add accordion and toggle keywords. ([53501](https://github.com/WordPress/gutenberg/pull/53501))
- Column block:
- - Add stretch alignment. ([53325](https://github.com/WordPress/gutenberg/pull/53325))
- - Exit on enter. ([53311](https://github.com/WordPress/gutenberg/pull/53311))
+ - Add a `stretch` option to block's vertical alignment options. ([53325](https://github.com/WordPress/gutenberg/pull/53325))
+ - Exit upon pressing enter in an empty paragraph at the end of the block. ([53311](https://github.com/WordPress/gutenberg/pull/53311))
- Classic block: Increase dimensions of modal and allow toggling fullscreen. ([53449](https://github.com/WordPress/gutenberg/pull/53449))
-- File block: Add spacing support. ([45107](https://github.com/WordPress/gutenberg/pull/45107))
-- Footnotes block: Add typography, dimensions, and border block supports. ([53044](https://github.com/WordPress/gutenberg/pull/53044))
+- Details block:
+ - Add `accordion` and `toggle` keywords to improve block's discoverability. ([53501](https://github.com/WordPress/gutenberg/pull/53501))
+ - Add layout and block spacing options. ([53282](https://github.com/WordPress/gutenberg/pull/53282))
+- File block: Add block spacing options. ([45107](https://github.com/WordPress/gutenberg/pull/45107))
- Image block: Add aspect ratio support to lightbox. ([52765](https://github.com/WordPress/gutenberg/pull/52765))
+- Post Content block: Add color controls. ([51326](https://github.com/WordPress/gutenberg/pull/51326))
- Remove "post" from block titles. ([53492](https://github.com/WordPress/gutenberg/pull/53492))
#### Patterns
-- Open detail view when duplicating pattern. ([53214](https://github.com/WordPress/gutenberg/pull/53214))
-- Prevent convert modal closing block options menu. ([53707](https://github.com/WordPress/gutenberg/pull/53707))
-- Skip migration logs in the Patterns screen. ([53626](https://github.com/WordPress/gutenberg/pull/53626))
+- Open detail view when duplicating a pattern. ([53214](https://github.com/WordPress/gutenberg/pull/53214))
+- Prevent the "create pattern" modal from closing the block options menu when it is closed. ([53707](https://github.com/WordPress/gutenberg/pull/53707))
+- Skip migration logs in the patterns screen. ([53626](https://github.com/WordPress/gutenberg/pull/53626))
+- Add missing full stop to string. ([53544](https://github.com/WordPress/gutenberg/pull/53544))
#### Global Styles
-- Global styles revisions: Add a reset to default revision. ([52965](https://github.com/WordPress/gutenberg/pull/52965))
-- Global styles revisions: Reduce visibility check from 2 to 1 revision. ([53281](https://github.com/WordPress/gutenberg/pull/53281))
-- Post Content: Add color controls. ([51326](https://github.com/WordPress/gutenberg/pull/51326))
+- Add a reset to default global styles revision ([52965](https://github.com/WordPress/gutenberg/pull/52965)) and reduce visibility check from two to one revision ([53281](https://github.com/WordPress/gutenberg/pull/53281)).
#### Media
- Adjust size of image previews in list view. ([53649](https://github.com/WordPress/gutenberg/pull/53649))
-- List View: Add media previews to list view for gallery and image blocks. ([53381](https://github.com/WordPress/gutenberg/pull/53381))
+- Add media previews to list view for gallery and image blocks. ([53381](https://github.com/WordPress/gutenberg/pull/53381))
#### Site Editor
-- Command Palette: Order template results in Site Editor. ([53286](https://github.com/WordPress/gutenberg/pull/53286))
-- Edit Site: Use progress bar for loading screen. ([53032](https://github.com/WordPress/gutenberg/pull/53032))
-
-#### Data Layer
-- Data: Warn if the 'useSelect' hook returns different values when called with the same state and parameters. ([53666](https://github.com/WordPress/gutenberg/pull/53666))
+- Expose `Theme` via private APIs ([53262](https://github.com/WordPress/gutenberg/pull/53262)), which was necessary to use the progress bar component for the site editor loading screen ([53032](https://github.com/WordPress/gutenberg/pull/53032)).
#### Block Editor
- Add `Opens in new Tab` control into Link Preview. ([53566](https://github.com/WordPress/gutenberg/pull/53566))
-
-#### Interactivity API
-- Update deepsignal version. ([53549](https://github.com/WordPress/gutenberg/pull/53549))
-
-#### Code Editor
-- Tweak, and add, more consistent commands. ([53496](https://github.com/WordPress/gutenberg/pull/53496))
-
-#### Themes
+- Dependencies: Bump `remove-accents` to 0.5.0. ([53420](https://github.com/WordPress/gutenberg/pull/53420))
+- Top-align Publish row in the post panel. ([53573](https://github.com/WordPress/gutenberg/pull/53573))
- Allow layout controls to be disabled per block from theme.json. ([53378](https://github.com/WordPress/gutenberg/pull/53378))
-
-#### Plugins API
-- Plugins: Introduce the 'usePluginContext' hook. ([53291](https://github.com/WordPress/gutenberg/pull/53291))
-
-#### Layout
-- Add layout and block spacing to details block. ([53282](https://github.com/WordPress/gutenberg/pull/53282))
-
-#### Typography
- Fluid typography: Add min and max viewport width configurable options. ([53081](https://github.com/WordPress/gutenberg/pull/53081))
### New APIs
#### Extensibility
-- Make useBlockEditingMode() public. ([52094](https://github.com/WordPress/gutenberg/pull/52094))
+- Make `useBlockEditingMode()` public. ([52094](https://github.com/WordPress/gutenberg/pull/52094))
### Bug Fixes
-- Command palette: Fix metrics for resting and no results view. ([53497](https://github.com/WordPress/gutenberg/pull/53497))
-- Fix top toolbar in the post editor with custom fields in Safari. ([53688](https://github.com/WordPress/gutenberg/pull/53688))
-- Improve metrics on post publish view buttons. ([53245](https://github.com/WordPress/gutenberg/pull/53245))
-- Set top toolbar size dynamically. ([53526](https://github.com/WordPress/gutenberg/pull/53526))
-- Support container queries in editor CSS. ([49915](https://github.com/WordPress/gutenberg/pull/49915))
+#### Commands
+- Style tweaks to fix metrics for resting and no results view in command palette. ([53497](https://github.com/WordPress/gutenberg/pull/53497))
+- Order template results in Site Editor, to fix some templates not displaying. ([53286](https://github.com/WordPress/gutenberg/pull/53286))
+- Don't allow access to Styles-related pages via the command palette in the hybrid theme. ([53123](https://github.com/WordPress/gutenberg/pull/53123))
#### Block Library
-- Button block: Memoize link value passed to the LinkControl. ([53507](https://github.com/WordPress/gutenberg/pull/53507))
-- Cover block: Fix flickering when inserted in templates and also fix isDark calculation bugs. ([53253](https://github.com/WordPress/gutenberg/pull/53253))
-- Footnotes:
- - Autosave is not slashing JSON. ([53664](https://github.com/WordPress/gutenberg/pull/53664))
+- Button block: Avoid losing user changes when the `ButtonEdit` component re-renders. ([53507](https://github.com/WordPress/gutenberg/pull/53507))
+- Cover block: Fix flickering when inserted in templates and also fix `isDark` calculation bugs. ([53253](https://github.com/WordPress/gutenberg/pull/53253))
+- Footnotes block:
+ - Ensure autosave works and escapes quotes as expected. ([53664](https://github.com/WordPress/gutenberg/pull/53664))
- Fix accidental override. ([53663](https://github.com/WordPress/gutenberg/pull/53663))
- Fix recursion into updating attributes when attributes is not an object. ([53257](https://github.com/WordPress/gutenberg/pull/53257))
+ - Remove Footnotes when interactive formatting is disabled. ([53474](https://github.com/WordPress/gutenberg/pull/53474))
- Image block:
- Fix image stretching with only height. ([53443](https://github.com/WordPress/gutenberg/pull/53443))
- Don't render `DimensionsTool` if it is not resizable. ([53181](https://github.com/WordPress/gutenberg/pull/53181))
- Fix stretched images constrained by max-width. ([53274](https://github.com/WordPress/gutenberg/pull/53274))
- Clear aspect ratio when wide aligned. ([53439](https://github.com/WordPress/gutenberg/pull/53439))
- - Dimensions Tool: Change the conditions underwhich we display the scale control. ([53334](https://github.com/WordPress/gutenberg/pull/53334))
- - Aspect Ratio: Reset height when selecting the original aspect ratio. ([53339](https://github.com/WordPress/gutenberg/pull/53339))
-- Latest Posts block: Make latest-posts ssr categories handling more defensive. ([53659](https://github.com/WordPress/gutenberg/pull/53659))
+ - Change the conditions under which we display the scale control. ([53334](https://github.com/WordPress/gutenberg/pull/53334))
+ - Reset height when selecting the original aspect ratio. ([53339](https://github.com/WordPress/gutenberg/pull/53339))
+- Latest Posts block: Make categories handling more defensive to prevent multisite error. ([53659](https://github.com/WordPress/gutenberg/pull/53659))
+- Media & Text block: Fix deprecation with `isStackOnMobile` default value changed. ([49538](https://github.com/WordPress/gutenberg/pull/49538))
- Inject theme stylesheet value as template part theme attribute. ([53423](https://github.com/WordPress/gutenberg/pull/53423))
-- Patterns: Add `delete_posts` to the wp_block (patterns) capabilities. ([53405](https://github.com/WordPress/gutenberg/pull/53405))
- Block serialization: Correctly compare default attribute values. ([53521](https://github.com/WordPress/gutenberg/pull/53521))
#### Block Editor
-- Fix Synced Patterns' color in quick inserter. ([53327](https://github.com/WordPress/gutenberg/pull/53327))
-- Hide pattern previews on hover in inserter. ([53331](https://github.com/WordPress/gutenberg/pull/53331))
- LinkControl: Prevent overflow when the title is a URL. ([53356](https://github.com/WordPress/gutenberg/pull/53356))
-- Safari: Fix ArrowUp on empty paragraph. ([53341](https://github.com/WordPress/gutenberg/pull/53341))
-- Safari: Fix Shift+Click multi select. ([53440](https://github.com/WordPress/gutenberg/pull/53440))
-- Selection: Restore focus after dragging out of the block repeatedly. ([53429](https://github.com/WordPress/gutenberg/pull/53429))
-- Writing flow: Avoid merging paragraph into Columns. ([53508](https://github.com/WordPress/gutenberg/pull/53508))
-- Writing flow: Fix vertical arrow keys not moving. ([53454](https://github.com/WordPress/gutenberg/pull/53454))
+- Fix broken flows on Safari, including `ArrowUp` functionality in an empty paragraph ([53341](https://github.com/WordPress/gutenberg/pull/53341)) and multi-selection upon shift plus click ([53440](https://github.com/WordPress/gutenberg/pull/53440)).
+- Restore focus after dragging out of the block repeatedly. ([53429](https://github.com/WordPress/gutenberg/pull/53429))
+- Avoid merging paragraph into a Columns block. ([53508](https://github.com/WordPress/gutenberg/pull/53508))
+- Prevent vertical arrow keys getting stuck in view. ([53454](https://github.com/WordPress/gutenberg/pull/53454))
+- Set top toolbar size dynamically. ([53526](https://github.com/WordPress/gutenberg/pull/53526))
+- Support container queries in editor CSS. ([49915](https://github.com/WordPress/gutenberg/pull/49915))
+- Copy tag name on internal paste. ([48254](https://github.com/WordPress/gutenberg/pull/48254))
#### Site Editor
- Add missing i18n in `HomeTemplateDetails`. ([53543](https://github.com/WordPress/gutenberg/pull/53543))
-- Adds site editor mobile block settings and styles. ([53412](https://github.com/WordPress/gutenberg/pull/53412))
-- Edit Site: Fix site editor canvas edit mode button. ([53730](https://github.com/WordPress/gutenberg/pull/53730))
+- Add buttons for block settings and styles in smaller viewport. ([53412](https://github.com/WordPress/gutenberg/pull/53412))
+- Ensure canvas edit mode button occupies the entire frame canvas. ([53730](https://github.com/WordPress/gutenberg/pull/53730))
- Fix document actions label helper method. ([52974](https://github.com/WordPress/gutenberg/pull/52974))
- Fix document title alignment in command palette button. ([53224](https://github.com/WordPress/gutenberg/pull/53224))
#### Post Editor
-- Fix crash by moving editor style logic into a hook with useMemo. ([53596](https://github.com/WordPress/gutenberg/pull/53596))
+- Address crash by moving editor style logic into a hook with `useMemo`. ([53596](https://github.com/WordPress/gutenberg/pull/53596))
- Fix support of sticky position in non-iframed post editor. ([53540](https://github.com/WordPress/gutenberg/pull/53540))
-- Fix the typo when setting the preview device type to 'Desktop'. ([53409](https://github.com/WordPress/gutenberg/pull/53409))
-- getInsertionPoint: Avoid returning a different object on every call. ([53722](https://github.com/WordPress/gutenberg/pull/53722))
+- Correct typo when setting the preview device type to 'Desktop'. ([53409](https://github.com/WordPress/gutenberg/pull/53409))
+- Avoid returning a different object on every call to `getInsertionPoint`. ([53722](https://github.com/WordPress/gutenberg/pull/53722))
+- Fix top toolbar in the post editor with custom fields in Safari. ([53688](https://github.com/WordPress/gutenberg/pull/53688))
+- Improve metrics on post publish view buttons. ([53245](https://github.com/WordPress/gutenberg/pull/53245))
#### Page Content Focus
- Fix missing Replace button in content-locked Image blocks. ([53410](https://github.com/WordPress/gutenberg/pull/53410))
-- Site Editor: Fix BlockPreview in Template panel when editing a page. ([53550](https://github.com/WordPress/gutenberg/pull/53550))
-- Use template.blocks in BlockPreview if it exists. ([53611](https://github.com/WordPress/gutenberg/pull/53611))
+- Fix BlockPreview in Template panel when editing a page in the site editor. ([53550](https://github.com/WordPress/gutenberg/pull/53550))
+- Use `template.blocks` in BlockPreview if it exists. ([53611](https://github.com/WordPress/gutenberg/pull/53611))
#### Navigation Menus
-- Fix: #52886 Make all the 'Loading' strings consistent. ([52901](https://github.com/WordPress/gutenberg/pull/52901))
-- Fix: Title is not copied correctly when duplicating navigation. ([53610](https://github.com/WordPress/gutenberg/pull/53610))
-- Revert Fix entity cache misses for single posts due to string as recordKey. ([53419](https://github.com/WordPress/gutenberg/pull/53419))
+- Make all the 'Loading' strings consistent. ([52901](https://github.com/WordPress/gutenberg/pull/52901))
+- Fix title not being copied correctly when duplicating navigation. ([53610](https://github.com/WordPress/gutenberg/pull/53610))
+- Remove "go to" for terms and posts. ([53408](https://github.com/WordPress/gutenberg/pull/53408))
#### Typography
- Fallback to default max viewport if layout wide size is fluid. ([53551](https://github.com/WordPress/gutenberg/pull/53551))
- Fix typo and add tests for fonts install endpoint. ([53644](https://github.com/WordPress/gutenberg/pull/53644))
#### Patterns
+- Fix Synced Patterns' color in quick inserter. ([53327](https://github.com/WordPress/gutenberg/pull/53327))
+- Hide pattern previews on hover in inserter. ([53331](https://github.com/WordPress/gutenberg/pull/53331))
+- Ensure it's possible to delete draft patterns. ([53405](https://github.com/WordPress/gutenberg/pull/53405))
- Fix pattern creation button in list view dropdown menu. ([53562](https://github.com/WordPress/gutenberg/pull/53562))
-- Fix: Sync status overlaps for some languages in Patterns post type page. ([53243](https://github.com/WordPress/gutenberg/pull/53243))
-
-#### Rich Text
-- Copy tag name on internal paste. ([48254](https://github.com/WordPress/gutenberg/pull/48254))
-- RichText: Remove 'Footnotes' when interactive formatting is disabled. ([53474](https://github.com/WordPress/gutenberg/pull/53474))
+- Prevent sync status overlapping for some languages in patterns. ([53243](https://github.com/WordPress/gutenberg/pull/53243))
#### Global Styles
- Fix push-to-global-styles clearing of attributes, border fallbacks, link hover colors, and behaviors. ([51621](https://github.com/WordPress/gutenberg/pull/51621))
@@ -183,33 +427,18 @@
- Include namespace in layout classname for non-core blocks. ([53404](https://github.com/WordPress/gutenberg/pull/53404))
#### Interactivity API
-- Add short-cirtuit to `useSignalEffect`. ([53358](https://github.com/WordPress/gutenberg/pull/53358))
+- Add short-circuit to `useSignalEffect`. ([53358](https://github.com/WordPress/gutenberg/pull/53358))
- Add support for underscores and leading dashes in the suffix part of the directive. ([53337](https://github.com/WordPress/gutenberg/pull/53337))
+- Update deepsignal version. ([53549](https://github.com/WordPress/gutenberg/pull/53549))
#### Components
-- Button: add `:Disabled` selector to reset hover color for disabled buttons. ([53411](https://github.com/WordPress/gutenberg/pull/53411))
-
-#### Template Editor
-- Remove "go to" for terms and posts. ([53408](https://github.com/WordPress/gutenberg/pull/53408))
-
-#### Custom Fields
-- Insert path and query args to form before submitting. ([53324](https://github.com/WordPress/gutenberg/pull/53324))
-
-#### Themes
-- Don't allow access to Styles-related pages via the command palette in the hybrid theme. ([53123](https://github.com/WordPress/gutenberg/pull/53123))
-
-#### Block Validation/Deprecation
-- Media & Text Block: Fix deprecation with `isStackOnMobile` default value changed. ([49538](https://github.com/WordPress/gutenberg/pull/49538))
-
-#### npm Packages
-- Add some missing package dependencies. ([41486](https://github.com/WordPress/gutenberg/pull/41486))
+- Button: Add `:Disabled` selector to reset hover color for disabled buttons. ([53411](https://github.com/WordPress/gutenberg/pull/53411))
+- Preferences Modal: Insert path and query args to form before submitting. ([53324](https://github.com/WordPress/gutenberg/pull/53324))
### Accessibility
- Type labels GH Action: Fix accessibility issues in error message. ([53371](https://github.com/WordPress/gutenberg/pull/53371))
-
-#### Block Library
- Add accessible description of current Navigation block state. ([53469](https://github.com/WordPress/gutenberg/pull/53469))
- Implement accessible version of Navigation overlay preview toggle control. ([53462](https://github.com/WordPress/gutenberg/pull/53462))
- Search Block: Fix unintended wrapping of button text in "Button only" style. ([53373](https://github.com/WordPress/gutenberg/pull/53373))
@@ -219,156 +448,145 @@
- Compute presets from `theme.json`: Skip those without classes or variables. ([53574](https://github.com/WordPress/gutenberg/pull/53574))
- Switch performance tests to Playwright. ([52022](https://github.com/WordPress/gutenberg/pull/52022))
-
-#### Block Editor
- Fix memory leaks in `
+
);
},
@@ -465,7 +465,7 @@ edit: ( { attributes, setAttributes } ) => {
setAttributes( { postsToShow: parseInt( val ) } );
}}
/>
-
+
);
},
diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md
index 216509ab1df13..ba24ff0f58401 100644
--- a/docs/reference-guides/block-api/block-metadata.md
+++ b/docs/reference-guides/block-api/block-metadata.md
@@ -601,6 +601,16 @@ PHP file to use when rendering the block type on the server to show on the front
- `$content` (`string`): The block default content.
- `$block` (`WP_Block`): The block instance.
+An example implementation of the `render.php` file defined with `render` could look like:
+
+```php
+
>
+
+
+```
+
+_Note: This file loads for every instance of the block type when rendering the page HTML on the server. Accounting for that is essential when declaring functions or classes in the file. The simplest way to avoid the risk of errors is to consume that shared logic from another file._
+
## Assets
### `WPDefinedPath`
diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md
index 5efc76064fe31..4a6101df3394c 100644
--- a/docs/reference-guides/block-api/block-transforms.md
+++ b/docs/reference-guides/block-api/block-transforms.md
@@ -96,7 +96,7 @@ A transformation of type `enter` is an object that takes the following parameter
- **type** _(string)_: the value `enter`.
- **regExp** _(RegExp)_: the Regular Expression to use as a matcher. If the value matches, the transformation will be applied.
-- **transform** _(function)_: a callback that receives the value that has been entered. It should return a block object or an array of block objects.
+- **transform** _(function)_: a callback that receives an object with a `content` field containing the value that has been entered. It should return a block object or an array of block objects.
- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from --- to Separator block**
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index de0ae39604ff5..b9a35e3fd5f51 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -98,7 +98,7 @@ A single column within a columns block. ([Source](https://github.com/WordPress/g
- **Name:** core/column
- **Category:** design
- **Parent:** core/columns
-- **Supports:** anchor, color (background, gradients, heading, link, text), layout, spacing (blockGap, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~
+- **Supports:** anchor, color (background, button, gradients, heading, link, text), layout, spacing (blockGap, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~
- **Attributes:** allowedBlocks, templateLock, verticalAlignment, width
## Columns
@@ -107,7 +107,7 @@ Display content in multiple columns, with blocks added to each column. ([Source]
- **Name:** core/columns
- **Category:** design
-- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), layout (default, ~~allowEditing~~, ~~allowInheriting~~, ~~allowSwitching~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align (full, wide), anchor, color (background, button, gradients, heading, link, text), layout (default, ~~allowEditing~~, ~~allowInheriting~~, ~~allowSwitching~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** isStackedOnMobile, templateLock, verticalAlignment
## Comment Author Avatar (deprecated)
@@ -302,7 +302,7 @@ Gather blocks in a layout container. ([Source](https://github.com/WordPress/gute
- **Name:** core/group
- **Category:** design
-- **Supports:** align (full, wide), anchor, ariaLabel, color (background, gradients, heading, link, text), dimensions (minHeight), layout (allowSizingOnChildren), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align (full, wide), anchor, ariaLabel, color (background, button, gradients, heading, link, text), dimensions (minHeight), layout (allowSizingOnChildren), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** allowedBlocks, tagName, templateLock
## Heading
@@ -665,7 +665,7 @@ An advanced block that allows displaying post types based on different query par
- **Name:** core/query
- **Category:** theme
- **Supports:** align (full, wide), layout, ~~html~~
-- **Attributes:** namespace, query, queryId, tagName
+- **Attributes:** enhancedPagination, namespace, query, queryId, tagName
## No results
@@ -705,7 +705,7 @@ Displays a list of page numbers for pagination ([Source](https://github.com/Word
- **Category:** theme
- **Parent:** core/query-pagination
- **Supports:** color (background, gradients, ~~text~~), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~
-- **Attributes:**
+- **Attributes:** midSize
## Previous Page
diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md
index fbdf0c5dfd81b..025b4eaf15ab8 100644
--- a/docs/reference-guides/data/data-core-block-editor.md
+++ b/docs/reference-guides/data/data-core-block-editor.md
@@ -1077,6 +1077,19 @@ _Returns_
- `boolean`: Whether block is first in multi-selection.
+### isGroupable
+
+Indicates if the provided blocks(by client ids) are groupable. We need to have at least one block, have a grouping block name set and be able to remove these blocks.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+- _clientIds_ `string[]`: Block client ids. If not passed the selected blocks client ids will be used.
+
+_Returns_
+
+- `boolean`: True if the blocks are groupable.
+
### isLastBlockChangePersistent
Returns true if the most recent block change is be considered persistent, or false otherwise. A persistent change is one committed by BlockEditorProvider via its `onChange` callback, in addition to `onInput`.
@@ -1141,6 +1154,19 @@ _Returns_
- `boolean`: Whether user is typing.
+### isUngroupable
+
+Indicates if a block is ungroupable. A block is ungroupable if it is a single grouping block with inner blocks. If a block has an `ungroup` transform, it is also ungroupable, without the requirement of being the default grouping block. Additionally a block can only be ungrouped if it has inner blocks and can be removed.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+- _clientId_ `string`: Client Id of the block. If not passed the selected block's client id will be used.
+
+_Returns_
+
+- `boolean`: True if the block is ungroupable.
+
### isValidTemplate
Returns whether the blocks matches the template or not.
diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md
index ba047160f047d..084c9c1d7a5fb 100644
--- a/docs/reference-guides/data/data-core-blocks.md
+++ b/docs/reference-guides/data/data-core-blocks.md
@@ -712,6 +712,10 @@ The actions in this package shouldn't be used directly. Instead, use the functio
-Nothing to document.
+### reapplyBlockTypeFilters
+
+Signals that all block types should be computed again. It uses stored unprocessed block types and all the most recent list of registered filters.
+
+It addresses the issue where third party block filters get registered after third party blocks. A sample sequence: 1. Filter A. 2. Block B. 3. Block C. 4. Filter D. 5. Filter E. 6. Block F. 7. Filter G. In this scenario some filters would not get applied for all blocks because they are registered too late.
diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md
index b579e7e658007..7d6a1deed455b 100644
--- a/docs/reference-guides/data/data-core-edit-post.md
+++ b/docs/reference-guides/data/data-core-edit-post.md
@@ -490,6 +490,10 @@ _Parameters_
- _mode_ `string`: The editor mode.
+### toggleDistractionFree
+
+Action that toggles Distraction free mode. Distraction free mode expects there are no sidebars, as due to the z-index values set, you can't close sidebars.
+
### toggleEditorPanelEnabled
Returns an action object used to enable or disable a panel in the editor.
diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md
index 83e9b09dfd7d8..0cac2268b2ab2 100644
--- a/docs/reference-guides/data/data-core-edit-site.md
+++ b/docs/reference-guides/data/data-core-edit-site.md
@@ -408,6 +408,10 @@ _Returns_
Undocumented declaration.
+### toggleDistractionFree
+
+Action that toggles Distraction free mode. Distraction free mode expects there are no sidebars, as due to the z-index values set, you can't close sidebars.
+
### toggleFeature
Dispatches an action that toggles a feature flag.
diff --git a/docs/toc.json b/docs/toc.json
index b523fcedea416..996c026cbe001 100644
--- a/docs/toc.json
+++ b/docs/toc.json
@@ -104,6 +104,7 @@
]
},
{ "docs/how-to-guides/curating-the-editor-experience.md": [] },
+ { "docs/how-to-guides/enqueueing-assets-in-the-editor.md": [] },
{ "docs/how-to-guides/feature-flags.md": [] },
{ "docs/how-to-guides/format-api.md": [] },
{
@@ -267,6 +268,9 @@
"docs/reference-guides/data/data-core-block-editor.md": []
},
{ "docs/reference-guides/data/data-core-blocks.md": [] },
+ {
+ "docs/reference-guides/data/data-core-commands.md": []
+ },
{
"docs/reference-guides/data/data-core-customize-widgets.md": []
},
diff --git a/gutenberg.php b/gutenberg.php
index 585e53409c014..3e9febccbe737 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -3,9 +3,9 @@
* Plugin Name: Gutenberg
* Plugin URI: https://github.com/WordPress/gutenberg
* Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality.
- * Requires at least: 6.1
+ * Requires at least: 6.2
* Requires PHP: 7.0
- * Version: 16.5.0-rc.1
+ * Version: 16.6.0-rc.1
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/lib/block-supports/colors.php b/lib/block-supports/colors.php
index 7ec32bf14d753..88e13abfc9cc9 100644
--- a/lib/block-supports/colors.php
+++ b/lib/block-supports/colors.php
@@ -16,10 +16,14 @@ function gutenberg_register_colors_support( $block_type ) {
$has_background_colors_support = true === $color_support || ( is_array( $color_support ) && _wp_array_get( $color_support, array( 'background' ), true ) );
$has_gradients_support = _wp_array_get( $color_support, array( 'gradients' ), false );
$has_link_colors_support = _wp_array_get( $color_support, array( 'link' ), false );
+ $has_button_colors_support = _wp_array_get( $color_support, array( 'button' ), false );
+ $has_heading_colors_support = _wp_array_get( $color_support, array( 'heading' ), false );
$has_color_support = $has_text_colors_support ||
$has_background_colors_support ||
$has_gradients_support ||
- $has_link_colors_support;
+ $has_link_colors_support ||
+ $has_button_colors_support ||
+ $has_heading_colors_support;
if ( ! $block_type->attributes ) {
$block_type->attributes = array();
diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php
index d4502aaa2e478..328d371c3e4fd 100644
--- a/lib/block-supports/elements.php
+++ b/lib/block-supports/elements.php
@@ -5,16 +5,6 @@
* @package gutenberg
*/
-/**
- * Get the elements class names.
- *
- * @param array $block Block object.
- * @return string The unique class name.
- */
-function gutenberg_get_elements_class_name( $block ) {
- return 'wp-elements-' . md5( serialize( $block ) );
-}
-
/**
* Update the block content with elements class names.
*
@@ -23,34 +13,79 @@ function gutenberg_get_elements_class_name( $block ) {
* @return string Filtered block content.
*/
function gutenberg_render_elements_support( $block_content, $block ) {
- if ( ! $block_content ) {
+ if ( ! $block_content || empty( $block['attrs'] ) ) {
return $block_content;
}
- $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
- $skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
+
+ $element_color_properties = array(
+ 'button' => array(
+ 'skip' => wp_should_skip_block_supports_serialization( $block_type, 'color', 'button' ),
+ 'paths' => array(
+ 'style.elements.button.color.text',
+ 'style.elements.button.color.background',
+ 'style.elements.button.color.gradient',
+ ),
+ ),
+ 'link' => array(
+ 'skip' => wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' ),
+ 'paths' => array(
+ 'style.elements.link.color.text',
+ 'style.elements.link.:hover.color.text',
+ ),
+ ),
+ 'heading' => array(
+ 'skip' => wp_should_skip_block_supports_serialization( $block_type, 'color', 'heading' ),
+ 'paths' => array(
+ 'style.elements.heading.color.text',
+ 'style.elements.heading.color.background',
+ 'style.elements.heading.color.gradient',
+ 'style.elements.h1.color.text',
+ 'style.elements.h1.color.background',
+ 'style.elements.h1.color.gradient',
+ 'style.elements.h2.color.text',
+ 'style.elements.h2.color.background',
+ 'style.elements.h2.color.gradient',
+ 'style.elements.h3.color.text',
+ 'style.elements.h3.color.background',
+ 'style.elements.h3.color.gradient',
+ 'style.elements.h4.color.text',
+ 'style.elements.h4.color.background',
+ 'style.elements.h4.color.gradient',
+ 'style.elements.h5.color.text',
+ 'style.elements.h5.color.background',
+ 'style.elements.h5.color.gradient',
+ 'style.elements.h6.color.text',
+ 'style.elements.h6.color.background',
+ 'style.elements.h6.color.gradient',
+ ),
+ ),
+ );
+
+ $skip_all_element_color_serialization = $element_color_properties['button']['skip'] &&
+ $element_color_properties['link']['skip'] &&
+ $element_color_properties['heading']['skip'];
- if ( $skip_link_color_serialization ) {
+ if ( $skip_all_element_color_serialization ) {
return $block_content;
}
- $link_color = null;
- if ( ! empty( $block['attrs'] ) ) {
- $link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', 'color', 'text' ), null );
- }
+ $element_colors_set = 0;
+
+ foreach ( $element_color_properties as $element_config ) {
+ if ( $element_config['skip'] ) {
+ continue;
+ }
- $hover_link_color = null;
- if ( ! empty( $block['attrs'] ) ) {
- $hover_link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', ':hover', 'color', 'text' ), null );
+ foreach ( $element_config['paths'] as $path ) {
+ if ( null !== _wp_array_get( $block['attrs'], explode( '.', $path ), null ) ) {
+ $element_colors_set++;
+ }
+ }
}
- /*
- * For now we only care about link colors.
- * This code in the future when we have a public API
- * should take advantage of WP_Theme_JSON_Gutenberg::compute_style_properties
- * and work for any element and style.
- */
- if ( null === $link_color && null === $hover_link_color ) {
+ if ( ! $element_colors_set ) {
return $block_content;
}
@@ -58,7 +93,7 @@ function gutenberg_render_elements_support( $block_content, $block ) {
// Add the class name to the first element, presuming it's the wrapper, if it exists.
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag() ) {
- $tags->add_class( gutenberg_get_elements_class_name( $block ) );
+ $tags->add_class( wp_get_elements_class_name( $block ) );
}
return $tags->get_updated_html();
@@ -80,33 +115,84 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$element_block_styles = isset( $block['attrs']['style']['elements'] ) ? $block['attrs']['style']['elements'] : null;
- /*
- * For now we only care about link color.
- */
- $skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
+ if ( ! $element_block_styles ) {
+ return null;
+ }
+
+ $skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
+ $skip_heading_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'heading' );
+ $skip_button_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'button' );
+ $skips_all_element_color_serialization = $skip_link_color_serialization &&
+ $skip_heading_color_serialization &&
+ $skip_button_color_serialization;
- if ( $skip_link_color_serialization ) {
+ if ( $skips_all_element_color_serialization ) {
return null;
}
- $class_name = gutenberg_get_elements_class_name( $block );
- $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null;
-
- gutenberg_style_engine_get_styles(
- $link_block_styles,
- array(
- 'selector' => ".$class_name a",
- 'context' => 'block-supports',
- )
+
+ $class_name = wp_get_elements_class_name( $block );
+
+ $element_types = array(
+ 'button' => array(
+ 'selector' => ".$class_name .wp-element-button, .$class_name .wp-block-button__link",
+ 'skip' => $skip_button_color_serialization,
+ ),
+ 'link' => array(
+ 'selector' => ".$class_name a",
+ 'hover_selector' => ".$class_name a:hover",
+ 'skip' => $skip_link_color_serialization,
+ ),
+ 'heading' => array(
+ 'selector' => ".$class_name h1, .$class_name h2, .$class_name h3, .$class_name h4, .$class_name h5, .$class_name h6",
+ 'skip' => $skip_heading_color_serialization,
+ 'elements' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ),
+ ),
);
- if ( isset( $link_block_styles[':hover'] ) ) {
- gutenberg_style_engine_get_styles(
- $link_block_styles[':hover'],
- array(
- 'selector' => ".$class_name a:hover",
- 'context' => 'block-supports',
- )
- );
+ foreach ( $element_types as $element_type => $element_config ) {
+ if ( $element_config['skip'] ) {
+ continue;
+ }
+
+ $element_style_object = _wp_array_get( $element_block_styles, array( $element_type ), null );
+
+ // Process primary element type styles.
+ if ( $element_style_object ) {
+ gutenberg_style_engine_get_styles(
+ $element_style_object,
+ array(
+ 'selector' => $element_config['selector'],
+ 'context' => 'block-supports',
+ )
+ );
+
+ if ( isset( $element_style_object[':hover'] ) ) {
+ gutenberg_style_engine_get_styles(
+ $element_style_object[':hover'],
+ array(
+ 'selector' => $element_config['hover_selector'],
+ 'context' => 'block-supports',
+ )
+ );
+ }
+ }
+
+ // Process related elements e.g. h1-h6 for headings.
+ if ( isset( $element_config['elements'] ) ) {
+ foreach ( $element_config['elements'] as $element ) {
+ $element_style_object = _wp_array_get( $element_block_styles, array( $element ), null );
+
+ if ( $element_style_object ) {
+ gutenberg_style_engine_get_styles(
+ $element_style_object,
+ array(
+ 'selector' => ".$class_name $element",
+ 'context' => 'block-supports',
+ )
+ );
+ }
+ }
+ }
}
return null;
diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php
index 6eecccfdc72c5..0cf501028efd8 100644
--- a/lib/block-supports/layout.php
+++ b/lib/block-supports/layout.php
@@ -529,24 +529,22 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support
* @return string Filtered block content.
*/
function gutenberg_render_layout_support_flag( $block_content, $block ) {
- $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
- $support_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
- $has_child_layout = isset( $block['attrs']['style']['layout']['selfStretch'] );
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
+ $block_supports_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
+ $layout_from_parent = $block['attrs']['style']['layout']['selfStretch'] ?? null;
- if ( ! $support_layout
- && ! $has_child_layout ) {
+ if ( ! $block_supports_layout && ! $layout_from_parent ) {
return $block_content;
}
$outer_class_names = array();
- if ( $has_child_layout && ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] || 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) ) {
-
+ if ( 'fixed' === $layout_from_parent || 'fill' === $layout_from_parent ) {
$container_content_class = wp_unique_id( 'wp-container-content-' );
$child_layout_styles = array();
- if ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
+ if ( 'fixed' === $layout_from_parent && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
$child_layout_styles[] = array(
'selector' => ".$container_content_class",
'declarations' => array(
@@ -554,7 +552,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
'box-sizing' => 'border-box',
),
);
- } elseif ( 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) {
+ } elseif ( 'fill' === $layout_from_parent ) {
$child_layout_styles[] = array(
'selector' => ".$container_content_class",
'declarations' => array(
@@ -572,15 +570,27 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
);
$outer_class_names[] = $container_content_class;
+ }
+
+ // Prep the processor for modifying the block output.
+ $processor = new WP_HTML_Tag_Processor( $block_content );
+ // Having no tags implies there are no tags onto which to add class names.
+ if ( ! $processor->next_tag() ) {
+ return $block_content;
}
- // Return early if only child layout exists.
- if ( ! $support_layout && ! empty( $outer_class_names ) ) {
- $content = new WP_HTML_Tag_Processor( $block_content );
- $content->next_tag();
- $content->add_class( implode( ' ', $outer_class_names ) );
- return (string) $content;
+ /*
+ * A block may not support layout but still be affected by a parent block's layout.
+ *
+ * In these cases add the appropriate class names and then return early; there's
+ * no need to investigate on this block whether additional layout constraints apply.
+ */
+ if ( ! $block_supports_layout && ! empty( $outer_class_names ) ) {
+ foreach ( $outer_class_names as $class_name ) {
+ $processor->add_class( $class_name );
+ }
+ return $processor->get_updated_html();
}
$global_settings = gutenberg_get_global_settings();
@@ -590,7 +600,6 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$class_names = array();
$layout_definitions = gutenberg_get_layout_definitions();
$container_class = wp_unique_id( 'wp-container-' );
- $layout_classname = '';
// Set the correct layout type for blocks using legacy content width.
if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
@@ -599,11 +608,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$root_padding_aware_alignments = _wp_array_get( $global_settings, array( 'useRootPaddingAwareAlignments' ), false );
- if (
- $root_padding_aware_alignments &&
- isset( $used_layout['type'] ) &&
- 'constrained' === $used_layout['type']
- ) {
+ if ( $root_padding_aware_alignments && isset( $used_layout['type'] ) && 'constrained' === $used_layout['type'] ) {
$class_names[] = 'has-global-padding';
}
@@ -690,49 +695,100 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$full_block_name = 'core' === $split_block_name[0] ? end( $split_block_name ) : implode( '-', $split_block_name );
$class_names[] = 'wp-block-' . $full_block_name . '-' . $layout_classname;
- $content_with_outer_classnames = '';
-
+ // Add classes to the outermost HTML tag if necessary.
if ( ! empty( $outer_class_names ) ) {
- $content_with_outer_classnames = new WP_HTML_Tag_Processor( $block_content );
- $content_with_outer_classnames->next_tag();
foreach ( $outer_class_names as $outer_class_name ) {
- $content_with_outer_classnames->add_class( $outer_class_name );
+ $processor->add_class( $outer_class_name );
}
-
- $content_with_outer_classnames = (string) $content_with_outer_classnames;
}
/**
- * The first chunk of innerContent contains the block markup up until the inner blocks start.
- * We want to target the opening tag of the inner blocks wrapper, which is the last tag in that chunk.
- */
- $inner_content_classnames = '';
-
- if ( isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) && count( $block['innerContent'] ) > 1 ) {
- $tags = new WP_HTML_Tag_Processor( $block['innerContent'][0] );
- $last_classnames = '';
- while ( $tags->next_tag() ) {
- $last_classnames = $tags->get_attribute( 'class' );
+ * Attempts to refer to the inner-block wrapping element by its class attribute.
+ *
+ * When examining a block's inner content, if a block has inner blocks, then
+ * the first content item will likely be a text (HTML) chunk immediately
+ * preceding the inner blocks. The last HTML tag in that chunk would then be
+ * an opening tag for an element that wraps the inner blocks.
+ *
+ * There's no reliable way to associate this wrapper in $block_content because
+ * it may have changed during the rendering pipeline (as inner contents is
+ * provided before rendering) and through previous filters. In many cases,
+ * however, the `class` attribute will be a good-enough identifier, so this
+ * code finds the last tag in that chunk and stores the `class` attribute
+ * so that it can be used later when working through the rendered block output
+ * to identify the wrapping element and add the remaining class names to it.
+ *
+ * It's also possible that no inner block wrapper even exists. If that's the
+ * case this code could apply the class names to an invalid element.
+ *
+ * Example:
+ *
+ * $block['innerBlocks'] = array( $list_item );
+ * $block['innerContent'] = array( '
', null, '
' );
+ *
+ * // After rendering, the initial contents may have been modified by other renderers or filters.
+ * $block_content = <<
+ *
+ *
Code
+ *
It's a list!
+ *
+ * HTML;
+ *
+ * Although it is possible that the original block-wrapper classes are changed in $block_content
+ * from how they appear in $block['innerContent'], it's likely that the original class attributes
+ * are still present in the wrapper as they are in this example. Frequently, additional classes
+ * will also be present; rarely should classes be removed.
+ *
+ * @TODO: Find a better way to match the first inner block. If it's possible to identify where the
+ * first inner block starts, then it will be possible to find the last tag before it starts
+ * and then that tag, if an opening tag, can be solidly identified as a wrapping element.
+ * Can some unique value or class or ID be added to the inner blocks when they process
+ * so that they can be extracted here safely without guessing? Can the block rendering function
+ * return information about where the rendered inner blocks start?
+ *
+ * @var string|null
+ */
+ $inner_block_wrapper_classes = null;
+ $first_chunk = $block['innerContent'][0] ?? null;
+ if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
+ $first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
+ while ( $first_chunk_processor->next_tag() ) {
+ $class_attribute = $first_chunk_processor->get_attribute( 'class' );
+ if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
+ $inner_block_wrapper_classes = $class_attribute;
+ }
}
-
- $inner_content_classnames = (string) $last_classnames;
}
- $content = $content_with_outer_classnames ? new WP_HTML_Tag_Processor( $content_with_outer_classnames ) : new WP_HTML_Tag_Processor( $block_content );
-
- if ( $inner_content_classnames ) {
- $content->next_tag( array( 'class_name' => $inner_content_classnames ) );
- foreach ( $class_names as $class_name ) {
- $content->add_class( $class_name );
+ /*
+ * If necessary, advance to what is likely to be an inner block wrapper tag.
+ *
+ * This advances until it finds the first tag containing the original class
+ * attribute from above. If none is found it will scan to the end of the block
+ * and fail to add any class names.
+ *
+ * If there is no block wrapper it won't advance at all, in which case the
+ * class names will be added to the first and outermost tag of the block.
+ * For cases where this outermost tag is the only tag surrounding inner
+ * blocks then the outer wrapper and inner wrapper are the same.
+ */
+ do {
+ if ( ! $inner_block_wrapper_classes ) {
+ break;
}
- } else {
- $content->next_tag();
- foreach ( $class_names as $class_name ) {
- $content->add_class( $class_name );
+
+ if ( false !== strpos( $processor->get_attribute( 'class' ), $inner_block_wrapper_classes ) ) {
+ break;
}
+ } while ( $processor->next_tag() );
+
+ // Add the remaining class names.
+ foreach ( $class_names as $class_name ) {
+ $processor->add_class( $class_name );
}
- return (string) $content;
+ return $processor->get_updated_html();
}
// Register the block support. (overrides core one).
diff --git a/lib/block-supports/settings.php b/lib/block-supports/settings.php
index 7c0dd719f41c3..aac4774b62fe8 100644
--- a/lib/block-supports/settings.php
+++ b/lib/block-supports/settings.php
@@ -5,18 +5,6 @@
* @package gutenberg
*/
-/**
- * Get the class name used on block level presets.
- *
- * @access private
- *
- * @param array $block Block object.
- * @return string The unique class name.
- */
-function _gutenberg_get_presets_class_name( $block ) {
- return 'wp-settings-' . md5( serialize( $block ) );
-}
-
/**
* Update the block content with block level presets class name.
*
@@ -47,7 +35,7 @@ function _gutenberg_add_block_level_presets_class( $block_content, $block ) {
// Add the class name to the first element, presuming it's the wrapper, if it exists.
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag() ) {
- $tags->add_class( _gutenberg_get_presets_class_name( $block ) );
+ $tags->add_class( _wp_get_presets_class_name( $block ) );
}
return $tags->get_updated_html();
@@ -76,7 +64,7 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) {
return null;
}
- $class_name = '.' . _gutenberg_get_presets_class_name( $block );
+ $class_name = '.' . _wp_get_presets_class_name( $block );
// the root selector for preset variables needs to target every possible block selector
// in order for the general setting to override any bock specific setting of a parent block or
@@ -129,7 +117,12 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) {
);
if ( ! empty( $styles ) ) {
- gutenberg_enqueue_block_support_styles( $styles );
+ /*
+ * This method is deprecated since WordPress 6.2.
+ * We could enqueue these styles separately,
+ * or print them out with other settings presets.
+ */
+ wp_enqueue_block_support_styles( $styles );
}
return null;
diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php
index c4e1316ac3893..d0721503fb101 100644
--- a/lib/block-supports/typography.php
+++ b/lib/block-supports/typography.php
@@ -415,7 +415,14 @@ function gutenberg_get_computed_fluid_typography_value( $args = array() ) {
/**
* Returns a font-size value based on a given font-size preset.
- * Takes into account fluid typography parameters and attempts to return a css formula depending on available, valid values.
+ * Takes into account fluid typography parameters and attempts to return a CSS
+ * formula depending on available, valid values.
+ *
+ * @since 6.1.0
+ * @since 6.1.1 Adjusted rules for min and max font sizes.
+ * @since 6.2.0 Added 'settings.typography.fluid.minFontSize' support.
+ * @since 6.3.0 Using layout.wideSize as max viewport width, and logarithmic scale factor to calculate minimum font scale.
+ * @since 6.4.0 Added configurable min and max viewport width values to the typography.fluid theme.json schema.
*
* @param array $preset {
* Required. fontSizes preset value as seen in theme.json.
diff --git a/lib/blocks.php b/lib/blocks.php
index f160d2a7080d3..34ef63f2e87ad 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -21,10 +21,8 @@ function gutenberg_reregister_core_block_types() {
'code',
'column',
'columns',
- 'comments',
'details',
'group',
- 'footnotes',
'html',
'list',
'list-item',
diff --git a/lib/compat/wordpress-6.2/block-editor.php b/lib/compat/wordpress-6.2/block-editor.php
deleted file mode 100644
index 4df2b6b2652e0..0000000000000
--- a/lib/compat/wordpress-6.2/block-editor.php
+++ /dev/null
@@ -1,45 +0,0 @@
- get_theme_support( 'disable-custom-colors' ),
- 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
- 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ),
- 'disableLayoutStyles' => get_theme_support( 'disable-layout-styles' ),
- 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ),
- 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ),
- 'enableCustomUnits' => get_theme_support( 'custom-units' ),
- );
-
- // Theme settings.
- $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) );
- if ( false !== $color_palette ) {
- $theme_settings['colors'] = $color_palette;
- }
-
- $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) );
- if ( false !== $font_sizes ) {
- $theme_settings['fontSizes'] = $font_sizes;
- }
-
- $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) );
- if ( false !== $gradient_presets ) {
- $theme_settings['gradients'] = $gradient_presets;
- }
-
- return $theme_settings;
- }
-}
diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php
deleted file mode 100644
index 12b19bdf4c54e..0000000000000
--- a/lib/compat/wordpress-6.2/block-patterns.php
+++ /dev/null
@@ -1,333 +0,0 @@
- _x( 'Banners', 'Block pattern category', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'buttons',
- array(
- 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Patterns that contain buttons and call to actions.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'columns',
- array(
- 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Multi-column patterns with more complex layouts.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'text',
- array(
- 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Patterns containing mostly text.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'query',
- array(
- 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'featured',
- array(
- 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A set of high quality curated patterns.', 'gutenberg' ),
- )
- );
-
- // Register new core block pattern categories.
- register_block_pattern_category(
- 'call-to-action',
- array(
- 'label' => _x( 'Call to Action', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Sections whose purpose is to trigger a specific action.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'team',
- array(
- 'label' => _x( 'Team', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A variety of designs to display your team members.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'testimonials',
- array(
- 'label' => _x( 'Testimonials', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Share reviews and feedback about your brand/business.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'services',
- array(
- 'label' => _x( 'Services', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Briefly describe what your business does and how you can help.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'contact',
- array(
- 'label' => _x( 'Contact', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Display your contact information.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'about',
- array(
- 'label' => _x( 'About', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Introduce yourself.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'portfolio',
- array(
- 'label' => _x( 'Portfolio', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Showcase your latest work.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'gallery',
- array(
- 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Different layouts for displaying images.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'media',
- array(
- 'label' => _x( 'Media', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Different layouts containing video or audio.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'posts',
- array(
- 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ),
- )
- );
- // Site building pattern categories.
- register_block_pattern_category(
- 'footer',
- array(
- 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A variety of footer designs displaying information and site navigation.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'header',
- array(
- 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A variety of header designs displaying your site title and navigation.', 'gutenberg' ),
- )
- );
-}
-add_action( 'init', 'gutenberg_register_core_block_patterns_categories' );
-
-/**
- * Register any patterns that the active theme may provide under its
- * `./patterns/` directory. Each pattern is defined as a PHP file and defines
- * its metadata using plugin-style headers. The minimum required definition is:
- *
- * /**
- * * Title: My Pattern
- * * Slug: my-theme/my-pattern
- * *
- *
- * The output of the PHP source corresponds to the content of the pattern, e.g.:
- *
- *
- *
- * If applicable, this will collect from both parent and child theme.
- *
- * Other settable fields include:
- *
- * - Description
- * - Viewport Width
- * - Categories (comma-separated values)
- * - Keywords (comma-separated values)
- * - Block Types (comma-separated values)
- * - Post Types (comma-separated values)
- * - Inserter (yes/no)
- *
- * @since 6.0.0
- * @access private
- */
-function gutenberg_register_theme_block_patterns() {
- $default_headers = array(
- 'title' => 'Title',
- 'slug' => 'Slug',
- 'description' => 'Description',
- 'viewportWidth' => 'Viewport Width',
- 'categories' => 'Categories',
- 'keywords' => 'Keywords',
- 'blockTypes' => 'Block Types',
- 'postTypes' => 'Post Types',
- 'inserter' => 'Inserter',
- 'templateTypes' => 'Template Types',
- );
-
- /*
- * Register patterns for the active theme. If the theme is a child theme,
- * let it override any patterns from the parent theme that shares the same slug.
- */
- $themes = array();
- $wp_theme = wp_get_theme();
- if ( $wp_theme->parent() ) {
- $themes[] = $wp_theme->parent();
- }
- $themes[] = $wp_theme;
-
- foreach ( $themes as $theme ) {
- $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
- if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) {
- continue;
- }
- if ( file_exists( $dirpath ) ) {
- $files = glob( $dirpath . '*.php' );
- if ( $files ) {
- foreach ( $files as $file ) {
- $pattern_data = get_file_data( $file, $default_headers );
-
- if ( empty( $pattern_data['slug'] ) ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %s: file name. */
- __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ),
- $file
- ),
- '6.0.0'
- );
- continue;
- }
-
- if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %1s: file name; %2s: slug value found. */
- __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ),
- $file,
- $pattern_data['slug']
- ),
- '6.0.0'
- );
- }
-
- if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
- continue;
- }
-
- // Title is a required property.
- if ( ! $pattern_data['title'] ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %1s: file name; %2s: slug value found. */
- __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ),
- $file
- ),
- '6.0.0'
- );
- continue;
- }
-
- // For properties of type array, parse data as comma-separated.
- foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes', 'templateTypes' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = array_filter(
- preg_split(
- '/[\s,]+/',
- (string) $pattern_data[ $property ]
- )
- );
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Parse properties of type int.
- foreach ( array( 'viewportWidth' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = (int) $pattern_data[ $property ];
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Parse properties of type bool.
- foreach ( array( 'inserter' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = in_array(
- strtolower( $pattern_data[ $property ] ),
- array( 'yes', 'true' ),
- true
- );
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Translate the pattern metadata.
- $text_domain = $theme->get( 'TextDomain' );
- //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
- if ( ! empty( $pattern_data['description'] ) ) {
- //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
- }
-
- // The actual pattern content is the output of the file.
- ob_start();
- include $file;
- $pattern_data['content'] = ob_get_clean();
- if ( ! $pattern_data['content'] ) {
- continue;
- }
-
- register_block_pattern( $pattern_data['slug'], $pattern_data );
- }
- }
- }
- }
-}
-remove_action( 'init', '_register_theme_block_patterns' );
-add_action( 'init', 'gutenberg_register_theme_block_patterns' );
-
-/**
- * Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase).
- *
- * @since 6.2.0
- *
- * @param array $pattern Pattern as returned from the Pattern Directory API.
- */
-function gutenberg_normalize_remote_pattern( $pattern ) {
- if ( isset( $pattern['block_types'] ) ) {
- $pattern['blockTypes'] = $pattern['block_types'];
- unset( $pattern['block_types'] );
- }
-
- if ( isset( $pattern['viewport_width'] ) ) {
- $pattern['viewportWidth'] = $pattern['viewport_width'];
- unset( $pattern['viewport_width'] );
- }
-
- return (array) $pattern;
-}
diff --git a/lib/compat/wordpress-6.2/block-template-utils.php b/lib/compat/wordpress-6.2/block-template-utils.php
deleted file mode 100644
index db9bf427e6b47..0000000000000
--- a/lib/compat/wordpress-6.2/block-template-utils.php
+++ /dev/null
@@ -1,97 +0,0 @@
- The template hierarchy.
- */
-function gutenberg_get_template_hierarchy( $slug, $is_custom = false, $template_prefix = '' ) {
- if ( 'index' === $slug ) {
- return array( 'index' );
- }
- if ( $is_custom ) {
- return array( 'page', 'singular', 'index' );
- }
- if ( 'front-page' === $slug ) {
- return array( 'front-page', 'home', 'index' );
- }
-
- $matches = array();
-
- $template_hierarchy = array( $slug );
- // Most default templates don't have `$template_prefix` assigned.
- if ( ! empty( $template_prefix ) ) {
- list( $type ) = explode( '-', $template_prefix );
- // We need these checks because we always add the `$slug` above.
- if ( ! in_array( $template_prefix, array( $slug, $type ), true ) ) {
- $template_hierarchy[] = $template_prefix;
- }
- if ( $slug !== $type ) {
- $template_hierarchy[] = $type;
- }
- } elseif ( preg_match( '/^(author|category|archive|tag|page)-.+$/', $slug, $matches ) ) {
- $template_hierarchy[] = $matches[1];
- } elseif ( preg_match( '/^(taxonomy|single)-(.+)$/', $slug, $matches ) ) {
- $type = $matches[1];
- $slug_remaining = $matches[2];
-
- $items = 'single' === $type ? get_post_types() : get_taxonomies();
- foreach ( $items as $item ) {
- if ( ! str_starts_with( $slug_remaining, $item ) ) {
- continue;
- }
-
- // If $slug_remaining is equal to $post_type or $taxonomy we have
- // the single-$post_type template or the taxonomy-$taxonomy template.
- if ( $slug_remaining === $item ) {
- $template_hierarchy[] = $type;
- break;
- }
-
- // If $slug_remaining is single-$post_type-$slug template.
- if ( strlen( $slug_remaining ) > strlen( $item ) + 1 ) {
- $template_hierarchy[] = "$type-$item";
- $template_hierarchy[] = $type;
- break;
- }
- }
- }
- // Handle `archive` template.
- if (
- str_starts_with( $slug, 'author' ) ||
- str_starts_with( $slug, 'taxonomy' ) ||
- str_starts_with( $slug, 'category' ) ||
- str_starts_with( $slug, 'tag' ) ||
- 'date' === $slug
- ) {
- $template_hierarchy[] = 'archive';
- }
- // Handle `single` template.
- if ( 'attachment' === $slug ) {
- $template_hierarchy[] = 'single';
- }
- // Handle `singular` template.
- if (
- str_starts_with( $slug, 'single' ) ||
- str_starts_with( $slug, 'page' ) ||
- 'attachment' === $slug
- ) {
- $template_hierarchy[] = 'singular';
- }
- $template_hierarchy[] = 'index';
- return $template_hierarchy;
-}
diff --git a/lib/compat/wordpress-6.2/blocks.php b/lib/compat/wordpress-6.2/blocks.php
deleted file mode 100644
index 94c6eaabcef9b..0000000000000
--- a/lib/compat/wordpress-6.2/blocks.php
+++ /dev/null
@@ -1,173 +0,0 @@
-= 6.2.
- *
- * @param string[] $attrs Array of allowed CSS attributes.
- * @return string[] CSS attributes.
- */
-function gutenberg_safe_style_attrs_6_2( $attrs ) {
- $attrs[] = 'position';
- $attrs[] = 'top';
- $attrs[] = 'right';
- $attrs[] = 'bottom';
- $attrs[] = 'left';
- $attrs[] = 'z-index';
- $attrs[] = 'box-shadow';
- $attrs[] = 'aspect-ratio';
-
- return $attrs;
-}
-add_filter( 'safe_style_css', 'gutenberg_safe_style_attrs_6_2' );
-
-/**
- * Helper function that constructs a WP_Query args array from
- * a `Query` block properties.
- *
- * It's used in QueryLoop, QueryPaginationNumbers and QueryPaginationNext blocks.
- *
- * `build_query_vars_from_query_block` was introduced in 5.8, for 6.1 we just need
- * to update that function and not create a new one.
- *
- * @param WP_Block $block Block instance.
- * @param int $page Current query's page.
- *
- * @return array Returns the constructed WP_Query arguments.
- */
-function gutenberg_build_query_vars_from_query_block( $block, $page ) {
- $query = array(
- 'post_type' => 'post',
- 'order' => 'DESC',
- 'orderby' => 'date',
- 'post__not_in' => array(),
- );
-
- if ( isset( $block->context['query'] ) ) {
- if ( ! empty( $block->context['query']['postType'] ) ) {
- $post_type_param = $block->context['query']['postType'];
- if ( is_post_type_viewable( $post_type_param ) ) {
- $query['post_type'] = $post_type_param;
- }
- }
- if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
- $sticky = get_option( 'sticky_posts' );
- if ( 'only' === $block->context['query']['sticky'] ) {
- /**
- * Passing an empty array to post__in will return have_posts() as true (and all posts will be returned).
- * Logic should be used before hand to determine if WP_Query should be used in the event that the array
- * being passed to post__in is empty.
- *
- * @see https://core.trac.wordpress.org/ticket/28099
- */
- $query['post__in'] = ! empty( $sticky ) ? $sticky : array( 0 );
- $query['ignore_sticky_posts'] = 1;
- } else {
- $query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
- }
- }
- if ( ! empty( $block->context['query']['exclude'] ) ) {
- $excluded_post_ids = array_map( 'intval', $block->context['query']['exclude'] );
- $excluded_post_ids = array_filter( $excluded_post_ids );
- $query['post__not_in'] = array_merge( $query['post__not_in'], $excluded_post_ids );
- }
- if (
- isset( $block->context['query']['perPage'] ) &&
- is_numeric( $block->context['query']['perPage'] )
- ) {
- $per_page = absint( $block->context['query']['perPage'] );
- $offset = 0;
-
- if (
- isset( $block->context['query']['offset'] ) &&
- is_numeric( $block->context['query']['offset'] )
- ) {
- $offset = absint( $block->context['query']['offset'] );
- }
-
- $query['offset'] = ( $per_page * ( $page - 1 ) ) + $offset;
- $query['posts_per_page'] = $per_page;
- }
-
- // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
- if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
- $tax_query = array();
- if ( ! empty( $block->context['query']['categoryIds'] ) ) {
- $tax_query[] = array(
- 'taxonomy' => 'category',
- 'terms' => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
- 'include_children' => false,
- );
- }
- if ( ! empty( $block->context['query']['tagIds'] ) ) {
- $tax_query[] = array(
- 'taxonomy' => 'post_tag',
- 'terms' => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
- 'include_children' => false,
- );
- }
- $query['tax_query'] = $tax_query;
- }
- if ( ! empty( $block->context['query']['taxQuery'] ) ) {
- $query['tax_query'] = array();
- foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
- if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
- $query['tax_query'][] = array(
- 'taxonomy' => $taxonomy,
- 'terms' => array_filter( array_map( 'intval', $terms ) ),
- 'include_children' => false,
- );
- }
- }
- }
- if (
- isset( $block->context['query']['order'] ) &&
- in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true )
- ) {
- $query['order'] = strtoupper( $block->context['query']['order'] );
- }
- if ( isset( $block->context['query']['orderBy'] ) ) {
- $query['orderby'] = $block->context['query']['orderBy'];
- }
- if (
- isset( $block->context['query']['author'] ) &&
- (int) $block->context['query']['author'] > 0
- ) {
- $query['author'] = (int) $block->context['query']['author'];
- }
- if ( ! empty( $block->context['query']['search'] ) ) {
- $query['s'] = $block->context['query']['search'];
- }
- if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) {
- $query['post_parent__in'] = array_filter( array_map( 'intval', $block->context['query']['parents'] ) );
- }
- }
-
- /**
- * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
- *
- * Anything to this filter should be compatible with the `WP_Query` API to form
- * the query context which will be passed down to the Query Loop Block's children.
- * This can help, for example, to include additional settings or meta queries not
- * directly supported by the core Query Loop Block, and extend its capabilities.
- *
- * Please note that this will only influence the query that will be rendered on the
- * front-end. The editor preview is not affected by this filter. Also, worth noting
- * that the editor preview uses the REST API, so, ideally, one should aim to provide
- * attributes which are also compatible with the REST API, in order to be able to
- * implement identical queries on both sides.
- *
- * @since 6.1.0
- *
- * @param array $query Array containing parameters for `WP_Query` as parsed by the block context.
- * @param WP_Block $block Block instance.
- * @param int $page Current query's page.
- */
- return apply_filters( 'query_loop_block_query_vars', $query, $block, $page );
-}
diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php
deleted file mode 100644
index 3897a8945a5f9..0000000000000
--- a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php
+++ /dev/null
@@ -1,84 +0,0 @@
-get_fields_for_response( $request );
- $keys = array( 'name', 'label', 'description' );
- $data = array();
- foreach ( $keys as $key ) {
- if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) {
- $data[ $key ] = $item[ $key ];
- }
- }
-
- $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
- $data = $this->add_additional_fields_to_object( $data, $request );
- $data = $this->filter_response_by_context( $data, $context );
- return rest_ensure_response( $data );
- }
-
- /**
- * Retrieves the block pattern category schema, conforming to JSON Schema.
- *
- * @since 6.0.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' => 'block-pattern-category',
- 'type' => 'object',
- 'properties' => array(
- 'name' => array(
- 'description' => __( 'The category name.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'label' => array(
- 'description' => __( 'The category label, in human readable format.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'description' => array(
- 'description' => __( 'The category description, in human readable format.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- ),
- );
-
- $this->schema = $schema;
-
- return $this->add_additional_fields_schema( $this->schema );
- }
-}
diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php
deleted file mode 100644
index 80de88aa31a81..0000000000000
--- a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php
+++ /dev/null
@@ -1,235 +0,0 @@
- 'call-to-action',
- 'columns' => 'text',
- 'query' => 'posts',
- );
-
- /**
- * Prepare a raw block pattern before it gets output in a REST API response.
- *
- * @since 6.0.0
- *
- * @param array $item Raw pattern as registered, before any changes.
- * @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 );
- $keys = array(
- 'name' => 'name',
- 'title' => 'title',
- 'description' => 'description',
- 'viewportWidth' => 'viewport_width',
- 'blockTypes' => 'block_types',
- 'postTypes' => 'post_types',
- 'categories' => 'categories',
- 'keywords' => 'keywords',
- 'content' => 'content',
- 'inserter' => 'inserter',
- 'templateTypes' => 'template_types',
- );
- $data = array();
- foreach ( $keys as $item_key => $rest_key ) {
- if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) {
- $data[ $rest_key ] = $item[ $item_key ];
- }
- }
- $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
- $data = $this->add_additional_fields_to_object( $data, $request );
- $data = $this->filter_response_by_context( $data, $context );
- return rest_ensure_response( $data );
- }
-
- /**
- * Retrieves the block pattern schema, conforming to JSON Schema.
- *
- * @since 6.0.0
- * @since 6.1.0 Added `post_types` property.
- *
- * @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' => 'block-pattern',
- 'type' => 'object',
- 'properties' => array(
- 'name' => array(
- 'description' => __( 'The pattern name.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'title' => array(
- 'description' => __( 'The pattern title, in human readable format.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'description' => array(
- 'description' => __( 'The pattern detailed description.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'viewport_width' => array(
- 'description' => __( 'The pattern viewport width for inserter preview.', 'gutenberg' ),
- 'type' => 'number',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'block_types' => array(
- 'description' => __( 'Block types that the pattern is intended to be used with.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'post_types' => array(
- 'description' => __( 'An array of post types that the pattern is restricted to be used with.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'categories' => array(
- 'description' => __( 'The pattern category slugs.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'keywords' => array(
- 'description' => __( 'The pattern keywords.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'template_types' => array(
- 'description' => __( 'An array of template types where the pattern fits.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'content' => array(
- 'description' => __( 'The pattern content.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'inserter' => array(
- 'description' => __( 'Determines whether the pattern is visible in inserter.', 'gutenberg' ),
- 'type' => 'boolean',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- ),
- );
-
- $this->schema = $schema;
-
- return $this->add_additional_fields_schema( $this->schema );
- }
-
- /**
- * Registers the routes for the objects of the controller.
- *
- * @since 6.0.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' ),
- ),
- 'schema' => array( $this, 'get_public_item_schema' ),
- ),
- true
- );
- }
- /**
- * Retrieves all block patterns.
- *
- * @since 6.0.0
- * @since 6.2.0 Added migration for old core pattern categories to the new ones.
- *
- * @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 ) {
- if ( ! $this->remote_patterns_loaded ) {
- // Load block patterns from w.org.
- gutenberg_load_remote_block_patterns(); // Patterns with the `core` keyword.
- gutenberg_load_remote_featured_patterns(); // Patterns in the `featured` category.
- gutenberg_register_remote_theme_patterns(); // Patterns requested by current theme.
-
- $this->remote_patterns_loaded = true;
- }
-
- $response = array();
- $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
- foreach ( $patterns as $pattern ) {
- $migrated_pattern = $this->migrate_pattern_categories( $pattern );
- $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request );
- $response[] = $this->prepare_response_for_collection( $prepared_pattern );
- }
- return rest_ensure_response( $response );
- }
-
- /**
- * Migrates old core pattern categories to new ones.
- *
- * Core pattern categories are being revamped and we need to handle the migration
- * to the new ones and ensure backwards compatibility.
- *
- * @since 6.2.0
- *
- * @param array $pattern Raw pattern as registered, before applying any changes.
- * @return array Migrated pattern.
- */
- protected function migrate_pattern_categories( $pattern ) {
- if ( isset( $pattern['categories'] ) && is_array( $pattern['categories'] ) ) {
- foreach ( $pattern['categories'] as $i => $category ) {
- if ( array_key_exists( $category, static::$categories_migration ) ) {
- $pattern['categories'][ $i ] = static::$categories_migration[ $category ];
- }
- }
- }
- return $pattern;
- }
-}
diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php
deleted file mode 100644
index 7043140c23fcf..0000000000000
--- a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php
+++ /dev/null
@@ -1,122 +0,0 @@
- true,
- 'order' => true,
- 'orderby' => true,
- 'page' => true,
- 'per_page' => true,
- 'search' => true,
- 'slug' => true,
- );
-
- $query_args = array_intersect_key( $request->get_params(), $valid_query_args );
-
- $query_args['locale'] = get_user_locale();
- $query_args['wp-version'] = $wp_version; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- it's defined in `version.php` above.
- $query_args['gutenberg-version'] = $gutenberg_data['Version'];
- $query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false;
- $query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false;
-
- $query_args = array_filter( $query_args );
-
- $transient_key = $this->get_transient_key( $query_args );
-
- /*
- * Use network-wide transient to improve performance. The locale is the only site
- * configuration that affects the response, and it's included in the transient key.
- */
- $raw_patterns = get_site_transient( $transient_key );
-
- if ( ! $raw_patterns ) {
- $api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args );
- if ( wp_http_supports( array( 'ssl' ) ) ) {
- $api_url = set_url_scheme( $api_url, 'https' );
- }
-
- /*
- * Default to a short TTL, to mitigate cache stampedes on high-traffic sites.
- * This assumes that most errors will be short-lived, e.g., packet loss that causes the
- * first request to fail, but a follow-up one will succeed. The value should be high
- * enough to avoid stampedes, but low enough to not interfere with users manually
- * re-trying a failed request.
- */
- $cache_ttl = 5;
- $wporg_response = wp_remote_get( $api_url );
- $raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) );
-
- if ( is_wp_error( $wporg_response ) ) {
- $raw_patterns = $wporg_response;
-
- } elseif ( ! is_array( $raw_patterns ) ) {
- // HTTP request succeeded, but response data is invalid.
- $raw_patterns = new WP_Error(
- 'pattern_api_failed',
- sprintf(
- /* translators: %s: Support forums URL. */
- __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.', 'gutenberg' ),
- __( 'https://wordpress.org/support/forums/', 'gutenberg' )
- ),
- array(
- 'response' => wp_remote_retrieve_body( $wporg_response ),
- )
- );
-
- } else {
- // Response has valid data.
- $cache_ttl = HOUR_IN_SECONDS;
- }
-
- set_site_transient( $transient_key, $raw_patterns, $cache_ttl );
- }
-
- if ( is_wp_error( $raw_patterns ) ) {
- $raw_patterns->add_data( array( 'status' => 500 ) );
-
- return $raw_patterns;
- }
-
- $response = array();
-
- if ( $raw_patterns ) {
- foreach ( $raw_patterns as $pattern ) {
- $response[] = $this->prepare_response_for_collection(
- $this->prepare_item_for_response( $pattern, $request )
- );
- }
- }
-
- return new WP_REST_Response( $response );
- }
-}
diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php
deleted file mode 100644
index 32a7a33157a74..0000000000000
--- a/lib/compat/wordpress-6.2/default-filters.php
+++ /dev/null
@@ -1,18 +0,0 @@
-name = $name;
- $this->value_starts_at = $value_start;
- $this->value_length = $value_length;
- $this->start = $start;
- $this->end = $end;
- $this->is_true = $is_true;
- }
-}
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
deleted file mode 100644
index e38bc55192317..0000000000000
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
+++ /dev/null
@@ -1,56 +0,0 @@
-start = $start;
- $this->end = $end;
- }
-}
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
deleted file mode 100644
index d61180074f608..0000000000000
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
+++ /dev/null
@@ -1,2291 +0,0 @@
- "c" not " c".
- * This would increase the size of the changes for some operations but leave more
- * natural-looking output HTML.
- * - Decode HTML character references within class names when matching. E.g. match having
- * class `1<"2` needs to recognize `class="1<"2"`. Currently the Tag Processor
- * will fail to find the right tag if the class name is encoded as such.
- * - Properly decode HTML character references in `get_attribute()`. PHP's
- * `html_entity_decode()` is wrong in a couple ways: it doesn't account for the
- * no-ambiguous-ampersand rule, and it improperly handles the way semicolons may
- * or may not terminate a character reference.
- *
- * @package WordPress
- * @subpackage HTML-API
- * @since 6.2.0
- */
-
-if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
- return;
-}
-
-/**
- * Modifies attributes in an HTML document for tags matching a query.
- *
- * ## Usage
- *
- * Use of this class requires three steps:
- *
- * 1. Create a new class instance with your input HTML document.
- * 2. Find the tag(s) you are looking for.
- * 3. Request changes to the attributes in those tag(s).
- *
- * Example:
- *
- * $tags = new WP_HTML_Tag_Processor( $html );
- * if ( $tags->next_tag( 'option' ) ) {
- * $tags->set_attribute( 'selected', true );
- * }
- *
- * ### Finding tags
- *
- * The `next_tag()` function moves the internal cursor through
- * your input HTML document until it finds a tag meeting any of
- * the supplied restrictions in the optional query argument. If
- * no argument is provided then it will find the next HTML tag,
- * regardless of what kind it is.
- *
- * If you want to _find whatever the next tag is_:
- *
- * $tags->next_tag();
- *
- * | Goal | Query |
- * |-----------------------------------------------------------|---------------------------------------------------------------------------------|
- * | Find any tag. | `$tags->next_tag();` |
- * | Find next image tag. | `$tags->next_tag( array( 'tag_name' => 'img' ) );` |
- * | Find next image tag (without passing the array). | `$tags->next_tag( 'img' );` |
- * | Find next tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'class_name' => 'fullwidth' ) );` |
- * | Find next image tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'tag_name' => 'img', 'class_name' => 'fullwidth' ) );` |
- *
- * If a tag was found meeting your criteria then `next_tag()`
- * will return `true` and you can proceed to modify it. If it
- * returns `false`, however, it failed to find the tag and
- * moved the cursor to the end of the file.
- *
- * Once the cursor reaches the end of the file the processor
- * is done and if you want to reach an earlier tag you will
- * need to recreate the processor and start over, as it's
- * unable to back up or move in reverse.
- *
- * See the section on bookmarks for an exception to this
- * no-backing-up rule.
- *
- * #### Custom queries
- *
- * Sometimes it's necessary to further inspect an HTML tag than
- * the query syntax here permits. In these cases one may further
- * inspect the search results using the read-only functions
- * provided by the processor or external state or variables.
- *
- * Example:
- *
- * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style.
- * $remaining_count = 5;
- * while ( $remaining_count > 0 && $tags->next_tag() ) {
- * if (
- * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) &&
- * 'jazzy' === $tags->get_attribute( 'data-style' )
- * ) {
- * $tags->add_class( 'theme-style-everest-jazz' );
- * $remaining_count--;
- * }
- * }
- *
- * `get_attribute()` will return `null` if the attribute wasn't present
- * on the tag when it was called. It may return `""` (the empty string)
- * in cases where the attribute was present but its value was empty.
- * For boolean attributes, those whose name is present but no value is
- * given, it will return `true` (the only way to set `false` for an
- * attribute is to remove it).
- *
- * ### Modifying HTML attributes for a found tag
- *
- * Once you've found the start of an opening tag you can modify
- * any number of the attributes on that tag. You can set a new
- * value for an attribute, remove the entire attribute, or do
- * nothing and move on to the next opening tag.
- *
- * Example:
- *
- * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) {
- * $tags->set_attribute( 'title', 'This groups the contained content.' );
- * $tags->remove_attribute( 'data-test-id' );
- * }
- *
- * If `set_attribute()` is called for an existing attribute it will
- * overwrite the existing value. Similarly, calling `remove_attribute()`
- * for a non-existing attribute has no effect on the document. Both
- * of these methods are safe to call without knowing if a given attribute
- * exists beforehand.
- *
- * ### Modifying CSS classes for a found tag
- *
- * The tag processor treats the `class` attribute as a special case.
- * Because it's a common operation to add or remove CSS classes, this
- * interface adds helper methods to make that easier.
- *
- * As with attribute values, adding or removing CSS classes is a safe
- * operation that doesn't require checking if the attribute or class
- * exists before making changes. If removing the only class then the
- * entire `class` attribute will be removed.
- *
- * Example:
- *
- * // from `Yippee!`
- * // to `Yippee!`
- * $tags->add_class( 'is-active' );
- *
- * // from `Yippee!`
- * // to `Yippee!`
- * $tags->add_class( 'is-active' );
- *
- * // from `Yippee!`
- * // to `Yippee!`
- * $tags->add_class( 'is-active' );
- *
- * // from ``
- * // to `
- * $tags->remove_class( 'rugby' );
- *
- * // from ``
- * // to `
- * $tags->remove_class( 'rugby' );
- *
- * // from ``
- * // to `
- * $tags->remove_class( 'rugby' );
- *
- * When class changes are enqueued but a direct change to `class` is made via
- * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`)
- * will take precedence over those made through `add_class` and `remove_class`.
- *
- * ### Bookmarks
- *
- * While scanning through the input HTMl document it's possible to set
- * a named bookmark when a particular tag is found. Later on, after
- * continuing to scan other tags, it's possible to `seek` to one of
- * the set bookmarks and then proceed again from that point forward.
- *
- * Because bookmarks create processing overhead one should avoid
- * creating too many of them. As a rule, create only bookmarks
- * of known string literal names; avoid creating "mark_{$index}"
- * and so on. It's fine from a performance standpoint to create a
- * bookmark and update it frequently, such as within a loop.
- *
- * $total_todos = 0;
- * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) {
- * $p->set_bookmark( 'list-start' );
- * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
- * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) {
- * $p->set_bookmark( 'list-end' );
- * $p->seek( 'list-start' );
- * $p->set_attribute( 'data-contained-todos', (string) $total_todos );
- * $total_todos = 0;
- * $p->seek( 'list-end' );
- * break;
- * }
- *
- * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) {
- * $total_todos++;
- * }
- * }
- * }
- *
- * ## Design and limitations
- *
- * The Tag Processor is designed to linearly scan HTML documents and tokenize
- * HTML tags and their attributes. It's designed to do this as efficiently as
- * possible without compromising parsing integrity. Therefore it will be
- * slower than some methods of modifying HTML, such as those incorporating
- * over-simplified PCRE patterns, but will not introduce the defects and
- * failures that those methods bring in, which lead to broken page renders
- * and often to security vulnerabilities. On the other hand, it will be faster
- * than full-blown HTML parsers such as DOMDocument and use considerably
- * less memory. It requires a negligible memory overhead, enough to consider
- * it a zero-overhead system.
- *
- * The performance characteristics are maintained by avoiding tree construction
- * and semantic cleanups which are specified in HTML5. Because of this, for
- * example, it's not possible for the Tag Processor to associate any given
- * opening tag with its corresponding closing tag, or to return the inner markup
- * inside an element. Systems may be built on top of the Tag Processor to do
- * this, but the Tag Processor is and should be constrained so it can remain an
- * efficient, low-level, and reliable HTML scanner.
- *
- * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy.
- * HTML5 specifies that certain invalid content be transformed into different forms
- * for display, such as removing null bytes from an input document and replacing
- * invalid characters with the Unicode replacement character U+FFFD �. Where errors
- * or transformations exist within the HTML5 specification, the Tag Processor leaves
- * those invalid inputs untouched, passing them through to the final browser to handle.
- * While this implies that certain operations will be non-spec-compliant, such as
- * reading the value of an attribute with invalid content, it also preserves a
- * simplicity and efficiency for handling those error cases.
- *
- * Most operations within the Tag Processor are designed to minimize the difference
- * between an input and output document for any given change. For example, the
- * `add_class` and `remove_class` methods preserve whitespace and the class ordering
- * within the `class` attribute; and when encountering tags with duplicated attributes,
- * the Tag Processor will leave those invalid duplicate attributes where they are but
- * update the proper attribute which the browser will read for parsing its value. An
- * exception to this rule is that all attribute updates store their values as
- * double-quoted strings, meaning that attributes on input with single-quoted or
- * unquoted values will appear in the output with double-quotes.
- *
- * @since 6.2.0
- */
-class WP_HTML_Tag_Processor {
- /**
- * The maximum number of bookmarks allowed to exist at
- * any given time.
- *
- * @since 6.2.0
- * @var int
- *
- * @see WP_HTML_Tag_Processor::set_bookmark()
- */
- const MAX_BOOKMARKS = 10;
-
- /**
- * Maximum number of times seek() can be called.
- * Prevents accidental infinite loops.
- *
- * @since 6.2.0
- * @var int
- *
- * @see WP_HTML_Tag_Processor::seek()
- */
- const MAX_SEEK_OPS = 1000;
-
- /**
- * The HTML document to parse.
- *
- * @since 6.2.0
- * @var string
- */
- protected $html;
-
- /**
- * The last query passed to next_tag().
- *
- * @since 6.2.0
- * @var array|null
- */
- private $last_query;
-
- /**
- * The tag name this processor currently scans for.
- *
- * @since 6.2.0
- * @var string|null
- */
- private $sought_tag_name;
-
- /**
- * The CSS class name this processor currently scans for.
- *
- * @since 6.2.0
- * @var string|null
- */
- private $sought_class_name;
-
- /**
- * The match offset this processor currently scans for.
- *
- * @since 6.2.0
- * @var int|null
- */
- private $sought_match_offset;
-
- /**
- * Whether to visit tag closers, e.g. , when walking an input document.
- *
- * @since 6.2.0
- * @var bool
- */
- private $stop_on_tag_closers;
-
- /**
- * How many bytes from the original HTML document have been read and parsed.
- *
- * This value points to the latest byte offset in the input document which
- * has been already parsed. It is the internal cursor for the Tag Processor
- * and updates while scanning through the HTML tokens.
- *
- * @since 6.2.0
- * @var int
- */
- private $bytes_already_parsed = 0;
-
- /**
- * Byte offset in input document where current tag name starts.
- *
- * Example:
- *
- *
...
- * 01234
- * - tag name starts at 1
- *
- * @since 6.2.0
- * @var int|null
- */
- private $tag_name_starts_at;
-
- /**
- * Byte length of current tag name.
- *
- * Example:
- *
- *
...
- * 01234
- * --- tag name length is 3
- *
- * @since 6.2.0
- * @var int|null
- */
- private $tag_name_length;
-
- /**
- * Byte offset in input document where current tag token ends.
- *
- * Example:
- *
- *
...
- * 0 1 |
- * 01234567890123456
- * --- tag name ends at 14
- *
- * @since 6.2.0
- * @var int|null
- */
- private $tag_ends_at;
-
- /**
- * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
.
- *
- * @var bool
- */
- private $is_closing_tag;
-
- /**
- * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name.
- *
- * Example:
- *
- * // Supposing the parser is working through this content
- * // and stops after recognizing the `id` attribute.
- * //
- * // ^ parsing will continue from this point.
- * $this->attributes = array(
- * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 )
- * );
- *
- * // When picking up parsing again, or when asking to find the
- * // `class` attribute we will continue and add to this array.
- * $this->attributes = array(
- * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ),
- * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 )
- * );
- *
- * // Note that only the `class` attribute value is stored in the index.
- * // That's because it is the only value used by this class at the moment.
- *
- * @since 6.2.0
- * @var WP_HTML_Attribute_Token[]
- */
- private $attributes = array();
-
- /**
- * Which class names to add or remove from a tag.
- *
- * These are tracked separately from attribute updates because they are
- * semantically distinct, whereas this interface exists for the common
- * case of adding and removing class names while other attributes are
- * generally modified as with DOM `setAttribute` calls.
- *
- * When modifying an HTML document these will eventually be collapsed
- * into a single `set_attribute( 'class', $changes )` call.
- *
- * Example:
- *
- * // Add the `wp-block-group` class, remove the `wp-group` class.
- * $classname_updates = array(
- * // Indexed by a comparable class name.
- * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS,
- * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS
- * );
- *
- * @since 6.2.0
- * @var bool[]
- */
- private $classname_updates = array();
-
- /**
- * Tracks a semantic location in the original HTML which
- * shifts with updates as they are applied to the document.
- *
- * @since 6.2.0
- * @var WP_HTML_Span[]
- */
- protected $bookmarks = array();
-
- const ADD_CLASS = true;
- const REMOVE_CLASS = false;
- const SKIP_CLASS = null;
-
- /**
- * Lexical replacements to apply to input HTML document.
- *
- * "Lexical" in this class refers to the part of this class which
- * operates on pure text _as text_ and not as HTML. There's a line
- * between the public interface, with HTML-semantic methods like
- * `set_attribute` and `add_class`, and an internal state that tracks
- * text offsets in the input document.
- *
- * When higher-level HTML methods are called, those have to transform their
- * operations (such as setting an attribute's value) into text diffing
- * operations (such as replacing the sub-string from indices A to B with
- * some given new string). These text-diffing operations are the lexical
- * updates.
- *
- * As new higher-level methods are added they need to collapse their
- * operations into these lower-level lexical updates since that's the
- * Tag Processor's internal language of change. Any code which creates
- * these lexical updates must ensure that they do not cross HTML syntax
- * boundaries, however, so these should never be exposed outside of this
- * class or any classes which intentionally expand its functionality.
- *
- * These are enqueued while editing the document instead of being immediately
- * applied to avoid processing overhead, string allocations, and string
- * copies when applying many updates to a single document.
- *
- * Example:
- *
- * // Replace an attribute stored with a new value, indices
- * // sourced from the lazily-parsed HTML recognizer.
- * $start = $attributes['src']->start;
- * $end = $attributes['src']->end;
- * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value );
- *
- * // Correspondingly, something like this will appear in this array.
- * $lexical_updates = array(
- * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' )
- * );
- *
- * @since 6.2.0
- * @var WP_HTML_Text_Replacement[]
- */
- protected $lexical_updates = array();
-
- /**
- * Tracks and limits `seek()` calls to prevent accidental infinite loops.
- *
- * @since 6.2.0
- * @var int
- *
- * @see WP_HTML_Tag_Processor::seek()
- */
- protected $seek_count = 0;
-
- /**
- * Constructor.
- *
- * @since 6.2.0
- *
- * @param string $html HTML to process.
- */
- public function __construct( $html ) {
- $this->html = $html;
- }
-
- /**
- * Finds the next tag matching the $query.
- *
- * @since 6.2.0
- *
- * @param array|string|null $query {
- * Optional. Which tag name to find, having which class, etc. Default is to find any tag.
- *
- * @type string|null $tag_name Which tag to find, or `null` for "any tag."
- * @type int|null $match_offset Find the Nth tag matching all search criteria.
- * 1 for "first" tag, 3 for "third," etc.
- * Defaults to first tag.
- * @type string|null $class_name Tag must contain this whole class name to match.
- * @type string|null $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
.
- * }
- * @return boolean Whether a tag was matched.
- */
- public function next_tag( $query = null ) {
- $this->parse_query( $query );
- $already_found = 0;
-
- do {
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- // Find the next tag if it exists.
- if ( false === $this->parse_next_tag() ) {
- $this->bytes_already_parsed = strlen( $this->html );
-
- return false;
- }
-
- // Parse all of its attributes.
- while ( $this->parse_next_attribute() ) {
- continue;
- }
-
- // Ensure that the tag closes before the end of the document.
- $tag_ends_at = strpos( $this->html, '>', $this->bytes_already_parsed );
- if ( false === $tag_ends_at ) {
- return false;
- }
- $this->tag_ends_at = $tag_ends_at;
- $this->bytes_already_parsed = $tag_ends_at;
-
- // Finally, check if the parsed tag and its attributes match the search query.
- if ( $this->matches() ) {
- ++$already_found;
- }
-
- /*
- * For non-DATA sections which might contain text that looks like HTML tags but
- * isn't, scan with the appropriate alternative mode. Looking at the first letter
- * of the tag name as a pre-check avoids a string allocation when it's not needed.
- */
- $t = $this->html[ $this->tag_name_starts_at ];
- if ( ! $this->is_closing_tag && ( 's' === $t || 'S' === $t || 't' === $t || 'T' === $t ) ) {
- $tag_name = $this->get_tag();
-
- if ( 'SCRIPT' === $tag_name && ! $this->skip_script_data() ) {
- $this->bytes_already_parsed = strlen( $this->html );
- return false;
- } elseif (
- ( 'TEXTAREA' === $tag_name || 'TITLE' === $tag_name ) &&
- ! $this->skip_rcdata( $tag_name )
- ) {
- $this->bytes_already_parsed = strlen( $this->html );
- return false;
- }
- }
- } while ( $already_found < $this->sought_match_offset );
-
- return true;
- }
-
-
- /**
- * Sets a bookmark in the HTML document.
- *
- * Bookmarks represent specific places or tokens in the HTML
- * document, such as a tag opener or closer. When applying
- * edits to a document, such as setting an attribute, the
- * text offsets of that token may shift; the bookmark is
- * kept updated with those shifts and remains stable unless
- * the entire span of text in which the token sits is removed.
- *
- * Release bookmarks when they are no longer needed.
- *
- * Example:
- *
- *
Surprising fact you may not know!
- * ^ ^
- * \-|-- this `H2` opener bookmark tracks the token
- *
- *
Surprising fact you may no…
- * ^ ^
- * \-|-- it shifts with edits
- *
- * Bookmarks provide the ability to seek to a previously-scanned
- * place in the HTML document. This avoids the need to re-scan
- * the entire document.
- *
- * Example:
- *
- *
One
Two
Three
- * ^^^^
- * want to note this last item
- *
- * $p = new WP_HTML_Tag_Processor( $html );
- * $in_list = false;
- * while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) {
- * if ( 'UL' === $p->get_tag() ) {
- * if ( $p->is_tag_closer() ) {
- * $in_list = false;
- * $p->set_bookmark( 'resume' );
- * if ( $p->seek( 'last-li' ) ) {
- * $p->add_class( 'last-li' );
- * }
- * $p->seek( 'resume' );
- * $p->release_bookmark( 'last-li' );
- * $p->release_bookmark( 'resume' );
- * } else {
- * $in_list = true;
- * }
- * }
- *
- * if ( 'LI' === $p->get_tag() ) {
- * $p->set_bookmark( 'last-li' );
- * }
- * }
- *
- * Bookmarks intentionally hide the internal string offsets
- * to which they refer. They are maintained internally as
- * updates are applied to the HTML document and therefore
- * retain their "position" - the location to which they
- * originally pointed. The inability to use bookmarks with
- * functions like `substr` is therefore intentional to guard
- * against accidentally breaking the HTML.
- *
- * Because bookmarks allocate memory and require processing
- * for every applied update, they are limited and require
- * a name. They should not be created with programmatically-made
- * names, such as "li_{$index}" with some loop. As a general
- * rule they should only be created with string-literal names
- * like "start-of-section" or "last-paragraph".
- *
- * Bookmarks are a powerful tool to enable complicated behavior.
- * Consider double-checking that you need this tool if you are
- * reaching for it, as inappropriate use could lead to broken
- * HTML structure or unwanted processing overhead.
- *
- * @since 6.2.0
- *
- * @param string $name Identifies this particular bookmark.
- * @return bool Whether the bookmark was successfully created.
- */
- public function set_bookmark( $name ) {
- if ( null === $this->tag_name_starts_at ) {
- return false;
- }
-
- if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= self::MAX_BOOKMARKS ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Too many bookmarks: cannot create any more.' ),
- '6.2.0'
- );
- return false;
- }
-
- $this->bookmarks[ $name ] = new WP_HTML_Span(
- $this->tag_name_starts_at - ( $this->is_closing_tag ? 2 : 1 ),
- $this->tag_ends_at
- );
-
- return true;
- }
-
-
- /**
- * Removes a bookmark that is no longer needed.
- *
- * Releasing a bookmark frees up the small
- * performance overhead it requires.
- *
- * @param string $name Name of the bookmark to remove.
- * @return bool Whether the bookmark already existed before removal.
- */
- public function release_bookmark( $name ) {
- if ( ! array_key_exists( $name, $this->bookmarks ) ) {
- return false;
- }
-
- unset( $this->bookmarks[ $name ] );
-
- return true;
- }
-
-
- /**
- * Skips contents of title and textarea tags.
- *
- * @since 6.2.0
- *
- * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state
- *
- * @param string $tag_name The lowercase tag name which will close the RCDATA region.
- * @return bool Whether an end to the RCDATA region was found before the end of the document.
- */
- private function skip_rcdata( $tag_name ) {
- $html = $this->html;
- $doc_length = strlen( $html );
- $tag_length = strlen( $tag_name );
-
- $at = $this->bytes_already_parsed;
-
- while ( false !== $at && $at < $doc_length ) {
- $at = strpos( $this->html, '', $at );
-
- // If there is no possible tag closer then fail.
- if ( false === $at || ( $at + $tag_length ) >= $doc_length ) {
- $this->bytes_already_parsed = $doc_length;
- return false;
- }
-
- $closer_potentially_starts_at = $at;
- $at += 2;
-
- /*
- * Find a case-insensitive match to the tag name.
- *
- * Because tag names are limited to US-ASCII there is no
- * need to perform any kind of Unicode normalization when
- * comparing; any character which could be impacted by such
- * normalization could not be part of a tag name.
- */
- for ( $i = 0; $i < $tag_length; $i++ ) {
- $tag_char = $tag_name[ $i ];
- $html_char = $html[ $at + $i ];
-
- if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
- $at += $i;
- continue 2;
- }
- }
-
- $at += $tag_length;
- $this->bytes_already_parsed = $at;
-
- /*
- * Ensure that the tag name terminates to avoid matching on
- * substrings of a longer tag name. For example, the sequence
- * "' !== $c ) {
- continue;
- }
-
- 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;
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Skips contents of script tags.
- *
- * @since 6.2.0
- *
- * @return bool Whether the script tag was closed before the end of the document.
- */
- private function skip_script_data() {
- $state = 'unescaped';
- $html = $this->html;
- $doc_length = strlen( $html );
- $at = $this->bytes_already_parsed;
-
- while ( false !== $at && $at < $doc_length ) {
- $at += strcspn( $html, '-<', $at );
-
- /*
- * For all script states a "-->" transitions
- * back into the normal unescaped script mode,
- * even if that's the current state.
- */
- if (
- $at + 2 < $doc_length &&
- '-' === $html[ $at ] &&
- '-' === $html[ $at + 1 ] &&
- '>' === $html[ $at + 2 ]
- ) {
- $at += 3;
- $state = 'unescaped';
- continue;
- }
-
- // Everything of interest past here starts with "<".
- if ( $at + 1 >= $doc_length || '<' !== $html[ $at++ ] ) {
- continue;
- }
-
- /*
- * Unlike with "-->", the "
- * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if (
- strlen( $html ) > $at + 3 &&
- '-' === $html[ $at + 2 ] &&
- '-' === $html[ $at + 3 ]
- ) {
- $closer_at = $at + 4;
- // If it's not possible to close the comment then there is nothing more to scan.
- if ( strlen( $html ) <= $closer_at ) {
- return false;
- }
-
- // Abruptly-closed empty comments are a sequence of dashes followed by `>`.
- $span_of_dashes = strspn( $html, '-', $closer_at );
- if ( '>' === $html[ $closer_at + $span_of_dashes ] ) {
- $at = $closer_at + $span_of_dashes + 1;
- continue;
- }
-
- /*
- * Comments may be closed by either a --> or an invalid --!>.
- * The first occurrence closes the comment.
- *
- * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment
- */
- $closer_at--; // Pre-increment inside condition below reduces risk of accidental infinite looping.
- while ( ++$closer_at < strlen( $html ) ) {
- $closer_at = strpos( $html, '--', $closer_at );
- if ( false === $closer_at ) {
- return false;
- }
-
- if ( $closer_at + 2 < strlen( $html ) && '>' === $html[ $closer_at + 2 ] ) {
- $at = $closer_at + 3;
- continue 2;
- }
-
- if ( $closer_at + 3 < strlen( $html ) && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) {
- $at = $closer_at + 4;
- continue 2;
- }
- }
- }
-
- /*
- *
- * The CDATA is case-sensitive.
- * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if (
- strlen( $html ) > $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 ) {
- return false;
- }
-
- $at = $closer_at + 3;
- continue;
- }
-
- /*
- *
- * These are ASCII-case-insensitive.
- * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if (
- strlen( $html ) > $at + 8 &&
- ( 'D' === $html[ $at + 2 ] || 'd' === $html[ $at + 2 ] ) &&
- ( 'O' === $html[ $at + 3 ] || 'o' === $html[ $at + 3 ] ) &&
- ( 'C' === $html[ $at + 4 ] || 'c' === $html[ $at + 4 ] ) &&
- ( 'T' === $html[ $at + 5 ] || 't' === $html[ $at + 5 ] ) &&
- ( 'Y' === $html[ $at + 6 ] || 'y' === $html[ $at + 6 ] ) &&
- ( 'P' === $html[ $at + 7 ] || 'p' === $html[ $at + 7 ] ) &&
- ( 'E' === $html[ $at + 8 ] || 'e' === $html[ $at + 8 ] )
- ) {
- $closer_at = strpos( $html, '>', $at + 9 );
- if ( false === $closer_at ) {
- return false;
- }
-
- $at = $closer_at + 1;
- continue;
- }
-
- /*
- * Anything else here is an incorrectly-opened comment and transitions
- * to the bogus comment state - skip to the nearest >.
- */
- $at = strpos( $html, '>', $at + 1 );
- continue;
- }
-
- /*
- * > is a missing end tag name, which is ignored.
- *
- * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name
- */
- if ( '>' === $html[ $at + 1 ] ) {
- $at++;
- continue;
- }
-
- /*
- * transitions to a bogus comment state – skip to the nearest >
- * 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 ) {
- return false;
- }
-
- $at = $closer_at + 1;
- continue;
- }
-
- /*
- * If a non-alpha starts the tag name in a tag closer it's a comment.
- * Find the first `>`, which closes the comment.
- *
- * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name
- */
- if ( $this->is_closing_tag ) {
- $closer_at = strpos( $html, '>', $at + 3 );
- if ( false === $closer_at ) {
- return false;
- }
-
- $at = $closer_at + 1;
- continue;
- }
-
- ++$at;
- }
-
- return false;
- }
-
- /**
- * Parses the next attribute.
- *
- * @since 6.2.0
- *
- * @return bool Whether an attribute was found before the end of the document.
- */
- 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 ) ) {
- return false;
- }
-
- /*
- * Treat the equal sign as a part of the attribute
- * name if it is the first encountered byte.
- *
- * @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
- */
- $name_length = '=' === $this->html[ $this->bytes_already_parsed ]
- ? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed + 1 )
- : strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed );
-
- // No attribute, just tag closer.
- if ( 0 === $name_length || $this->bytes_already_parsed + $name_length >= strlen( $this->html ) ) {
- return false;
- }
-
- $attribute_start = $this->bytes_already_parsed;
- $attribute_name = substr( $this->html, $attribute_start, $name_length );
- $this->bytes_already_parsed += $name_length;
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- $this->skip_whitespace();
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- $has_value = '=' === $this->html[ $this->bytes_already_parsed ];
- if ( $has_value ) {
- ++$this->bytes_already_parsed;
- $this->skip_whitespace();
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- switch ( $this->html[ $this->bytes_already_parsed ] ) {
- case "'":
- case '"':
- $quote = $this->html[ $this->bytes_already_parsed ];
- $value_start = $this->bytes_already_parsed + 1;
- $value_length = strcspn( $this->html, $quote, $value_start );
- $attribute_end = $value_start + $value_length + 1;
- $this->bytes_already_parsed = $attribute_end;
- break;
-
- default:
- $value_start = $this->bytes_already_parsed;
- $value_length = strcspn( $this->html, "> \t\f\r\n", $value_start );
- $attribute_end = $value_start + $value_length;
- $this->bytes_already_parsed = $attribute_end;
- }
- } else {
- $value_start = $this->bytes_already_parsed;
- $value_length = 0;
- $attribute_end = $attribute_start + $name_length;
- }
-
- if ( $attribute_end >= strlen( $this->html ) ) {
- return false;
- }
-
- if ( $this->is_closing_tag ) {
- return true;
- }
-
- /*
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
- */
- $comparable_name = strtolower( $attribute_name );
-
- // If an attribute is listed many times, only use the first declaration and ignore the rest.
- if ( ! array_key_exists( $comparable_name, $this->attributes ) ) {
- $this->attributes[ $comparable_name ] = new WP_HTML_Attribute_Token(
- $attribute_name,
- $value_start,
- $value_length,
- $attribute_start,
- $attribute_end,
- ! $has_value
- );
- }
-
- return true;
- }
-
- /**
- * Move the internal cursor past any immediate successive whitespace.
- *
- * @since 6.2.0
- *
- * @return void
- */
- private function skip_whitespace() {
- $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n", $this->bytes_already_parsed );
- }
-
- /**
- * Applies attribute updates and cleans up once a tag is fully parsed.
- *
- * @since 6.2.0
- *
- * @return void
- */
- private function after_tag() {
- $this->get_updated_html();
- $this->tag_name_starts_at = null;
- $this->tag_name_length = null;
- $this->tag_ends_at = null;
- $this->is_closing_tag = null;
- $this->attributes = array();
- }
-
- /**
- * Converts class name updates into tag attributes updates
- * (they are accumulated in different data formats for performance).
- *
- * @since 6.2.0
- *
- * @see WP_HTML_Tag_Processor::$lexical_updates
- * @see WP_HTML_Tag_Processor::$classname_updates
- *
- * @return void
- */
- private function class_name_updates_to_attributes_updates() {
- if ( count( $this->classname_updates ) === 0 ) {
- return;
- }
-
- $existing_class = $this->get_enqueued_attribute_value( 'class' );
- if ( null === $existing_class || true === $existing_class ) {
- $existing_class = '';
- }
-
- if ( false === $existing_class && isset( $this->attributes['class'] ) ) {
- $existing_class = substr(
- $this->html,
- $this->attributes['class']->value_starts_at,
- $this->attributes['class']->value_length
- );
- }
-
- if ( false === $existing_class ) {
- $existing_class = '';
- }
-
- /**
- * Updated "class" attribute value.
- *
- * This is incrementally built while scanning through the existing class
- * attribute, skipping removed classes on the way, and then appending
- * added classes at the end. Only when finished processing will the
- * value contain the final new value.
-
- * @var string $class
- */
- $class = '';
-
- /**
- * Tracks the cursor position in the existing
- * class attribute value while parsing.
- *
- * @var int $at
- */
- $at = 0;
-
- /**
- * Indicates if there's any need to modify the existing class attribute.
- *
- * If a call to `add_class()` and `remove_class()` wouldn't impact
- * the `class` attribute value then there's no need to rebuild it.
- * For example, when adding a class that's already present or
- * removing one that isn't.
- *
- * This flag enables a performance optimization when none of the enqueued
- * class updates would impact the `class` attribute; namely, that the
- * processor can continue without modifying the input document, as if
- * none of the `add_class()` or `remove_class()` calls had been made.
- *
- * This flag is set upon the first change that requires a string update.
- *
- * @var bool $modified
- */
- $modified = false;
-
- // Remove unwanted classes by only copying the new ones.
- $existing_class_length = strlen( $existing_class );
- while ( $at < $existing_class_length ) {
- // Skip to the first non-whitespace character.
- $ws_at = $at;
- $ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at );
- $at += $ws_length;
-
- // Capture the class name – it's everything until the next whitespace.
- $name_length = strcspn( $existing_class, " \t\f\r\n", $at );
- if ( 0 === $name_length ) {
- // If no more class names are found then that's the end.
- break;
- }
-
- $name = substr( $existing_class, $at, $name_length );
- $at += $name_length;
-
- // If this class is marked for removal, start processing the next one.
- $remove_class = (
- isset( $this->classname_updates[ $name ] ) &&
- self::REMOVE_CLASS === $this->classname_updates[ $name ]
- );
-
- // If a class has already been seen then skip it; it should not be added twice.
- if ( ! $remove_class ) {
- $this->classname_updates[ $name ] = self::SKIP_CLASS;
- }
-
- if ( $remove_class ) {
- $modified = true;
- continue;
- }
-
- /*
- * Otherwise, append it to the new "class" attribute value.
- *
- * There are options for handling whitespace between tags.
- * Preserving the existing whitespace produces fewer changes
- * to the HTML content and should clarify the before/after
- * content when debugging the modified output.
- *
- * This approach contrasts normalizing the inter-class
- * whitespace to a single space, which might appear cleaner
- * in the output HTML but produce a noisier change.
- */
- $class .= substr( $existing_class, $ws_at, $ws_length );
- $class .= $name;
- }
-
- // Add new classes by appending those which haven't already been seen.
- foreach ( $this->classname_updates as $name => $operation ) {
- if ( self::ADD_CLASS === $operation ) {
- $modified = true;
-
- $class .= strlen( $class ) > 0 ? ' ' : '';
- $class .= $name;
- }
- }
-
- $this->classname_updates = array();
- if ( ! $modified ) {
- return;
- }
-
- if ( strlen( $class ) > 0 ) {
- $this->set_attribute( 'class', $class );
- } else {
- $this->remove_attribute( 'class' );
- }
- }
-
- /**
- * Applies attribute updates to HTML document.
- *
- * @since 6.2.0
- * @since 6.2.1 Accumulates shift for internal cursor and passed pointer.
- *
- * @param int $shift_this_point Accumulate and return shift for this position.
- * @return int How many bytes the given pointer moved in response to the updates.
- */
- private function apply_attributes_updates( $shift_this_point = 0 ) {
- if ( ! count( $this->lexical_updates ) ) {
- return 0;
- }
-
- $accumulated_shift_for_given_point = 0;
-
- /*
- * Attribute updates can be enqueued in any order but updates
- * to the document must occur in lexical order; that is, each
- * replacement must be made before all others which follow it
- * at later string indices in the input document.
- *
- * Sorting avoid making out-of-order replacements which
- * can lead to mangled output, partially-duplicated
- * attributes, and overwritten attributes.
- */
- usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) );
-
- $bytes_already_copied = 0;
- $output_buffer = '';
- foreach ( $this->lexical_updates as $diff ) {
- $shift = strlen( $diff->text ) - ( $diff->end - $diff->start );
-
- // Adjust the cursor position by however much an update affects it.
- if ( $diff->start <= $this->bytes_already_parsed ) {
- $this->bytes_already_parsed += $shift;
- }
-
- // Accumulate shift of the given pointer within this function call.
- if ( $diff->start <= $shift_this_point ) {
- $accumulated_shift_for_given_point += $shift;
- }
-
- $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied );
- $output_buffer .= $diff->text;
- $bytes_already_copied = $diff->end;
- }
-
- $this->html = $output_buffer . substr( $this->html, $bytes_already_copied );
-
- /*
- * Adjust bookmark locations to account for how the text
- * replacements adjust offsets in the input document.
- */
- foreach ( $this->bookmarks as $bookmark ) {
- /*
- * Each lexical update which appears before the bookmark's endpoints
- * might shift the offsets for those endpoints. Loop through each change
- * and accumulate the total shift for each bookmark, then apply that
- * shift after tallying the full delta.
- */
- $head_delta = 0;
- $tail_delta = 0;
-
- foreach ( $this->lexical_updates as $diff ) {
- $update_head = $bookmark->start >= $diff->start;
- $update_tail = $bookmark->end >= $diff->start;
-
- if ( ! $update_head && ! $update_tail ) {
- break;
- }
-
- $delta = strlen( $diff->text ) - ( $diff->end - $diff->start );
-
- if ( $update_head ) {
- $head_delta += $delta;
- }
-
- if ( $update_tail ) {
- $tail_delta += $delta;
- }
- }
-
- $bookmark->start += $head_delta;
- $bookmark->end += $tail_delta;
- }
-
- $this->lexical_updates = array();
-
- return $accumulated_shift_for_given_point;
- }
-
- /**
- * Move the internal cursor in the Tag Processor to a given bookmark's location.
- *
- * In order to prevent accidental infinite loops, there's a
- * maximum limit on the number of times seek() can be called.
- *
- * @since 6.2.0
- *
- * @param string $bookmark_name Jump to the place in the document identified by this bookmark name.
- * @return bool Whether the internal cursor was successfully moved to the bookmark's location.
- */
- public function seek( $bookmark_name ) {
- if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Unknown bookmark name.' ),
- '6.2.0'
- );
- return false;
- }
-
- if ( ++$this->seek_count > self::MAX_SEEK_OPS ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Too many calls to seek() - this can lead to performance issues.' ),
- '6.2.0'
- );
- return false;
- }
-
- // Flush out any pending updates to the document.
- $this->get_updated_html();
-
- // 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' ) );
- }
-
- /**
- * Compare two WP_HTML_Text_Replacement objects.
- *
- * @since 6.2.0
- *
- * @param WP_HTML_Text_Replacement $a First attribute update.
- * @param WP_HTML_Text_Replacement $b Second attribute update.
- * @return int Comparison value for string order.
- */
- private static function sort_start_ascending( $a, $b ) {
- $by_start = $a->start - $b->start;
- if ( 0 !== $by_start ) {
- return $by_start;
- }
-
- $by_text = isset( $a->text, $b->text ) ? strcmp( $a->text, $b->text ) : 0;
- if ( 0 !== $by_text ) {
- return $by_text;
- }
-
- /*
- * This code should be unreachable, because it implies the two replacements
- * start at the same location and contain the same text.
- */
- return $a->end - $b->end;
- }
-
- /**
- * Return the enqueued value for a given attribute, if one exists.
- *
- * Enqueued updates can take different data types:
- * - If an update is enqueued and is boolean, the return will be `true`
- * - If an update is otherwise enqueued, the return will be the string value of that update.
- * - If an attribute is enqueued to be removed, the return will be `null` to indicate that.
- * - If no updates are enqueued, the return will be `false` to differentiate from "removed."
- *
- * @since 6.2.0
- *
- * @param string $comparable_name The attribute name in its comparable form.
- * @return string|boolean|null Value of enqueued update if present, otherwise false.
- */
- private function get_enqueued_attribute_value( $comparable_name ) {
- if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) {
- return false;
- }
-
- $enqueued_text = $this->lexical_updates[ $comparable_name ]->text;
-
- // Removed attributes erase the entire span.
- if ( '' === $enqueued_text ) {
- return null;
- }
-
- /*
- * Boolean attribute updates are just the attribute name without a corresponding value.
- *
- * This value might differ from the given comparable name in that there could be leading
- * or trailing whitespace, and that the casing follows the name given in `set_attribute`.
- *
- * Example:
- *
- * $p->set_attribute( 'data-TEST-id', 'update' );
- * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' );
- *
- * Detect this difference based on the absence of the `=`, which _must_ exist in any
- * attribute containing a value, e.g. ``.
- * ¹ ²
- * 1. Attribute with a string value.
- * 2. Boolean attribute whose value is `true`.
- */
- $equals_at = strpos( $enqueued_text, '=' );
- if ( false === $equals_at ) {
- return true;
- }
-
- /*
- * Finally, a normal update's value will appear after the `=` and
- * be double-quoted, as performed incidentally by `set_attribute`.
- *
- * e.g. `type="text"`
- * ¹² ³
- * 1. Equals is here.
- * 2. Double-quoting starts one after the equals sign.
- * 3. Double-quoting ends at the last character in the update.
- */
- $enqueued_value = substr( $enqueued_text, $equals_at + 2, -1 );
- return html_entity_decode( $enqueued_value );
- }
-
- /**
- * Returns the value of a requested attribute from a matched tag opener if that attribute exists.
- *
- * Example:
- *
- * $p = new WP_HTML_Tag_Processor( '
Test
' );
- * $p->next_tag( array( 'class_name' => 'test' ) ) === true;
- * $p->get_attribute( 'data-test-id' ) === '14';
- * $p->get_attribute( 'enabled' ) === true;
- * $p->get_attribute( 'aria-label' ) === null;
- *
- * $p->next_tag() === false;
- * $p->get_attribute( 'class' ) === null;
- *
- * @since 6.2.0
- *
- * @param string $name Name of attribute whose value is requested.
- * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`.
- */
- public function get_attribute( $name ) {
- if ( null === $this->tag_name_starts_at ) {
- return null;
- }
-
- $comparable = strtolower( $name );
-
- /*
- * For every attribute other than `class` it's possible to perform a quick check if
- * there's an enqueued lexical update whose value takes priority over what's found in
- * the input document.
- *
- * The `class` attribute is special though because of the exposed helpers `add_class`
- * and `remove_class`. These form a builder for the `class` attribute, so an additional
- * check for enqueued class changes is required in addition to the check for any enqueued
- * attribute values. If any exist, those enqueued class changes must first be flushed out
- * into an attribute value update.
- */
- if ( 'class' === $name ) {
- $this->class_name_updates_to_attributes_updates();
- }
-
- // Return any enqueued attribute value updates if they exist.
- $enqueued_value = $this->get_enqueued_attribute_value( $comparable );
- if ( false !== $enqueued_value ) {
- return $enqueued_value;
- }
-
- if ( ! isset( $this->attributes[ $comparable ] ) ) {
- return null;
- }
-
- $attribute = $this->attributes[ $comparable ];
-
- /*
- * This flag distinguishes an attribute with no value
- * from an attribute with an empty string value. For
- * unquoted attributes this could look very similar.
- * It refers to whether an `=` follows the name.
- *
- * e.g.
- * ¹ ²
- * 1. Attribute `boolean-attribute` is `true`.
- * 2. Attribute `empty-attribute` is `""`.
- */
- if ( true === $attribute->is_true ) {
- return true;
- }
-
- $raw_value = substr( $this->html, $attribute->value_starts_at, $attribute->value_length );
-
- return html_entity_decode( $raw_value );
- }
-
- /**
- * Gets lowercase names of all attributes matching a given prefix in the current tag.
- *
- * Note that matching is case-insensitive. This is in accordance with the spec:
- *
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * Example:
- *
- * $p = new WP_HTML_Tag_Processor( '
' );
- * $p->next_tag() === true;
- * $p->get_tag() === 'DIV';
- *
- * $p->next_tag() === false;
- * $p->get_tag() === null;
- *
- * @since 6.2.0
- *
- * @return string|null Name of currently matched tag in input HTML, or `null` if none found.
- */
- public function get_tag() {
- 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 );
- }
-
- /**
- * Indicates if the current tag token is a tag closer.
- *
- * Example:
- *
- * $p = new WP_HTML_Tag_Processor( '' );
- * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
- * $p->is_tag_closer() === false;
- *
- * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
- * $p->is_tag_closer() === true;
- *
- * @since 6.2.0
- *
- * @return bool Whether the current tag is a tag closer.
- */
- public function is_tag_closer() {
- return $this->is_closing_tag;
- }
-
- /**
- * Updates or creates a new attribute on the currently matched tag with the passed value.
- *
- * For boolean attributes special handling is provided:
- * - When `true` is passed as the value, then only the attribute name is added to the tag.
- * - When `false` is passed, the attribute gets removed if it existed before.
- *
- * For string attributes, the value is escaped using the `esc_attr` function.
- *
- * @since 6.2.0
- * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names.
- *
- * @param string $name The attribute name to target.
- * @param string|bool $value The new attribute value.
- * @return bool Whether an attribute value was set.
- */
- public function set_attribute( $name, $value ) {
- if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
- return false;
- }
-
- /*
- * WordPress rejects more characters than are strictly forbidden
- * in HTML5. This is to prevent additional security risks deeper
- * in the WordPress and plugin stack. Specifically the
- * less-than (<) greater-than (>) and ampersand (&) aren't allowed.
- *
- * The use of a PCRE match enables looking for specific Unicode
- * code points without writing a UTF-8 decoder. Whereas scanning
- * for one-byte characters is trivial (with `strcspn`), scanning
- * for the longer byte sequences would be more complicated. Given
- * that this shouldn't be in the hot path for execution, it's a
- * reasonable compromise in efficiency without introducing a
- * noticeable impact on the overall system.
- *
- * @see https://html.spec.whatwg.org/#attributes-2
- *
- * @TODO as the only regex pattern maybe we should take it out? are
- * Unicode patterns available broadly in Core?
- */
- if ( preg_match(
- '~[' .
- // Syntax-like characters.
- '"\'>& =' .
- // Control characters.
- '\x{00}-\x{1F}' .
- // HTML noncharacters.
- '\x{FDD0}-\x{FDEF}' .
- '\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}' .
- '\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}' .
- '\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}' .
- '\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}' .
- '\x{10FFFE}\x{10FFFF}' .
- ']~Ssu',
- $name
- ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Invalid attribute name.' ),
- '6.2.0'
- );
-
- return false;
- }
-
- /*
- * > The values "true" and "false" are not allowed on boolean attributes.
- * > To represent a false value, the attribute has to be omitted altogether.
- * - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes
- */
- if ( false === $value ) {
- return $this->remove_attribute( $name );
- }
-
- if ( true === $value ) {
- $updated_attribute = $name;
- } else {
- $escaped_new_value = esc_attr( $value );
- $updated_attribute = "{$name}=\"{$escaped_new_value}\"";
- }
-
- /*
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
- */
- $comparable_name = strtolower( $name );
-
- if ( isset( $this->attributes[ $comparable_name ] ) ) {
- /*
- * Update an existing attribute.
- *
- * Example – set attribute id to "new" in :
- *
- *
- * ^-------------^
- * start end
- * replacement: `id="new"`
- *
- * Result:
- */
- $existing_attribute = $this->attributes[ $comparable_name ];
- $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
- $existing_attribute->start,
- $existing_attribute->end,
- $updated_attribute
- );
- } else {
- /*
- * Create a new attribute at the tag's name end.
- *
- * Example – add attribute id="new" to :
- *
- *
- * ^
- * start and end
- * replacement: ` id="new"`
- *
- * Result:
- */
- $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
- $this->tag_name_starts_at + $this->tag_name_length,
- $this->tag_name_starts_at + $this->tag_name_length,
- ' ' . $updated_attribute
- );
- }
-
- /*
- * Any calls to update the `class` attribute directly should wipe out any
- * enqueued class changes from `add_class` and `remove_class`.
- */
- if ( 'class' === $comparable_name && ! empty( $this->classname_updates ) ) {
- $this->classname_updates = array();
- }
-
- return true;
- }
-
- /**
- * Remove an attribute from the currently-matched tag.
- *
- * @since 6.2.0
- *
- * @param string $name The attribute name to remove.
- * @return bool Whether an attribute was removed.
- */
- public function remove_attribute( $name ) {
- if ( $this->is_closing_tag ) {
- return false;
- }
-
- /*
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
- */
- $name = strtolower( $name );
-
- /*
- * Any calls to update the `class` attribute directly should wipe out any
- * enqueued class changes from `add_class` and `remove_class`.
- */
- if ( 'class' === $name && count( $this->classname_updates ) !== 0 ) {
- $this->classname_updates = array();
- }
-
- /*
- * If updating an attribute that didn't exist in the input
- * document, then remove the enqueued update and move on.
- *
- * For example, this might occur when calling `remove_attribute()`
- * after calling `set_attribute()` for the same attribute
- * and when that attribute wasn't originally present.
- */
- if ( ! isset( $this->attributes[ $name ] ) ) {
- if ( isset( $this->lexical_updates[ $name ] ) ) {
- unset( $this->lexical_updates[ $name ] );
- }
- return false;
- }
-
- /*
- * Removes an existing tag attribute.
- *
- * Example – remove the attribute id from :
- *
- * ^-------------^
- * start end
- * replacement: ``
- *
- * Result:
- */
- $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement(
- $this->attributes[ $name ]->start,
- $this->attributes[ $name ]->end,
- ''
- );
-
- return true;
- }
-
- /**
- * Adds a new class name to the currently matched tag.
- *
- * @since 6.2.0
- *
- * @param string $class_name The class name to add.
- * @return bool Whether the class was set to be added.
- */
- public function add_class( $class_name ) {
- if ( $this->is_closing_tag ) {
- return false;
- }
-
- if ( null !== $this->tag_name_starts_at ) {
- $this->classname_updates[ $class_name ] = self::ADD_CLASS;
- }
-
- return true;
- }
-
- /**
- * Removes a class name from the currently matched tag.
- *
- * @since 6.2.0
- *
- * @param string $class_name The class name to remove.
- * @return bool Whether the class was set to be removed.
- */
- public function remove_class( $class_name ) {
- if ( $this->is_closing_tag ) {
- return false;
- }
-
- if ( null !== $this->tag_name_starts_at ) {
- $this->classname_updates[ $class_name ] = self::REMOVE_CLASS;
- }
-
- return true;
- }
-
- /**
- * Returns the string representation of the HTML Tag Processor.
- *
- * @since 6.2.0
- *
- * @see WP_HTML_Tag_Processor::get_updated_html()
- *
- * @return string The processed HTML.
- */
- public function __toString() {
- return $this->get_updated_html();
- }
-
- /**
- * Returns the string representation of the HTML Tag Processor.
- *
- * @since 6.2.0
- * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates.
- *
- * @return string The processed HTML.
- */
- public function get_updated_html() {
- $requires_no_updating = 0 === count( $this->classname_updates ) && 0 === count( $this->lexical_updates );
-
- /*
- * When there is nothing more to update and nothing has already been
- * updated, return the original document and avoid a string copy.
- */
- if ( $requires_no_updating ) {
- return $this->html;
- }
-
- /*
- * Keep track of the position right before the current tag. This will
- * be necessary for reparsing the current tag after updating the HTML.
- */
- $before_current_tag = $this->tag_name_starts_at - 1;
-
- /*
- * 1. Apply the enqueued edits and update all the pointers to reflect those changes.
- */
- $this->class_name_updates_to_attributes_updates();
- $before_current_tag += $this->apply_attributes_updates( $before_current_tag );
-
- /*
- * 2. Rewind to before the current tag and reparse to get updated attributes.
- *
- * At this point the internal cursor points to the end of the tag name.
- * Rewind before the tag name starts so that it's as if the cursor didn't
- * move; a call to `next_tag()` will reparse the recently-updated attributes
- * and additional calls to modify the attributes will apply at this same
- * location.
- *
- *
Previous HTMLMore HTML
- * ^ | back up by the length of the tag name plus the opening <
- * \<-/ back up by strlen("em") + 1 ==> 3
- */
-
- // Store existing state so it can be restored after reparsing.
- $previous_parsed_byte_count = $this->bytes_already_parsed;
- $previous_query = $this->last_query;
-
- // Reparse attributes.
- $this->bytes_already_parsed = $before_current_tag;
- $this->next_tag();
-
- // Restore previous state.
- $this->bytes_already_parsed = $previous_parsed_byte_count;
- $this->parse_query( $previous_query );
-
- return $this->html;
- }
-
- /**
- * Parses tag query input into internal search criteria.
- *
- * @since 6.2.0
- *
- * @param array|string|null $query {
- * Optional. Which tag name to find, having which class, etc. Default is to find any tag.
- *
- * @type string|null $tag_name Which tag to find, or `null` for "any tag."
- * @type int|null $match_offset Find the Nth tag matching all search criteria.
- * 1 for "first" tag, 3 for "third," etc.
- * Defaults to first tag.
- * @type string|null $class_name Tag must contain this class name to match.
- * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
.
- * }
- * @return void
- */
- private function parse_query( $query ) {
- if ( null !== $query && $query === $this->last_query ) {
- return;
- }
-
- $this->last_query = $query;
- $this->sought_tag_name = null;
- $this->sought_class_name = null;
- $this->sought_match_offset = 1;
- $this->stop_on_tag_closers = false;
-
- // A single string value means "find the tag of this name".
- if ( is_string( $query ) ) {
- $this->sought_tag_name = $query;
- return;
- }
-
- // An empty query parameter applies no restrictions on the search.
- if ( null === $query ) {
- return;
- }
-
- // If not using the string interface, an associative array is required.
- if ( ! is_array( $query ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'The query argument must be an array or a tag name.' ),
- '6.2.0'
- );
- return;
- }
-
- if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) {
- $this->sought_tag_name = $query['tag_name'];
- }
-
- if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) {
- $this->sought_class_name = $query['class_name'];
- }
-
- if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) {
- $this->sought_match_offset = $query['match_offset'];
- }
-
- if ( isset( $query['tag_closers'] ) ) {
- $this->stop_on_tag_closers = 'visit' === $query['tag_closers'];
- }
- }
-
-
- /**
- * Checks whether a given tag and its attributes match the search criteria.
- *
- * @since 6.2.0
- *
- * @return boolean Whether the given tag and its attribute match the search criteria.
- */
- private function matches() {
- if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) {
- return false;
- }
-
- // Does the tag name match the requested tag name in a case-insensitive manner?
- if ( null !== $this->sought_tag_name ) {
- /*
- * String (byte) length lookup is fast. If they aren't the
- * same length then they can't be the same string values.
- */
- if ( strlen( $this->sought_tag_name ) !== $this->tag_name_length ) {
- return false;
- }
-
- /*
- * Check each character to determine if they are the same.
- * Defer calls to `strtoupper()` to avoid them when possible.
- * Calling `strcasecmp()` here tested slowed than comparing each
- * character, so unless benchmarks show otherwise, it should
- * not be used.
- *
- * It's expected that most of the time that this runs, a
- * lower-case tag name will be supplied and the input will
- * contain lower-case tag names, thus normally bypassing
- * the case comparison code.
- */
- for ( $i = 0; $i < $this->tag_name_length; $i++ ) {
- $html_char = $this->html[ $this->tag_name_starts_at + $i ];
- $tag_char = $this->sought_tag_name[ $i ];
-
- if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
- return false;
- }
- }
- }
-
- $needs_class_name = null !== $this->sought_class_name;
-
- if ( $needs_class_name && ! isset( $this->attributes['class'] ) ) {
- return false;
- }
-
- /*
- * Match byte-for-byte (case-sensitive and encoding-form-sensitive) on the class name.
- *
- * This will overlook certain classes that exist in other lexical variations
- * than was supplied to the search query, but requires more complicated searching.
- */
- if ( $needs_class_name ) {
- $class_start = $this->attributes['class']->value_starts_at;
- $class_end = $class_start + $this->attributes['class']->value_length;
- $class_at = $class_start;
-
- /*
- * Ensure that boundaries surround the class name to avoid matching on
- * substrings of a longer name. For example, the sequence "not-odd"
- * should not match for the class "odd" even though "odd" is found
- * within the class attribute text.
- *
- * See https://html.spec.whatwg.org/#attributes-3
- * See https://html.spec.whatwg.org/#space-separated-tokens
- */
- while (
- // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
- false !== ( $class_at = strpos( $this->html, $this->sought_class_name, $class_at ) ) &&
- $class_at < $class_end
- ) {
- /*
- * Verify this class starts at a boundary.
- */
- if ( $class_at > $class_start ) {
- $character = $this->html[ $class_at - 1 ];
-
- if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) {
- $class_at += strlen( $this->sought_class_name );
- continue;
- }
- }
-
- /*
- * Verify this class ends at a boundary as well.
- */
- if ( $class_at + strlen( $this->sought_class_name ) < $class_end ) {
- $character = $this->html[ $class_at + strlen( $this->sought_class_name ) ];
-
- if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) {
- $class_at += strlen( $this->sought_class_name );
- continue;
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- return true;
- }
-}
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
deleted file mode 100644
index b3f70c8e7c57f..0000000000000
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
+++ /dev/null
@@ -1,63 +0,0 @@
-start = $start;
- $this->end = $end;
- $this->text = $text;
- }
-}
diff --git a/lib/compat/wordpress-6.2/menu.php b/lib/compat/wordpress-6.2/menu.php
deleted file mode 100644
index e0d582ad3dc98..0000000000000
--- a/lib/compat/wordpress-6.2/menu.php
+++ /dev/null
@@ -1,35 +0,0 @@
- $menu_item ) {
- if ( str_contains( $menu_item[2], 'site-editor.php?postType=wp_template_part' ) && ! str_contains( $menu_item[2], 'path=' ) ) {
- $submenu['themes.php'][ $index ][2] = 'site-editor.php?postType=wp_template_part&path=/wp_template_part/all';
- break;
- }
- }
-}
-add_action( 'admin_menu', 'gutenberg_update_template_parts_menu_url' );
diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php
deleted file mode 100644
index 97f7daecdff2f..0000000000000
--- a/lib/compat/wordpress-6.2/rest-api.php
+++ /dev/null
@@ -1,145 +0,0 @@
-register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' );
-
-/**
- * Add extra collection params to pattern directory requests.
- *
- * @param array $query_params JSON Schema-formatted collection parameters.
- * @return array Updated parameters.
- */
-function gutenberg_pattern_directory_collection_params_6_2( $query_params ) {
- $query_params['page'] = array(
- 'description' => __( 'Current page of the collection.', 'gutenberg' ),
- 'type' => 'integer',
- 'default' => 1,
- 'sanitize_callback' => 'absint',
- 'validate_callback' => 'rest_validate_request_arg',
- 'minimum' => 1,
- );
-
- $query_params['per_page'] = array(
- 'description' => __( 'Maximum number of items to be returned in result set.', 'gutenberg' ),
- 'type' => 'integer',
- 'default' => 100,
- 'minimum' => 1,
- 'maximum' => 100,
- 'sanitize_callback' => 'absint',
- 'validate_callback' => 'rest_validate_request_arg',
- );
-
- $query_params['offset'] = array(
- 'description' => __( 'Offset the result set by a specific number of items.', 'gutenberg' ),
- 'type' => 'integer',
- );
-
- $query_params['order'] = array(
- 'description' => __( 'Order sort attribute ascending or descending.', 'gutenberg' ),
- 'type' => 'string',
- 'default' => 'desc',
- 'enum' => array( 'asc', 'desc' ),
- );
-
- $query_params['orderby'] = array(
- 'description' => __( 'Sort collection by post attribute.', 'gutenberg' ),
- 'type' => 'string',
- 'default' => 'date',
- 'enum' => array(
- 'author',
- 'date',
- 'id',
- 'include',
- 'modified',
- 'parent',
- 'relevance',
- 'slug',
- 'include_slugs',
- 'title',
- 'favorite_count',
- ),
- );
-
- return $query_params;
-}
-add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' );
-
-/**
- * Updates REST API response for the sidebars and marks them as 'inactive'.
- *
- * Note: This can be a part of the `prepare_item_for_response` in `class-wp-rest-sidebars-controller.php`.
- *
- * @param WP_REST_Response $response The sidebar response object.
- * @return WP_REST_Response $response Updated response object.
- */
-function gutenberg_modify_rest_sidebars_response( $response ) {
- if ( wp_is_block_theme() ) {
- $response->data['status'] = 'inactive';
- }
- return $response;
-}
-add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' );
-
-if ( ! function_exists( 'add_block_pattern_block_types_schema' ) ) {
- /**
- * Add the `block_types` value to the `pattern-directory-item` schema.
- *
- * @since 6.2.0 Added 'block_types' property.
- */
- function add_block_pattern_block_types_schema() {
- register_rest_field(
- 'pattern-directory-item',
- 'block_types',
- array(
- 'schema' => array(
- 'description' => __( 'The block types which can use this pattern.', 'gutenberg' ),
- 'type' => 'array',
- 'uniqueItems' => true,
- 'items' => array( 'type' => 'string' ),
- 'context' => array( 'view', 'embed' ),
- ),
- )
- );
- }
-}
-add_filter( 'rest_api_init', 'add_block_pattern_block_types_schema' );
-
-
-if ( ! function_exists( 'filter_block_pattern_response' ) ) {
- /**
- * Add the `block_types` value into the API response.
- *
- * @since 6.2.0 Added 'block_types' property.
- *
- * @param WP_REST_Response $response The response object.
- * @param object $raw_pattern The unprepared pattern.
- */
- function filter_block_pattern_response( $response, $raw_pattern ) {
- $data = $response->get_data();
- $data['block_types'] = array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types );
- $response->set_data( $data );
- return $response;
- }
-}
-add_filter( 'rest_prepare_block_pattern', 'filter_block_pattern_response', 10, 2 );
-
-
-/**
- * Registers the block pattern directory.
- */
-function gutenberg_register_rest_pattern_directory() {
- $pattern_directory_controller = new Gutenberg_REST_Pattern_Directory_Controller_6_2();
- $pattern_directory_controller->register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' );
diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php
deleted file mode 100644
index 37c1ced3c8cfc..0000000000000
--- a/lib/compat/wordpress-6.2/script-loader.php
+++ /dev/null
@@ -1,75 +0,0 @@
-query( 'wp-inert-polyfill', 'registered' );
- if ( ! $script ) {
- $scripts->add( 'wp-inert-polyfill', gutenberg_url( 'build/vendors/inert-polyfill' . $extension ), array() );
- }
-
- $script = $scripts->query( 'wp-polyfill', 'registered' );
- $script->deps = array_merge( $script->deps, array( 'wp-inert-polyfill' ) );
-}
-add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts_62' );
-
-/**
- * This function takes care of adding inline styles
- * in the proper place, depending on the theme in use.
- *
- * This method was added to core in 5.9.1, but with a single param ($style). The second param ($priority) was
- * added post 6.0, so the 6.1 release needs to have wp_enqueue_block_support_styles updated to include this param.
- *
- * For block themes, it's loaded in the head.
- * For classic ones, it's loaded in the body
- * because the wp_head action happens before
- * the render_block.
- *
- * @link https://core.trac.wordpress.org/ticket/53494.
- *
- * @deprecated 6.2 Block supports styles are now stored for enqueuing via the style engine API. See: packages/style-engine/README.md.
- *
- * @param string $style String containing the CSS styles to be added.
- * @param int $priority To set the priority for the add_action.
- */
-function gutenberg_enqueue_block_support_styles( $style, $priority = 10 ) {
- _deprecated_function( __FUNCTION__, '6.2' );
-
- $action_hook_name = 'wp_footer';
- if ( wp_is_block_theme() ) {
- $action_hook_name = 'wp_head';
- }
- add_action(
- $action_hook_name,
- static function () use ( $style ) {
- echo "\n";
- },
- $priority
- );
-}
-
-add_filter(
- 'block_editor_settings_all',
- static function( $settings ) {
- // We must override what core is passing now.
- $settings['__unstableIsBlockBasedTheme'] = wp_is_block_theme();
- return $settings;
- },
- 100
-);
diff --git a/lib/compat/wordpress-6.2/site-editor.php b/lib/compat/wordpress-6.2/site-editor.php
deleted file mode 100644
index b6246e49c6d11..0000000000000
--- a/lib/compat/wordpress-6.2/site-editor.php
+++ /dev/null
@@ -1,24 +0,0 @@
-post ) ) {
- return $settings;
- }
-
- unset( $settings['__unstableHomeTemplate'] );
-
- return $settings;
-}
-add_filter( 'block_editor_settings_all', 'gutenberg_site_editor_unset_homepage_setting', 10, 2 );
diff --git a/lib/compat/wordpress-6.2/theme.php b/lib/compat/wordpress-6.2/theme.php
deleted file mode 100644
index 3e7b31109745a..0000000000000
--- a/lib/compat/wordpress-6.2/theme.php
+++ /dev/null
@@ -1,23 +0,0 @@
-is_block_theme() ) {
- set_theme_mod( 'wp_classic_sidebars', $wp_registered_sidebars );
- }
-}
-add_action( 'switch_theme', 'gutenberg_set_classic_sidebars', 10, 2 );
diff --git a/lib/compat/wordpress-6.2/widgets.php b/lib/compat/wordpress-6.2/widgets.php
deleted file mode 100644
index ce37f1bd9a34d..0000000000000
--- a/lib/compat/wordpress-6.2/widgets.php
+++ /dev/null
@@ -1,35 +0,0 @@
-is_registered( $pattern_name ) || $registry->is_registered( "core/$pattern_name" );
@@ -154,7 +154,7 @@ function gutenberg_register_remote_theme_patterns() {
$patterns_registry = WP_Block_Patterns_Registry::get_instance();
foreach ( $patterns as $pattern ) {
$pattern['source'] = 'pattern-directory/theme'; // Added in 6.3.0.
- $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern );
+ $normalized_pattern = wp_normalize_remote_block_pattern( $pattern );
$pattern_name = sanitize_title( $normalized_pattern['title'] );
// Some patterns might be already registered as core patterns with the `core` prefix.
$is_registered = $patterns_registry->is_registered( $pattern_name ) || $patterns_registry->is_registered( "core/$pattern_name" );
diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php
index ac40e6b842f52..0a5b026cded3b 100644
--- a/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php
+++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php
@@ -13,7 +13,15 @@
*
* @see WP_REST_Controller
*/
-class Gutenberg_REST_Block_Patterns_Controller_6_3 extends Gutenberg_REST_Block_Patterns_Controller_6_2 {
+class Gutenberg_REST_Block_Patterns_Controller_6_3 extends WP_REST_Block_Patterns_Controller {
+ /**
+ * Defines whether remote patterns should be loaded.
+ *
+ * @since 6.0.0
+ * @var bool
+ */
+ private $remote_patterns_loaded;
+
/**
* Prepare a raw block pattern before it gets output in a REST API response.
*
@@ -158,4 +166,33 @@ public function get_item_schema() {
return $this->add_additional_fields_schema( $this->schema );
}
+
+ /**
+ * Retrieves all block patterns.
+ *
+ * @since 6.0.0
+ * @since 6.2.0 Added migration for old core pattern categories to the new ones.
+ *
+ * @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 ) {
+ if ( ! $this->remote_patterns_loaded ) {
+ // Load block patterns from w.org.
+ gutenberg_load_remote_block_patterns(); // Patterns with the `core` keyword.
+ gutenberg_load_remote_featured_patterns(); // Patterns in the `featured` category.
+ gutenberg_register_remote_theme_patterns(); // Patterns requested by current theme.
+
+ $this->remote_patterns_loaded = true;
+ }
+
+ $response = array();
+ $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
+ foreach ( $patterns as $pattern ) {
+ $migrated_pattern = $this->migrate_pattern_categories( $pattern );
+ $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request );
+ $response[] = $this->prepare_response_for_collection( $prepared_pattern );
+ }
+ return rest_ensure_response( $response );
+ }
}
diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php
index d7fa31cd33fe6..130da6ed774d8 100644
--- a/lib/compat/wordpress-6.3/rest-api.php
+++ b/lib/compat/wordpress-6.3/rest-api.php
@@ -106,3 +106,32 @@ function gutenberg_register_rest_navigation_fallbacks() {
$editor_settings->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_rest_navigation_fallbacks' );
+
+/**
+ * Add extra collection params to themes requests.
+ *
+ * @param array $query_params JSON Schema-formatted collection parameters.
+ * @return array Updated parameters.
+ */
+function gutenberg_themes_collection_params_6_3( $query_params ) {
+ $query_params['is_block_theme'] = array(
+ 'description' => __( 'Whether the theme is a block-based theme.' ),
+ 'type' => 'boolean',
+ 'readonly' => true,
+ );
+ return $query_params;
+}
+add_filter( 'rest_themes_collection_params', 'gutenberg_themes_collection_params_6_3' );
+
+/**
+ * Updates REST API response for the themes and adds the `is_block_theme` flag.
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param WP_Theme $theme Theme object used to create response.
+ * @return WP_REST_Response $response Updated response object.
+ */
+function gutenberg_modify_rest_themes_response( $response, $theme ) {
+ $response->data['is_block_theme'] = $theme->is_block_theme();
+ return $response;
+}
+add_filter( 'rest_prepare_theme', 'gutenberg_modify_rest_themes_response', 10, 2 );
diff --git a/lib/experimental/fonts/font-face/class-wp-font-face-resolver.php b/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php
similarity index 95%
rename from lib/experimental/fonts/font-face/class-wp-font-face-resolver.php
rename to lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php
index 16e74d6051aa7..84375b6c52f5d 100644
--- a/lib/experimental/fonts/font-face/class-wp-font-face-resolver.php
+++ b/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php
@@ -4,7 +4,7 @@
*
* @package WordPress
* @subpackage Fonts
- * @since X.X.X
+ * @since 6.4.0
*/
if ( class_exists( 'WP_Font_Face_Resolver' ) ) {
@@ -25,12 +25,12 @@ class WP_Font_Face_Resolver {
/**
* Gets fonts defined in theme.json.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @return array Returns the font-families, each with their font-face variations.
*/
public static function get_fonts_from_theme_json() {
- $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->get_settings();
+ $settings = gutenberg_get_global_settings();
// Bail out early if there are no font settings.
if ( empty( $settings['typography'] ) || empty( $settings['typography']['fontFamilies'] ) ) {
@@ -43,7 +43,7 @@ public static function get_fonts_from_theme_json() {
/**
* Parse theme.json settings to extract font definitions with variations grouped by font-family.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $settings Font settings to parse.
* @return array Returns an array of fonts, grouped by font-family.
@@ -81,7 +81,7 @@ private static function parse_settings( array $settings ) {
/**
* Converts font-face properties from theme.json format.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $font_face_definition The font-face definitions to convert.
* @param string $font_family_property The value to store in the font-face font-family property.
@@ -115,7 +115,7 @@ private static function convert_font_face_properties( array $font_face_definitio
* replaced with the URI to the font file's location in the theme. When a "src"
* beings with this placeholder, it is replaced, converting the src into a URI.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $src An array of font file sources to process.
* @return array An array of font file src URI(s).
@@ -139,7 +139,7 @@ private static function to_theme_file_uri( array $src ) {
/**
* Converts all first dimension keys into kebab-case.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $data The array to process.
* @return array Data with first dimension keys converted into kebab-case.
diff --git a/lib/experimental/fonts/font-face/class-wp-font-face.php b/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php
similarity index 96%
rename from lib/experimental/fonts/font-face/class-wp-font-face.php
rename to lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php
index 482bf4d42396d..0489fe4f4fbd5 100644
--- a/lib/experimental/fonts/font-face/class-wp-font-face.php
+++ b/lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php
@@ -4,7 +4,7 @@
*
* @package WordPress
* @subpackage Fonts
- * @since X.X.X
+ * @since 6.4.0
*/
if ( class_exists( 'WP_Font_Face' ) ) {
@@ -14,14 +14,14 @@
/**
* Font Face generates and prints `@font-face` styles for given fonts.
*
- * @since X.X.X
+ * @since 6.4.0
*/
class WP_Font_Face {
/**
* The font-face property defaults.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @var string[]
*/
@@ -35,7 +35,7 @@ class WP_Font_Face {
/**
* Valid font-face property names.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @var string[]
*/
@@ -59,7 +59,7 @@ class WP_Font_Face {
/**
* Valid font-display values.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @var string[]
*/
@@ -70,7 +70,7 @@ class WP_Font_Face {
* where the key is the attribute name and the
* value is its value.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @var string[]
*/
@@ -79,13 +79,13 @@ class WP_Font_Face {
/**
* Creates and initializes an instance of WP_Font_Face.
*
- * @since X.X.X
+ * @since 6.4.0
*/
public function __construct() {
/**
* Filters the font-face property defaults.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $defaults {
* An array of required font-face properties and defaults.
@@ -111,7 +111,7 @@ function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5'
/**
* Generates and prints the `@font-face` styles for the given fonts.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $fonts The fonts to generate and print @font-face styles.
*/
@@ -132,7 +132,7 @@ public function generate_and_print( array $fonts ) {
/**
* Validates each of the font-face properties.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $fonts The fonts to valid.
* @return array Prepared font-faces organized by provider and font-family.
@@ -158,7 +158,7 @@ private function validate_fonts( array $fonts ) {
/**
* Validates each font-face property.
*
- * @since X.X.X
+ * @since 6.4.0
*
* @param array $font_face Font face properties to validate.
* @return false|array Validated font-face on success. Else, false.
@@ -212,7 +212,7 @@ private function validate_font_face_properties( array $font_face ) {
/**
* Gets the `
{ children }
-
+
);
}
diff --git a/packages/edit-site/src/components/block-editor/site-editor-canvas.js b/packages/edit-site/src/components/block-editor/site-editor-canvas.js
index 692b7fd5eadc1..9e89e91f655c4 100644
--- a/packages/edit-site/src/components/block-editor/site-editor-canvas.js
+++ b/packages/edit-site/src/components/block-editor/site-editor-canvas.js
@@ -11,8 +11,6 @@ import {
BlockList,
BlockTools,
__unstableUseClipboardHandler as useClipboardHandler,
- __unstableUseTypingObserver as useTypingObserver,
- BlockEditorKeyboardShortcuts,
store as blockEditorStore,
} from '@wordpress/block-editor';
import {
@@ -29,7 +27,7 @@ import EditorCanvas from './editor-canvas';
import EditorCanvasContainer from '../editor-canvas-container';
import useSiteEditorSettings from './use-site-editor-settings';
import { store as editSiteStore } from '../../store';
-import { FOCUSABLE_ENTITIES } from './constants';
+import { FOCUSABLE_ENTITIES } from '../../utils/constants';
import { unlock } from '../../lock-unlock';
import PageContentFocusManager from '../page-content-focus-manager';
@@ -78,11 +76,7 @@ export default function SiteEditorCanvas() {
! isMobileViewport;
const contentRef = useRef();
- const mergedRefs = useMergeRefs( [
- contentRef,
- useClipboardHandler(),
- useTypingObserver(),
- ] );
+ const mergedRefs = useMergeRefs( [ contentRef, useClipboardHandler() ] );
const isTemplateTypeNavigation = templateType === 'wp_navigation';
@@ -121,7 +115,6 @@ export default function SiteEditorCanvas() {
}
} }
>
-
{ resizeObserver }
+