Make WordPress Core

Keyboard Shortcuts | Hide comment threads

A Week in Core – June 28, 2021

Welcome back to a new issue of Week in Core. Let’s take a look at what changed on Trac between June 21 and June 28, 2021.

  • 60 commits
  • 64 contributors
  • 79 tickets created
  • 8 tickets reopened
  • 78 tickets closed

Please note that the WordPress Core team released WordPress 5.8 beta 3 and beta 4 last week. Everyone is welcome to help testing the next major release of WordPress 🌟

Ticket numbers are based on the Trac timeline for the period above. The following is a summary of commits, organized by component and/or focus.

Code changes

Build/Test tools

  • Add the regenerator-runtime script as a dependency to wp-polyfill#52941
  • Correct PHPUnit version requirement in tests using ::createPartialMock()#52625
  • Replace assertEquals() with assertSameSets() in text widget tests – #52482, #52625
  • Require PHPUnit >= 6 in tests using ::createPartialMock()#52625
  • Use assertSame() in _wp_to_kebab_case() tests – #52482, #52625, #53397
  • Use more appropriate assertions in a few tests – #52625

Bundled Themes

  • Improve display of blocks in widget areas – #53422
  • Improve Gutenberg check before activating an FSE theme – #53410
  • Prevent a Full Site Editing theme from being activated when Gutenberg is not active – #53410
  • Remove mention of “FSE” in Core – #53497
  • Remove unexpected border around the Theme Details button – #53473
  • Twenty Nineteen: Update margins on full- and wide-aligned blocks in the editor – #53428
  • Twenty Thirteen: Improve the display of the Query Loop block#53438
  • Twenty Twenty-One: Add margins around content in Post Template block – #53389, #53398
  • Twenty Twenty-One: Add spacing around Query block when there is a background color – #53398
  • Twenty Twenty-One: Use the theme version when enqueueing theme assets – #53502
  • Twenty Twenty: Remove extra margin within the Query Loop block – #53482

Coding standards

  • Apply an alignment fix from running composer format#53481
  • Fix WPCS issues in [51227]#53475
  • Use a consistent check for parent items in WP_Walker#53474

Docs

  • Document api_version and variations properties in WP_Block_Type::__construct()#53518
  • Fix typo in widgets-block-editor feature documentation – #53424
  • Remove inaccurate @since tag#53461, #50105
  • Shorten the copyright notice for the WP_REST_Sidebars_Controller class – #41683
  • Update documentation for WP_Widget_Block per the documentation standards – #52628, #53461
  • Various docblock corrections for code added in 5.8 – #53461
  • Various filter docblock improvements – #52920

Editor

  • Allow custom-units to be an array – #53472
  • Correct variable names in get_block_editor_settings()#53458
  • Ports theme.json changes for beta 3 – #53397
  • Do not load a default font family for themes with theme.json
  • Move caching to endpoint for unique responses – #53435
  • Package updates for Beta 3 – #53397
  • Package updates including fixes from Gutenberg for WordPress 5.8 RC1 – #53397
  • Remove empty blocks/query-loop directory – #52991
  • Send locale, version with remote pattern requests – #53435
  • Update the packages with a number of fixes targeted for Beta 4 – #53397

General

  • Ensure svn:eol-style is consistently set for all recently added files – #53528

Media

  • Add lazy-loading support to block-based widgets – #53463, #53464
  • Correct undefined variable in wp_ajax_query_attachments – #50105
  • Improve upload page media item layout on smaller screens – #51754
  • Prevent uploading and show an error message when the server doesn’t support editing of WebP files and image sub-sizes cannot be created – #53475
  • Prevent uploading and show an error message when the server doesn’t support editing of WebP images, take II. Add new, better error message for it – #53475
  • Revert r51211 to restore ms-files.php assets – #53492, #53475

REST API

  • Include the sidebar ID when saving a widget – #53452
  • Retrieve latest widgets before loading sidebars – #53489

Site Health

  • Add a unique wrapper for dashboard widget content – #53521

Widgets

  • Fix non-multi widgets not appearing in wp_inactive_widgets – #53534
  • Add editor styles to the widgets block editor – #53344. – #53388
  • Add missing label and description to Customizer controls – #53487
  • Add support for the Widgets Editor on after_setup_theme instead of widgets_init#53424
  • Avoid a TypeError when adding a widget in the Customizer – #53488, #53421, #53419
  • Fix an “Invalid value” warning when adding a new widget in the Customizer – #53479
  • Fix widget preview not working if widget registered via a instance
  • Remove unnecessary gutenberg_ functions – #53441
  • Stop loading wp-editor and the Block Directory assets on the widgets screen – #53437, #53397

Props

Thanks to the 64 people who contributed to WordPress Core on Trac last week: @noisysocks (10), @desrosj (9), @ryelle (7), @hellofromTonya (5), @nosolosw (4), @zieladam (4), @SergeyBiryukov (4), @spacedmonkey (4), @aristath (3), @scruffian (3), @peterwilsoncc (3), @azaozz (3), @audrasjb (3), @gziolo (3), @johnbillion (3), @walbo (3), @caseymilne (3), @youknowriad (2), @jorbin (2), @Mamaduka (2), @dd32 (2), @joedolson (2), @kevin940726 (2), @jamesros161 (2), @chanthaboune (2), @timothyblynjacobs (2), @jorgefilipecosta (2), @marybaum (1), @ocean90 (1), @daisyo (1), @tellyworth (1), @sumaiyasiddika (1), @danieldudzic (1), @mkaz (1), @Presskopp (1), @joen (1), @sabernhardt (1), @andraganescu (1), @sunxiyuan (1), @isabel_brison (1), @kraftner (1), @onemaggie (1), @jffng (1), @otto42 (1), @Boniu91 (1), @Clorith (1), @alanjacobmathew (1), @mbabker (1), @ntsekouras (1), @strategio (1), @poena (1), @naoki0h (1), @ixkaito (1), @antpb (1), @aleperez92 (1), @iandunn (1), @barry (1), @mukesh27 (1), @herregroen (1), @jeherve (1), @hellofromtonya (1), @adamsilverstein (1), @cbringmann (1), and @AlePerez92 (1).

