Map nav menu locations on theme switch
This will send nav menu locations through three levels of mapping: 1. If both themes have only one location, that gets mapped. 2. If both themes have locations with the same slug, they get mapped. 3. Locations that (even partially) match slugs from a similar kind of menu location will get mapped. Menu locations are mapped for Live Previews in the Customizer and during theme switches. Props westonruter, obenland, welcher, melchoyce. Fixes #39692. git-svn-id: https://develop.svn.wordpress.org/trunk@41237 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
9ab26b8800
commit
d339af1bc1
|
@ -27,12 +27,12 @@ final class WP_Customize_Nav_Menus {
|
|||
public $manager;
|
||||
|
||||
/**
|
||||
* Previewed Menus.
|
||||
* Original nav menu locations before the theme was switched.
|
||||
*
|
||||
* @since 4.3.0
|
||||
* @since 4.9.0
|
||||
* @var array
|
||||
*/
|
||||
public $previewed_menus;
|
||||
protected $original_nav_menu_locations;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -42,8 +42,8 @@ final class WP_Customize_Nav_Menus {
|
|||
* @param object $manager An instance of the WP_Customize_Manager class.
|
||||
*/
|
||||
public function __construct( $manager ) {
|
||||
$this->previewed_menus = array();
|
||||
$this->manager = $manager;
|
||||
$this->original_nav_menu_locations = get_nav_menu_locations();
|
||||
|
||||
// See https://github.com/xwp/wp-customize-snapshots/blob/962586659688a5b1fd9ae93618b7ce2d4e7a421c/php/class-customize-snapshot-manager.php#L469-L499
|
||||
add_action( 'customize_register', array( $this, 'customize_register' ), 11 );
|
||||
|
@ -582,6 +582,12 @@ final class WP_Customize_Nav_Menus {
|
|||
$choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '…' );
|
||||
}
|
||||
|
||||
// Attempt to re-map the nav menu location assignments when previewing a theme switch.
|
||||
$mapped_nav_menu_locations = array();
|
||||
if ( ! $this->manager->is_theme_active() ) {
|
||||
$mapped_nav_menu_locations = wp_map_nav_menu_locations( get_nav_menu_locations(), $this->original_nav_menu_locations );
|
||||
}
|
||||
|
||||
foreach ( $locations as $location => $description ) {
|
||||
$setting_id = "nav_menu_locations[{$location}]";
|
||||
|
||||
|
@ -600,6 +606,11 @@ final class WP_Customize_Nav_Menus {
|
|||
) );
|
||||
}
|
||||
|
||||
// Override the assigned nav menu location if mapped during previewed theme switch.
|
||||
if ( isset( $mapped_nav_menu_locations[ $location ] ) ) {
|
||||
$this->manager->set_post_value( $setting_id, $mapped_nav_menu_locations[ $location ] );
|
||||
}
|
||||
|
||||
$this->manager->add_control( new WP_Customize_Nav_Menu_Location_Control( $this->manager, $setting_id, array(
|
||||
'label' => $description,
|
||||
'location_id' => $location,
|
||||
|
|
|
@ -262,6 +262,7 @@ add_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
|
|||
add_action( 'template_redirect', 'wp_shortlink_header', 11, 0 );
|
||||
add_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
|
||||
add_action( 'init', 'check_theme_switched', 99 );
|
||||
add_action( 'after_switch_theme', '_wp_menus_changed' );
|
||||
add_action( 'after_switch_theme', '_wp_sidebars_changed' );
|
||||
add_action( 'wp_print_styles', 'print_emoji_styles' );
|
||||
|
||||
|
|
|
@ -1026,3 +1026,111 @@ function _wp_delete_customize_changeset_dependent_auto_drafts( $post_id ) {
|
|||
}
|
||||
add_action( 'delete_post', '_wp_delete_customize_changeset_dependent_auto_drafts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle menu config after theme change.
|
||||
*
|
||||
* @access private
|
||||
* @since 4.9.0
|
||||
*/
|
||||
function _wp_menus_changed() {
|
||||
$old_nav_menu_locations = get_option( 'theme_switch_menu_locations', array() );
|
||||
$new_nav_menu_locations = get_nav_menu_locations();
|
||||
$mapped_nav_menu_locations = wp_map_nav_menu_locations( $new_nav_menu_locations, $old_nav_menu_locations );
|
||||
|
||||
set_theme_mod( 'nav_menu_locations', $mapped_nav_menu_locations );
|
||||
delete_option( 'theme_switch_menu_locations' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps nav menu locations according to assignments in previously active theme.
|
||||
*
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @param array $new_nav_menu_locations New nav menu locations assignments.
|
||||
* @param array $old_nav_menu_locations Old nav menu locations assignments.
|
||||
* @return array Nav menus mapped to new nav menu locations.
|
||||
*/
|
||||
function wp_map_nav_menu_locations( $new_nav_menu_locations, $old_nav_menu_locations ) {
|
||||
$registered_nav_menus = get_registered_nav_menus();
|
||||
|
||||
// Short-circuit if there are no old nav menu location assignments to map.
|
||||
if ( empty( $old_nav_menu_locations ) ) {
|
||||
return $new_nav_menu_locations;
|
||||
}
|
||||
|
||||
// If old and new theme have just one location, map it and we're done.
|
||||
if ( 1 === count( $old_nav_menu_locations ) && 1 === count( $registered_nav_menus ) ) {
|
||||
$new_nav_menu_locations[ key( $registered_nav_menus ) ] = array_pop( $old_nav_menu_locations );
|
||||
return $new_nav_menu_locations;
|
||||
}
|
||||
|
||||
$old_locations = array_keys( $old_nav_menu_locations );
|
||||
|
||||
// Map locations with the same slug.
|
||||
foreach ( $registered_nav_menus as $location => $name ) {
|
||||
if ( in_array( $location, $old_locations, true ) ) {
|
||||
$new_nav_menu_locations[ $location ] = $old_nav_menu_locations[ $location ];
|
||||
unset( $old_nav_menu_locations[ $location ] );
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no old nav menu locations left, then we're done.
|
||||
if ( empty( $old_nav_menu_locations ) ) {
|
||||
return $new_nav_menu_locations;
|
||||
}
|
||||
|
||||
/*
|
||||
* If old and new theme both have locations that contain phrases
|
||||
* from within the same group, make an educated guess and map it.
|
||||
*/
|
||||
$common_slug_groups = array(
|
||||
array( 'header', 'main', 'navigation', 'primary', 'top' ),
|
||||
array( 'bottom', 'footer', 'secondary', 'subsidiary' ),
|
||||
);
|
||||
|
||||
// Go through each group...
|
||||
foreach ( $common_slug_groups as $slug_group ) {
|
||||
|
||||
// ...and see if any of these slugs...
|
||||
foreach ( $slug_group as $slug ) {
|
||||
|
||||
// ...and any of the new menu locations...
|
||||
foreach ( $registered_nav_menus as $new_location => $name ) {
|
||||
|
||||
// ...actually match!
|
||||
if ( false === stripos( $new_location, $slug ) && false === stripos( $slug, $new_location ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Then see if any of the old locations...
|
||||
foreach ( $old_nav_menu_locations as $location => $menu_id ) {
|
||||
|
||||
// ...and any slug in the same group...
|
||||
foreach ( $slug_group as $slug ) {
|
||||
|
||||
// ... have a match as well.
|
||||
if ( false === stripos( $location, $slug ) && false === stripos( $slug, $location ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure this location wasn't mapped and removed previously.
|
||||
if ( ! empty( $old_nav_menu_locations[ $location ] ) ) {
|
||||
|
||||
// We have a match that can be mapped!
|
||||
$new_nav_menu_locations[ $new_location ] = $old_nav_menu_locations[ $location ];
|
||||
|
||||
// Remove the mapped location so it can't be mapped again.
|
||||
unset( $old_nav_menu_locations[ $location ] );
|
||||
|
||||
// Go back and check the next new menu location.
|
||||
continue 3;
|
||||
}
|
||||
} // endforeach ( $slug_group as $slug )
|
||||
} // endforeach ( $old_nav_menu_locations as $location => $menu_id )
|
||||
} // endforeach foreach ( $registered_nav_menus as $new_location => $name )
|
||||
} // endforeach ( $slug_group as $slug )
|
||||
} // endforeach ( $common_slug_groups as $slug_group )
|
||||
|
||||
return $new_nav_menu_locations;
|
||||
}
|
||||
|
|
|
@ -691,6 +691,7 @@ function switch_theme( $stylesheet ) {
|
|||
}
|
||||
|
||||
$nav_menu_locations = get_theme_mod( 'nav_menu_locations' );
|
||||
add_option( 'theme_switch_menu_locations', $nav_menu_locations );
|
||||
|
||||
if ( func_num_args() > 1 ) {
|
||||
$stylesheet = func_get_arg( 1 );
|
||||
|
@ -731,13 +732,6 @@ function switch_theme( $stylesheet ) {
|
|||
if ( 'wp_ajax_customize_save' === current_action() ) {
|
||||
remove_theme_mod( 'sidebars_widgets' );
|
||||
}
|
||||
|
||||
if ( ! empty( $nav_menu_locations ) ) {
|
||||
$nav_mods = get_theme_mod( 'nav_menu_locations' );
|
||||
if ( empty( $nav_mods ) ) {
|
||||
set_theme_mod( 'nav_menu_locations', $nav_menu_locations );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_option( 'theme_switched', $old_theme->get_stylesheet() );
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group navmenus
|
||||
*/
|
||||
class Tests_Nav_Menu_Theme_Change extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Unregister all nav menu locations.
|
||||
foreach ( array_keys( get_registered_nav_menus() ) as $location ) {
|
||||
unregister_nav_menu( $location );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register nav menu locations.
|
||||
*
|
||||
* @param array $locations Location slugs.
|
||||
*/
|
||||
function register_nav_menu_locations( $locations ) {
|
||||
foreach ( $locations as $location ) {
|
||||
register_nav_menu( $location, ucfirst( $location ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Two themes with one location each should just map, switching to a theme not previously-active.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_one_location_each() {
|
||||
$this->register_nav_menu_locations( array( 'primary' ) );
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'unique-slug' => 1,
|
||||
);
|
||||
$old_next_theme_nav_menu_locations = array(); // It was not active before.
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = array(
|
||||
'primary' => 1,
|
||||
);
|
||||
$this->assertEquals( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Locations with the same name should map, switching to a theme not previously-active.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_locations_with_same_slug() {
|
||||
$this->register_nav_menu_locations( array( 'primary', 'secondary' ) );
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'primary' => 1,
|
||||
'secondary' => 2,
|
||||
);
|
||||
|
||||
$old_next_theme_nav_menu_locations = array(); // It was not active before.
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = $prev_theme_nav_menu_locations;
|
||||
$this->assertEquals( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the new theme was previously active, we should honor any changes to nav menu mapping done when the other theme was active.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_new_theme_previously_active() {
|
||||
$this->register_nav_menu_locations( array( 'primary' ) );
|
||||
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'primary' => 1,
|
||||
'secondary' => 2,
|
||||
);
|
||||
|
||||
// Nav menu location assignments that were set on the next theme when it was previously active.
|
||||
$old_next_theme_nav_menu_locations = array(
|
||||
'primary' => 3,
|
||||
);
|
||||
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = wp_array_slice_assoc( $prev_theme_nav_menu_locations, array_keys( get_registered_nav_menus() ) );
|
||||
$this->assertEquals( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make educated guesses on theme locations.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_location_guessing() {
|
||||
$this->register_nav_menu_locations( array( 'primary', 'secondary' ) );
|
||||
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'header' => 1,
|
||||
'footer' => 2,
|
||||
);
|
||||
|
||||
$old_next_theme_nav_menu_locations = array();
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = array(
|
||||
'primary' => 1,
|
||||
'secondary' => 2,
|
||||
);
|
||||
$this->assertEquals( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure two locations that fall in the same group don't get the same menu assigned.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_location_guessing_one_menu_per_group() {
|
||||
$this->register_nav_menu_locations( array( 'primary' ) );
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'top-menu' => 1,
|
||||
'secondary' => 2,
|
||||
);
|
||||
|
||||
$old_next_theme_nav_menu_locations = array();
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = array(
|
||||
'main' => 1,
|
||||
);
|
||||
$this->assertEqualSets( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure two locations that fall in the same group get menus assigned from the same group.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_location_guessing_one_menu_per_location() {
|
||||
$this->register_nav_menu_locations( array( 'primary', 'main' ) );
|
||||
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'navigation-menu' => 1,
|
||||
'top-menu' => 2,
|
||||
);
|
||||
|
||||
$old_next_theme_nav_menu_locations = array();
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = array(
|
||||
'main' => 1,
|
||||
'primary' => 2,
|
||||
);
|
||||
$this->assertEquals( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Technically possible to register menu locations numerically.
|
||||
*
|
||||
* @covers wp_map_nav_menu_locations()
|
||||
*/
|
||||
function test_numerical_locations() {
|
||||
$this->register_nav_menu_locations( array( 'primary', 1 ) );
|
||||
|
||||
$prev_theme_nav_menu_locations = array(
|
||||
'main' => 1,
|
||||
'secondary' => 2,
|
||||
'tertiary' => 3,
|
||||
);
|
||||
|
||||
$old_next_theme_nav_menu_locations = array();
|
||||
$new_next_theme_nav_menu_locations = wp_map_nav_menu_locations( $old_next_theme_nav_menu_locations, $prev_theme_nav_menu_locations );
|
||||
|
||||
$expected_nav_menu_locations = array(
|
||||
'primary' => 1,
|
||||
);
|
||||
$this->assertEqualSets( $expected_nav_menu_locations, $new_next_theme_nav_menu_locations );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue