Widget Improvements in WordPress 4.9

On the heels of adding TinyMCE rich editing to the Text widget and the media widgets in 4.8, there are another round of improvements coming to the Text widget and Video widget in 4.9, among other improvements to widgets.

Shortcodes in Text Widget

One very longstanding request—for over 8 years—has been to support shortcodes in the Text widget (#10457). This is finally implemented in WordPress 4.9. It is no longer required to have plugins and themes do add_filter( 'widget_text', 'do_shortcode' ). Core now will do_shortcode() at the widget_text_content filter (added in 4.8) in the same way it is applied in the_content at priority 11, after wpautop() and shortcode_unautop(). If a plugin has added do_shortcode() to widget_text then this filter will be suspended while the widget runs to prevent shortcodes from being applied twice. If a Text widget is in legacy mode then it will manually do_shortcode() as well.

One reason for the long delay with adding shortcode support in Text widgets was due to many shortcodes looking for a global $post when they run. Since the global $post varies depending on whatever the main query is, the shortcodes in a Text widget could render wildly different on different templates of a site. The solution worked out was to temporarily nullify the global $post before doing the shortcodes so that they will consistently have the same global state, with this global $post then restored after the shortcodes are done. So if you have shortcodes that depend on a global $post—or call get_post()—then you should make sure that they short-circuit when $post is null in order for them to behave properly if used in the Text widget.

As of [42185] this nullification of $post is only done for archive (non-singular) queries; for singular queries, the $post will instead be set to be the current main queried post via get_queried_object(). This ensures that the global $post is consistent and explicit. This setting of the $post global while applying filters (and shortcodes) is also now implemented for the Custom HTML widget. Additionally, to ensure that gallery shortcodes that lack ids do not end up listing out every attachment in the media library, a shortcode_atts_gallery filter has been added which makes sure the shortcode’s id attribute is set to -1 when the widget is rendered on archive templates. This allows you to embed the gallery for any currently queried post in the sidebar. You should make sure such a Text widget is not displayed on an archive template by either adding it exclusively to a sidebar that appears on singular templates, or by using a feature like Jetpack’s Widget Visibility to hide the widget on non-singular templates.

Media in Text Widget

One reason why shortcode support in the Text widget was needed in this release is because 4.9 also allows media to be embedded in the Text widget (#40854). There is now the same “Add Media” button in the rich Text widget as on the post editor, allowing you to add images, galleries, videos, audio, and other media. To support these, core also needed to support shortcodes like captionaudiovideo, and gallery. Note there are also dedicated widgets (Image, Audio, Video, and Gallery) for these media types as well.

Having separate media-specific widgets helps with discovery and allows us to provide streamlined interfaces for each media type. For example, the Image widget now has a field specifically for supplying the link URL (see #41274), and the Video widget now provides more guidance to users when supplying external URLs (#42039). The media-specific widgets are closely aligned with blocks in Gutenberg; the existence of media inside the Text widget will align with eventual nested blocks in Gutenberg, and would be treated as Classic Text blocks in any future migration from widgets to blocks.

Embeds in Text Widget and Video Widget

One shortcode not mentioned above is embed. This one was more difficult to support because oEmbeds have not been supported anywhere other than post content. This was because there were dependencies on having a post as context for the sake of caching, as the responses to oEmbed requests get stored in postmeta. However, as of #34115 if there is no post as context the oEmbeds will now get cached in an oembed_cache custom post type instead. Since a Text widget will explicitly nullify the global $post while shortcodes are processed, this means oEmbeds will get cached in this custom post type. Similarly to how do_shortcode() now applies in the widget_text_content filter like it applies on the_content, so too now WP_Embed::autoembed() and WP_Embed::run_shortcode() both also now run on widget_text_content.

In WordPress 4.8 the Video widget was introduced with support for displaying an uploaded video file, a YouTube video, or a video from Vimeo. Each of these were displayed using MediaElement.js. Just as oEmbeds are now able to be displayed in the Text widget, so too now the Video widget has been expanded to support any oEmbed provider for video. See #42039.

Theme Styling Changes

As with the previously-introduced media widgets (#32417) and the new Gallery widget (#41914), some themes will need to be updated to ensure the proper styling is applied to media and embeds that appear in the widget area context, since previously they would only appear in post content. Please follow #42203 and #41969 for style changes that are made to the core bundled themes, as you may need to make similar changes to your themes.

Improved Theme Switching

A longstanding difficulty with widgets has been where they end up when switching from one theme to another. With #39693 this experience is improved in 4.9 by having logic that is able to better map widgets between the themes’ widget areas. As noted by @obenland in [41555], there are three levels of mapping:

  1. If both themes have only one sidebar, they gets mapped.
  2. If both themes have sidebars with the same slug (e.g. sidebar-1), they get mapped.
  3. Sidebars that (even partially) match slugs from a similar kind of sidebar will get mapped. For example, if one theme as a widget area called “Primary” and another theme has “Main” then the widgets will be mapped between these widget areas. Similarly, widgets would get mapped from “Bottom” to “Footer”.

The names for the widget areas used for the mapping groups were obtained by gathering statistics from all the themes on WordPress.org.

Widget Saved State on Admin Screen

With #23120 there is now an indication for whether or not changes to a given widget has been saved on the widgets admin screen. (Widgets in the Customizer already had a saved state by virtue of being registered as regular settings.) When first opening a widget, the button will say “Saved” and appear disabled. Once a change is made to the widget (e.g. a change event triggered), then the button will become enabled and say “Save”. If you try leaving the admin screen at this point, an “Are you sure?” message will appear alerting that if you leave your changes will be lost. If you cancel, then the first widget with unsaved changes will be scrolled into view, expanded, and focused. Upon hitting “Save” the spinner will appear and then upon a successful save it will switch to “Saved” and become disabled. The “Close” link has been changed to “Done” and it only appears when the changes have been saved. Note that the HTML5 checkValidity method will now be called on the widget form prior to attempting to submit, and submitting will be blocked if it returns false. If you have JavaScript-based fields in the widget, make sure that you trigger change events whenever changes are written into any hidden inputs; this was already a requirement for widgets in the Customizer.

Related Tickets

  • #10457: Parse shortcodes in text widgets by default
  • #23120: There should be indication that widget settings have been saved
  • #34115: oEmbed not working on author page without posts
  • #38017: Add widget instance to remaining widget argument filters
  • #39693: Fix missing assignment of widgets on theme switch
  • #40442: Widgets: Rename “Custom Menu” widget to “Menu”
  • #40854: Allow media to be embedded in Text widget
  • #41274: Improve discoverability of link URL in Image widget.
  • #41610: Widgets: Change “close” to “done?”
  • #41914: Widgets: Add gallery widget
  • #41969: Ensure Gallery widget is styled properly across widget areas in bundled themes
  • #42039: Widgets: Enable oEmbed support for Video widget
  • #42203: Ensure media & embeds in Text widget are styled properly across widget areas in bundled themes

See full list of tickets in the Widgets component with the 4.9 milestone.

#4-9, #dev-notes, #feature-oembed, #media, #media-widgets, #widgets

Media Widgets for Images, Video, and Audio

As first introduced in the Image Widget Merge Proposal, WordPress 4.8 includes media widgets (#32417) for not only images (#39993) but also video (#39994) and audio (#39995), on top of an extensible base for introducing additional media widgets in the future, such as for galleries and playlists. To quote [40640]:

The last time a new widget was introduced, Vuvuzelas were a thing, Angry Birds started taking over phones, and WordPress stopped shipping with Kubrick. Seven years and 17 releases without new widgets have been enough, time to spice up your sidebar!

Since widgets are a very old part of WordPress (since 2.2), widgets in core have been very much entirely built using PHP with some Ajax sprinkled on top. In the time since WP_Widget was introduced in 2.8, WordPress has made dramatic shifts toward developing interfaces in JavaScript, including with the Customizer in 3.4 and the Media Library in 3.5, and more recently with the focus on the REST API and the editor (Gutenberg).

Given that the media widgets are naturally interfacing with the media library JS, it is necessary that the media widgets make use of JavaScript to construct their UI instead of relying on PHP. The media widgets fully reuse the existing media modal frames for not only selecting the media to display but also to edit all of its properties: attachments can be selected from the media library, while external media can be inserted by URL.

Initial groundwork for shimming JavaScript into widgets was added in 3.9 via the widget-added and widget-updated events, which are also being utilized in 4.8 with the Addition of TinyMCE to the Text Widget. A more recent proposal for making JavaScript more of a first class citizen can be found in #33507 and the media widgets incorporate some of its patterns that were also prototyped in the JS Widgets plugin. The media widgets make use of a Backbone View to manage the widget control’s UI and a Backbone Model for reading and manipulating the widget instance data.

Base Media Widget

PHP: wp-includes/widgets/class-wp-widget-media.php
JS: wp-admin/js/widgets/media-widgets.js

The three widgets all extend a WP_Widget_Media PHP abstract class; in JS the Backbone view wp.mediaWidgets.MediaWidgetControl and wp.mediaWidgets.MediaWidgetModel are extended. A unique aspect of how the media widgets work is how instance data is validated and sanitized. Normally widgets utilize procedural code to sanitize instances via a subclassed WP_Widget::update() method. The media widgets, however, make use of a REST API schema returned from WP_Widget_Media::get_instance_schema() to sanitize instances declaratively. The WP_Widget_Media::update() method iterates over the schema and uses it to sanitize and validate the instance properties. (Adding schemas to the base WP_Widget class is also proposed in #35574.) The JSON schema is extended to include a couple custom properties: media_prop provides the media JS property name that the widget instance maps, and the should_preview_update flag indicates whether a change to that prop should cause the control preview to re-render (this would be retired with a React rewrite and/or leveraging of corresponding blocks in Gutenberg).

The WP_Widget_Media abstract class has one abstract method which subclasses must implement: WP_Widget_Media::render_media(). This is the method that WP_Widget_Media::widget() calls with the $instance props to render the media for a given widget media type. Before the props are passed to the method, the $instance is applied through  widget_{$id_base}_instance filters.

A media widget subclass should output additional JS templates as the control needs by extending WP_Widget_Media::render_control_template_scripts(). Scripts that the widget control requires can be enqueued by extending WP_Widget_Media::enqueue_admin_scripts(); this is also where the PHP exports can be done with calls to wp_add_inline_script().

The REST API schema is exported from PHP to JS on the subclassed MediaWidgetModel prototypes. Other properties on the prototypes which should be extended include l10n and mime_type. The model subclasses are registered by assigning them to the wp.mediaWidgets.modelConstructors object, keyed by the widget ID base (e.g. media_image, media_video, etc). In the same way, the Backbone View wp.mediaWidgets.MediaWidgetControl is subclassed and registered by adding to the wp.mediaWidgets.controlConstructors object, also keyed by widget ID base. This is similar to how control types are registered in the Customizer.

The MediaWidgetControl and MediaWidgetModel will be instantiated once a widget control is expanded. Their instances will be then added to the wp.mediaWidgets.widgetControls object and wp.mediaWidgets.modelCollection respectively.

There is a subclass of a media controller and a couple media views in the wp.mediaWidgets namespace. These extensions to media classes are needed due to current limitations in media extensibility. They may be removed/reduced with improvements in #40427.

As with the incorporation of TinyMCE into the Text widget, the incorporation of the media library into the media widget has necessitated constructing the widget’s form fields differently than how they are normally done. Widgets in core have historically utilized static HTML for their control form fields. Every time a user hits “Save” the form fields get sent in an Ajax request which passes them to the WP_Widget::update() method and then the Ajax response sends back the output of WP_Widget::form() which then replaces the entire form. (Note widgets in the Customizer behave differently since there is no Save button in the widget, as updates are synced and previewed as changes are made; read more about Live Widget Previews.) This worked for static HTML forms in the past, but in the case of the media widgets the UI built with JavaScript instead of server-side PHP.

To avoid having to rebuild the media preview every time the user hits Save on the admin screen, the media widget puts its UI elements outside of the container that is “managed” by the server which gets replaced with each save. (A similar approach has also been employed by the new TinyMCE-extended Text widget in 4.8.) Since core does not yet represent a widget’s state in a JavaScript model (again see #33507), the media widget syncs its MediaWidgetModel props with hidden inputs that get rendered by WP_Media_Widget::form() in order to be sent to the server for preview and saving. The container for the media widget’s fields is .media-widget-control and the traditional container for a widget’s input fields as rendered by WP_Widget::form() is .widget-content:

For examples of how to implement media widgets, see the three implementations included in 4.8 as follows.

Image Widget

PHP: wp-includes/widgets/class-wp-widget-media-image.php
JS: wp-admin/js/widgets/media-image-widget.js

Field Type Default Description
attachment_id integer 0 Attachment post ID
url string "" URL to the media file
title string "" Title for the widget
size string "medium" Size
width integer 0 Width
height integer 0 Height
caption string "" Caption
alt string "" Alternative Text
link_type string "none" Link To
link_url string "" URL
image_classes string "" Image CSS Class
link_classes string "" Link CSS Class
link_rel string "" Link Rel
link_target_blank boolean false Open link in a new tab
image_title string "" Image Title Attribute

Video Widget

PHP: wp-includes/widgets/class-wp-widget-media-video.php
JS: wp-admin/js/widgets/media-video-widget.js

The video widget allows for embeddable video formats to be selected from the media library or linked to externally by URL. In addition, URLs to YouTube or Vimeo may also be provided since the video shortcode logic supports rendering them via MediaElement.js. It is possible for plugins to add support for additional oEmbed providers via extending the video widget control’s isHostedVideo method; for more, see the Jetpack PR for adding VideoPress support.

Field Type Default Description
attachment_id integer 0 Attachment post ID
url string "" URL to the media file
title string "" Title for the widget
preload string "metadata" Preload
loop boolean false Loop
content string "" Tracks (subtitles, captions, descriptions, chapters, or metadata)
mp4 string "" URL to the mp4 video source file
m4v string "" URL to the m4v video source file
webm string "" URL to the webm video source file
ogv string "" URL to the ogv video source file
flv string "" URL to the flv video source file

Note that the presence of format-specific fields is dependent on what is returned by wp_get_video_extensions().

Audio Widget

PHP: wp-includes/widgets/class-wp-widget-media-audio.php
JS: wp-admin/js/widgets/media-audio-widget.js

The audio widget allows for embeddable audio formats to be selected from the media library or linked to externally by URL. Note that there are no oEmbed audio formats supported since the audio shortcode logic only supports rendering players for actual audio files.

Field Type Default Description
attachment_id integer 0 Attachment post ID
url string "" URL to the media file
title string "" Title for the widget
preload string "none" Preload
loop boolean false Loop
mp3 string "" URL to the mp3 audio source file
ogg string "" URL to the ogg audio source file
m4a string "" URL to the m4a audio source file
wav string "" URL to the wav audio source file

Note that the presence of format-specific fields is dependent on what is returned by wp_get_audio_extensions().

Default Themes Updates

Themes that add custom styles to the MediaElement.js player (namely Twenty Thirteen and Twenty Fourteen) were updated from just styling it within syndicated content, to also include instances within widgets. Most themes don’t restrict styles for captioned images or media players to just post content, that is, limit CSS selectors to classes output by post_class(). If your theme does, make sure to either remove that constraint or include a .widget selector.

Conclusion

The work on the new media widgets in core was conducted in the Core Media Widgets plugin and on its corresponding wp-core-media-widgets GitHub repo. Many of the decisions that were made in the architecture of the feature can be found there in the GitHub issues and pull requests.

Keep in mind that the media widgets will likely undergo many more changes with the incorporation of the Gutenberg editor, and that widgets themselves will likely see many changes to align with Gutenberg’s editor blocks which are now being prototyped. Essentially if you can insert a given type of block into the editor, there should also be a widget available for representing the same content.

#4-8, #dev-notes, #media-widgets

Image Widget Merge Proposal


As part of this year’s Customization Focus, @westonruter, @obenland, @adamsilverstein, @timmydcrawford, @gonom9, and I have been working on creating an image widget for core. This new widget lets you quickly and easily add an image to your site’s widget areas.

You may have previously seen this come up as a media widget on Trac, before we decided to split the widget up into individual media-focused widgets. Having single widgets makes them more discoverable, and eases the way forward for similar blocks in the Editor. The image widget is the first in a series of media-focused widgets we’ll be introducing to core.

You can check out the widget on GitHub, install the plugin from WordPress.org, or check out the Trac ticket.

Why make an image widget?

People want to add images to their widget areas. Visit any subject blog across the internet and you’ll likely find images in the sidebars. People add photos of themselves along with bio blurbs, link to their books, promote upcoming events with graphics, partner with other blogs and exchange ads, and add call-to-actions using images.

The current process for adding an image to your widget areas is quite painful. You need to:

  1. Upload or select an image in your media library.
  2. Copy the image URL.
  3. Go to the widgets admin screen, or the widgets panel in the Customizer.
  4. Create a new Text widget.
  5. Add the image to the Text widget using HTML, which is often a barrier for new or non-technical site maintainers. (See #35243 for work to add a visual mode to Text widget.)

This is a huge barrier to what should be a relatively simple task. This new image widget makes it much easier to add an image to your widget areas by natively integrating with core’s media library.

How is it implemented?

There hasn’t been any new widget types added to core in a long time; the Custom Menu widget was the last widget added in WordPress 3.0—almost 7 years ago! (The core themes Twenty Eleven and Twenty Fourteen did include their Ephemera widgets since then.) Since widgets are a very old part of WordPress, widgets in core have been very much entirely built using PHP with some Ajax sprinkled on top. In the time since WP_Widget was introduced in 2.8, WordPress has made dramatic shifts toward developing interfaces in JavaScript, including with the Customizer in 3.4 and the Media Library in 3.5, and more recently with the focus on the REST API.

Given that the media widgets are naturally interfacing with the media library JS, it is necessary that the media widgets make use of JavaScript to construct their UI instead of relying on PHP. Initial groundwork for shimming JavaScript into widgets was added in 3.9 via the widget-added and widget-updated events. A more recent proposal for making JavaScript more of a first class citizen can be found in #33507 and the media widgets incorporate some of its patterns that were also prototyped in the JS Widgets plugin. The media widgets make use of a Backbone View to manage the widget control’s UI and a Backbone Model for reading and manipulating the widget instance data.

Another unique aspect of how the media widgets work is how instance data is sanitized. Normally widgets write procedural code to sanitize instances via a subclassed WP_Widget::update() method. The media widgets, however, make use of a REST API schema returned from WP_Widget_Media::get_instance_schema() to sanitize instances declaratively. The WP_Widget_Media::update() method iterates over the schema and uses it to sanitize and validate the instance properties. Adding schemas to the base WP_Widget class is also proposed in #35574.

Please test

Please test the image widget. For now, you can grab the latest version of the widget on GitHub. You can either check it out locally using Git, or download a zip by clicking “Clone or download” → “Download ZIP.” Alternately, you can download the nightly version of the plugin from the WordPress.org plugin directory.

#image-widget, #media-widgets