Congrats and welcome to our 3 new contributors of the week! @sumaiyasiddika, @sunxiyuan, and @alanjacobmathew ♥️

Core committers: @desrosj (24), @sergeybiryukov (11), @ryelle (4), @youknowriad (3), @noisysocks (3), @iandunn (3), @jorbin (2), @azaozz (2), @jorgefilipecosta (2), @clorith (1), @timothyblynjacobs (1), @joedolson (1), @flixos90 (1), @peterwilsoncc (1), and @antpb (1).

#5-8, #week-in-core

Blocks in an iframed (template) editor

The new template editor is loaded in an iframe to isolate it from the rest of the admin screen. This has the following benefits:

  • Admin styles no longer affect the editor content, so there’s no need to reset any of these rules.
  • Content styles no longer affect the admin screen, so block and theme CSS rules no longer need to be prefixed.
  • Viewport relative CSS units will work correctly. The dimensions of the editor content is usually not the same as the dimensions of the admin page, so without an iframe units like vw will be relative to the admin page.
  • Media queries will also work natively, without needing to fake them, as we did before, which is fragile.
  • In general, it makes the lives of block and theme authors easier because styles from the front-end can be dropped in with very little, if nothing, to adjust. This also applies to lighter blocks, where the editor DOM structure matches the front-end, which we highly recommend when possible.
  • With a separate window for the editor content, it’s possible for the selection in the editor to remain visible while also having a (collapsed) selection in the editor UI, for example an input field for a URL.

We currently only iframe new editors. While the new template editor has been iframed, the post editor remains unchanged. We do this to gradually test how existing blocks from plugins work within an iframed editor, since there are cases where a block could look broken or (less likely) error. We hereby urge plugin authors to test their blocks with the new template editor and contact us if they need help to adjust blocks to work in the iframe.

Document and window

The iframe will have a different document and window than the admin page, which is now the parent window. Editor scripts are loaded in the admin page, so accessing the document or window to do something with the content will no longer work.

Most blocks written in React should continue to work properly, except if you rely on document or window. To fix, you need to create ref to access the relative document (ownerDocument) or window (defaultView). Regardless of the iframe, it is good practice to do this and avoid the use of globals.

const ref = useRef();
useEffect( () => {
  const { ownerDocument } = ref.current;
  const { defaultView } = ownerDocument;
  // Set ownerDocument.title for example.
}, [] );
const props = useBlockProps( { ref } );

If you attach event handlers, remember that the useEffect callback will not be called if the ref changes, so it is good practice to use the new useRefEffect API, which will call the given callback if the ref change in addition to any dependencies passed.

const ref = useRefEffect( ( element ) => {
  const { ownerDocument } = element;
  const { defaultView } = ownerDocument;
  defaultView.addEventListener( ... );
  return () => {
    defaultView.removeEventListener( ... );
  };
}, [] );
const props = useBlockProps( { ref } );

Other frameworks and libraries

For the editor, scripts such as jQuery are loaded in the parent window (admin page), which is fine. When using these to interact with a block in the iframe, you should pass the element reference.

const ref = useRefEffect( ( element ) => {
  jQuery( element ).masonry( … );
  return () => {
    defaultView.jQuery( element ).masonry( 'destroy' );
  }
}, [] );
const props = useBlockProps( { ref } )

But what if the library is using the global window or document and it’s out of your control?

Submit an issue or PR for the library to use ownerDocument and defaultView instead of the globals. Ideally, any library should allow initialisation with an element in an iframe as the target. It’s never impossible. Feel free to contact us to mention the issue.

In the meantime, you can use the script that is loaded inside the iframe. We’ve loaded all front-end scripts in the iframe to fix these cases, but note that ideally you shouldn’t use scripts loaded in the iframe at all. You can use defaultView to access the script.

const ref = useRefEffect( ( element ) => {
  const { ownerDocument } = element;
  const { defaultView } = ownerDocument;
 
  // Use the script loaded in the iframe.
  // Script are loaded asynchronously, so check is the script is loaded.
  // After the dependencies have loaded, the block will re-render.
  if ( defaultView.jQuery ) {
    return;
  }
 
  defaultView.jQuery( element ).masonry( … );
  return () => {
    defaultView.jQuery( element ).masonry( 'destroy' );
  }
} );
const props = useBlockProps( { ref } );

And that’s it! In summary, any problem that a block might have in the iframe is caused by using the document or window global at the root of it, either in the block’s code or a third party library. Ideally, all code uses the ownerDocument and defaultView relative properties.

#5-8, #dev-notes, #gutenberg

In general, it makes the lives of block and theme authors easier because styles from the front-end can be dropped in with very little, if nothing, to adjust.

Except that they still need separate styles for the post editor, so it’s not really easier. (This is simply going back to the good old days of Classic editor in an iframe.)

It is worth noting that we’re in the middle of a multi-year project to update the whole of WordPress. Because we are trying to minimize fully breaking changes, there will necessarily be some overlap as we make progress, and reducing the work to “simply going back” isn’t accurate.

No one has to like everything being done in the project—it would be strange if they did!—but there is a bit more nuance to the components than your comment suggests.

Is there a mechanism to opt-in to the iframe approach for the post template editor?

Sorry, typo – meant the Post editor! 😅

You mean for the site as a whole or for specific blocks? We could add an option for the site as a whole, but it would be experimental.

mungkin beberapa cara diatas akan saya praktekan disitus sinopsis saya terbaru.

I believe you’d want to add a ! in that code example to check if jQuery exists:

