diff --git a/.eslintrc.js b/.eslintrc.js index 13c7e260e9bd9..4063bb42cf32a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -105,6 +105,7 @@ const restrictedImports = [ 'lowerCase', 'map', 'mapKeys', + 'mapValues', 'maxBy', 'memoize', 'merge', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d3ba813d3cd8a..9bdd6f4594ae9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ # Documentation -/docs @ajitbohra @ryanwelcher @juanmaguitar @fabiankaegy +/docs @ajitbohra @ryanwelcher @juanmaguitar @fabiankaegy @ndiego # Schemas /schemas/json @ajlende @@ -57,7 +57,7 @@ # Tooling /bin @ntwb @nerrad @ajitbohra /bin/api-docs @ntwb @nerrad @ajitbohra -/docs/tool @ajitbohra +/docs/tool @ajitbohra @ndiego /packages/babel-plugin-import-jsx-pragma @ntwb @nerrad @ajitbohra /packages/babel-plugin-makepot @ntwb @nerrad @ajitbohra /packages/babel-preset-default @gziolo @ntwb @nerrad @ajitbohra diff --git a/.github/workflows/check-components-changelog.yml b/.github/workflows/check-components-changelog.yml index 23fbe9183ca8d..c0cb4894009a2 100644 --- a/.github/workflows/check-components-changelog.yml +++ b/.github/workflows/check-components-changelog.yml @@ -42,8 +42,8 @@ jobs: exit 1 fi - pr_link_pattern="\(\[#${PR_NUMBER}\]\(https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}\)\)" - pr_link_grep_pattern="(\[#${PR_NUMBER}\](https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}))" + pr_link_pattern="\[#${PR_NUMBER}\]\(https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER}\)" + pr_link_grep_pattern="\[#${PR_NUMBER}\](https://github\.com/WordPress/gutenberg/pull/${PR_NUMBER})" unreleased_section=$(sed -n '/^## Unreleased$/,/^## /p' "${changelog_path}") diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js index e82091ff84374..3f1512ef0feb7 100644 --- a/bin/packages/build-worker.js +++ b/bin/packages/build-worker.js @@ -103,9 +103,13 @@ async function buildCSS( file ) { 'animations', 'z-index', ] - // Editor styles should be excluded from the default CSS vars output. + // Editor and component styles should be excluded from the default CSS vars output. .concat( - file.includes( 'common.scss' ) || ! file.includes( 'block-library' ) + file.includes( 'common.scss' ) || + ! ( + file.includes( 'block-library' ) || + file.includes( 'components' ) + ) ? [ 'default-custom-properties' ] : [] ) diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index dfee1faa50065..69be77ac66991 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -919,6 +919,10 @@ function getContributorProps( pullRequests ) { getContributorPropsMarkdownList, ] )( pullRequests ); + if ( ! contributorsList ) { + return ''; + } + return ( '## First time contributors' + '\n\n' + diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 0804c794e9d8e..57c6674500a22 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -3,7 +3,6 @@ */ const fs = require( 'fs' ); const path = require( 'path' ); -const { mapValues } = require( 'lodash' ); const SimpleGit = require( 'simple-git' ); /** @@ -475,10 +474,26 @@ async function runPerformanceTests( branches, options ) { ( r ) => r[ branch ][ dataPoint ] ); } ); - const medians = mapValues( resultsByDataPoint, median ); + // @ts-ignore + const medians = Object.fromEntries( + Object.entries( resultsByDataPoint ).map( + ( [ dataPoint, dataPointResults ] ) => [ + dataPoint, + median( dataPointResults ), + ] + ) + ); // Format results as times. - results[ testSuite ][ branch ] = mapValues( medians, formatTime ); + // @ts-ignore + results[ testSuite ][ branch ] = Object.fromEntries( + Object.entries( medians ).map( + ( [ dataPoint, dataPointMedian ] ) => [ + dataPoint, + formatTime( dataPointMedian ), + ] + ) + ); } } diff --git a/bin/plugin/commands/test/changelog.js b/bin/plugin/commands/test/changelog.js index 6d201fbc27c96..52a9391af11d1 100644 --- a/bin/plugin/commands/test/changelog.js +++ b/bin/plugin/commands/test/changelog.js @@ -485,6 +485,11 @@ describe( 'getContributorProps', () => { // npm run other:changelog -- --milestone="Gutenberg 11.3" expect( getContributorProps( pullRequests ) ).toMatchSnapshot(); } ); + test( 'do not include first time contributors section if there are not any', () => { + expect( + getContributorProps( pullRequests.slice( 0, 4 ) ) + ).toMatchInlineSnapshot( `""` ); + } ); } ); describe( 'getContributorList', () => { diff --git a/changelog.txt b/changelog.txt index 79f119e74f776..9256c5a58eff2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,8 +1,6 @@ == Changelog == -= 15.7.0-rc.1 = - - += 15.7.0 = ## Changelog @@ -57,6 +55,8 @@ - Adjust copy of Site Logo Block. ([49540](https://github.com/WordPress/gutenberg/pull/49540)) - Adapt flex child controls for Spacer. ([49362](https://github.com/WordPress/gutenberg/pull/49362)) - Social Icon: Update the `link` and `mail` block variation's icons. ([49952](https://github.com/WordPress/gutenberg/pull/49952)) +- Cover: Re-instate overflow:Hidden rule to fix issue with border radius. ([50209](https://github.com/WordPress/gutenberg/pull/50209)) +- Fix site logo preview image size with long filenames. ([50242](https://github.com/WordPress/gutenberg/pull/50242)) #### Components - CheckboxControl: Add support custom IDs. ([49977](https://github.com/WordPress/gutenberg/pull/49977)) @@ -76,6 +76,7 @@ - Do not add unregistered style variations to the theme.json schema. ([49807](https://github.com/WordPress/gutenberg/pull/49807)) - Update preset styles to use Selectors API. ([49427](https://github.com/WordPress/gutenberg/pull/49427)) - Change the 'WP_Theme_JSON_Data_Gutenberg' class directory. ([50062](https://github.com/WordPress/gutenberg/pull/50062)) +- Layout: Fix issue where saving user global styles included layout definitions in layout settings. ([50268](https://github.com/WordPress/gutenberg/pull/50268)) #### Site Editor - Fix screen flash when deleting templates in templates list. ([48449](https://github.com/WordPress/gutenberg/pull/48449)) @@ -88,7 +89,7 @@ - Correctly return 'isResolving' from 'useAlternativeTemplateParts' hook. ([49921](https://github.com/WordPress/gutenberg/pull/49921)) #### Patterns -- Increase dimensions of the pattern modal that appears when creating a new page. ([49859](https://github.com/WordPress/gutenberg/pull/49859)) +- Increase the dimensions of the pattern modal that appears when creating a new page. ([49859](https://github.com/WordPress/gutenberg/pull/49859)) - Update full screen modal dimensions, and pattern grids. ([49894](https://github.com/WordPress/gutenberg/pull/49894)) - Increase pattern modal dimensions when creating a new template. ([49722](https://github.com/WordPress/gutenberg/pull/49722)) @@ -206,6 +207,8 @@ The following contributors merged PRs in this release: @aaronrobertshaw @adamziel @ajlende @alexstine @andrewserong @annziel @aurooba @bdurette @bph @carolinan @chad1008 @chintu51 @courtneyr-dev @dcalhoun @derekblank @draganescu @ellatrix @fluiddot @gaambo @geriux @getdave @gigitux @guarani @gvgvgvijayan @gziolo @hellofromtonya @jameskoster @jasmussen @jeryj @jhnstn @jsnajdr @juanmaguitar @kevin940726 @m0hanraj @MaggieCabrera @MahendraBishnoi29 @Mamaduka @masteradhoc @mburridge @mcsf @mikachan @mirka @mokagio @mtias @ndiego @noahtallen @ntsekouras @oandregal @ObliviousHarmony @priethor @ramonjd @scruffian @SiobhyB @Soean @sque89 @t-hamano @talldan @tellthemachines @tyrann0us @tyxla @Wtower @youknowriad @zzap + + = 15.6.2 = diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 8d9f46120d2f5..3dd8863859e37 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -17,7 +17,7 @@ We release a new major version approximately every two weeks. The current and ne - **On the date of the current milestone**, we publish a release candidate and make it available for plugin authors and users to test. If any regressions are found with a release candidate, a new one can be published. On this date, all remaining PRs on the milestone are moved automatically to the next release. Release candidates should be versioned incrementally, starting with `-rc.1`, then `-rc.2`, and so on. [Preparation of the release post starts here](/docs/block-editor/contributors/code/release/#writing-the-release-notes-and-post) and spans until the final release. -- **One week after the first release candidate**, the stable version is created based on the last release candidate and any necessary regression fixes. Once the stable version is released, the release post is published, including a [performance audit](/docs/block-editor/contributors/testing-overview/#performance-testing). +- **One week after the first release candidate**, the stable version is created based on the last release candidate and any necessary regression fixes. Once the stable version is released and the release post is published. If critical bugs are discovered on stable versions of the plugin, patch versions can be released at any time. @@ -103,8 +103,7 @@ Documenting the release is a group effort between the release manager, Gutenberg 1. Curating the changelog - Wednesday after the RC release to Friday 2. Selecting the release highlights - Friday to Monday 3. Drafting the release post - Monday to Wednesday -4. Running the performance tests - Wednesday right after the stable release -5. Publishing the post - Wednesday after stable release +4. Publishing the post - Wednesday after stable release #### 1. Curating the changelog @@ -140,21 +139,6 @@ When possible, the highlighted changes in the release post should include an ani These visual assets should maintain consistency with previous release posts; using lean, white themes helps in this regard and visually integrate well with the [make.wordpress.org/core](https://make.wordpress.org/core/) blog aesthetics. Including copyrighted material should be avoided, and browser plugins that can be seen in the browser canvas (spell checkers, form fillers, etc.) disabled when capturing the assets. -#### 4. Running the performance tests - -The post should also include a performance audit at the end, comparing the current Gutenberg release with both the previous one and the latest WordPress major version. There are GitHub worfklows in place to do this comparison as part of the Continuous Integration setup, so the performance audit results can be found at the workflow run generated by the release commit in the [Performance Tests workflows](https://github.com/WordPress/gutenberg/actions/workflows/performance.yml) page, with the job name `Compare performance with current WordPress Core and previous Gutenberg versions`. - -If the GitHub workflow fails, the performance audit can be executed locally using `bin/plugin/cli.js perf` and passing the branches to run the performance suite against as parameters. In addition, the current major WP version can be passed to avoid running tests against the WP `trunk`. Example: - -``` -node ./bin/plugin/cli.js perf release/x.y release/x.z wp/a.b --wp-version wp.major -``` - -The performance values usually displayed in the release post are: - -- Time to the first block (test named `firstBlock`) -- KeyPress Event (typing) (test named `type`) - #### 5. Publishing the post Once the post content is ready, an author already having permissions to post in [make.wordpress.org/core](https://make.wordpress.org/core/) will create a new draft and import the content; this post should be published after the actual release, helping external media to echo and amplify the release news. Remember asking for peer review is encouraged by the [make/core posting guidelines](https://make.wordpress.org/core/handbook/best-practices/post-comment-guidelines/#peer-review)! @@ -165,7 +149,7 @@ Occasionally it's necessary to create a minor release (i.e. X.Y.**Z**) of the Pl As you proceed with the following process, it's worth bearing in mind that such minor releases are not created as branches in their own right (e.g. `release/12.5.0`) but are simply [tags](https://github.com/WordPress/gutenberg/releases/tag/v12.5.1). -The method for minor releases is nearly identical to the main Plugin release process (see above) but has some notable exceptions. Please make sure to read _the whole_ of this guide before proceeding. +The method for minor releases is nearly identical to the main Plugin release process (see above) but has some notable exceptions. Please make sure to read _the entire_ guide before proceeding. #### Updating the release branch @@ -217,7 +201,7 @@ This is expected. The draft release will contain only the plugin zip. Only once > Do I need to publish point releases to WordPress.org? -Yes. The method for this is identical to the main Plugin release process. You will need a Gutenberg Core team member to approve the release workflow. +Yes. The method for this is identical to the main Plugin release process. You will need a member of the Gutenberg Core team the Gutenberg Release team to approve the release workflow. > The release process failed to cherry-pick version bump commit to the trunk branch. diff --git a/docs/explanations/architecture/performance.md b/docs/explanations/architecture/performance.md index 94848d1386d98..b57a2bba1db61 100644 --- a/docs/explanations/architecture/performance.md +++ b/docs/explanations/architecture/performance.md @@ -6,9 +6,9 @@ Performance is a key feature for editor applications and the Block editor is not To ensure the block editor stays performant across releases and development, we monitor some key metrics using [performance benchmark job](#the-performance-benchmark-job). -**Loading Time:** The time it takes to load an editor page. This includes time the server takes to respond, times to first paint, first contentful paint, DOM content load complete, load complete and first block render. -**Typing Time:** The time it takes for the browser to respond while typing on the editor. -**Block Selection Time:** The time it takes for the browser to respond after a user selects block. (Inserting a block is also equivalent to selecting a block. Monitoring the selection is sufficient to cover both metrics). +- **Loading Time:** The time it takes to load an editor page. This includes time the server takes to respond, times to first paint, first contentful paint, DOM content load complete, load complete and first block render. +- **Typing Time:** The time it takes for the browser to respond while typing on the editor. +- **Block Selection Time:** The time it takes for the browser to respond after a user selects block. (Inserting a block is also equivalent to selecting a block. Monitoring the selection is sufficient to cover both metrics). ## Key Performance Decisions and Solutions @@ -53,12 +53,12 @@ To achieve that the command first prepares the following folder structure: Once the directory above is in place, the performance command loop over the performance test suites (post editor and site editor) and does the following: - 1- Start the environment for branch1 - 2- Run the performance test for the current suite - 3- Stop the environment for branch1 - 4- Repeat the first 3 steps for all other branches - 5- Repeat the previous 4 steps 3 times. - 6- Compute medians for all the performance metrics of the current suite. +1. Start the environment for `branch1` +2. Run the performance test for the current suite +3. Stop the environment for `branch1` +4. Repeat the first 3 steps for all other branches +5. Repeat the previous 4 steps 3 times. +6. Compute medians for all the performance metrics of the current suite. Once all the test suites are executed, a summary report is printed. diff --git a/docs/explanations/faq.md b/docs/explanations/faq.md index f9c6b059e133d..4a365ce5b41d1 100644 --- a/docs/explanations/faq.md +++ b/docs/explanations/faq.md @@ -151,7 +151,7 @@ This is the canonical list of keyboard shortcuts: Z - Show or hide the settings sidebar. + Show or hide the Settings sidebar. Ctrl+Shift+, , diff --git a/docs/how-to-guides/curating-the-editor-experience.md b/docs/how-to-guides/curating-the-editor-experience.md index 02f9cfba765d8..b35911d69c602 100644 --- a/docs/how-to-guides/curating-the-editor-experience.md +++ b/docs/how-to-guides/curating-the-editor-experience.md @@ -20,7 +20,7 @@ Alongside the ability to lock moving or removing blocks, the [Navigation Block]( **Apply block locking to patterns or templates** -When building patterns or templates, theme authors can use these same UI tools to set the default locked state of blocks. For example, a theme author could lock various pieces of a header. Keep in mind that by default, users with editing access can unlock these blocks. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways and here’s more context on [creating a template with locked blocks](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/). You can build these patterns in the editor itself, including adding locking options, before following the [documentation to register them](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/). +When building patterns or templates, theme authors can use these same UI tools to set the default locked state of blocks. For example, a theme author could lock various pieces of a header. Keep in mind that by default, users with editing access can unlock these blocks. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways and here’s more context on [creating a template with locked blocks](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/). You can build these patterns in the editor itself, including adding locking options, before following the [documentation to register them](/docs/reference-guides/block-api/block-patterns.md). **Apply content only editing in patterns or templates** @@ -33,7 +33,7 @@ This functionality was introduced in WordPress 6.1. In contrast to block locking - Additional child blocks cannot be inserted, further preserving the design and layout. - There is a link in the block toolbar to ‘Modify’ that a user can toggle on/off to have access to the broader design tools. Currently, it's not possibly to programmatically remove this option. -This option can be applied to Columns, Cover, and Group blocks as well as third-party blocks that have the templateLock attribute in its block.json. To adopt this functionality, you need to use `"templateLock":"contentOnly"`. [Here's an example of a pattern](https://gist.github.com/annezazu/d62acd2514cea558be6cea97fe28ff3c) with this functionality in place. For more information, please [review the relevant documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-templates/#locking). +This option can be applied to Columns, Cover, and Group blocks as well as third-party blocks that have the templateLock attribute in its block.json. To adopt this functionality, you need to use `"templateLock":"contentOnly"`. [Here's an example of a pattern](https://gist.github.com/annezazu/d62acd2514cea558be6cea97fe28ff3c) with this functionality in place. For more information, please [review the relevant documentation](/docs/reference-guides/block-api/block-templates.md#locking). Note: There is no UI in place to manage content locking and it must be managed at the code level. @@ -232,7 +232,7 @@ Continuing the examples with duotone, this means you could allow full access to } ``` -You can read more about how best to [turn on/off options with theme.json here](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/). +You can read more about how best to [turn on/off options with theme.json here](/docs/how-to-guides/themes/theme-json.md). **Disable inherit default layout** @@ -326,7 +326,62 @@ add_filter( 'wp_theme_json_data_theme', 'example_filter_theme_json_data_theme' ) The filter receives an instance of the `WP_Theme_JSON_Data class` with the data for the respective layer. Then, you pass new data in a valid theme.json-like structure to the `update_with( $new_data )` method. A theme.json version number is required in `$new_data`. -Read more about this functionality in the [Filters for theme.json data dev note](https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/). + +## Limiting interface options with client-side filters + +WordPress 6.2 introduced a new client-side filter allowing you to modify block-level [theme.json settings](/docs/reference-guides/theme-json-reference/theme-json-living.md#settings) before the editor is rendered. + +The filter is called `blockEditor.useSetting.before` and can be used in the JavaScript code as follows: + +``` +import { addFilter } from '@wordpress/hooks'; + +/** + * Limit the Column block's spacing options to pixels. + */ +addFilter( + 'blockEditor.useSetting.before', + 'example/useSetting.before', + ( settingValue, settingName, clientId, blockName ) => { + if ( blockName === Media & Text block'core/column' && settingName === 'spacing.units' ) { + return [ 'px' ]; + } + return settingValue; + } +); +``` + +This example will restrict the available spacing units for the Column block to just pixels. As discussed above, a similar restriction could be applied using theme.json filters or directly in a theme’s theme.json file using block-level settings. + +However, the `blockEditor.useSetting.before` filter is unique because it allows you to modify settings according to the block’s location, neighboring blocks, the current user’s role, and more. The possibilities for customization are extensive. + +In the following example, text color controls are disabled for the Heading block whenever the block is placed inside of a Media & Text block. + +``` +import { select } from '@wordpress/data'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Disable text color controls on Heading blocks when placed inside of Media & Text blocks. + */ +addFilter( + 'blockEditor.useSetting.before', + 'example/useSetting.before', + ( settingValue, settingName, clientId, blockName ) => { + if ( blockName === 'core/heading' ) { + const { getBlockParents, getBlockName } = select( 'core/block-editor' ); + const blockParents = getBlockParents( clientId, true ); + const inMediaText = blockParents.some( ( ancestorId ) => getBlockName( ancestorId ) === 'core/media-text' ); + + if ( inMediaText && settingName === 'color.text' ) { + return false; + } + } + + return settingValue; + } +); +``` ## Remove access to functionality @@ -340,7 +395,7 @@ This prevents both the ability to both create new block templates or edit them f **Create an allow or disallow list to limit block options** -There might be times when you don’t want access to a block at all to be available for users. To control what’s available in the inserter, you can take two approaches: [an allow list](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#using-an-allow-list) that disables all blocks except those on the list or a [deny list that unregisters specific blocks](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#using-a-deny-list). +There might be times when you don’t want access to a block at all to be available for users. To control what’s available in the inserter, you can take two approaches: [an allow list](/docs/reference-guides/filters/block-filters.md#using-an-allow-list) that disables all blocks except those on the list or a [deny list that unregisters specific blocks](/docs/reference-guides/filters/block-filters.md#using-a-deny-list). **Disable pattern directory** @@ -360,7 +415,7 @@ Read more about this functionality in the [Page creation patterns in WordPress 6 **Lock patterns** -As mentioned in the prior section on Locking APIs, aspects of patterns themselves can be locked so that the important aspects of the design can be preserved. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways. You can build these patterns in the editor itself, including adding locking options, before [following the documentation to register them](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/). +As mentioned in the prior section on Locking APIs, aspects of patterns themselves can be locked so that the important aspects of the design can be preserved. [Here’s an example of a pattern](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805) with various blocks locked in different ways. You can build these patterns in the editor itself, including adding locking options, before [following the documentation to register them](/docs/reference-guides/block-api/block-patterns.md). **Prioritize specific patterns from the Pattern Directory** @@ -372,7 +427,7 @@ With WordPress 6.0 themes can register patterns from [Pattern Directory](https:/ } ``` -Note that this field requires using [version 2 of theme.json](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/). The content creator will then find the respective Pattern in the inserter “Patterns” tab in the categories that match the categories from the Pattern Directory. +Note that this field requires using [version 2 of theme.json](/docs/reference-guides/theme-json-reference/theme-json-living.md). The content creator will then find the respective Pattern in the inserter “Patterns” tab in the categories that match the categories from the Pattern Directory. ## Combining approaches diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 5956f36d52563..4a3dc8f70a85e 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -1174,7 +1174,7 @@ Currently block variations exist for "header" and "footer" values of the area te ### patterns -
Supported in WordPress from version 6.0 using [version 2](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/) of `theme.json`.
+
Supported in WordPress from version 6.0 using version 2 of theme.json.
Within this field themes can list patterns to register from [Pattern Directory](https://wordpress.org/patterns/). The `patterns` field is an array of pattern `slugs` from the Pattern Directory. Pattern slugs can be extracted by the `url` in single pattern view at the Pattern Directory. For example in this url `https://wordpress.org/patterns/pattern/partner-logos` the slug is `partner-logos`. diff --git a/docs/manifest.json b/docs/manifest.json index 304acd00a79ea..8b6677d39c559 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1577,6 +1577,12 @@ "markdown_source": "../packages/dom/README.md", "parent": "packages" }, + { + "title": "@wordpress/e2e-test-utils-playwright", + "slug": "packages-e2e-test-utils-playwright", + "markdown_source": "../packages/e2e-test-utils-playwright/README.md", + "parent": "packages" + }, { "title": "@wordpress/e2e-test-utils", "slug": "packages-e2e-test-utils", diff --git a/docs/reference-guides/block-api/block-deprecation.md b/docs/reference-guides/block-api/block-deprecation.md index 7154f84072e61..d40c13c040612 100644 --- a/docs/reference-guides/block-api/block-deprecation.md +++ b/docs/reference-guides/block-api/block-deprecation.md @@ -55,7 +55,9 @@ Deprecations are defined on a block type as its `deprecated` property, an array - _Return_ - `boolean`: Whether or not this otherwise valid block is eligible to be migrated by this deprecation. -It's important to note that `attributes`, `supports`, and `save` are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. +
+It's important to note that attributes, supports, and save are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. +
### Example: diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 6c6879d36107a..e5d20bb7b1edd 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -226,7 +226,7 @@ Displays a title with the number of comments ([Source](https://github.com/WordPr ## Cover -Add an image or video with a text overlay — great for headers. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/cover)) +Add an image or video with a text overlay. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/cover)) - **Name:** core/cover - **Category:** media @@ -235,30 +235,12 @@ Add an image or video with a text overlay — great for headers. ([Source](https ## Details -A block that displays a summary and shows or hides additional content. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/details)) +Hide and show additional content. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/details)) - **Name:** core/details - **Category:** text -- **Supports:** align, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** showContent - -## Details Content - -Add content that may be shown or hidden via a Details block. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/details-content)) - -- **Name:** core/details-content -- **Category:** text -- **Supports:** color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ -- **Attributes:** - -## Details Summary - -Provide summary text used to toggle the display of content inside a Details block. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/details-summary)) - -- **Name:** core/details-summary -- **Category:** text -- **Supports:** color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~ -- **Attributes:** summary +- **Supports:** align (full, wide), color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ +- **Attributes:** showContent, summary ## Embed @@ -383,7 +365,7 @@ Show login & logout links. ([Source](https://github.com/WordPress/gutenberg/tree - **Name:** core/loginout - **Category:** theme -- **Supports:** anchor, className, typography (~~fontSize~~) +- **Supports:** anchor, className, typography (fontSize, lineHeight) - **Attributes:** displayLoginAsForm, redirectToCurrent ## Media & Text diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index ca925ef741e77..1ee04e09550e2 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -126,6 +126,18 @@ _Returns_ - `any`: The current theme. +### getCurrentThemeGlobalStylesRevisions + +Returns the revisions of the current global styles theme. + +_Parameters_ + +- _state_ `State`: Data state. + +_Returns_ + +- `Object | null`: The current global styles. + ### getCurrentUser Returns the current user. @@ -317,6 +329,18 @@ _Returns_ - `any`: The entity record's save error. +### getNavigationFallbackId + +Retrieve the fallback Navigation. + +_Parameters_ + +- _state_ `State`: Data state. + +_Returns_ + +- `EntityRecordKey | undefined`: The ID for the fallback Navigation post. + ### getRawEntityRecord Returns the entity's record object by key, with its attributes mapped to their raw values. @@ -607,6 +631,18 @@ _Returns_ - `Object`: Action object. +### receiveNavigationFallbackId + +Returns an action object signalling that the fallback Navigation Menu id has been received. + +_Parameters_ + +- _fallbackId_ `integer`: the id of the fallback Navigation Menu + +_Returns_ + +- `Object`: Action object. + ### receiveThemeSupports > **Deprecated** since WP 5.9, this is not useful anymore, use the selector direclty. diff --git a/gutenberg.php b/gutenberg.php index 6a5b12a03f26f..a8f5ac802806d 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * 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 PHP: 5.6 - * Version: 15.7.0-rc.1 + * Version: 15.7.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 8fc7a9bdcafb3..f1d29217e3883 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -265,12 +265,11 @@ function gutenberg_get_typography_value_and_unit( $raw_value, $options = array() return null; } - // Converts numeric values to pixel values by default. if ( empty( $raw_value ) ) { return null; } - // Converts numbers to pixel values by default. + // Converts numeric values to pixel values by default. if ( is_numeric( $raw_value ) ) { $raw_value = $raw_value . 'px'; } @@ -429,7 +428,10 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty } // Checks if fluid font sizes are activated. - $typography_settings = gutenberg_get_global_settings( array( 'typography' ) ); + $global_settings = gutenberg_get_global_settings(); + $typography_settings = isset( $global_settings['typography'] ) ? $global_settings['typography'] : array(); + $layout_settings = isset( $global_settings['layout'] ) ? $global_settings['layout'] : array(); + $should_use_fluid_typography = isset( $typography_settings['fluid'] ) && ( true === $typography_settings['fluid'] || is_array( $typography_settings['fluid'] ) ) ? @@ -443,7 +445,7 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty $fluid_settings = isset( $typography_settings['fluid'] ) && is_array( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); // Defaults. - $default_maximum_viewport_width = '1600px'; + $default_maximum_viewport_width = isset( $layout_settings['wideSize'] ) ? $layout_settings['wideSize'] : '1600px'; $default_minimum_viewport_width = '320px'; $default_minimum_font_size_factor_max = 0.75; $default_minimum_font_size_factor_min = 0.25; diff --git a/lib/blocks.php b/lib/blocks.php index f7ad8a11c88d9..bbee108b71c5f 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -23,8 +23,6 @@ function gutenberg_reregister_core_block_types() { 'columns', 'comments', 'details', - 'details-content', - 'details-summary', 'group', 'html', 'list', @@ -137,8 +135,6 @@ function gutenberg_reregister_core_block_types() { $block_folders = $details['block_folders']; $block_names = $details['block_names']; - $registry = WP_Block_Type_Registry::get_instance(); - foreach ( $block_folders as $folder_name ) { $block_json_file = $blocks_dir . $folder_name . '/block.json'; @@ -150,10 +146,7 @@ function gutenberg_reregister_core_block_types() { continue; } - if ( $registry->is_registered( $metadata['name'] ) ) { - $registry->unregister( $metadata['name'] ); - } - + gutenberg_deregister_core_block_and_assets( $metadata['name'] ); gutenberg_register_core_block_assets( $folder_name ); register_block_type_from_metadata( $block_json_file ); } @@ -165,9 +158,7 @@ function gutenberg_reregister_core_block_types() { $sub_block_names_normalized = is_string( $sub_block_names ) ? array( $sub_block_names ) : $sub_block_names; foreach ( $sub_block_names_normalized as $block_name ) { - if ( $registry->is_registered( $block_name ) ) { - $registry->unregister( $block_name ); - } + gutenberg_deregister_core_block_and_assets( $block_name ); gutenberg_register_core_block_assets( $block_name ); } @@ -178,6 +169,28 @@ function gutenberg_reregister_core_block_types() { add_action( 'init', 'gutenberg_reregister_core_block_types' ); +/** + * Deregisters the existing core block type and its assets. + * + * @param string $block_name The name of the block. + * + * @return void + */ +function gutenberg_deregister_core_block_and_assets( $block_name ) { + $registry = WP_Block_Type_Registry::get_instance(); + if ( $registry->is_registered( $block_name ) ) { + $block_type = $registry->get_registered( $block_name ); + if ( ! empty( $block_type->view_script_handles ) ) { + foreach ( $block_type->view_script_handles as $view_script_handle ) { + if ( str_starts_with( $view_script_handle, 'wp-block-' ) ) { + wp_deregister_script( $view_script_handle ); + } + } + } + $registry->unregister( $block_name ); + } +} + /** * Registers block styles for a core block. * diff --git a/lib/client-assets.php b/lib/client-assets.php index 9c0483ea539b9..9757e4b7ff24a 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -77,16 +77,8 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v $scripts->add( $handle, $src, $deps, $ver, ( $in_footer ? 1 : null ) ); } - /* - * `WP_Dependencies::set_translations` will fall over on itself if setting - * translations on the `wp-i18n` handle, since it internally adds `wp-i18n` - * as a dependency of itself, exhausting memory. The same applies for the - * polyfill and hooks scripts, which are dependencies _of_ `wp-i18n`. - * - * See: https://core.trac.wordpress.org/ticket/46089 - */ - if ( ! in_array( $handle, array( 'wp-i18n', 'wp-polyfill', 'wp-hooks' ), true ) ) { - $scripts->set_translations( $handle, 'default' ); + if ( in_array( 'wp-i18n', $deps, true ) ) { + $scripts->set_translations( $handle ); } /* 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 index 8c41e732154b4..f06302d4b742f 100644 --- 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 @@ -39,10 +39,10 @@ * * Example: * ```php - * $tags = new WP_HTML_Tag_Processor( $html ); - * if ( $tags->next_tag( 'option' ) ) { - * $tags->set_attribute( 'selected', true ); - * } + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( 'option' ) ) { + * $tags->set_attribute( 'selected', true ); + * } * ``` * * ### Finding tags @@ -55,7 +55,7 @@ * * If you want to _find whatever the next tag is_: * ```php - * $tags->next_tag(); + * $tags->next_tag(); * ``` * * | Goal | Query | @@ -88,17 +88,17 @@ * * Example: * ```php - * // 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--; - * } + * // 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 @@ -117,10 +117,10 @@ * * Example: * ```php - * 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 ( $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 @@ -142,29 +142,29 @@ * * Example: * ```php - * // 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 `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' ); + * // 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 @@ -185,24 +185,24 @@ * bookmark and update it frequently, such as within a loop. * * ```php - * $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; - * } + * $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++; - * } + * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { + * $total_todos++; * } * } + * } * ``` * * ## Design and limitations @@ -229,11 +229,11 @@ * 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 + * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). + * 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 @@ -253,9 +253,10 @@ class WP_HTML_Tag_Processor { * The maximum number of bookmarks allowed to exist at * any given time. * - * @see set_bookmark() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::set_bookmark() */ const MAX_BOOKMARKS = 10; @@ -263,9 +264,10 @@ class WP_HTML_Tag_Processor { * Maximum number of times seek() can be called. * Prevents accidental infinite loops. * - * @see seek() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ const MAX_SEEK_OPS = 1000; @@ -317,23 +319,6 @@ class WP_HTML_Tag_Processor { */ private $stop_on_tag_closers; - /** - * Holds updated HTML as updates are applied. - * - * Updates and unmodified portions of the input document are - * appended to this value as they are applied. It will hold - * a copy of the updated document up until the point of the - * latest applied update. The fully-updated HTML document - * will comprise this value plus the part of the input document - * which follows that latest update. - * - * @see $bytes_already_copied - * - * @since 6.2.0 - * @var string - */ - private $output_buffer = ''; - /** * How many bytes from the original HTML document have been read and parsed. * @@ -346,23 +331,6 @@ class WP_HTML_Tag_Processor { */ private $bytes_already_parsed = 0; - /** - * How many bytes from the input HTML document have already been - * copied into the output buffer. - * - * Lexical updates are enqueued and processed in batches. Prior - * to any given update in the input document, there might exist - * a span of HTML unaffected by any changes. This span ought to - * be copied verbatim into the output buffer before applying the - * following update. This value will point to the starting byte - * offset in the input document where that unaffected span of - * HTML starts. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_copied = 0; - /** * Byte offset in input document where current tag name starts. * @@ -421,23 +389,23 @@ class WP_HTML_Tag_Processor { * * Example: * ```php - * // 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. + * // 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 @@ -458,12 +426,12 @@ class WP_HTML_Tag_Processor { * * Example: * ```php - * // 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 - * ); + * // 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 @@ -512,16 +480,16 @@ class WP_HTML_Tag_Processor { * * Example: * ```php - * // 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' ) - * ); + * // 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 @@ -532,9 +500,10 @@ class WP_HTML_Tag_Processor { /** * Tracks and limits `seek()` calls to prevent accidental infinite loops. * - * @see seek * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ protected $seek_count = 0; @@ -754,9 +723,10 @@ public function release_bookmark( $name ) { /** * Skips contents of title and textarea tags. * - * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state * @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. */ @@ -1303,8 +1273,7 @@ private function skip_whitespace() { * @return void */ private function after_tag() { - $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $this->get_updated_html(); $this->tag_name_starts_at = null; $this->tag_name_length = null; $this->tag_ends_at = null; @@ -1316,11 +1285,11 @@ private function after_tag() { * Converts class name updates into tag attributes updates * (they are accumulated in different data formats for performance). * - * @see $lexical_updates - * @see $classname_updates - * * @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() { @@ -1460,15 +1429,19 @@ private function class_name_updates_to_attributes_updates() { * Applies attribute updates to HTML document. * * @since 6.2.0 + * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. * - * @return void + * @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() { + private function apply_attributes_updates( $shift_this_point = 0 ) { if ( ! count( $this->lexical_updates ) ) { - return; + 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 @@ -1481,12 +1454,28 @@ private function apply_attributes_updates() { */ usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); + $bytes_already_copied = 0; + $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { - $this->output_buffer .= substr( $this->html, $this->bytes_already_copied, $diff->start - $this->bytes_already_copied ); - $this->output_buffer .= $diff->text; - $this->bytes_already_copied = $diff->end; + $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. @@ -1527,6 +1516,8 @@ private function apply_attributes_updates() { } $this->lexical_updates = array(); + + return $accumulated_shift_for_given_point; } /** @@ -1576,8 +1567,6 @@ public function seek( $bookmark_name ) { // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->bytes_already_copied = $this->bytes_already_parsed; - $this->output_buffer = substr( $this->html, 0, $this->bytes_already_copied ); return $this->next_tag( array( 'tag_closers' => 'visit' ) ); } @@ -1676,14 +1665,14 @@ private function get_enqueued_attribute_value( $comparable_name ) { * * Example: * ```php - * $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; + * $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 @@ -1755,20 +1744,20 @@ public function get_attribute( $name ) { * > case-insensitive match for each other. * - HTML 5 spec * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); * - * $p->next_tag() === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; + * $p->next_tag() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; * ``` * * @since 6.2.0 * + * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive + * * @param string $prefix Prefix of requested attribute names. * @return array|null List of attribute names, or `null` when no tag opener is matched. */ @@ -1793,12 +1782,12 @@ function get_attribute_names_with_prefix( $prefix ) { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag() === true; - * $p->get_tag() === 'DIV'; + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag() === true; + * $p->get_tag() === 'DIV'; * - * $p->next_tag() === false; - * $p->get_tag() === null; + * $p->next_tag() === false; + * $p->get_tag() === null; * ``` * * @since 6.2.0 @@ -1845,12 +1834,12 @@ public function has_self_closing_flag() { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; + * $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; + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === true; * ``` * * @since 6.2.0 @@ -2110,7 +2099,8 @@ public function remove_class( $class_name ) { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 - * @see get_updated_html + * + * @see WP_HTML_Tag_Processor::get_updated_html() * * @return string The processed HTML. */ @@ -2122,6 +2112,7 @@ public function __toString() { * 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. */ @@ -2132,46 +2123,24 @@ public function get_updated_html() { * 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 && 0 === $this->bytes_already_copied ) { + if ( $requires_no_updating ) { return $this->html; } /* - * If there are no updates left to apply, but some have already - * been applied, then finish by copying the rest of the input - * to the end of the updated document and return. + * Keep track of the position right before the current tag. This will + * be necessary for reparsing the current tag after updating the HTML. */ - if ( $requires_no_updating && $this->bytes_already_copied > 0 ) { - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->bytes_already_copied = strlen( $this->output_buffer ); - return $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - } - - // Apply the updates, rewind to before the current tag, and reparse the attributes. - $content_up_to_opened_tag_name = $this->output_buffer . substr( - $this->html, - $this->bytes_already_copied, - $this->tag_name_starts_at + $this->tag_name_length - $this->bytes_already_copied - ); + $before_current_tag = $this->tag_name_starts_at - 1; /* - * 1. Apply the edits by flushing them to the output buffer and updating the copied byte count. - * - * Note: `apply_attributes_updates()` modifies `$this->output_buffer`. + * 1. Apply the enqueued edits and update all the pointers to reflect those changes. */ $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); /* - * 2. Replace the original HTML with the now-updated HTML so that it's possible to - * seek to a previous location and have a consistent view of the updated document. - */ - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->output_buffer = $content_up_to_opened_tag_name; - $this->bytes_already_copied = strlen( $this->output_buffer ); - - /* - * 3. Point this tag processor at the original tag opener and consume it + * 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 @@ -2183,9 +2152,19 @@ public function get_updated_html() { * ^ | back up by the length of the tag name plus the opening < * \<-/ back up by strlen("em") + 1 ==> 3 */ - $this->bytes_already_parsed = strlen( $content_up_to_opened_tag_name ) - $this->tag_name_length - 1; + + // 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; } diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php index 149a6a18e1450..142cbf4e1477b 100644 --- a/lib/compat/wordpress-6.2/script-loader.php +++ b/lib/compat/wordpress-6.2/script-loader.php @@ -129,28 +129,11 @@ function gutenberg_resolve_assets_override() { $scripts = ob_get_clean(); - /* - * Generate font @font-face styles for the site editor iframe. - * Use the registered font families for printing. - */ - if ( class_exists( 'WP_Fonts' ) ) { - $wp_fonts = wp_fonts(); - $registered = $wp_fonts->get_registered_font_families(); - if ( ! empty( $registered ) ) { - $queue = $wp_fonts->queue; - $done = $wp_fonts->done; - - $wp_fonts->done = array(); - $wp_fonts->queue = $registered; - - ob_start(); - $wp_fonts->do_items(); - $styles .= ob_get_clean(); - - // Reset the Web Fonts API. - $wp_fonts->done = $done; - $wp_fonts->queue = $queue; - } + // Generate font @font-face styles. + if ( function_exists( 'wp_print_fonts' ) ) { + ob_start(); + wp_print_fonts( true ); + $styles .= ob_get_clean(); } return array( diff --git a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php index 65fcefe8ac9c8..5b38484f659ac 100644 --- a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php +++ b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php @@ -39,10 +39,10 @@ * * Example: * ```php - * $tags = new WP_HTML_Tag_Processor( $html ); - * if ( $tags->next_tag( 'option' ) ) { - * $tags->set_attribute( 'selected', true ); - * } + * $tags = new WP_HTML_Tag_Processor( $html ); + * if ( $tags->next_tag( 'option' ) ) { + * $tags->set_attribute( 'selected', true ); + * } * ``` * * ### Finding tags @@ -55,7 +55,7 @@ * * If you want to _find whatever the next tag is_: * ```php - * $tags->next_tag(); + * $tags->next_tag(); * ``` * * | Goal | Query | @@ -88,17 +88,17 @@ * * Example: * ```php - * // 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--; - * } + * // 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 @@ -117,10 +117,10 @@ * * Example: * ```php - * 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 ( $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 @@ -142,29 +142,29 @@ * * Example: * ```php - * // 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 `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' ); + * // 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 @@ -185,24 +185,24 @@ * bookmark and update it frequently, such as within a loop. * * ```php - * $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; - * } + * $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++; - * } + * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { + * $total_todos++; * } * } + * } * ``` * * ## Design and limitations @@ -229,11 +229,11 @@ * 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 + * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). + * 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 @@ -253,9 +253,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * The maximum number of bookmarks allowed to exist at * any given time. * - * @see set_bookmark() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::set_bookmark() */ const MAX_BOOKMARKS = 10; @@ -263,9 +264,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * Maximum number of times seek() can be called. * Prevents accidental infinite loops. * - * @see seek() * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ const MAX_SEEK_OPS = 1000; @@ -317,23 +319,6 @@ class Gutenberg_HTML_Tag_Processor_6_3 { */ private $stop_on_tag_closers; - /** - * Holds updated HTML as updates are applied. - * - * Updates and unmodified portions of the input document are - * appended to this value as they are applied. It will hold - * a copy of the updated document up until the point of the - * latest applied update. The fully-updated HTML document - * will comprise this value plus the part of the input document - * which follows that latest update. - * - * @see $bytes_already_copied - * - * @since 6.2.0 - * @var string - */ - private $output_buffer = ''; - /** * How many bytes from the original HTML document have been read and parsed. * @@ -346,23 +331,6 @@ class Gutenberg_HTML_Tag_Processor_6_3 { */ private $bytes_already_parsed = 0; - /** - * How many bytes from the input HTML document have already been - * copied into the output buffer. - * - * Lexical updates are enqueued and processed in batches. Prior - * to any given update in the input document, there might exist - * a span of HTML unaffected by any changes. This span ought to - * be copied verbatim into the output buffer before applying the - * following update. This value will point to the starting byte - * offset in the input document where that unaffected span of - * HTML starts. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_copied = 0; - /** * Byte offset in input document where current tag name starts. * @@ -421,23 +389,23 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * * Example: * ```php - * // 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. + * // 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 @@ -458,12 +426,12 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * * Example: * ```php - * // 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 - * ); + * // 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 @@ -512,16 +480,16 @@ class Gutenberg_HTML_Tag_Processor_6_3 { * * Example: * ```php - * // 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' ) - * ); + * // 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 @@ -532,9 +500,10 @@ class Gutenberg_HTML_Tag_Processor_6_3 { /** * Tracks and limits `seek()` calls to prevent accidental infinite loops. * - * @see seek * @since 6.2.0 * @var int + * + * @see WP_HTML_Tag_Processor::seek() */ protected $seek_count = 0; @@ -754,9 +723,10 @@ public function release_bookmark( $name ) { /** * Skips contents of title and textarea tags. * - * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state * @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. */ @@ -1303,8 +1273,7 @@ private function skip_whitespace() { * @return void */ private function after_tag() { - $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $this->get_updated_html(); $this->tag_name_starts_at = null; $this->tag_name_length = null; $this->tag_ends_at = null; @@ -1316,11 +1285,11 @@ private function after_tag() { * Converts class name updates into tag attributes updates * (they are accumulated in different data formats for performance). * - * @see $lexical_updates - * @see $classname_updates - * * @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() { @@ -1460,15 +1429,19 @@ private function class_name_updates_to_attributes_updates() { * Applies attribute updates to HTML document. * * @since 6.2.0 + * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. * - * @return void + * @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() { + private function apply_attributes_updates( $shift_this_point = 0 ) { if ( ! count( $this->lexical_updates ) ) { - return; + 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 @@ -1481,12 +1454,28 @@ private function apply_attributes_updates() { */ usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); + $bytes_already_copied = 0; + $output_buffer = ''; foreach ( $this->lexical_updates as $diff ) { - $this->output_buffer .= substr( $this->html, $this->bytes_already_copied, $diff->start - $this->bytes_already_copied ); - $this->output_buffer .= $diff->text; - $this->bytes_already_copied = $diff->end; + $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. @@ -1527,6 +1516,8 @@ private function apply_attributes_updates() { } $this->lexical_updates = array(); + + return $accumulated_shift_for_given_point; } /** @@ -1576,8 +1567,6 @@ public function seek( $bookmark_name ) { // Point this tag processor before the sought tag opener and consume it. $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->bytes_already_copied = $this->bytes_already_parsed; - $this->output_buffer = substr( $this->html, 0, $this->bytes_already_copied ); return $this->next_tag( array( 'tag_closers' => 'visit' ) ); } @@ -1676,14 +1665,14 @@ private function get_enqueued_attribute_value( $comparable_name ) { * * Example: * ```php - * $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; + * $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 @@ -1755,20 +1744,20 @@ public function get_attribute( $name ) { * > case-insensitive match for each other. * - HTML 5 spec * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag( array( 'class_name' => 'test' ) ) === true; + * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); * - * $p->next_tag() === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; + * $p->next_tag() === false; + * $p->get_attribute_names_with_prefix( 'data-' ) === null; * ``` * * @since 6.2.0 * + * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive + * * @param string $prefix Prefix of requested attribute names. * @return array|null List of attribute names, or `null` when no tag opener is matched. */ @@ -1793,12 +1782,12 @@ function get_attribute_names_with_prefix( $prefix ) { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag() === true; - * $p->get_tag() === 'DIV'; + * $p = new WP_HTML_Tag_Processor( '
Test
' ); + * $p->next_tag() === true; + * $p->get_tag() === 'DIV'; * - * $p->next_tag() === false; - * $p->get_tag() === null; + * $p->next_tag() === false; + * $p->get_tag() === null; * ``` * * @since 6.2.0 @@ -1845,12 +1834,12 @@ public function has_self_closing_flag() { * * Example: * ```php - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; + * $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; + * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); + * $p->is_tag_closer() === true; * ``` * * @since 6.2.0 @@ -2110,7 +2099,8 @@ public function remove_class( $class_name ) { * Returns the string representation of the HTML Tag Processor. * * @since 6.2.0 - * @see get_updated_html + * + * @see WP_HTML_Tag_Processor::get_updated_html() * * @return string The processed HTML. */ @@ -2122,6 +2112,7 @@ public function __toString() { * 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. */ @@ -2132,46 +2123,24 @@ public function get_updated_html() { * 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 && 0 === $this->bytes_already_copied ) { + if ( $requires_no_updating ) { return $this->html; } /* - * If there are no updates left to apply, but some have already - * been applied, then finish by copying the rest of the input - * to the end of the updated document and return. + * Keep track of the position right before the current tag. This will + * be necessary for reparsing the current tag after updating the HTML. */ - if ( $requires_no_updating && $this->bytes_already_copied > 0 ) { - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->bytes_already_copied = strlen( $this->output_buffer ); - return $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - } - - // Apply the updates, rewind to before the current tag, and reparse the attributes. - $content_up_to_opened_tag_name = $this->output_buffer . substr( - $this->html, - $this->bytes_already_copied, - $this->tag_name_starts_at + $this->tag_name_length - $this->bytes_already_copied - ); + $before_current_tag = $this->tag_name_starts_at - 1; /* - * 1. Apply the edits by flushing them to the output buffer and updating the copied byte count. - * - * Note: `apply_attributes_updates()` modifies `$this->output_buffer`. + * 1. Apply the enqueued edits and update all the pointers to reflect those changes. */ $this->class_name_updates_to_attributes_updates(); - $this->apply_attributes_updates(); + $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); /* - * 2. Replace the original HTML with the now-updated HTML so that it's possible to - * seek to a previous location and have a consistent view of the updated document. - */ - $this->html = $this->output_buffer . substr( $this->html, $this->bytes_already_copied ); - $this->output_buffer = $content_up_to_opened_tag_name; - $this->bytes_already_copied = strlen( $this->output_buffer ); - - /* - * 3. Point this tag processor at the original tag opener and consume it + * 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 @@ -2183,9 +2152,19 @@ public function get_updated_html() { * ^ | back up by the length of the tag name plus the opening < * \<-/ back up by strlen("em") + 1 ==> 3 */ - $this->bytes_already_parsed = strlen( $content_up_to_opened_tag_name ) - $this->tag_name_length - 1; + + // 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; } diff --git a/lib/compat/wordpress-6.3/script-loader.php b/lib/compat/wordpress-6.3/script-loader.php index c735f3b8a792a..8b00e10d09b66 100644 --- a/lib/compat/wordpress-6.3/script-loader.php +++ b/lib/compat/wordpress-6.3/script-loader.php @@ -56,7 +56,7 @@ function _gutenberg_get_iframed_editor_assets() { ob_start(); wp_print_styles(); - wp_print_fonts(); + wp_print_fonts( true ); $styles = ob_get_clean(); ob_start(); diff --git a/lib/compat/wordpress-6.3/theme-previews.php b/lib/compat/wordpress-6.3/theme-previews.php index 5671274925765..e73c13daa7cfb 100644 --- a/lib/compat/wordpress-6.3/theme-previews.php +++ b/lib/compat/wordpress-6.3/theme-previews.php @@ -6,32 +6,27 @@ */ /** - * Filters the blog option to return the directory for the previewed theme. + * Filters the blog option to return the path for the previewed theme. * - * @param string $current_stylesheet The current theme directory. - * @return string The previewed theme directory. + * @param string $current_stylesheet The current theme's stylesheet or template path. + * @return string The previewed theme's stylesheet or template path. */ -function gutenberg_theme_preview_stylesheet( $current_stylesheet = null ) { - $preview_stylesheet = ! empty( $_GET['theme_preview'] ) ? $_GET['theme_preview'] : null; - $wp_theme = wp_get_theme( $preview_stylesheet ); - if ( ! is_wp_error( $wp_theme->errors() ) ) { - return sanitize_text_field( $preview_stylesheet ); +function gutenberg_get_theme_preview_path( $current_stylesheet = null ) { + // Don't allow non-admins to preview themes. + if ( ! current_user_can( 'switch_themes' ) ) { + return; } - return $current_stylesheet; -} - -/** - * Filters the blog option to return the parent theme directory for the previewed theme. - * - * @param string $current_stylesheet The current theme directory. - * @return string The previewed theme directory. - */ -function gutenberg_theme_preview_template( $current_stylesheet = null ) { $preview_stylesheet = ! empty( $_GET['theme_preview'] ) ? $_GET['theme_preview'] : null; $wp_theme = wp_get_theme( $preview_stylesheet ); if ( ! is_wp_error( $wp_theme->errors() ) ) { - return sanitize_text_field( $wp_theme->get_template() ); + if ( current_filter() === 'template' ) { + $theme_path = $wp_theme->get_template(); + } else { + $theme_path = $wp_theme->get_stylesheet(); + } + + return sanitize_text_field( $theme_path ); } return $current_stylesheet; @@ -41,6 +36,11 @@ function gutenberg_theme_preview_template( $current_stylesheet = null ) { * Adds a middleware to the REST API to set the theme for the preview. */ function gutenberg_attach_theme_preview_middleware() { + // Don't allow non-admins to preview themes. + if ( ! current_user_can( 'switch_themes' ) ) { + return; + } + wp_add_inline_script( 'wp-api-fetch', sprintf( @@ -104,7 +104,7 @@ function addLivePreviewButton() { * Adds a nonce for the theme activation link. */ function block_theme_activate_nonce() { - $nonce_handle = 'switch-theme_' . gutenberg_theme_preview_stylesheet(); + $nonce_handle = 'switch-theme_' . gutenberg_get_theme_preview_path(); ?>