Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored rendering logic for nav block #33224

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 0 additions & 154 deletions lib/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,160 +165,6 @@ function( $menu_item ) {
}
add_filter( 'wp_nav_menu_objects', 'gutenberg_remove_block_nav_menu_items', 10 );

/**
* Recursively converts a list of menu items into a list of blocks. This is a
* helper function used by `gutenberg_output_block_nav_menu()`.
*
* Transformation depends on the menu item type. Link menu items are turned into
* a `core/navigation-link` block. Block menu items are simply parsed.
*
* @param array $menu_items The menu items to convert, sorted by each menu item's menu order.
* @param array $menu_items_by_parent_id All menu items, indexed by their parent's ID.

* @return array Updated menu items, sorted by each menu item's menu order.
*/
function gutenberg_convert_menu_items_to_blocks(
$menu_items,
&$menu_items_by_parent_id
) {
if ( empty( $menu_items ) ) {
return array();
}

$blocks = array();

foreach ( $menu_items as $menu_item ) {
if ( 'block' === $menu_item->type ) {
$parsed_blocks = parse_blocks( $menu_item->content );

if ( count( $parsed_blocks ) ) {
$block = $parsed_blocks[0];
} else {
$block = array(
'blockName' => 'core/freeform',
'attrs' => array(
'originalContent' => $menu_item->content,
),
);
}
} else {
$block = array(
'blockName' => 'core/navigation-link',
'attrs' => array(
'label' => $menu_item->title,
'url' => $menu_item->url,
),
);
}

$block['innerBlocks'] = gutenberg_convert_menu_items_to_blocks(
isset( $menu_items_by_parent_id[ $menu_item->ID ] )
? $menu_items_by_parent_id[ $menu_item->ID ]
: array(),
$menu_items_by_parent_id
);

$blocks[] = $block;
}

return $blocks;
}

/**
* Shim that causes `wp_nav_menu()` to output a Navigation block instead of a
* nav menu when the theme supports block menus. The Navigation block is
* constructed by transforming the stored tree of menu items into a tree of
* blocks.
*
* Specifically, this shim makes it so that `wp_nav_menu()` returns early when
* the theme supports block menus. When merged to Core, this functionality
* should exist in `wp_nav_menu()` after `$sorted_menu_items` is set. The
* duplicated code (marked using BEGIN and END) can be deleted.
*
* This shim can be removed when the Gutenberg plugin requires a WordPress
* version that has the ticket below.
*
* @see https://core.trac.wordpress.org/ticket/50544
*
* @param string|null $output Nav menu output to short-circuit with. Default null.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*
* @return string|null Nav menu output to short-circuit with.
*/
function gutenberg_output_block_nav_menu( $output, $args ) {
if ( ! current_theme_supports( 'block-nav-menus' ) ) {
return null;
}

// BEGIN: Code that already exists in wp_nav_menu().

// Get the nav menu based on the requested menu.
$menu = wp_get_nav_menu_object( $args->menu );

// Get the nav menu based on the theme_location.
$locations = get_nav_menu_locations();
if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
}

// Get the first menu that has items if we still can't find a menu.
if ( ! $menu && ! $args->theme_location ) {
$menus = wp_get_nav_menus();
foreach ( $menus as $menu_maybe ) {
$menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
if ( $menu_items ) {
$menu = $menu_maybe;
break;
}
}
}

if ( empty( $args->menu ) ) {
$args->menu = $menu;
}

// If the menu exists, get its items.
if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
}

// Set up the $menu_item variables.
_wp_menu_item_classes_by_context( $menu_items );

$sorted_menu_items = array();
foreach ( (array) $menu_items as $menu_item ) {
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
}

unset( $menu_items, $menu_item );

// END: Code that already exists in wp_nav_menu().

$menu_items_by_parent_id = array();
foreach ( $sorted_menu_items as $menu_item ) {
$menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
}

$block_attributes = array();
if ( isset( $args->block_attributes ) ) {
$block_attributes = $args->block_attributes;
}

$navigation_block = array(
'blockName' => 'core/navigation',
'attrs' => $block_attributes,
'innerBlocks' => gutenberg_convert_menu_items_to_blocks(
isset( $menu_items_by_parent_id[0] )
? $menu_items_by_parent_id[0]
: array(),
$menu_items_by_parent_id
),
);

return render_block( $navigation_block );
}
add_filter( 'pre_wp_nav_menu', 'gutenberg_output_block_nav_menu', 10, 2 );

/**
* Shim that makes nav-menus.php nicely display a menu item with its `type` set to
* `'block'`.
Expand Down
155 changes: 155 additions & 0 deletions packages/block-library/src/navigation/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ function render_block_core_navigation( $attributes, $content, $block ) {
if ( empty( $block->inner_blocks ) ) {
if ( array_key_exists( '__unstableLocation', $attributes ) ) {
$location = $attributes['__unstableLocation'];
unset( $attributes['__unstableLocation'] );
$maybe_classic_navigation = gutenberg_render_menu_from_location( $location, $attributes );
if ( $maybe_classic_navigation ) {
return $maybe_classic_navigation;
Expand Down Expand Up @@ -274,3 +275,157 @@ function block_core_navigation_typographic_presets_backcompatibility( $parsed_bl
}

add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' );

/**
* Shim that causes `wp_nav_menu()` to output a Navigation block instead of a
* nav menu when the theme supports block menus. The Navigation block is
* constructed by transforming the stored tree of menu items into a tree of
* blocks.
*
* Specifically, this shim makes it so that `wp_nav_menu()` returns early when
* the theme supports block menus. When merged to Core, this functionality
* should exist in `wp_nav_menu()` after `$sorted_menu_items` is set. The
* duplicated code (marked using BEGIN and END) can be deleted.
*
* This shim can be removed when the Gutenberg plugin requires a WordPress
* version that has the ticket below.
*
* @see https://core.trac.wordpress.org/ticket/50544
*
* @param string|null $output Nav menu output to short-circuit with. Default null.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*
* @return string|null Nav menu output to short-circuit with.
*/
function gutenberg_output_block_nav_menu( $output, $args ) {
if ( ! current_theme_supports( 'block-nav-menus' ) ) {
return null;
}

// BEGIN: Code that already exists in wp_nav_menu().

// Get the nav menu based on the requested menu.
$menu = wp_get_nav_menu_object( $args->menu );

// Get the nav menu based on the theme_location.
$locations = get_nav_menu_locations();
if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
}

// Get the first menu that has items if we still can't find a menu.
if ( ! $menu && ! $args->theme_location ) {
$menus = wp_get_nav_menus();
foreach ( $menus as $menu_maybe ) {
$menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
if ( $menu_items ) {
$menu = $menu_maybe;
break;
}
}
}

if ( empty( $args->menu ) ) {
$args->menu = $menu;
}

// If the menu exists, get its items.
if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
}

// Set up the $menu_item variables.
_wp_menu_item_classes_by_context( $menu_items );

$sorted_menu_items = array();
foreach ( (array) $menu_items as $menu_item ) {
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
}

unset( $menu_items, $menu_item );

// END: Code that already exists in wp_nav_menu().

$menu_items_by_parent_id = array();
foreach ( $sorted_menu_items as $menu_item ) {
$menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
}

$block_attributes = array();
if ( isset( $args->block_attributes ) ) {
$block_attributes = $args->block_attributes;
}

$navigation_block = array(
'blockName' => 'core/navigation',
'attrs' => $block_attributes,
'innerBlocks' => gutenberg_convert_menu_items_to_blocks(
isset( $menu_items_by_parent_id[0] )
? $menu_items_by_parent_id[0]
: array(),
$menu_items_by_parent_id
),
);

return render_block( $navigation_block );
}
add_filter( 'pre_wp_nav_menu', 'gutenberg_output_block_nav_menu', 10, 2 );

/**
* Recursively converts a list of menu items into a list of blocks. This is a
* helper function used by `gutenberg_output_block_nav_menu()`.
*
* Transformation depends on the menu item type. Link menu items are turned into
* a `core/navigation-link` block. Block menu items are simply parsed.
*
* @param array $menu_items The menu items to convert, sorted by each menu item's menu order.
* @param array $menu_items_by_parent_id All menu items, indexed by their parent's ID.

* @return array Updated menu items, sorted by each menu item's menu order.
*/
function gutenberg_convert_menu_items_to_blocks(
$menu_items,
&$menu_items_by_parent_id
) {
if ( empty( $menu_items ) ) {
return array();
}

$blocks = array();

foreach ( $menu_items as $menu_item ) {
if ( 'block' === $menu_item->type ) {
$parsed_blocks = parse_blocks( $menu_item->content );

if ( count( $parsed_blocks ) ) {
$block = $parsed_blocks[0];
} else {
$block = array(
'blockName' => 'core/freeform',
'attrs' => array(
'originalContent' => $menu_item->content,
),
);
}
} else {
$block = array(
'blockName' => 'core/navigation-link',
'attrs' => array(
'label' => $menu_item->title,
'url' => $menu_item->url,
),
);
}

$block['innerBlocks'] = gutenberg_convert_menu_items_to_blocks(
isset( $menu_items_by_parent_id[ $menu_item->ID ] )
? $menu_items_by_parent_id[ $menu_item->ID ]
: array(),
$menu_items_by_parent_id
);

$blocks[] = $block;
}

return $blocks;
}