1
if ( <strong>!</strong> defaultView.jQuery ) {

On layout and content width in WordPress 5.8

WordPress 5.8 introduces Global Settings and Global Styles. They allow theme authors to control and style the available features in the editor and the different blocks using a theme.json.

By using a theme.json file, in addition to the Global styles and settings capabilities, theme authors opt-in into the layout feature for block containers.

Layout config

Historically, themes had the responsibility to provide CSS styles in order to support aligning content (left, right). With the introduction of the block editor in WordPress 5.0, new alignments has been added to the mix (wide, full). In addition to that, the block editor allowed users to use container blocks (group, columns blocks) which potentially can change how their inner blocks are positioned and aligned. Taking all these variations into consideration has become a very difficult task for theme authors. To address these issues, WordPress 5.8 introduces the layout feature and config.

How do I migrate my theme

Themes that have a centered content area, need to define a layout setting in their `theme.json` file:

{
   "settings": {
       "layout": {
           "contentSize": "800px",
           "wideSize": "1000px"
       }
   }
}  

The block-editor will automatically read this config and provide the corresponding styles in the editor. It will allow all alignments to work properly without requiring the `add_theme_support( ‘align-wide’ )` call.

Themes are still required to provide the corresponding styles in the frontend of their sites, something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.entry-content > * {
    max-width: 800px;
    margin-left: auto !important;
    margin-right: auto !important;
}
 
.entry-content > .alignwide {
    max-width: 1000px;
}
 
.entry-content > .alignfull {
    max-width: none;
}
 
.entry-content > .alignleft {
    float: left;
    margin-right: 2em;
}
 
.entry-content > .alignright {
    float: right;
    margin-right: 2em;
}

Note:

It’s not possible for WordPress to generate these styles automatically for all themes because the entry-content className in the example above is not mandatory and may not exist. In the future, with the introduction of the upcoming block themes, these styles won’t be needed anymore.

Nested Blocks

For themes with the layout config enabled, container blocks (like group blocks) do not automatically inherit the layout config. Meaning the blocks added inside the containers will by default take all the available space and not have any wide/full alignments options unless the user defines the wide and content sizes for that particular container block or “inherits” the config from the default layout.

This also means that themers can drop any alignment specific CSS that was added specifically to support nested blocks.

#5-8, #dev-notes, #gutenberg

Meaning the blocks added inside the containers will by default take all the available space and not have any wide/full alignments options unless the user defines the wide and content sizes for that particular container block or “inherits” the config from the default layout.

How about blocks which do not have alignment options such as Paragraph? The work around I’ve found is to place a single column block, align that, and then the paragraph block. But this feels clunky.

Agreed. I’ve always thought it interesting that the Heading block lets you set wide/full alignment, but the Paragraph block does not.

Just put the paragraph(s) into a Group or Cover block, and set the wide/full alignment there…

I already described such a solution as… clunky

I’ve been enabling paragraph alignment in editor.js on my recent themes.

Good remarks here, I agree that the alignment options seem independent of the block itself but rather an option that should be added to all blocks depending on their containers instead.

The challenges there is how to do it without having a big impact on the UI (block toolbars…). I’d love to explore this further.

Dev Chat Agenda for June 30, 2021

Here is the agenda for this week’s developer meetings to occur at the following times: Wednesday, June 30, 2021 at 05:00 AM UTC and Wednesday, June 30, 2021 at 08:00 PM UTC.

Blog Post Highlights

5.8 Schedule Review

  • Beta 4 released last week and RC 1 yesterday and now under hard string freeze
  • Focus now shifts fully to RC phase with RC 2 release in 6 days on Tuesday, July 6th
  • Also working to publish Dev Notes, Field Guide and email to plugin/theme authors
  • Next RC Scrub before RC 2 is Monday, July 5, 2021 at 08:00 PM UTC (this needs a scrub host/coordinator)
  • RC 2 in 6 days on Tuesday, July 6th
  • RC 3 in 13 days on Tuesday, July 13th
  • 5.8 release in 20 days on Tuesday, July 20th

Components check-in and status updates

  • 5.8 plans and help needed
  • Check-in with each component for status updates.
  • Poll for components that need assistance.

Open Floor

Do you have something to propose for the agenda, or a specific item relevant to the usual agenda items above?

Please leave a comment, and say whether or not you’ll be in the chat, so the group can either give you the floor or bring up your topic for you accordingly.

This meeting happens in the #core channel. To join the meeting, you’ll need an account on the Making WordPress Slack.

#5-8, #agenda, #dev-chat

Introducing “Update URI” plugin header in WordPress 5.8

WordPress 5.8 introduces a new header available for plugin authors. This allows third-party plugins to avoid accidentally being overwritten with an update of a plugin of a similar name from the WordPress.org Plugin Directory.

Previously, any custom plugin which used the same slug than a plugin hosted on WordPress.org was taking a significant risk of being overwritten by an update of the latter.

WordPress 5.8 introduces a new Update URI plugin header field. If the value of this new field matches any URI other than http://wayback.fauppsala.se:80/wayback/20210708001655/https://wordpress.org/plugins/{$slug}/ or w.org/plugin/{$slug}, WordPress will not attempt to update it.

If set, the Update URI header field should be a valid URI and have a unique hostname.

Please note that authors of plugins hosted by WordPress.org don’t need to use this new header.

Some examples include:

  • Update URI: http://wayback.fauppsala.se:80/wayback/20210708001655/https://wordpress.org/plugins/example-plugin/
  • Update URI: http://wayback.fauppsala.se:80/wayback/20210708001655/https://example.com/my-plugin/
  • Update URI: my-custom-plugin-name

Update URI: false also works, and unless there is some code handling the false hostname (see the hook introduced below), the plugin will not be updated.

If the header is used on a w.org hosted plugin, the WordPress.org API will only return updates for the plugin if it matches the following format:

  • http://wayback.fauppsala.se:80/wayback/20210708001655/https://wordpress.org/plugins/{$slug}/
  • w.org/plugin/{$slug}

If the header has any other value, the API will not return any result. It will ignore the plugin for update purposes.

Additionally, WordPress 5.8 introduce the update_plugins_{$hostname} filter, which third-party plugins can use to offer updates for a given hostname.

This new hook filters the update response for a given plugin hostname. The dynamic portion of the hook name, $hostname, refers to the hostname of the URI specified in the Update URI header field.

The hook has four arguments:

  • $update: The plugin update data with the latest details. Default false.
  • $plugin_data: The list of headers provided by the plugin.
  • $plugin_file: The plugin filename.
  • $locales: Installed locales to look translations for.

They can be used to filter the update response in multiple use cases.

For reference, see tickets #32101, #23318, and changelog [50921].

Thanks @milana_cap for proofreading.

#5-8, #dev-notes

Awesome! Does this mean we can finally migrate plugin slugs on the repo?

If the value of this new field matches any URI other than http://wayback.fauppsala.se:80/wayback/20210708001655/https://wordpress.org/plugins/{$slug}/ or w.org/plugin/{$slug}, WordPress will not attempt to update it.

I read {$slug} as current slug, not any valid slug in the repo. So I don’t think this opens for migration. It could be interesting to investigate the possibility of using this mechanism that way, however.

The idea of “migrating” plugins to another slug doesn’t work for other reasons, none of which are very particular to the org plugin repository. Doing so would be a completely separate discussion, realistically. Although, not a very long one, as there is no real need to ever change the slug of a plugin.

The impact of the slug is real as long as this value gets the massive boost of 2. One cannot find my plugin in other languages but English, even when 100% translated. There’s a prominent bug lingering in Jetpack’s ElasticSearch, hidden at WordPress.com’s side, which biases search for non-English toward a slug-match or otherwise installation-count. Either migrating the plugin slug or disavowing their weight would solve the issue. Or, of course, fixing that bug would also help :), but I have no access to the source to help pinpoint that.

