diff --git a/lib/class-wp-rest-menu-items-controller.php b/lib/class-wp-rest-menu-items-controller.php
index c0f70208c4279..f6cf192a80722 100644
--- a/lib/class-wp-rest-menu-items-controller.php
+++ b/lib/class-wp-rest-menu-items-controller.php
@@ -569,9 +569,15 @@ public function prepare_item_for_response( $post, $request ) {
if ( in_array( 'title', $fields, true ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
+ /** This filter is documented in wp-includes/post-template.php */
+ $title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID );
+
+ /** This filter is documented in wp-includes/class-walker-nav-menu.php */
+ $title = apply_filters( 'nav_menu_item_title', $title, $menu_item, null, 0 );
+
$data['title'] = array(
- 'raw' => $menu_item->post_title,
- 'rendered' => $menu_item->title,
+ 'raw' => $menu_item->title,
+ 'rendered' => $title,
);
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
diff --git a/packages/block-library/src/navigation/placeholder.js b/packages/block-library/src/navigation/placeholder.js
index ed2aa4b43afea..0e50078babbb8 100644
--- a/packages/block-library/src/navigation/placeholder.js
+++ b/packages/block-library/src/navigation/placeholder.js
@@ -1,8 +1,6 @@
/**
* External dependencies
*/
-
-import { escape } from 'lodash';
import classnames from 'classnames';
/**
@@ -93,9 +91,7 @@ function mapMenuItemsToBlocks( nodes ) {
type,
id,
url,
- label: ! title.rendered
- ? __( '(no title)' )
- : escape( title.rendered ),
+ label: ! title.rendered ? __( '(no title)' ) : title.rendered,
opensInNewTab: false,
},
innerBlocks
@@ -136,9 +132,7 @@ function convertPagesToBlocks( pages ) {
type,
id,
url,
- label: ! title.rendered
- ? __( '(no title)' )
- : escape( title.rendered ),
+ label: ! title.rendered ? __( '(no title)' ) : title.rendered,
opensInNewTab: false,
} )
);
diff --git a/phpunit/class-rest-nav-menu-items-controller-test.php b/phpunit/class-rest-nav-menu-items-controller-test.php
index dcc8c5ef4a3fa..69593227a32ac 100644
--- a/phpunit/class-rest-nav-menu-items-controller-test.php
+++ b/phpunit/class-rest-nav-menu-items-controller-test.php
@@ -177,6 +177,66 @@ public function test_get_item() {
$this->check_get_menu_item_response( $response, 'view' );
}
+ /**
+ * Test that title.raw contains the verbatim title and that title.rendered
+ * has been passed through the_title which escapes & characters.
+ *
+ * @see https://github.com/WordPress/gutenberg/pull/24673
+ */
+ public function test_get_item_escapes_title() {
+ wp_set_current_user( self::$admin_id );
+
+ $menu_item_id = wp_update_nav_menu_item(
+ $this->menu_id,
+ 0,
+ array(
+ 'menu-item-type' => 'taxonomy',
+ 'menu-item-object' => 'post_tag',
+ 'menu-item-object-id' => $this->tag_id,
+ 'menu-item-status' => 'publish',
+ 'menu-item-title' => 'Foo & bar',
+ )
+ );
+
+ $request = new WP_REST_Request(
+ 'GET',
+ "/__experimental/menu-items/$menu_item_id"
+ );
+ $request->set_query_params(
+ array(
+ 'context' => 'edit',
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ if ( ! is_multisite() ) {
+ // Check that title.raw is the unescaped title and that
+ // title.rendered has been run through the_title.
+ $this->assertEquals(
+ array(
+ 'rendered' => 'Foo & bar',
+ 'raw' => 'Foo & bar',
+ ),
+ $response->get_data()['title']
+ );
+ } else {
+ // In a multisite, administrators do not have unfiltered_html and
+ // post_title is ran through wp_kses before being saved in the
+ // database. Running the title through the_title does nothing in
+ // this case.
+ $this->assertEquals(
+ array(
+ 'rendered' => 'Foo & bar',
+ 'raw' => 'Foo & bar',
+ ),
+ $response->get_data()['title']
+ );
+ }
+
+ wp_delete_post( $menu_item_id );
+ }
+
/**
*
*/
@@ -726,7 +786,7 @@ protected function check_menu_item_data( $post, $data, $context, $links ) {
$this->assertEquals( $post->title, $data['title']['rendered'] );
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
if ( 'edit' === $context ) {
- $this->assertEquals( $post->post_title, $data['title']['raw'] );
+ $this->assertEquals( $post->title, $data['title']['raw'] );
} else {
$this->assertFalse( isset( $data['title']['raw'] ) );
}