Adjacent Post Navigation Changes in WordPress 6.9 and Compatibility Issues

TL;DR

WordPress 6.9 introduced a fix for adjacent post navigation when posts have identical publication dates. While this resolved a long-standing bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority., it inadvertently caused infinite loops in some extensions that modify the get_adjacent_post() WHERE clause.

What Changed in WordPress 6.9

In WordPress 6.9 (Trac #8107), a bug fix landed where next/previous post navigation failed when multiple posts shared identical post_date values. This commonly occurred when bulk-publishing draft posts.

The Technical Change

The get_adjacent_post() functionโ€™s WHERE clause was modified to include ID-based comparison as a tiebreaker:

Before (WordPress 6.8 and earlier):

WHERE p.post_date > '2024-01-01 12:00:00' 
  AND p.post_type = 'post'

After (WordPress 6.9):

WHERE (
    p.post_date > '2024-01-01 12:00:00' 
    OR (
      p.post_date = '2024-01-01 12:00:00' 
      AND p.ID > 123
    )
  ) 
  AND p.post_type = 'post'

This ensures deterministic ordering when posts have identical dates, using the post ID as a secondary sort criterion.

Additionally, the ORDER BY clause was updated:

-- Before
ORDER BY p.post_date DESC 
LIMIT 1

-- After  
ORDER BY p.post_date DESC, 
         p.ID DESC 
LIMIT 1

The Problem: Infinite Loops in Some Themes/Plugins

As Trac ticket #64390 documents, some plugins and themes modify adjacent post navigation to change behavior. For example, WooCommerceโ€™s Storefront theme navigates between products instead of regular posts. These plugins use the get_{$adjacent}_post_where filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. to replace the current postโ€™s date with a different postโ€™s date.

Hereโ€™s what was happening:

  1. PluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party hooksHooks In WordPress theme and development, hooks are functions that can be applied to an action or a Filter in WordPress. Actions are functions performed when a certain event occurs in WordPress. Filters allow you to modify certain functions. Arguments used to hook both filters and actions look the same. into get_previous_post_where filter.
  2. Plugin does string replacement: replaces $current_post->post_date with $target_product->post_date.
  3. Problem: The new WHERE clause structure includes the post date in TWO places (date comparison AND ID comparison).
  4. Simple string replacement modified the date comparison but left the ID comparison unchanged.
  5. Query returns the same post repeatedly โ†’ infinite loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop..

Real-World Example: Storefront Theme

The fix to the WooCommerce Storefront theme illustrates this issue. They had to add special handling for the ID comparison:

// Replace the post date (works as before)
$where = str_replace( $post->post_date, $new->post_date, $where );

// NEW: Also need to replace the ID comparison (WordPress 6.9+)
if ( strpos( $where, 'AND p.ID ' ) !== false ) {
    $search = sprintf( 'AND p.ID %s ', $this->previous ? '<' : '>' );
    $target = $search . $post->ID;
    $replace = $search . $new->ID;
    $where = str_replace( $target, $replace, $where );
}

For Plugin Developers: Detecting and Fixing the Issue

How to Detect If Your Plugin Is Affected

Your plugin is likely affected if it:

  1. Uses the get_{$adjacent}_post_where filter.
  2. Performs string replacement on post dates in the WHERE clause.
  3. Changes which post is considered โ€œadjacentโ€ (like navigating between custom post types instead).

To test if your plugin works correctly with WordPress 6.9:

  1. Create 3-4 posts with identical publication dates (bulk publish drafts).
  2. Navigate between them using your pluginโ€™s adjacent post functionality.
  3. Verify that navigation moves to different posts (not the same post repeatedly).
  4. Check for infinite loops or performance issues.

Quick Fix: Handle the ID Comparison

If youโ€™re doing date replacement in the WHERE clause, you also need to handle the ID comparison. There are numerous ways to tie a knot, but the following example is loosely inspired by Storefrontโ€™s recent fix.

add_filter( 'get_next_post_where', 'example_custom_adjacent_post_where', 10, 5 );
add_filter( 'get_previous_post_where', 'example_custom_adjacent_post_where', 10, 5 );

function example_custom_adjacent_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {

    // IMPORTANT: Replace this with your logic to find the desired adjacent post.
    $adjacent_post = example_find_adjacent_post_function( $post );
	
    if ( $adjacent_post instanceof WP_Post ) {
        // Replace the date comparison.
        $where = str_replace( $post->post_date, $adjacent_post->post_date, $where );

        // Replace the post ID in the comparison.
        $where = preg_replace(
            "/AND p\.ID (<|>) {$post->ID}\)/",
            "AND p.ID $1 {$adjacent_post->ID})",
            $where
        );
    }

    return $where;
}

You could also add a version_compare( $wp_version, '6.9', '>=' ) test if youโ€™d like to support multiple versions.

Important: donโ€™t forget to first test any code on your site, and customize it according to your needs.

Next time

At the time, this was not a known breaking change, and was classed as a bug fix.

However as @jmdodd points out in the ticket, changes to WP_Query, especially SQL changes should be, by default, communicated more widely. Going forward:

  1. Reach out to known plugins using the get_{$adjacent}_post_where filter.
  2. Include a migrationMigration Moving the code, database and media files for a website site from one server to another. Most typically done when changing hosting companies. guidance in the 6.9 field guideField guide The field guide is a type of blogpost published on Make/Core during the release candidate phase of the WordPress release cycle. The field guide generally lists all the dev notes published during the beta cycle. This guide is linked in the about page of the corresponding version of WordPress, in the release post and in the HelpHub version page. (as applicable).
  3. Test against any known, popular plugins that modify adjacent post queries.

Whatโ€™s Next and Getting Help

Discussions have started on the ticket about ways to make this more robust in future WordPress versions. If youโ€™re experiencing issues related to this change:

  1. Check Trac ticket #64390 for the latest updates.
  2. Ask questions in the #core channel on WordPress SlackSlack Slack is a Collaborative Group Chat Platform https://slack.com/. The WordPress community has its own Slack Channel at https://make.wordpress.org/chat/..
  3. Review the original fix PR #10394.

Thank you for your patience and understanding. If you maintain a plugin affected by this change, please update it using the guidance above, and donโ€™t hesitate to reach out if you need assistance.

Thanks to @westonruter @isabel_brison @andrewserong @jmdodd for helping to prepare this post.

#6-9, #dev-notes

Ability to Hide Blocks in WordPress 6.9

WordPress 6.9 now includes a built-in feature to hide blocks, making it easy to tuck content away without deleting it. You can now hide blocks: select a blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience., click the ellipsis, and choose โ€œHideโ€. Hidden blocks are visually removed from the editor, and fully omitted from the published markup. Scripts and styles for hidden blocks are also omitted from the rendered page by default (see WordPress 6.9 Frontend Performance Field Guide for more details).

To unhide a block, open the List View, identify hidden blocks via the โ€œHiddenโ€ icon next to them, open the ellipsis menu again, and choose โ€œShowโ€. You can also toggle Hide/Show from the keyboard: use Ctrl + Shift + H on Windows orย Linux, โŒ˜ + Shift + H on macOS.

How to disable the hide option

Because it is implemented as a standard Block APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. support flag, opting into or out of this capabilitycapability Aย capabilityย is permission to perform one or more types of task. Checking if a user has a capability is performed by the current_user_can function. Each user of a WordPress site might have some permissions but not others, depending on theirย role. For example, users who have the Author role usually have permission to edit their own posts (the โ€œedit_postsโ€ capability), but not permission to edit other usersโ€™ posts (the โ€œedit_others_postsโ€ capability). aligns with the rest of the block supports.

The support is enabled by default for every block type except for a short list of coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. blocks. To disable the support selectively, hook into the block_type_metadata() filter, adjust the metadata, and update the supports.visibility flag:

function disable_block_visibility_support( $metadata ) {
	// Disable visibility support for the core/group block.
	if ( isset( $metadata['name'] ) && 'core/group' === $metadata['name'] ) {
		$metadata['supports']['visibility'] = false;
	}
	return $metadata;
}
add_filter( 'block_type_metadata', 'disable_block_visibility_support' );

For additional implementation details and history, see Gutenberg PR #71203.


Props to @joen for co-authoring the note.
Props to @westonruter, @ramonopoly for review.

#6-9, #dev-notes, #dev-notes-6-9

Miscellaneous Editor Changes in WordPress 6.9

BlockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. Variations: Haveย getActiveBlockVariationย fall back to default variation

WordPress 6.9 introduces aย small changeย to active block variation detection.

To understand what that change looks like, recall that a block variation can include anย isActiveย criterionย that will be used to determine if a given block instance matches the variation.

The change concerns the case in which none of theย isActiveย criteria of a block typeโ€™s variations match a given block instance. In this case, WordPress 6.9 will use the variation with theย isDefaultย propertyย set toย trueย as fallback, if any.

Two limitations apply:

  1. The variation with theย isDefaultย property is only used as fallback if it doesnโ€™tย alsoย include anย isActiveย criterion, as that would mean that the variation did not match that criterion (and thus cannot be assumed to be active).
  2. The fallback is only applied for theย blockย andย transformย scopes but not to theย inserter, as to not affect the block name displayed there.

In practice, the updated behavior should be consistent with usersโ€™ and developersโ€™ expectations, as theย isDefaultย variation is typically used to set a default block variation different from the โ€œvanillaโ€ block type.

Together with the improvements made to active block variation detectionย in WordPress 6.6ย , this change allows block authors to express the criteria for active block detection in a very succinct and JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML.-only manner. For an example, see how these principles were applied toย rewrite Group block variations.

CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress.-data: Media Entity Deprecation

What is being deprecated?

In WordPress 6.9, theย @wordpress/core-dataย packageโ€™sย mediaย entity is being deprecated. This entity is usually accessed through a range of APIs:

  • Theย getMediaย andย getMediaItemsย selectors or resolvers.
  • Theย saveMediaย andย deleteMediaย actions.
  • Theย getEntityRecord,ย getEntityRecordsย and other selectors or resolvers when passed theย 'root'ย andย 'media'ย params (e.g.ย getEntityRecords( 'root', 'media' );).
  • Theย editEntityRecord,ย saveEntityRecord,ย deleteEntityRecordย and other actions when passed theย 'root'ย andย 'media'ย params (e.g.ย deleteEntityRecord( 'root', 'media', 123)).

These APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. calls will continue to work in WordPress 6.9, but will now log a deprecation message that recommends switching to a different signature.

Why is this being deprecated?

Theย mediaย entity was found to be duplicating another entity,ย attachment. This attachment entity is usually accessed through API calls likeย getEntityRecords( 'postType', 'attachment' );. These two entities both internally use theย mediaย REST APIREST API The REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think โ€œphone appโ€ or โ€œwebsiteโ€) can communicate with the data store (think โ€œdatabaseโ€ or โ€œfile systemโ€) https://developer.wordpress.org/rest-api/. endpoint and return results forย attachmentย post types.