Migrating slugs is not happening. So, find another solution to your presumed problems.

Alas, no, it wouldn’t work properly. Slug is the current slug, as Knut said. Changing it would .. well, try to break it.

This part doesn’t seem right:

If the header has any other value, the API will not return any result. It will ignore the plugin for update purposes.

Doing that would allow placeholder plugins in the repository. When I read the discussion, I thought it meant that plugins in the repository were not allowed to have any value other than the WP repo. In other words, other values would be ignored for plugins in the repo, meaning that they would still be updated as usual from the WP repo, and not externally (or disabled).

No, the Update URI will not be allowed to be set to be incorrect in the repository.

How this will be accomplished is still being worked on, but suffice it to say that attempts to put in the wrong Update URI into plugin code hosted here will not be allowed. If we find such, then it will be either corrected or eliminated.

Does this apply to themes as well? It’s nearly the same basic system so I would assume so but there’s no mention in this post.

Themes are different, and would need a somewhat different approach. We haven’t discussed it yet in any great detail, however if it works out well for plugins, then themes will likely get a similar treatment in the future.

Wait, how are themes that different? They seem identical to me in terms of how updating would be handled.

Themes are updated by uploading a ZIP of the theme. Plugins are updated directly in SVN. Different processes. The API changes will be similar, but different in the internal code.

I don’t follow; the wp_update_themes/plugins functions simply compile a list of installed themes/plugins to send to the update-check API endpoint to get info on newer versions. This new Update URI header should be dictating wether or not a theme/plugin is *included* in the sent list, right? Why would the backend structure of the repo matter?

Because none of the themes or plugins in the repo should have this header at all. We have to *prevent* them from including it. That’s why it matters.

Editor Chat Agenda: 30 June 2021

Facilitator and notetaker: @itsjusteileen

This is the agenda for the weekly editor chat scheduled for Wednesday, June 30, 2021, 04:00 PM GMT+1.

This meeting is held in the #core-editor channel in the Making WordPress Slack.

If you are not able to attend the meeting, you are encouraged to share anything relevant for the discussion:

  • If you have anything to share for the Task Coordination section, please leave it as a comment on this post.
  • If you have anything to propose for the agenda or other specific items related to those listed above, please leave a comment below.

#agenda, #core-editor, #core-editor-agenda, #meetings

Various Block Editor API removals in WordPress 5.8

Removed APIs

Keeping deprecated JavaScript APIs around has a cost on performance and bundle size. This means at some point we need to consider removing some of the very old deprecated APIs, especially the ones with very low usage. WordPress 5.8 does remove two APIs that were deprecated on WordPress 5.2.

  • EditorGlobalKeyboardShortcuts component, this was a component used to define some keyboard shortcuts of the block editor, it was not really meant to be used by third-party developers. We verified that it’s not used by any plugin in the repository but if you rely on this component, it should be safe to just replace with VisualEditorGlobalKeyboardShortcuts.
  • The hasUploadPermissions selector from the core store. We contacted all the three plugins in the repository that were still using this API. If you’re still relying on this selector on a private site/plugin, it can be safely replaced with select( 'core' ).canUser( 'create', 'media' )

Removed blocks

Before WordPress 5.0 and in the very early days of the Gutenberg plugin, we used to have a block called Subheading, this block has never been included in WordPress Core as stable, it was hidden and deprecated very early. We expect its usage to be very small. WordPress 5.8 removes the code of this block meaning that if you open content relying on that block in the editor, it will ask you to fallback to the HTML block instead. We don’t expect this to have a noticeable impact on the frontend of your site.

#5-8, #block-editor, #dev-notes

REST API Changes in WordPress 5.8

The following is a snapshot of some of the changes to the REST API in WordPress 5.8. For more details, see the full list of closed tickets.

Widgets

WordPress 5.8 sees the introduction of a new block-based widgets editor and with it the creation of several REST API endpoints dedicated to widget management. Before diving in to how the new endpoints operate, I’d like to provide some background about how widgets work that should make the following sections more clear.

