diff --git a/lib/class-wp-rest-menu-items-controller.php b/lib/class-wp-rest-menu-items-controller.php index 1e1b84975abeb..980435663122f 100644 --- a/lib/class-wp-rest-menu-items-controller.php +++ b/lib/class-wp-rest-menu-items-controller.php @@ -312,6 +312,7 @@ protected function prepare_item_for_database( $request ) { 'menu-item-db-id' => $menu_item_db_id, 'menu-item-object-id' => $menu_item_obj->object_id, 'menu-item-object' => $menu_item_obj->object, + 'menu-item-content' => $menu_item_obj->menu_item_content, 'menu-item-parent-id' => $menu_item_obj->menu_item_parent, 'menu-item-position' => $position, 'menu-item-title' => $menu_item_obj->title, @@ -331,6 +332,7 @@ protected function prepare_item_for_database( $request ) { 'menu-item-db-id' => 0, 'menu-item-object-id' => 0, 'menu-item-object' => '', + 'menu-item-content' => '', 'menu-item-parent-id' => 0, 'menu-item-position' => 0, 'menu-item-type' => 'custom', @@ -374,6 +376,14 @@ protected function prepare_item_for_database( $request ) { } } + if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) { + if ( is_string( $request['content'] ) ) { + $prepared_nav_item['menu-item-content'] = $request['content']; + } elseif ( isset( $request['content']['raw'] ) ) { + $prepared_nav_item['menu-item-content'] = $request['content']['raw']; + } + } + $taxonomy = get_taxonomy( 'nav_menu' ); $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; // If menus submitted, cast to int. @@ -596,6 +606,20 @@ public function prepare_item_for_response( $post, $request ) { $data['object_id'] = absint( $menu_item->object_id ); } + if ( rest_is_field_included( 'content', $fields ) ) { + $data['content'] = array(); + } + if ( rest_is_field_included( 'content.raw', $fields ) ) { + $data['content']['raw'] = $menu_item->content; + } + if ( rest_is_field_included( 'content.rendered', $fields ) ) { + /** This filter is documented in wp-includes/post-template.php */ + $data['content']['rendered'] = apply_filters( 'the_content', $menu_item->content ); + } + if ( rest_is_field_included( 'content.block_version', $fields ) ) { + $data['content']['block_version'] = block_version( $menu_item->content ); + } + if ( in_array( 'parent', $fields, true ) ) { // Same as post_parent, expose as integer. $data['parent'] = absint( $menu_item->menu_item_parent ); @@ -787,7 +811,7 @@ public function get_item_schema() { $schema['properties']['type'] = array( 'description' => __( 'The family of objects originally represented, such as "post_type" or "taxonomy".', 'gutenberg' ), 'type' => 'string', - 'enum' => array( 'taxonomy', 'post_type', 'post_type_archive', 'custom' ), + 'enum' => array( 'taxonomy', 'post_type', 'post_type_archive', 'custom', 'html' ), 'context' => array( 'view', 'edit', 'embed' ), 'default' => 'custom', ); @@ -861,6 +885,35 @@ public function get_item_schema() { 'default' => 0, ); + $schema['properties']['content'] = array( + 'description' => __( 'HTML content to display for this menu item. May contain blocks.', 'gutenberg' ), + 'context' => array( 'view', 'edit', 'embed' ), + 'type' => 'object', + 'arg_options' => array( + 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). + 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). + ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'HTML content, as it exists in the database.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'edit' ), + ), + 'rendered' => array( + 'description' => __( 'HTML content, transformed for display.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'block_version' => array( + 'description' => __( 'Version of the block format used in the HTML content.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'edit' ), + 'readonly' => true, + ), + ), + ); + $schema['properties']['target'] = array( 'description' => __( 'The target attribute of the link element for this menu item.', 'gutenberg' ), 'type' => 'string', diff --git a/lib/compat.php b/lib/compat.php index 202c1345c2a8a..e6579a880dbee 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -415,7 +415,11 @@ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parse $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block ); $context = array( - 'postId' => $post->ID, + 'query' => array( 'categoryIds' => array() ), + ); + + if ( isset( $post ) ) { + $context['postId'] = $post->ID; /* * The `postType` context is largely unnecessary server-side, since the @@ -423,10 +427,8 @@ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parse * manifest is expected to be shared between the server and the client, * it should be included to consistently fulfill the expectation. */ - 'postType' => $post->post_type, - - 'query' => array( 'categoryIds' => array() ), - ); + $context['postType'] = $post->post_type; + } if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) { foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) { @@ -461,3 +463,51 @@ function gutenberg_render_block_with_assigned_block_context( $pre_render, $parse * @see WP_Block::render */ remove_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); + +function gutenberg_update_nav_menu_item_content( $menu_id, $menu_item_db_id, $args ) { + global $wp_customize; + + // Support setting content in customize_save admin-ajax.php requests by + // grabbing the unsanitized $_POST values. + if ( isset( $wp_customize ) ) { + $values = $wp_customize->unsanitized_post_values(); + if ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) { + if ( is_string( $values[ "nav_menu_item[$menu_item_db_id]" ]['content'] ) ) { + $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content']; + } elseif ( isset( $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw'] ) ) { + $args['menu-item-content'] = $values[ "nav_menu_item[$menu_item_db_id]" ]['content']['raw']; + } + } + } + + $defaults = array( + 'menu-item-content' => '', + ); + + $args = wp_parse_args( $args, $defaults ); + + update_post_meta( $menu_item_db_id, '_menu_item_content', $args['menu-item-content'] ); +} +add_action( 'wp_update_nav_menu_item', 'gutenberg_update_nav_menu_item_content', 10, 3 ); + +function gutenberg_setup_html_nav_menu_item( $menu_item ) { + if ( 'html' === $menu_item->type ) { + $menu_item->type_label = __( 'HTML', 'gutenberg' ); + $menu_item->content = ! isset( $menu_item->content ) ? get_post_meta( $menu_item->db_id, '_menu_item_content', true ) : $menu_item->content; + } + + return $menu_item; +} +add_filter( 'wp_setup_nav_menu_item', 'gutenberg_setup_html_nav_menu_item' ); + +function gutenberg_output_html_nav_menu_item( $item_output, $item, $depth, $args ) { + if ( 'html' === $item->type ) { + $item_output = $args->before; + /** This filter is documented in wp-includes/post-template.php */ + $item_output .= apply_filters( 'the_content', $item->content ); + $item_output .= $args->after; + } + + return $item_output; +} +add_filter( 'walker_nav_menu_start_el', 'gutenberg_output_html_nav_menu_item', 10, 4 ); diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index c52ba5b9581cf..f885008f37e29 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -206,7 +206,10 @@ function Navigation( { > {