The entity system maintains a cache of REST API data, and the two separate entity types result in two separate caches that might contain different states for the same data. If the caches become desynchonized, this can result in noticeable bugs where a user interface might show outdated data, or old data overwrites newer data when saved.

Various solutions for fixing this issue were explored, but the option that was found to be safest was to deprecate the media entity in favor of the attachment entity.

How to update your code to use theย attachmentย entity

Replacing API calls

The following code snippet shows some examples of how to update your API calls:

wp.data.select( 'core' ).getMedia( 123 ); // now deprecated
wp.data.select( 'core' ).getEntityRecord( 'root', 'media', 123 ); // now deprecated
wp.data.select( 'core' ).getEntityRecord( 'postType', 'attachment', 123 ); // recommended update

wp.data.select( 'core' ).getMediaItems(); // now deprecated
wp.data.select( 'core' ).getEntityRecords( 'root', 'media' ); // now deprecated
wp.data.select( 'core' ).getEntityRecords( 'postType', 'attachment' ); // recommended update

wp.data.dispatch( 'core' ).saveMedia( 123, { // ... media data } ); // now deprecated
wp.data.dispatch( 'core' ).saveEntityRecord( 'root', 'media', 123, { // ... media data } ); // now deprecated
wp.data.dispatch( 'core' ).saveEntityRecord( 'postType', 'attachment', 123,  { // ... media data } ); // recommended update

wp.data.dispatch( 'core' ).deleteMedia( 123 ); // now deprecated
wp.data.dispatch( 'core' ).deleteEntityRecord( 'root', 'media', 123 ); // now deprecated
wp.data.dispatch( 'core' ).deleteEntityRecord( 'postType', 'attachment', 123 ); // recommended update
Changes to theย captionย property when using theย getEditedEntityRecordย selector

When making an API call for the โ€˜editedโ€™ version of an entity record (e.g.ย getEditedEntityRecord( 'root', 'media', 123 )), the core data package converts theย captionย property to a string:

caption: "My image's caption"

The attachment entity doesnโ€™t perform the same processing, an API call likeย getEditedEntityRecord( 'postType', 'attachment', 123 )ย returns theย captionย in its object form:

caption: {
    raw: "My image's caption",
    rendered: "My image's caption"
}

Theย caption.rawย property corresponds to the string value that theย mediaย entity returns for theย caption.

When switching to the โ€˜attachmentโ€™ entity, some code may need to be updated to account for this difference.

Block Editor: Support passing updater function to setAttributes

Starting from WordPress 6.9, theย setAttributeย prop passed to a blockโ€™sย Editย component will now support an updater function as an argument.

The updater is a pure function; it takes only the current attributes as its argument and returns the updated attributes.

// Toggle a setting when the user clicks the button.
const toggleSetting = () =>
	setAttributes( ( currentAttr ) => ( {
		mySetting: ! currentAttr.mySetting,
	} ) );

// Append item to the list.
const addListItem = ( newListItem ) =>
	setAttributes( ( currentAttr ) => ( {
		list: [ ...currentAttr.list, newListItem ],
	} ) );

SelectControl: Moved class names to the component root

Theย SelectControlย componentโ€™s class assignments have been adjusted for better consistency with other form components, such asย TextControl. Both the default class name (components-select-control) and any custom class name passed via theย classNameย prop are now applied to the root element of the component, rather than to the internal element.

While this update does not alter the componentโ€™s behavior or visual design, custom CSSCSS Cascading Style Sheets. that previously depended on the inner structure may need to be updated.

Before:

<div class="components-base-control">
  <div class="components-base-control__field">
    <div class="components-flex components-input-base components-select-control my-custom-classname">
      <div class="components-flex-item">
        <label class="components-truncate components-text components-input-control__label">Label</label>
      </div>
      <div class="components-input-control__container">
        <select class="components-select-control__input"></select>
        <div aria-hidden="true" class="components-input-control__backdrop"></div>
      </div>
    </div>
  </div>
</div>

After:

<div class="components-base-control components-select-control my-custom-classname">
  <div class="components-base-control__field">
    <div class="components-flex components-input-base">
      <div class="components-flex-item">
        <label class="components-truncate components-text components-input-control__label">Label</label>
      </div>
      <div class="components-input-control__container">
        <select class="components-select-control__input"></select>
        <div aria-hidden="true" class="components-input-control__backdrop"></div>
      </div>
    </div>
  </div>
</div>

Registering custom social icons

WordPress 6.9 adds support for registering custom social icons to the Social Icons block. With this update, developers can create plugins to define new services and SVGs, and expose them to both the editor and frontend through block variations and PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher filters. For styling, matching brand colors can be handled in CSS and loaded for both views.โ€‹

Example for Ko-fi variation:

registerBlockVariation('core/social-link', {
	name: 'kofi',
	title: 'Ko-fi',
	icon: (
		<SVG role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
			<Path d="M11.351 2.715c-2.7 0-4.986.025-6.83.26C2.078 3.285 0 5.154 0 8.61c0 3.506.182 6.13 1.585 8.493 1.584 2.701 4.233 4.182 7.662 4.182h.83c4.209 0 6.494-2.234 7.637-4a9.5 9.5 0 0 0 1.091-2.338C21.792 14.688 24 12.22 24 9.208v-.415c0-3.247-2.13-5.507-5.792-5.87-1.558-.156-2.65-.208-6.857-.208m0 1.947c4.208 0 5.09.052 6.571.182 2.624.311 4.13 1.584 4.13 4v.39c0 2.156-1.792 3.844-3.87 3.844h-.935l-.156.649c-.208 1.013-.597 1.818-1.039 2.546-.909 1.428-2.545 3.064-5.922 3.064h-.805c-2.571 0-4.831-.883-6.078-3.195-1.09-2-1.298-4.155-1.298-7.506 0-2.181.857-3.402 3.012-3.714 1.533-.233 3.559-.26 6.39-.26m6.547 2.287c-.416 0-.65.234-.65.546v2.935c0 .311.234.545.65.545 1.324 0 2.051-.754 2.051-2s-.727-2.026-2.052-2.026m-10.39.182c-1.818 0-3.013 1.48-3.013 3.142 0 1.533.858 2.857 1.949 3.897.727.701 1.87 1.429 2.649 1.896a1.47 1.47 0 0 0 1.507 0c.78-.467 1.922-1.195 2.623-1.896 1.117-1.039 1.974-2.364 1.974-3.897 0-1.662-1.247-3.142-3.039-3.142-1.065 0-1.792.545-2.338 1.298-.493-.753-1.246-1.298-2.312-1.298"/>
		</SVG>
	),
	attributes: {
		service: 'kofi',
	},
	isActive: ['service']
});

Filters and CSS support synchronization with the frontend output. For more comprehensive references and setup routines, see this Developer Blog post.โ€‹


Props to content authors @bernhard-reiter, @greenshady, @mamaduka, @talldanwp, @wildworks

6-9, #dev-notes, #dev-notes-6-9

Updates to the HTML API in 6.9

WordPress 6.9 brings an abundance of quiet improvements to the HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways.. Updates in this release mostly represent applications of the HTML API to existing code in CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress.; these updates increase WordPressโ€™ reliability, improve its security hardening, and reduce maintenance burden on the project.

Major Updates

WP_HTML_Processorโ€‹::โ€‹serialize_token() is now public.

The HTML Processorโ€™s serialize_token() method returns a fully-normalized and well-formed representation of the currently-matched token. It was introduced in #62036 for WordPress 6.7 as a private method which performs the heavy-lifting for how the HTML API turns โ€œjunkโ€ inputs into equivalent well-formed outputs. For example:

$html = '5 < 8 & <tag a=v a="dup"id=di></3>bl&#97rg';
echo WP_HTML_Processor::normalize( $html );
// 5 &lt; 8 &amp; <tag a="v" id="di"><!--3-->blarg</tag>

Its value outside of WP_HTML_Processor::normalize() became evident, however, particularly in the creation of โ€œserialization builders1โ€ which make it possible to modify more of the HTML structure than the HTML Processor itself does. In typical HTML API loops, this method can be used to partially extract portions of the document safely:

// Extract the outerHTML of every paragraph element.
$processor = WP_HTML_Processor::create_fragment( $html );
$content   = '';
while ( $processor->next_tag( 'P' ) ) {
    $content .= $processor->serialize_token();
    $depth    = $processor->get_current_depth();
    while (
        $processor->next_token() &&
        $processor->get_current_depth() > $depth
    ) {
        $content .= $processor->serialize_token();
    }
    $content .= $processor->serialize_token();
    $content .= "\n\n";
}

WordPress understands JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a userโ€™s browser. https://www.javascript.com/. .dataset properties.

HTML provides a convenient mechanism tying HTML and JavaScript together through the custom data attributes on a tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.). These are the attributes starting with data- like dataโ€‘wpโ€‘interactive or dataโ€‘postโ€‘id and their values are available on the corresponding Element object in JavaScript through the .dataset property:

<span data-order="Carrots please!">
    What should we order?
</span>
<script>
document.body.addEventListener(
    'click',
    event => alert( event.target.dataset.order )
);
</script>

There are endless ways this integration can be used to add a level of dynamism to a site. Unfortunately, how the name of these attributes is transformed looks simpler than it is. For example, the dataโ€‘wpโ€‘bindโ€‘โ€‘class HTML attribute corresponds to the wpBindโ€‘Class dataset property.

To prevent confusion, WordPress 6.9 includes two new functions to map between the HTML and JavaScript names: wp_js_dataset_name() indicates what would appear on the .dataset property in a browser while wp_html_custom_data_attribute_name() indicates what name should be used in HTML to produce the .dataset property of a given name. For example:

// What would this HTML attribute name correspond to in JavaScript?
echo wp_js_dataset_name( 'data-one-two--three---four' );
// oneTwo-Three--Four

// What HTML attribute name is necessary to produce the given JavaScript name?
echo wp_html_custom_data_attribute_name( 'postId.guid' );
// data-post-id.guid

No more hard-coding HTML string assertions in unit tests.

WordPress is full of unit tests asserting specific HTML transformations. The expected outputs for these tests are usually hard-coded and sent to $this->assertSame() to compare against the actual outputs from the code under test. Unfortunately this tends to produce a high rate of false positives because of trivialities like adding an attribute in a different order than was expected, using single-quotes around an attribute value rather than double-quotes, leaving extra whitespace or not enough, or using the mistaken self-closer on an <img> or <br> tag.

When two HTML strings produce the same result in a browser they should pass regardless of their insignificant differences. To ease the development of these kinds of tests and to reduce their false-positive rates, WordPress 6.9 introduces a new method on the WP_UnitTestClass base class: $this->assertEqualHTML().

This new test assertion verifies that two strings are equivalent representations of the same normative HTML. They compare HTML strings semantically, provide more useful output than string comparison when they fail to assert, and theyโ€™re even aware of blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. semantics.

$this->assertEqualHTML(
  "<img src='puppy&period;jpg'   loading=lazy>",
  '<img loading="l&#97zy"src="puppy.jpg"/>'
);
 โœ”๏ธŽ Is equivalent html

Time: 00:00.038, Memory: 40.00 MB

OK (1 test, 1 assertion)

This test case would pass since the arguments are two equivalent constructions of the same IMG element. However, a few small changes and it succinctly highlights their differences. The addition of the block comment delimiter is for illustrative purposes only.

$this->assertEqualHTML(
    "<!-- wp:image {\"id\":5} --><img src='puppy.jpg' loading=lazy>",
    '<!-- wp:img {"id":6} --><img loading="lazy" data-priority=5 src=puppy.jpg/>'
);
 โœ˜ Is equivalent html
   โ”
   โ”œ HTML markup was not equivalent.
   โ”œ Failed asserting that two strings are identical.
   โ”Š ---ยทExpected
   โ”Š +++ยทActual
   โ”Š @@ @@
   โ”Š -'BLOCK["core/image"]
   โ”Š +'BLOCK["core/img"]
   โ”Š    {
   โ”Š -ยทยทยทยท"id": 5
   โ”Š +ยทยทยทยท"id": 6
   โ”Š    }
   โ”Š    <img>
   โ”Š +ยทยทยทยทdata-priority="5"
   โ”Š      loading="lazy"
   โ”Š -ยทยทยทยทsrc="puppy.jpg"
   โ”Š +ยทยทยทยทsrc="puppy.jpg/"
   โ”Š  '
   โ”‚
   โ•ต /WordPress-develop/tests/phpunit/includes/abstract-testcase.php:1235
   โ•ต /WordPress-develop/tests/phpunit/tests/html/equivalentHtmlTest.php:10
   โ”ด

Time: 00:00.038, Memory: 40.00 MB

The HTML API received minor updates.

  • The Tag Processorโ€™s constructor will now cast null to an empty string. Similarly, the static creator methods on the HTML Processor will return null instead of an instance of the WP_HTML_Processor class. In each case a _doing_it_wrong() notice will alert developers that these classes expect a string input. This change prevents burying the type errors, which leads to unexpected crashes later on, such as when calling get_updated_html().
  • When calling set_modifiable_text() on a SCRIPT element, updates are rejected if they contain <script or </script in them. This is a conservative measure to avoid entering the script data double escaped state (personal blogblog (versus network, site)) which is prone to misinterpretation.

Full Changelog

Enhancements

  • wp_js_dataset_name() and wp_html_custom_data_attribute_name() map between HTML attributes and the .dataset property in JavaScript. [#61501, PR#9953]
  • The WP_UnitTestClass now contains an assertEqualHTML() method which determines if two strings represent the same normative HTML. [#63527, PR#8882]
  • Multiple length checks are safely skipped when processing SCRIPT content due to an early minimum-length check. [#63738, PR#9230]
  • Encoding detection in METAMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. tags is simplified, leading to a minor performance lift. [#63738, PR#9231]
  • WP_HTML_Processor::serialize_token() is now public, making it easier to mix the full safety of the HTML API with outside code modifying and combining HTML. [#63823, PR#9456]
  • The Tag Processor and HTML Processor handle invalidinvalid A resolution on the bug tracker (and generally common in software development, sometimes also notabug) that indicates the ticket is not a bug, is a support request, or is generally invalid. null inputs safely. [#63854, PR#9545]
  • set_modifiable_text() rejects additional contents inside a SCRIPT element when the contents could disturb its normal closing. [#63738, PR#9560]

Bug Fixes

  • Attempting to avoid the HTTP Referer problem, quirks mode is referred to as indicated_compatibility_mode. [#63391, PR#9401]
  • wp_kses() no longer unescapes escaped numeric character references for users without unfiltered_html, preserving more of the actual entered content in a post or comment. [#63630, PR#9099]
  • SCRIPT tags are properly closed in the presence of abruptly-closed HTML comments within the contents, and when the closing SCRIPT tagโ€™s tag name is delimited by a form-feed. [#63738, PR#9397]
  • wp_kses() now allows some previously-missing HTML5 semantic tags and their attributes. [#63786, PR#9379]
  • set_attribute() directly escapes syntax characters into HTML character references to avoid problems with double-escaping logic. This ensures that all values are represented accurately in the resulting HTML. [#64054, PR#10143]

Core refactors

A number of places in Core were updated to benefit from the HTML API.

  • Several of the unit tests now rely on assertEqualHTML(), including for block supports, wp_rel_nofollow(), wp_rel_ugc(), wp_kses, post-filtering, media, oEmbed filtering. [#59622, #63694, PR#5486, PR#9251, PR#9255, PR#9257, PR#9258, PR#9259, PR#9264]
  • get_url_in_content() relies on the Tag Processor to more reliably detect links. Besides improving general HTML parsing, this new version always returns the decoded href attribute, preventing confusion in downstream code. [#63694, PR#9272]
  • Processing for image blocks in classic themes is now performed via the HTML API rather than with PCREs. [#63694, PR#10218]

Acknowledgements

Props to @jonsurrell and @westonruter for reviewing this post.

  1. Methods to replace innerHTML and outerHTML, wrap an element, unwrap an element, insert elements, and more are possible by scanning through a document and conditionally copying the normalized tokens into an output string. โ†ฉ๏ธŽ

#6-9, #dev-notes, #dev-notes-6-9, #html-api

PHP 8.5 support in WordPress 6.9

PHP 8.5 was released on November 20th. Contributors to WordPress have been busy in recent months preparing for this version and weโ€™re happy to report that all issues reported against PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher 8.5 have been addressed in WordPress 6.9 RC2. Compared to previous PHP releases relatively few changes were required, mostly to address new deprecations and warnings. Take a look at the PHP 8.5 support tracking ticket if youโ€™re interested.

Due to the acknowledgement that WordPress is rarely used in isolation (without any theme or plugins), support is labelled as โ€œbetaBeta A pre-release of software that is given out to a large group of users to trial under real conditions. Beta versions have gone through alpha testing in-house and are generally fairly close in look, feel and function to the final product; however, design changes often occur as part of the process. supportโ€ until at least 10% of all WordPress sites are running that version or later, as this indicates good compatibility across the wider ecosystem of plugins and themes.

PHP Compatibility and WordPress Versions page in the WordPress Core Handbook

Following the established guidelines, support for any given version of PHP is labelled as โ€œbeta supportโ€ until at least 10% of all WordPress sites are running that version or later. When WordPress 6.9 ships on December 2nd, support for PHP 8.5 and 8.4 will be labelled as โ€œbeta supportโ€. PHP versions 8.3 back to 7.2 are fully supported.

If you discover a bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. or compatibility issue while running WordPress with PHP 8.5, please create a ticket on Trac.

Summary

As always, reading through the complete upgrade document is recommended.

Even as WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. continues to expand its support for new versions of PHP, support for older versions will remain as-is in WordPress 6.9, staying at PHP 7.2.24 and higher. This will continue to be evaluated and no change will be made until usage numbers show that the impact on users will be minimal.

WordPress continues to encourage all users to run the latest and greatest versions of PHP, including PHP 8.5.

Props @desrosj, @jorbin for reviewing this post.

#6-9, #dev-notes, #dev-notes-6-9, #php-compatibility

Accessibility Improvements in WordPress 6.9

WordPress 6.9 brings extensive accessibilityAccessibility Accessibility (commonly shortened to a11y) refers to the design of products, devices, services, or environments for people with disabilities. The concept of accessible design ensures both โ€œdirect accessโ€ (i.e. unassisted) and โ€œindirect accessโ€ meaning compatibility with a personโ€™s assistive technology (for example, computer screen readers). (https://en.wikipedia.org/wiki/Accessibility) improvements across WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. and GutenbergGutenberg The Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses โ€˜blocksโ€™ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/, continuing the goals to meet web content accessibility standards throughout WordPress and make it easier to author accessible content. These updates include changes to administration, customization, login and registration, bundled themes, and the blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. editor.

Core

Improvements to WordPress Core include 33 accessibility enhancements and bugbug A bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. fixes. Major changes include numerous new or improved screen reader notifications, a complete update of CSSCSS Cascading Style Sheets. generated content to ensure no excess content is spoken, and code changes to ensure proper semantics and improved focus management.

Administration

  • #47101 โ€“ Improve accessibility when deleting terms via AJAX: color contrast & spoken message.
  • #48655 โ€“ Improve the โ€œAdd-itemโ€ function in menus (esp. for pages)
  • #63118 โ€“ Hide โ€œSkip to Toolbarโ€ shortcut on small screens within adminadmin (and super admin)
  • #63126 โ€“ Theme preview model and Media library model having issues with Shift/Ctrl + Shift next and previous arrows.
  • #63449 โ€“ Low color contrast for <code> elements in description text on Settings > General page
  • #63546 โ€“ Fix unclosed li element in pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party-editor.php
  • #63603 โ€“ Replace deprecated / non-standard CSS for `speak` and `aural`
  • #63723 โ€“ On the Add New plugin page, put the Add Plugins screen description above the filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. menu

CustomizerCustomizer Tool built into WordPress core that hooks into most modern themes. You can use it to preview and modify many of your siteโ€™s appearance settings.

  • #42078 โ€“ Customize: fix the color hue picker HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. and accessibility
  • #47579 โ€“ Customizer โ€œSelect logoโ€ and โ€œSelect site iconโ€ look like drop areas, but are buttons.
  • #50696 โ€“ UIUI User interface & Accessibility issues in customizer menus section
  • #63011 โ€“ Customizer: The back button is not keyboard focusable
  • #63832 โ€“ Loss of focus when setting or changing the Site Logo or Site Icon in Customizer

Editing

  • #63460 โ€“ Increase color contrast for embed template
  • #61959 โ€“ Enhance Support for `popovertarget` and `popover` Attributes in Native Browser Popover APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways.

Login and Registration

  • #63281 โ€“ Password field has wrong focus on installations
  • #63286 โ€“ User profile first name, last name, nickname and email fields should have autocomplete attributes for accessibility
  • #48345 โ€“ Add Caps lock message to login screen

Media

  • #63114 โ€“ No screen reader announcements for upload image errors
  • #63238 โ€“ Remove `target=โ€_blankโ€` from Browser Uploader Link
  • #63239 โ€“ Button focus visibility issue in media upload page
  • #63571 โ€“ Excessive Spacing Between Right SidebarSidebar A sidebar in WordPress is referred to a widget-ready area used by WordPress themes to display information that is not a part of the main content. It is not always a vertical column on the side. It can be a horizontal rectangle below or above the content area, footer, header, or any where in the theme. Items in Edit Media Screen on Mobile View
  • #63973 โ€“ Add Media Button missing aria-haspopup and aria-controls

Miscellaneous

  • #40428 โ€“ Introduce best practices to hide CSS generated content from assistive technologies
  • #44267 โ€“ Privacy Request List Table: A way to show the time of request when itโ€™s older than 24 hours.
  • #63030 โ€“ Update CSS for `::-moz-placeholder` color
  • #63620 โ€“ Remove translator comments when hidden text matches visible text
  • #63950 โ€“ Tabbing through database upgrade screen shows โ€œWordPressโ€ text over logo

Bundled Themes

  • #10219 โ€“ โ€œOlder Entriesโ€ and โ€œNewer Entriesโ€ links are wrong when entries displayed in ascending order
  • #44656 โ€“ Multiple themes: Empty site title leaves empty anchor tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) in headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitorโ€™s opinion about your content and you/ your organizationโ€™s brand. It may also look different on different screen sizes.
  • #52116 โ€“ Twenty Twenty: Menu + Search can cause a scroll jump on close
  • #63875 โ€“ Twenty Twenty-Two and Twenty Twenty-Five: <pre> tag overflows container, causing horizontal scroll

Widgets

  • #63531 โ€“ CategoryCategory The 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging. dropdown does not meet WCAGWCAG WCAG is an acronym for Web Content Accessibility Guidelines. These guidelines are helping make sure the internet is accessible to all people no matter how they would need to access the internet (screen-reader, keyboard only, etc) https://www.w3.org/TR/WCAG21/. 2.2 A on windows and some linux systems

Gutenberg

Changes within Gutenberg include 44 accessibility fixes and enhancements, including the addition of new blocks and the block Notes feature that have undergone accessibility reviews. Numerous fundamental components have had accessibility improvements to ensure that interfaces across the editor are more consistent and understandable.ย 

Blocks

  • #68662 โ€“ Cover: Fix placeholder color options keyboard accessibility
  • #68909 โ€“ Site Title: Fix logic for โ€˜aria-currentโ€™ attribute
  • #69628 โ€“ Site Title: Prevent saving and rendering a value made of only spaces
  • #69689 โ€“ Navigation Link, Navigation Submenu: Remove the title attribute controls
  • #69821 โ€“ Social Icons: Remove custom placeholder state
  • #69837 โ€“ Navigation block: fix submenu Escape key behavior
  • #70139 โ€“ Button Block: Add HTML Element selection in Advanced settings
  • #70192 โ€“ Button: Avoid focus loss when unlinking using keyboard
  • #70210ย  โ€“ Columns block: Donโ€™t use ToolsPanelItem for Columns setting
  • #70730 โ€“ a11yAccessibility Accessibility (commonly shortened to a11y) refers to the design of products, devices, services, or environments for people with disabilities. The concept of accessible design ensures both โ€œdirect accessโ€ (i.e. unassisted) and โ€œindirect accessโ€ meaning compatibility with a personโ€™s assistive technology (for example, computer screen readers). (https://en.wikipedia.org/wiki/Accessibility): Comments Pagination Nav Wrapper
  • #64119 โ€“ Add Accordions Block
  • #73177 โ€“ Fix a11y of descriptions and alerts for โ€œInvalidinvalid A resolution on the bug tracker (and generally common in software development, sometimes also notabug) that indicates the ticket is not a bug, is a support request, or is generally invalid.โ€ Nav Items

Components

  • #67792 โ€“ Improve the EntitiesSavedStates modal dialog design and labeling
  • #69011 โ€“ Remove non translatable additional info from font size picker visual label and improve labeling
  • #69441 โ€“ ARIA: Fix invalid `DropdownMenu` children structure
  • #68633 โ€“ Global Styles: Prevent Unwanted ItemGroup List Rendering in Border Panel
  • #68542 โ€“ Button: Update hover styles to account for pressed state for `tertiary button`
  • #69609 โ€“ ActionModal: Add support for customisable `focusOnMount`
  • #69904 โ€“ Add new HTMLElementControl component
  • #70591 โ€“ `FormTokenField`: Fix focus lost on tab when `__experimentalExpandOnFocus` is set
  • #70096 โ€“ Components: Fix label and placeholder handling in `LinkControlSearchInput`
  • #70660 โ€“ Autocomplete: Prevent text cursor position loss when clicking to insert an item
  • #70146 โ€“ Color Picker: Improve color picker slider focus styles

Data Views

  • #67874 โ€“ Display Checkbox by default in dataviews
  • #69876 โ€“ DataViews: Always show primary action for list layout if hover isnโ€™t supported
  • #71561 โ€“ DataViews: Custom `empty` elements are no longer wrapped in `<p>` tags to improve accessibility
  • #72417 โ€“ DataViews: Use Text-Based Links for Primary Actions
  • #72501 โ€“ Dataviews: Make bulk actions text based.

Editor

  • #68481 โ€“ Fix CSS classes for the post editor iframeiframe iFrame is an acronym for an inline frame. An iFrame is used inside a webpage to load another HTML document and render it. This HTML document may also contain JavaScript and/or CSS which is loaded at the time when iframe tag is parsed by the userโ€™s browser. body.
  • #68975 โ€“ Close patterns modal on insertion and focus on inserted pattern
  • #69305 โ€“ Swap fullscreen mode snackbar notice message.
  • #69334 โ€“ InputControl: Ensure consistent placeholder color
  • #69378 โ€“ Button: Remove fixed width from small and compact buttons with icons
  • #69451 โ€“ Editor: Refactor the โ€˜PostVisibilityโ€™ component
  • #69520 โ€“ Fix shift+tab from post title
  • #69724 โ€“ Post Template Panel: Preserve parent modal when closing template creation dialog
  • #68631 โ€“ Global Styles: Fix incorrect usage of ItemGroup in the Background image panel
  • #69813 โ€“ Background Image Panel: fix focus loss
  • #70128 โ€“ Global Styles: Move `Randomize colors` button to Edit Palette panel
  • #70133 โ€“ Editor: Add label in`TextareaControl` in CollabSidebar
  • #69278 โ€“ Toolbar: Adjust colors for dark mode support
  • #70451ย  โ€“ feat: clarify label & add help text with link for Link Rel

Miscellaneous

  • #69440 โ€“ Make password protected input fields consistent.
  • #70091 โ€“ Templates: Add back button & fix focus loss when navigating through template creation flow

Acknowledgements

Props to @jorbin and @jeffpaul for reviewing this post.

#6-9, #accessibility, #dev-notes, #dev-notes-6-9

Admin menu search query changed

When creating and editing menus in the Menus interface, searching for posts and pages has historically used a full text search as a query. This can make some pages difficult to find, if they use a title that primarily uses common words in the site content.

In WordPress 6.9, the search query arguments have been changed to limit searches to only the post title. This is intended to make it easier to find the post youโ€™re looking for in search results.

The query change adds the argument search_columns with the value array( โ€˜post_titleโ€™ ) to the search. The columns correspond to the database columns in the wp_posts table.

To facilitate sites that may need the previous search logic, a filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. has been added to easily modify the search query.

/**
 * Filter the menu quick search arguments.
 *
 * @since 6.9.0
 *
 * @param array $args {
 * ย  ย  Menu quick search arguments.
 *
 * ย  ย  @type booleanย  ย  ย  $no_found_rowsย  ย  ย  ย  ย  Whether to return found rows data. Default true.
 * ย  ย  @type booleanย  ย  ย  $update_post_meta_cache Whether to update post meta cache. Default false.
 * ย  ย  @type booleanย  ย  ย  $update_post_term_cache Whether to update post term cache. Default false.
 * ย  ย  @type intย  ย  ย  ย  ย  $posts_per_page ย  ย  ย  ย  Number of posts to return. Default 10.
 * ย  ย  @type string ย  ย  ย  $post_typeย  ย  ย  ย  ย  ย  ย  Type of post to return.
 * ย  ย  @type string ย  ย  ย  $sย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  Search query.
 * ย  ย  @type arrayย  ย  ย  ย  $search_columns ย  ย  ย  ย  Which post table columns to query.
 * }
 */
 $query_args = apply_filters( 'wp_ajax_menu_quick_search_args', $query_args );

To restore the previous behavior, you can unset the additional argument:

/**
 * Restore pre-6.9 menu search arguments.
 *
 * @param array $args Array of arguments as documented.
 *
 * @return array
 */
function restore_menu_quick_search_args( $args ) {
ย  ย  unset( $args['search_columns'] );
ย ย ย ย return $args;
}
add_filter( 'wp_ajax_menu_quick_search_args', 'restore_menu_quick_search_args' );

Related ticketticket Created for both bug reports and feature development on the bug tracker.: Improve the โ€œAdd itemโ€ function in menus.

Acknowledgements

Props to @jorbin for reviewing this post.

#6-9, #dev-notes, #dev-notes-6-9

Legacy Internet Explorer Code Removed

With WordPress 6.9, numerous legacy features that were used to support Internet Explorer have been removed. All versions of Internet Explorer have been unsupported in WordPress since version 5.8, released in July 2021. These changes continue the process of removing code that existed only to provide support for these browsers.

Removed Support for IE Conditional Scripts and Styles

Conditional comment support was added for scripts in WordPress 4.2, and for styles sometime around version 2.6.0, but was made more easily available in WordPress 3.6.

Conditional comments were a mechanism to pass scripts and styles for specific versions of IE. The last browser to support conditional comments in any way was IE9, which has been out of support by WordPress since version 4.8. No supported browser uses scripts or styles provided within conditional comments, treating them the same as any other commented code.

The feature has been removed, and using the โ€˜conditionalโ€™ argument will throw a deprecation notice (โ€œIE conditional comments are ignored by all supported browsers.โ€) if WP_DEBUG is set to true.ย 

Any style or script currently loaded using conditional arguments will be ignored, as will any dependencies of those styles or scripts if they are not already required by another script or style.

IE Conditional scripts and styles were removed in core ticket #63821.

Updates to bundled themes

All bundled themes that used conditional comments have been updated to remove usages of conditional comments and CSSCSS Cascading Style Sheets. syntax only used to support Internet Explorer.

CSS and JSJS JavaScript, a web scripting language typically executed in the browser. Often used for advanced user interfaces and behaviors. files that were only required conditionally remain in place as blank files, containing only comments to indicate when the related support was removed.ย 

Conditional comments were removed from bundled themes in #58836.ย 

IE Compatibility Removals

Compatibility scripts were removed from the Media Elements instantiation process in #63471.

#62128 removed IE-specific hacks and EOT file rules from the Genericons stylesheet for four bundled themes, from Twenty Thirteen to Twenty Sixteen.

Acknowledgements

Props to @jorbin, @westonruter, @jonsurrell and @sabernhardt for reviewing this post.

#6-9, #dev-notes, #dev-notes-6-9

Introducing the streaming block parser in WordPress 6.9

WordPress 6.9 introduces the WP_Block_Processor class โ€” a new tool inspired by the HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. and designed for efficiently scanning, understanding, and modifying blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. structure in HTML documents.

Continue on to learn about this new class, its use-cases, and how you can take advantage of it for more efficient server-side processing.

Continue reading โ†’

#6-9, #block-api, #dev-notes, #dev-notes-6-9

URL-escaping functions can support HTTPS as the default protocol in WordPress 6.9

When a string passed to the esc_url() and esc_url_raw() functions does not include a protocol (https://, http://, etc.), WordPress will prepend http:// to the URLURL A specific web address of a website or web page on the Internet, such as a websiteโ€™s URL www.wordpress.org string before further processing and returning it. This is a reasonable fallback behavior, but there is currently no way to override the default protocol with another (such as https:// for social media profiles).

Starting in WordPress 6.9, the esc_url() and esc_url_raw() functions will now prepend https:// to the URL if it does not already contain a scheme and the first item in the $protocols array is 'https'. The current behavior (prepending http://) will continue when any other value is listed first within the $protocols array.

<?php
/*
 * Recommended approach for defaulting to 'https' while maintaining
 * backward compatibility.
 * 
 * Example: no protocol with $protocols and 'https' first.
 *
 * Output:
 * - WordPress >= 6.9: 'https://profiles.wordpress.org'
 * - WordPress  < 6.9: 'http://profiles.wordpress.org'
 */
echo esc_url( 'profiles.wordpress.org', array( 'https', 'http' ) );

By including 'https' first and then 'http' in the array:

  • WordPress 6.9 would prepend https:// to an incomplete URL.
  • Older WordPress versions would continue to prepend http://, which remains the default scheme when the $protocols argument is not defined. If the array only contains 'https', esc_url() would return an empty string because http is not included in the list of valid protocols.

Additional examples of output include:

<?php
/*
 * Example 1: no protocol included in $url.
 * 
 * Output:
 * - WordPress >= 6.9: 'http://profiles.wordpress.org'
 * - WordPress  < 6.9: 'http://profiles.wordpress.org'
 */
echo esc_url( 'profiles.wordpress.org' );

/*
 * Example 2: 'http' protocol included in $url.
 *
 * Output:
 * - WordPress >= 6.9: 'http://profiles.wordpress.org'
 * - WordPress  < 6.9: 'http://profiles.wordpress.org'
 */
echo esc_url( 'http://profiles.wordpress.org' );

/*
 * Example 3: 'https' protocol included in $url.
 *
 * Output:
 * - WordPress >= 6.9: 'https://profiles.wordpress.org'
 * - WordPress  < 6.9: 'https://profiles.wordpress.org'
 */
echo esc_url( 'https://profiles.wordpress.org' );

/*
 * Example 4: no protocol with $protocols and 'http' first.
 *
 * Output:
 * - WordPress >= 6.9: 'http://profiles.wordpress.org'
 * - WordPress  < 6.9: 'http://profiles.wordpress.org'
 */
echo esc_url( 'profiles.wordpress.org', array( 'http', 'https' ) );
       
/*
 * Example 5: no protocol in $url with $protocols and no 'http'.
 *
 * Output:
 * - WordPress >= 6.9: 'https://profiles.wordpress.org'
 * - WordPress  < 6.9: ''
 *
 * Note: if 'http' is not included in the $protocols array,
 * the fully escaped URL will not pass the final check that
 * a valid, allowed protocol is included.
 */
echo esc_url( 'profiles.wordpress.org', array( 'https' ) );
             
/*
 * Example 6: protocol within $url missing within $protocols.
 *
 * Output for all:
 * - WordPress >= 6.9: ''
 * - WordPress  < 6.9: ''
 *
 * Note: if 'http' is not included in the $protocols array,
 * the fully escaped URL will not pass the final check that
 * a valid, allowed protocol is included.
 */
echo esc_url( 'https://profiles.wordpress.org', array( 'http' ) );
echo esc_url( 'http://profiles.wordpress.org', array( 'https' ) );
echo esc_url( 'mailto:[email protected]', array( 'https', 'http' ) );

For more information, refer to #52886.


Props to @desrosj for co-authoring the note, writing the code examples and expanding on details. Props to @jorbin for review.

#6-9, #dev-notes, #dev-notes-6-9