Instance Widgets

The predominant way to create widgets is to subclass the WP_Widget base class and register the widget with register_widget. These are referred to as “multi” widgets. These widgets have multiple instances that are identified by their number, an incrementing integer for each widget type.

Each instance has its own setting values. These are stored and fetched by WP_Widget which allows for the REST API to include these values. However, since a widget’s instance can contain arbitrary data, for example a DateTime object, the REST API cannot always serialize a widget to JSON. As such, a widget’s data is always serialized using the PHP serialize function and then base64 encoded. This data is also exposed with a hash value which is a wp_hash signature of this value to prevent clients from sending arbitrary data to be deserialized with unserialize.

For widgets that can be safely accept and expose their instance data as JSON, pass the show_instance_in_rest flag in the $widget_options parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ExampleWidget extends WP_Widget {
    ...
    /**
     * Sets up the widget
     */
    public function __construct() {
        $widget_ops = array(
            // ...other options here
            'show_instance_in_rest' => true,
            // ...other options here
        );
        parent::__construct( 'example_widget', 'ExampleWidget', $widget_ops );
    }
    ...
}

Reference Widgets

Far less common, but still supported, are widgets that are registered using wp_register_sidebar_widget and wp_register_widget_control directly. These are referred to as “reference”, “function-based”, or “non-multi” widgets. These widgets can store their data in an arbitrary location. As such, their instance values are never included in the REST API.

Widget Types

Accessible via /wp/v2/widget-types, the widget types endpoint describes the different widget types that are registered on the server. The endpoint is accessible to users who have permission to edit_theme_options. By default, this is limited to Administrator users.

Response Format

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "id": "pages",
  "name": "Pages",
  "description": "A list of your site’s Pages.",
  "is_multi": true,
  "classname": "widget_pages",
  "_links": {
    "collection": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/widget-types"
      }
    ],
    "self": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/widget-types/pages"
      }
    ]
  }
}

Encode Endpoint

Multi widgets have access to the /wp/v2/widget-types/<widget>/encode endpoint. This endpoint is used to convert html form data for the widget to the next instance for the widget, render the widget form, and render the widget preview.

For example, let’s say we want to interact with the Meta widget. First, we’ll want to request the widget form from the server.

POST /wp/v2/widget-types/meta/encode
1
2
3
4
{
  "instance": {},
  "number": 1
}

For now, let’s assume we’re working with a new widget. The instance is empty because this is a new widget, so we’ll be rendering an empty form. The number argument can be omitted, but including one is recommended to provide stable input ids. You’ll receive a response similar to this. The widget preview has been snipped for brevity.

1
2
3
4
5
6
7
8
9
10
11
{
  "form": "<p><label for=\"widget-meta-1-title\">Title:</label><input class=\"widefat\" id=\"widget-meta-1-title\" name=\"widget-meta[1][title]\" type=\"text\" value=\"\" /></p>",
  "preview": "<div class=\"widget widget_meta\">...</div>",
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjA6IiI7fQ==",
    "hash": "77e9f20acb54fa4f77de5a865333c0e6",
    "raw": {
      "title": ""
    }
  }
}

The provided form can then be rendered and edited by the user. When you want to render a new preview or are ready to begin saving, call the encode endpoint again with the url encoded form data and the instance value returned from the first response.

POST /wp/v2/widget-types/meta/encode
1
2
3
4
5
6
7
8
9
10
11
{
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjA6IiI7fQ==",
    "hash": "77e9f20acb54fa4f77de5a865333c0e6",
    "raw": {
      "title": ""
    }
  },
  "number": 1,
  "form_data": "widget-meta%5B1%5D%5Btitle%5D=Site+Info"
}

The REST API will call the widget’s update function to calculate the new instance based on the provided form data. The instance object can then be used to save a widget via the widgets endpoint.

1
2
3
4
5
6
7
8
9
10
11
{
  "form": "<p><label for=\"widget-meta-1-title\">Title:</label><input class=\"widefat\" id=\"widget-meta-1-title\" name=\"widget-meta[1][title]\" type=\"text\" value=\"Site Info\" /></p>",
  "preview": "<div class=\"widget widget_meta\">...</div>",
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjk6IlNpdGUgSW5mbyI7fQ==",
    "hash": "0e9a5bff2d28cba322c8cd27cd4e77af",
    "raw": {
      "title": "Site Info"
    }
  }
}

Widgets Endpoint

The widgets endpoint is used for performing CRUD operations on the saved widgets. Like the widget types endpoint, the widgets endpoints required the edit_theme_options capability to access.

To retrieve widgets, make a GET request to the /wp/v2/widgets endpoint. The sidebar parameter can be used to limit the response to widgets belonging to the requested sidebar.

To create a widget, for instance the widget from our previous example, make a POST request to the /wp/v2/widgets endpoint. The instance is the same value returned from the encode endpoint. The id_base is the unique identifier for the widget type and sidebar is the id of the sidebar to assign the widget to. Both are required.

POST /wp/v2/widgets
1
2
3
4
5
6
7
8
9
10
11
{
    "instance": {
        "encoded": "YToxOntzOjU6InRpdGxlIjtzOjk6IlNpdGUgSW5mbyI7fQ==",
        "hash": "0e9a5bff2d28cba322c8cd27cd4e77af",
        "raw": {
            "title": "Site Info"
        }
    },
    "sidebar": "sidebar-1",
    "id_base": "meta"
}

