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

[Draft] Use template parts to preserve navigation between theme switches #36117

Closed
wants to merge 10 commits into from
13 changes: 13 additions & 0 deletions lib/full-site-editing/template-parts.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ function gutenberg_register_wp_template_part_area_taxonomy() {
if ( ! defined( 'WP_TEMPLATE_PART_AREA_HEADER' ) ) {
define( 'WP_TEMPLATE_PART_AREA_HEADER', 'header' );
}
if ( ! defined( 'WP_TEMPLATE_PART_AREA_PRIMARY_MENU' ) ) {
define( 'WP_TEMPLATE_PART_AREA_PRIMARY_MENU', 'primary-menu' );
}
if ( ! defined( 'WP_TEMPLATE_PART_AREA_FOOTER' ) ) {
define( 'WP_TEMPLATE_PART_AREA_FOOTER', 'footer' );
}
Expand Down Expand Up @@ -189,6 +192,16 @@ function gutenberg_get_allowed_template_part_areas() {
'icon' => 'header',
'area_tag' => 'header',
),
array(
'area' => WP_TEMPLATE_PART_AREA_PRIMARY_MENU,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be an area per semantic menu type, or it could be more of a new, free field that can take any value..

'label' => __( 'Primary menu', 'gutenberg' ),
'description' => __(
'The primary menu template defines a page area that typically contains a main navigation.',
'gutenberg'
),
'icon' => 'primary-menu',
'area_tag' => 'div',
),
array(
'area' => WP_TEMPLATE_PART_AREA_FOOTER,
'label' => __( 'Footer', 'gutenberg' ),
Expand Down
122 changes: 122 additions & 0 deletions lib/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,125 @@ function gutenberg_register_navigation_post_type() {
register_post_type( 'wp_navigation', $args );
}
add_action( 'init', 'gutenberg_register_navigation_post_type' );

/**
* Copies the navigationMenuId attribute from navigation template parts in the old theme, to
* corresponding ones in the new theme.
*
* @param string $new_name Name of the new theme.
* @param WP_Theme $new_theme New theme.
* @param WP_Theme $old_theme Old theme.
* @see switch_theme WordPress action.
*/
function gutenberg_migrate_nav_on_theme_switch( $new_name, $new_theme, $old_theme ) {
$old_theme_name = $old_theme->get_stylesheet();
$settings_old_theme = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data();
$old_nav_parts = get_navigation_template_part_names( $settings_old_theme->get_template_parts() );

WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data();
$new_theme_name = $new_theme->get_stylesheet();
$settings_new_theme = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data();
$new_nav_parts = get_navigation_template_part_names( $settings_new_theme->get_template_parts() );

$common_parts = array_intersect( $old_nav_parts, $new_nav_parts );
foreach ( $common_parts as $common_part ) {
// Get a navigation template part from the old theme.
$id = $old_theme_name . '//' . $common_part;
$old_template_part = gutenberg_get_block_template( $id, 'wp_template_part' );
if ( ! $old_template_part || empty( $old_template_part->content ) || empty( $old_template_part->wp_id ) ) {
continue;
}

// Extract the navigationMenuId from the old template part.
$old_blocks = parse_blocks( $old_template_part->content );
if (
! $old_blocks ||
'core/navigation' !== $old_blocks[0]['blockName'] ||
empty( $old_blocks[0]['attrs']['navigationMenuId'] )
) {
continue;
}
$old_nav_menu_id = $old_blocks[0]['attrs']['navigationMenuId'];

// Get a navigation template part from the new theme.
$new_id = $new_theme_name . '//' . $common_part;
$new_template_part = gutenberg_get_block_template( $new_id, 'wp_template_part' );

// Set the old post_name to something else because there is a hook in place that prevents
// the new template part from getting the same slug as the old template part.
// @TODO: Remove this once the post_name is retired as a slug container.
if ( $old_template_part->wp_id ) {
$old_post = get_post( $old_template_part->wp_id );
$old_post->post_name = 'temp';
wp_update_post( $old_post );
}

// Create a navigation template part for the new theme if one doesn't already exist.
if ( ! $new_template_part || empty( $new_template_part->content ) || empty( $new_template_part->wp_id ) ) {
$template_file = _gutenberg_get_template_file( 'wp_template_part', $common_part );
$block_template = _gutenberg_build_template_result_from_file( $template_file, 'wp_template_part' );
$template_part_args = array(
'post_type' => $block_template->type,
'post_name' => $common_part,
'post_title' => $common_part,
'post_content' => $block_template->content,
'post_status' => 'publish',
'tax_input' => array(
'wp_theme' => array(
$new_theme_name,
),
'wp_template_part_area' => array(
$block_template->area,
),
),
);
$template_part_post_id = wp_insert_post( $template_part_args );
wp_set_post_terms( $template_part_post_id, $block_template->area, 'wp_template_part_area' );
wp_set_post_terms( $template_part_post_id, $new_theme_name, 'wp_theme' );
$new_template_part = gutenberg_get_block_template( $new_id, 'wp_template_part' );
}

// Apply the previous navigation post ID to the navigation block in the new theme.
$new_blocks = parse_blocks( $new_template_part->content );
if (
$new_blocks &&
! empty( $new_blocks[0]['blockName'] ) &&
is_array( $new_blocks[0]['attrs'] ) &&
'core/navigation' === $new_blocks[0]['blockName']
) {
$new_blocks[0]['attrs']['navigationMenuId'] = $old_nav_menu_id;
$new_post = get_post( $new_template_part->wp_id );
$new_post->post_name = $common_part;
$new_post->post_content = serialize_blocks( $new_blocks );
wp_update_post( $new_post );
}

// Restore the old post_name to the old template part.
// @TODO: Remove this once the post_name is retired as a slug container.
if ( $old_template_part->wp_id ) {
$old_post = get_post( $old_template_part->wp_id );
$old_post->post_name = $common_part;
wp_update_post( $old_post );
}
}
}

/**
* Returns a list of template parts representing labeled navigation areas such as primary, secondary, etc.
*
* @param array[] $template_parts Template parts from theme.json.
* @return array List of template parts na
*/
function get_navigation_template_part_names( $template_parts ) {
$menu_parts = array();
foreach ( $template_parts as $key => $part ) {
if ( WP_TEMPLATE_PART_AREA_PRIMARY_MENU === $part['area'] ) {
$menu_parts[] = $key;
}
}
return $menu_parts;
}

// Set a priority such that WP_Theme_JSON_Resolver_Gutenberg still has contains the cached data.
// This will clean the cache which may be unexpected, so it would be better to introduce a `before_theme_switch` action.
add_action( 'switch_theme', 'gutenberg_migrate_nav_on_theme_switch', -200, 3 );