The endpoint will return information about the newly created widget.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
  "id": "meta-1",
  "id_base": "meta",
  "sidebar": "sidebar-1",
  "rendered": "<div class=\"widget widget_meta\">...</div>",
  "rendered_form": "<p><label for=\"widget-meta-1-title\">Title:</label><input class=\"widefat\" id=\"widget-meta-1-title\" name=\"widget-meta[1][title]\" type=\"text\" value=\"Site Info\" /></p>",
  "instance": {
    "encoded": "YToxOntzOjU6InRpdGxlIjtzOjk6IlNpdGUgTWV0YSI7fQ==",
    "hash": "dac44c3ebfa0428fed61829fa151e4e8",
    "raw": {
      "title": "Site Info"
    }
  },
  "_links": {
    "self": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/widgets/meta-1"
      }
    ],
    "collection": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/widgets"
      }
    ],
    "about": [
      {
        "embeddable": true,
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/widget-types/meta"
      }
    ],
    "wp:sidebar": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/sidebars/sidebar-1/"
      }
    ],
    "curies": [
      {
        "name": "wp",
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://api.w.org/{rel}",
        "templated": true
      }
    ]
  }
}

Since the meta widget (and all other built-in widgets) is registered with show_instance_in_rest enabled you could bypass the encode endpoint and use instance.raw instead. For example, if we wanted to update the widget to have a new title, we could make the following PUT request to /wp/v2/widgets/meta-1.

PUT /wp/v2/widgets/meta-1
1
2
3
4
5
6
7
{
    "instance": {
        "raw": {
            "title": "Site Meta"
        }
    }
}

A PUT request can also be made to update the sidebar assigned to a widget by passing a new sidebar id in the request.

To delete a widget, send a DELETE request to the individual widget route. By default, deleting a widget will move a widget to the Inactive Widgets area. To permanently delete a widget, use the force parameter. For example: DELETE /wp/v2/widgets/meta-1?force=true.

Sidebars Endpoints

Found under /wp/v2/sidebars, the sidebars endpoint is used to manage a site’s registered sidebars (widget areas) and their assigned widgets. For example, the following is the response for the first footer area in the Twenty Twenty theme.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
  "id": "sidebar-1",
  "name": "Footer #1",
  "description": "Widgets in this area will be displayed in the first column in the footer.",
  "class": "",
  "before_widget": "<div class=\"widget %2$s\"><div class=\"widget-content\">",
  "after_widget": "</div></div>",
  "before_title": "<h2 class=\"widget-title subheading heading-size-3\">",
  "after_title": "</h2>",
  "status": "active",
  "widgets": [
    "recent-posts-2",
    "recent-comments-2",
    "meta-1"
  ],
  "_links": {
    "collection": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/sidebars"
      }
    ],
    "self": [
      {
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/sidebars/sidebar-1"
      }
    ],
    "wp:widget": [
      {
        "embeddable": true,
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://trunk.test/wp-json/wp/v2/widgets?sidebar=sidebar-1"
      }
    ],
    "curies": [
      {
        "name": "wp",
        "href": "http://wayback.fauppsala.se:80/wayback/20210708001655/https://api.w.org/{rel}",
        "templated": true
      }
    ]
  }
}

The widgets property contains an ordered list of widget ids. While all other properties are readonly, the widgets property can be used to reorder a sidebar’s assigned widgets. Any widget ids omitted when updating the sidebar will be assigned to the Inactive Widgets sidebar area.

For example, making a PUT request to /wp/v2/sidebars/sidebar-1 with the following body will remove the Recent Comments widget, and move the Meta widget to the top of the sidebar.

PUT /wp/v2/sidebars/sidebar-1
1
2
3
4
5
6
{
  "widgets": [
    "meta-1",
    "recent-posts-2"
  ]
}

For more information about the changes to widgets in 5.8, check out the Block-based Widgets Editor dev note.

Additional Changes

Posts Collection Tax Query Accepts operator

By default, a post must contain at least one of the requested terms to be included in the response. As of [51026], the REST API accepts a new operator property that can be set to AND to require a post to contain all of the requested terms.

For example, /wp/v2/posts?tags[terms]=1,2,3&tags[operator]=AND will return posts that have tags with the ids of 1, 2, and 3.

See #41287 for more details.

Props to @noisysocks for reviewing.

#5-8, #dev-notes, #rest-api

Block-based Widgets Editor in WordPress 5.8

WordPress 5.8 introduces a new block-based widgets editor to the Widgets screen (Appearance → Widgets) and Customizer (Appearance → Customize → Widgets). The new editor allows users to add blocks to their widget areas using the familiar block editor interface introduced in WordPress 5.0. This gives users powerful new ways to customise their sites using the rich library of core and third party blocks. Existing widgets and third party widgets will continue to work and can be used alongside blocks.

Opting out of the block-based widgets editor

The block-based widgets editor is enabled in WordPress 5.8 by default. There are several ways to restore the classic editor:

New Widgets screen

The widgets.php admin screen (Appearance → Widgets) now loads a block-based widgets editor which exists in the @wordpress/edit-widgets package.

The editor is built using React and is similar to the editor used for posts and pages (@wordpress/edit-post) and uses many of the same subsystems: @wordpress/interface and @wordpress/components for UI, @wordpress/block-editor for block editing, @wordpress/data and @wordpress/core-data for persisting changes, and so on.

A new filterable function, wp_use_widgets_block_editor(), is used by widgets.php to determine whether to load the new block-based editor or the classic editor.

The Widgets screen is extendable via block editor APIs such as registerPlugin, registerBlockType, registerBlockVariation, and so on.

The Widgets screen uses new REST API endpoints which are detailed in a seperate dev note.

New Customizer control

The Widgets section in the Customizer (Appearance → Customize → Widgets) now loads a new control (WP_Sidebar_Block_Editor_Control) which contains an embedded block-based widgets editor that exists in the @wordpress/customize-widgets package.

The editor is built using React and uses @wordpress/block-editor and @wordpress/components to implement its block editing interface. It does not use @wordpress/data or @wordpress/core-data to persist changes. Instead, the existing Customizer JavaScript API is used.

A new filterable function, wp_use_widgets_block_editor(), is used by WP_Customize_Manager to determine whether or not to log the new block-based editor control or the classic editor control.

The block-based widgets editor in the Customizer is extendable via block editor APIs such as registerBlockType, registerBlockVariation, and so on.

New block: Legacy Widget

Existing widgets and third party widgets can be edited in the block-based widgets editor via the new Legacy Widget block. This block has an identifier of core/legacy-widget and exists in the @wordpress/widgets package. The Legacy Widget block is compatible with most third party widgets.

Broadly speaking, the Legacy Widget block has three states:

  1. Select. When first inserted, the block displays a list of widgets available to choose from. The list can be customised using the widget_types_to_hide_from_legacy_widget_block filter.
  2. Edit. When selected, the block displays the widget’s control form fields. This is determined by the widget’s WP_Widget::form() implementation.
  3. Preview. When not selected, the block displays a preview of how the widget will look once saved. This is determined by the widget’s WP_Widget::widget() implementation. A “No preview available.” message is automatically shown when widget() does not output any meaningful HTML. Learn more.

The Legacy Widget block is not available in other block editors including the post editor, though this can be enabled for advanced use cases.

New widget: Block

Blocks added to widget areas are persisted using the same widget storage mechanism added in WordPress 2.8. Under the hood, each block is serialised into HTML and stored in a block widget. This is represented by a new WP_Widget_Block subclass that extends WP_Widget. A block widget is a specialised case of the HTML widget and works very similarly.

If blocks are added to a widget area, and then the block-based widgets editor is disabled, the blocks will remain visible on the frontend and in the classic widgets screen.

Tips to prepare for the new block-based widgets editor

Use the widget-added event to bind event handlers

The Legacy Widget block will display a widget’s control form in a way that is very similar to the Customizer and is therefore compatible with most third party widgets. Care must be taken, however, to always initialise event handlers when the widget-added jQuery event is triggered on document.

1
2
3
4
5
6
7
8
( function ( $ ) {
    $( document ).on( 'widget-added', function ( $control ) {
        $control.find( '.change-password' ).on( 'change', function () {
            var isChecked = $( this ).prop( 'checked' );
            $control.find( '.password' ).toggleClass( 'hidden', ! isChecked );
        } );
    } );
} )( jQuery );

Use block_categories_all instead of block_categories

The block_categories filter has been deprecated and will only be called in the post and page block editor. Plugin developers that wish to support the widgets block editor should use the new block_categories_all filter which is called in all editors. See #52920 for more details.

Allow migrating from widgets to blocks

Many core and third party widgets have a functionally equivalent block. For example, core’s Recent Posts widget is analogous to core’s Latest Posts block.

In order to avoid duplicate functionality, is is recommended that plugin authors provide a way for users to convert their existing widgets to any equivalent block. WordPress 5.8 provides a mechanism for doing this using block transforms:

  1. Configure your widget to display its instance in the REST API by setting show_instance_in_rest to true in $widget_options.
  2. Add a block transform to your block from the core/legacy-widget block.
  3. Hide your widget from the Legacy Widget block using the widget_types_to_hide_from_legacy_widget_block filter.

There is a guide containing more detailed instructions in the Block Editor Handbook.

Don’t use @wordpress/editor

Many legacy widgets call the wp.editor.initialize() JavaScript function to instantiate a TinyMCE editor. If a plugin or block uses the @wordpress/editor package and enqueues wp-editor as a script dependency, this will re-define the wp.editor global, often resulting in a wp.editor.initialize is undefined error.


Written by @andraganescu and @noisysocks.
Thanks to @talldanwp, @isabel_brison, @kevin940726, and @get_dave for reviewing.

#5-8 #dev-notes #feature-widgets-block-editor #widgets #block-editor

Miscellaneous developer focused changes in WordPress 5.8


Update on July 1, 2021: Added new filter info in Revisions section. – @milana_cap


WordPress 5.8 brings a lot of smaller changes that developers should know about. Here’s a breakdown.

Build/Test Tools: Remove IE11 from the list of supported browsers

In WordPress 5.8, phase one of the dropping support for IE11 plan will take place. When considering three different data points, IE11 usage has fallen below a 1% average. After a discussion and debate, the decision was made to remove support for IE 11. In addition to opening the door for using more modern APIs, this will result in smaller script files, lower maintenance burden, and decreased build times.

The wp-polyfill script is responsible for ensuring all newer features function in the older browsers supported by WordPress. In past releases, this script was a copy of the file distributed in the @babel/polyfill package, which among other things, included regenerator-runtime.

This package was deprecated and has been replaced with the core-js package in the build process. core-js allows the polyfill file to be built dynamically, but no longer includes the regenerator-runtime script.

The regenerator-runtime script handle has been added to WordPress Core, and has been added as a dependency to wp-polyfill in order to be backwards compatible with any plugin or theme registering wp-polyfill as a script dependency expecting regenerator-runtime to be present.

It is recommended that developers add the regenerator-runtime script as a dependency to any script that requires it. In future releases, removing regenerator-runtime as a dependency of wp-polyfill will be considered.

There are several other polyfill scripts included in WordPress for the sole purpose of IE11 support. They will continue to be included, but will no longer be loaded by default. They are:

  • wp-polyfill-dom-rect
  • wp-polyfill-element-closest
  • wp-polyfill-fetch
  • wp-polyfill-formdata
  • wp-polyfill-node-contains
  • wp-polyfill-object-fit
  • wp-polyfill-url

For more information, see #52941, #53077, and #53078.

Formatting: More consistency and control over wp_get_document_title()

In wp_get_document_title(), the returned value is currently passed directly through wptexturize(), convert_chars(), and capital_P_dangit(), and is done so after the document_title_parts filter is run.

This makes it impossible to fully control the output of wp_get_document_title() and is inconsistent with how other similar text is processed with these functions.

The new document_title filter, which is run immediately before returning the results of the wp_get_document_title() function, moves the three formatting functions mentioned above to the new filter hook. This allows developers to further modify the title after being prepared by WordPress, or to modify the functions hooked to this filter as they wish.

For more information, see #51643.

General: Consistent type for integer properties of WP_Post, WP_Term, WP_User and a bookmark object

Some properties of the WP_PostWP_Term, and WP_User classes are documented as integers, so it should be a safe assumption to always treat them as such. However, that is not the case when get_post() or get_term() is called with an editattribute, or js context, because all values are run through esc_attr() or esc_js() in that case, and these properties are unexpectedly converted to strings.

This applies to the following functions:

  • sanitize_post_field()
  • sanitize_term_field()
  • sanitize_user_field()
  • sanitize_bookmark_field()

and the following properties:

  • WP_Post::ID
  • WP_Post::post_parent
  • WP_Post::menu_order
  • WP_Term::term_id
  • WP_Term::term_taxonomy_id
  • WP_Term::parent
  • WP_Term::count
  • WP_Term::term_group
  • WP_User::ID
  • $bookmark::link_id
  • $bookmark::link_rating

As WordPress moves towards strict type comparisons (see #52627 or #52482) it is important to make the type of these properties consistent in all contexts, so that using strict comparison does not cause unexpected issues.

In WordPress 5.8, these functions and properties will now reliably return an be set to integer values.

For more information, see #53235.

Posts/Post Types: Use _prime_post_caches() for speeding up cached get_pages() call

The get_pages() function uses a cache containing the ID of pages matching parameters of a previous call.

The IDs are, on a subsequent call with the same parameters, inflated using the get_post() function call. This works well in terms of the same request, as all the pages were previously added to the in-memory cache.

However, on a subsequent request, when the cache is hit, there are likely no pages already in the in-memory cache, and those need to be fetched from the backend cache server, one by one.

By taking advantage of wp_cache_get_multiple() instead of fetching each individual page from the backend cache server one by one, sites with persistent cache backend will have improved speed, but also sites without a persistent cache backend will benefit from the bulk SQL query constructed by the _prime_post_caches() function.

The performance gains should be most noticeable in case a site has a lot of pages which are being requested via get_pages() function.

For more information, see #51469.

Users: Pass $userdata to the actions and filters in wp_insert_user()

There are several action and filter hooks within wp_insert_user() that would benefit from being able to access the raw $userdata array passed into the function. One use case where this would be useful is extending the wp user import-csv command in WP-CLI. This command is a wrapper for wp_insert_user() but it’s not possible to provide custom user meta fields.

Here are the hooks gaining a new parameter:

  • wp_pre_insert_user_data (filter)
  • insert_user_meta (filter)
  • profile_update (action)
  • user_register (action)

This will allow hooked functions to perform more contextual adjustments to new users, and makes supplying custom user meta fields possible.

For more information, see #53110.

Media: Introduce image_editor_output_format filter

As detailed in a previously published dev note, WordPress 5.8 now supports the WebP image format.

WebP is a modern image format that provides improved lossless and lossy compression for images on the web. WebP images are around 30% smaller on average than their JPEG or PNG equivalents, resulting in sites that are faster and use less bandwidth. WebP is supported in all modern browsers according to caniuse.

WordPress 5.8 adds WebP support by @adamsilverstein

When images are uploaded, WordPress generates smaller sub sizes as defined using add_image_size(). By default, WordPress will generate these sub sizes in the same format as the original. Because of the performance benefits of the WebP format, it may be desirable for sub sizes to be generated in WebP instead of the original format.

In WordPress 5.8, the new image_editor_output_format filter hook can be used to change the file format used for image sub sizes. This can be used to switch all sub sizes to WebP, or any other desired format (JPEG, etc.).

The following example shows how to generate all sub sizes for JPG images using WebP:

1
2
3
4
5
6
7
<?php
function mysite_wp_image_editor_output_format( $formats ) {
    $formats['image/jpg'] = 'image/webp';
 
    return $formats;
}
add_filter( 'image_editor_output_format', 'mysite_wp_image_editor_output_format' );

Note: both the GD and ImageMagick libraries support the WebP format in both lossy and lossless. However, only ImageMagick supports animated images.

Setting the output format to WebP will verify if the web server supports it, and if not it will not change the format, i.e. won’t work.

For more information, see #52867.

General: Pass the scheme to all the *_url filters

A new parameter representing the URL $scheme has been added to admin_url, includes_url, network_admin_url and user_admin_url filters. This parameter is used for giving context to the URL (such as http, https, login, login_post, admin, relative or null) and was already present in other URL related filters (home_url, rest_url, site_url, to name a few).

For more information, see #52813.

Posts/Post Types: Improve post_exists() query

In previous releases, the post_exists() function did not make use of the database indexes available and was generating a poorly performing query. A new $status parameter has been added to allow developers to specify post_type, post_date and post_status to ensure that the wp_posts table’s type_status_date index is used when determining if a post exists.

For more information, see #34012.

Themes: Introduce the delete_theme and deleted_theme action hooks

These new theme action hooks bring parity to the plugin deletion process and fire immediately before and after an attempt to delete a theme, respectively.

For more information see #16401.

Posts/Post Types: Revisions are now enabled for Reusable Blocks post type

As per ticket #53072, the wp_block post type –which is used for Reusable blocks– now supports revisions. It allows users and developers to rely on revision history for their Reusable blocks.

Changes in the_password_form filter

As per ticket #29008, the the_password_form filter now passes the post object so it can be used by developers to directly get the current post data when they override the password form rendering. This closes a long awaited ticket.

Revisions: Add a post type-specific filter to wp_revisions_to_keep()

A new wp_{$post_type}_revisions_to_keep filter has been added that makes it convenient to filter the number of revisions created for a specific post type.

This new filter will override both the value of WP_POST_REVISIONS and the wp_revisions_to_keep filter.

For more information, see #51550.


Last modified on Thursday July 1, 2021 at 10:43 UTC.

Props @desrosj, @azaozz and @audrasjb for review and additional content.

#5-8, #dev-notes