diff --git src/wp-admin/css/customize-nav-menus.css src/wp-admin/css/customize-nav-menus.css
index aa0deed..fc14598 100644
|
|
button.not-a-button { |
654 | 654 | } |
655 | 655 | |
656 | 656 | #custom-menu-item-name.invalid, |
657 | | #custom-menu-item-url.invalid { |
| 657 | #custom-menu-item-url.invalid, |
| 658 | .menu-name-field.invalid, |
| 659 | .menu-name-field.invalid:focus { |
658 | 660 | border: 1px solid #f00; |
659 | 661 | } |
660 | 662 | |
diff --git src/wp-admin/js/customize-nav-menus.js src/wp-admin/js/customize-nav-menus.js
index ee1e7b9..563f71c 100644
|
|
|
864 | 864 | button.removeClass( 'open' ); |
865 | 865 | button.attr( 'aria-expanded', 'false' ); |
866 | 866 | content.slideUp( 'fast' ); |
| 867 | content.find( '.menu-name-field' ).removeClass( 'invalid' ); |
867 | 868 | } |
868 | 869 | } |
869 | 870 | }); |
… |
… |
|
900 | 901 | return; |
901 | 902 | } |
902 | 903 | menuId = matches[1]; |
903 | | option = new Option( setting().name, menuId ); |
| 904 | option = new Option( displayNavMenuName( setting().name ), menuId ); |
904 | 905 | control.container.find( 'select' ).append( option ); |
905 | 906 | }); |
906 | 907 | api.bind( 'remove', function( setting ) { |
… |
… |
|
926 | 927 | } |
927 | 928 | control.container.find( 'option[value=' + menuId + ']' ).remove(); |
928 | 929 | } else { |
929 | | control.container.find( 'option[value=' + menuId + ']' ).text( setting().name ); |
| 930 | control.container.find( 'option[value=' + menuId + ']' ).text( displayNavMenuName( setting().name ) ); |
930 | 931 | } |
931 | 932 | }); |
932 | 933 | } |
… |
… |
|
1636 | 1637 | */ |
1637 | 1638 | ready: function() { |
1638 | 1639 | var control = this, |
1639 | | menuId = control.params.menu_id; |
| 1640 | menuId = control.params.menu_id, |
| 1641 | menu = control.setting(), |
| 1642 | name; |
1640 | 1643 | |
1641 | 1644 | if ( 'undefined' === typeof this.params.menu_id ) { |
1642 | 1645 | throw new Error( 'params.menu_id was not defined' ); |
… |
… |
|
1667 | 1670 | this._setupTitle(); |
1668 | 1671 | |
1669 | 1672 | // Add menu to Custom Menu widgets. |
1670 | | if ( control.setting() ) { |
| 1673 | if ( menu ) { |
| 1674 | name = displayNavMenuName( menu.name ); |
| 1675 | |
1671 | 1676 | api.control.each( function( widgetControl ) { |
1672 | 1677 | if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) { |
1673 | 1678 | return; |
1674 | 1679 | } |
1675 | 1680 | var select = widgetControl.container.find( 'select' ); |
1676 | 1681 | if ( select.find( 'option[value=' + String( menuId ) + ']' ).length === 0 ) { |
1677 | | select.append( new Option( control.setting().name, menuId ) ); |
| 1682 | select.append( new Option( name, menuId ) ); |
1678 | 1683 | } |
1679 | 1684 | } ); |
1680 | | $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first' ).append( new Option( control.setting().name, menuId ) ); |
| 1685 | $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first' ).append( new Option( name, menuId ) ); |
1681 | 1686 | } |
1682 | 1687 | }, |
1683 | 1688 | |
… |
… |
|
1708 | 1713 | }); |
1709 | 1714 | |
1710 | 1715 | control.setting.bind( function( to ) { |
| 1716 | var name; |
1711 | 1717 | if ( false === to ) { |
1712 | 1718 | control._handleDeletion(); |
1713 | 1719 | } else { |
1714 | 1720 | // Update names in the Custom Menu widgets. |
| 1721 | name = displayNavMenuName( to.name ); |
1715 | 1722 | api.control.each( function( widgetControl ) { |
1716 | 1723 | if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) { |
1717 | 1724 | return; |
1718 | 1725 | } |
1719 | 1726 | var select = widgetControl.container.find( 'select' ); |
1720 | | select.find( 'option[value=' + String( menuId ) + ']' ).text( to.name ); |
| 1727 | select.find( 'option[value=' + String( menuId ) + ']' ).text( name ); |
1721 | 1728 | }); |
1722 | | $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first option[value=' + String( menuId ) + ']' ).text( to.name ); |
| 1729 | $( '#available-widgets-list .widget-inside:has(input.id_base[value=nav_menu]) select:first option[value=' + String( menuId ) + ']' ).text( name ); |
1723 | 1730 | } |
1724 | 1731 | } ); |
1725 | 1732 | |
… |
… |
|
1878 | 1885 | if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) { |
1879 | 1886 | container.find( '.theme-location-set' ).hide(); |
1880 | 1887 | } else { |
1881 | | container.find( '.theme-location-set' ).show().find( 'span' ).text( menuSetting().name ); |
| 1888 | container.find( '.theme-location-set' ).show().find( 'span' ).text( displayNavMenuName( menuSetting().name ) ); |
1882 | 1889 | } |
1883 | 1890 | }; |
1884 | 1891 | |
… |
… |
|
1910 | 1917 | return; |
1911 | 1918 | } |
1912 | 1919 | |
1913 | | // Empty names are not allowed (will not be saved), don't update to one. |
1914 | | if ( menu.name ) { |
1915 | | var section = control.container.closest( '.accordion-section' ), |
1916 | | menuId = control.params.menu_id, |
1917 | | controlTitle = section.find( '.accordion-section-title' ), |
1918 | | sectionTitle = section.find( '.customize-section-title h3' ), |
1919 | | location = section.find( '.menu-in-location' ), |
1920 | | action = sectionTitle.find( '.customize-action' ); |
| 1920 | var section = control.container.closest( '.accordion-section' ), |
| 1921 | menuId = control.params.menu_id, |
| 1922 | controlTitle = section.find( '.accordion-section-title' ), |
| 1923 | sectionTitle = section.find( '.customize-section-title h3' ), |
| 1924 | location = section.find( '.menu-in-location' ), |
| 1925 | action = sectionTitle.find( '.customize-action' ), |
| 1926 | name = displayNavMenuName( menu.name ); |
1921 | 1927 | |
1922 | | // Update the control title |
1923 | | controlTitle.text( menu.name ); |
1924 | | if ( location.length ) { |
1925 | | location.appendTo( controlTitle ); |
1926 | | } |
| 1928 | // Update the control title |
| 1929 | controlTitle.text( name ); |
| 1930 | if ( location.length ) { |
| 1931 | location.appendTo( controlTitle ); |
| 1932 | } |
1927 | 1933 | |
1928 | | // Update the section title |
1929 | | sectionTitle.text( menu.name ); |
1930 | | if ( action.length ) { |
1931 | | action.prependTo( sectionTitle ); |
1932 | | } |
| 1934 | // Update the section title |
| 1935 | sectionTitle.text( name ); |
| 1936 | if ( action.length ) { |
| 1937 | action.prependTo( sectionTitle ); |
| 1938 | } |
1933 | 1939 | |
1934 | | // Update the nav menu name in location selects. |
1935 | | api.control.each( function( control ) { |
1936 | | if ( /^nav_menu_locations\[/.test( control.id ) ) { |
1937 | | control.container.find( 'option[value=' + menuId + ']' ).text( menu.name ); |
1938 | | } |
1939 | | } ); |
| 1940 | // Update the nav menu name in location selects. |
| 1941 | api.control.each( function( control ) { |
| 1942 | if ( /^nav_menu_locations\[/.test( control.id ) ) { |
| 1943 | control.container.find( 'option[value=' + menuId + ']' ).text( name ); |
| 1944 | } |
| 1945 | } ); |
1940 | 1946 | |
1941 | | // Update the nav menu name in all location checkboxes. |
1942 | | section.find( '.customize-control-checkbox input' ).each( function() { |
1943 | | if ( $( this ).prop( 'checked' ) ) { |
1944 | | $( '.current-menu-location-name-' + $( this ).data( 'location-id' ) ).text( menu.name ); |
1945 | | } |
1946 | | } ); |
1947 | | } |
| 1947 | // Update the nav menu name in all location checkboxes. |
| 1948 | section.find( '.customize-control-checkbox input' ).each( function() { |
| 1949 | if ( $( this ).prop( 'checked' ) ) { |
| 1950 | $( '.current-menu-location-name-' + $( this ).data( 'location-id' ) ).text( name ); |
| 1951 | } |
| 1952 | } ); |
1948 | 1953 | } ); |
1949 | 1954 | }, |
1950 | 1955 | |
… |
… |
|
2182 | 2187 | |
2183 | 2188 | /** |
2184 | 2189 | * Create the new menu with the name supplied. |
2185 | | * |
2186 | | * @returns {boolean} |
2187 | 2190 | */ |
2188 | 2191 | submit: function() { |
2189 | 2192 | |
… |
… |
|
2195 | 2198 | customizeId, |
2196 | 2199 | placeholderId = api.Menus.generatePlaceholderAutoIncrementId(); |
2197 | 2200 | |
| 2201 | if ( ! name ) { |
| 2202 | nameInput.addClass( 'invalid' ); |
| 2203 | nameInput.focus(); |
| 2204 | return; |
| 2205 | } |
| 2206 | |
2198 | 2207 | customizeId = 'nav_menu[' + String( placeholderId ) + ']'; |
2199 | 2208 | |
2200 | 2209 | // Register the menu control setting. |
… |
… |
|
2220 | 2229 | params: { |
2221 | 2230 | id: customizeId, |
2222 | 2231 | panel: 'nav_menus', |
2223 | | title: name, |
| 2232 | title: displayNavMenuName( name ), |
2224 | 2233 | customizeAction: api.Menus.data.l10n.customizingMenus, |
2225 | 2234 | type: 'nav_menu', |
2226 | 2235 | priority: 10, |
… |
… |
|
2231 | 2240 | |
2232 | 2241 | // Clear name field. |
2233 | 2242 | nameInput.val( '' ); |
| 2243 | nameInput.removeClass( 'invalid' ); |
2234 | 2244 | |
2235 | 2245 | wp.a11y.speak( api.Menus.data.l10n.menuAdded ); |
2236 | 2246 | |
… |
… |
|
2300 | 2310 | var insertedMenuIdMapping = {}; |
2301 | 2311 | |
2302 | 2312 | _( data.nav_menu_updates ).each(function( update ) { |
2303 | | var oldCustomizeId, newCustomizeId, oldSetting, newSetting, settingValue, oldSection, newSection; |
| 2313 | var oldCustomizeId, newCustomizeId, customizeId, oldSetting, newSetting, setting, settingValue, oldSection, newSection, wasSaved; |
2304 | 2314 | if ( 'inserted' === update.status ) { |
2305 | 2315 | if ( ! update.previous_term_id ) { |
2306 | 2316 | throw new Error( 'Expected previous_term_id' ); |
… |
… |
|
2322 | 2332 | if ( ! settingValue ) { |
2323 | 2333 | throw new Error( 'Did not expect setting to be empty (deleted).' ); |
2324 | 2334 | } |
2325 | | settingValue = _.clone( settingValue ); |
| 2335 | settingValue = $.extend( _.clone( settingValue ), update.saved_value ); |
2326 | 2336 | |
2327 | 2337 | insertedMenuIdMapping[ update.previous_term_id ] = update.term_id; |
2328 | 2338 | newCustomizeId = 'nav_menu[' + String( update.term_id ) + ']'; |
… |
… |
|
2380 | 2390 | } |
2381 | 2391 | |
2382 | 2392 | // @todo Update the Custom Menu selects, ensuring the newly-inserted IDs are used for any that have selected a placeholder menu. |
| 2393 | } else if ( 'updated' === update.status ) { |
| 2394 | customizeId = 'nav_menu[' + String( update.term_id ) + ']'; |
| 2395 | if ( ! api.has( customizeId ) ) { |
| 2396 | throw new Error( 'Expected setting to exist: ' + customizeId ); |
| 2397 | } |
| 2398 | |
| 2399 | // Make sure the setting gets updated with its sanitized server value (specifically the conflict-resolved name). |
| 2400 | setting = api( customizeId ); |
| 2401 | if ( ! _.isEqual( update.saved_value, setting.get() ) ) { |
| 2402 | wasSaved = api.state( 'saved' ).get(); |
| 2403 | setting.set( update.saved_value ); |
| 2404 | setting._dirty = false; |
| 2405 | api.state( 'saved' ).set( wasSaved ); |
| 2406 | } |
2383 | 2407 | } |
2384 | 2408 | } ); |
2385 | 2409 | |
… |
… |
|
2527 | 2551 | return 'nav_menu_item[' + menuItemId + ']'; |
2528 | 2552 | } |
2529 | 2553 | |
| 2554 | /** |
| 2555 | * Apply sanitize_text_field()-like logic to the supplied name, returning a |
| 2556 | * "unnammed" fallback string if the name is then empty. |
| 2557 | * |
| 2558 | * @param {string} name |
| 2559 | * @returns {string} |
| 2560 | */ |
| 2561 | function displayNavMenuName( name ) { |
| 2562 | name = $( '<div>' ).text( name ).html(); // Emulate esc_html() which is used in wp-admin/nav-menus.php. |
| 2563 | name = $.trim( name ); |
| 2564 | return name || api.Menus.data.l10n.unnamed; |
| 2565 | } |
| 2566 | |
2530 | 2567 | })( wp.customize, wp, jQuery ); |
diff --git src/wp-includes/class-wp-customize-nav-menus.php src/wp-includes/class-wp-customize-nav-menus.php
index 7fbbda8..a9aae0d 100644
|
|
final class WP_Customize_Nav_Menus { |
281 | 281 | 'itemTypes' => $this->available_item_types(), |
282 | 282 | 'l10n' => array( |
283 | 283 | 'untitled' => _x( '(no label)', 'missing menu item navigation label' ), |
| 284 | 'unnamed' => _x( '(unnamed)', 'Missing menu name.' ), |
284 | 285 | 'custom_label' => __( 'Custom Link' ), |
285 | 286 | /* translators: %s: Current menu location */ |
286 | 287 | 'menuLocation' => __( '(Currently set to: %s)' ), |
diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php
index 5dd5293..7db4606 100644
|
|
class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { |
1182 | 1182 | if ( false === $nav_menu_setting->save() ) { |
1183 | 1183 | $this->update_status = 'error'; |
1184 | 1184 | $this->update_error = new WP_Error( 'nav_menu_setting_failure' ); |
| 1185 | return; |
1185 | 1186 | } |
1186 | 1187 | |
1187 | 1188 | if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) { |
… |
… |
class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { |
1207 | 1208 | if ( false === $parent_nav_menu_item_setting->save() ) { |
1208 | 1209 | $this->update_status = 'error'; |
1209 | 1210 | $this->update_error = new WP_Error( 'nav_menu_item_setting_failure' ); |
| 1211 | return; |
1210 | 1212 | } |
1211 | 1213 | |
1212 | 1214 | if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) { |
… |
… |
class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { |
1606 | 1608 | $value = array_merge( $default, $value ); |
1607 | 1609 | $value = wp_array_slice_assoc( $value, array_keys( $default ) ); |
1608 | 1610 | |
| 1611 | if ( '' === $value['name'] ) { |
| 1612 | $value['name'] = _x( '(unnamed)', 'Missing menu name.' ); |
| 1613 | } |
| 1614 | |
1609 | 1615 | $value['name'] = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php. |
1610 | 1616 | $value['description'] = sanitize_text_field( $value['description'] ); |
1611 | 1617 | $value['parent'] = max( 0, intval( $value['parent'] ) ); |
… |
… |
class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { |
1669 | 1675 | } else { |
1670 | 1676 | // Insert or update menu. |
1671 | 1677 | $menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) ); |
1672 | | if ( isset( $value['name'] ) ) { |
1673 | | $menu_data['menu-name'] = $value['name']; |
| 1678 | $menu_data['menu-name'] = $value['name']; |
| 1679 | if ( '' === trim( $menu_data['menu-name'] ) ) { |
| 1680 | $menu_data['menu-name'] = _x( '(unnamed)', 'Missing menu name.' ); |
| 1681 | } |
| 1682 | |
| 1683 | $menu_id = $is_placeholder ? 0 : $this->term_id; |
| 1684 | $r = wp_update_nav_menu_object( $menu_id, $menu_data ); |
| 1685 | $original_name = $menu_data['menu-name']; |
| 1686 | $name_conflict_suffix = 1; |
| 1687 | while ( is_wp_error( $r ) && 'menu_exists' === $r->get_error_code() ) { |
| 1688 | $name_conflict_suffix += 1; |
| 1689 | /* translators: 1: original menu name, 2: duplicate count */ |
| 1690 | $menu_data['menu-name'] = sprintf( __( '%1$s (%2$d)' ), $original_name, $name_conflict_suffix ); |
| 1691 | $r = wp_update_nav_menu_object( $menu_id, $menu_data ); |
1674 | 1692 | } |
1675 | 1693 | |
1676 | | $r = wp_update_nav_menu_object( $is_placeholder ? 0 : $this->term_id, $menu_data ); |
1677 | 1694 | if ( is_wp_error( $r ) ) { |
1678 | 1695 | $this->update_status = 'error'; |
1679 | 1696 | $this->update_error = $r; |
… |
… |
class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { |
1764 | 1781 | 'previous_term_id' => $this->previous_term_id, |
1765 | 1782 | 'error' => $this->update_error ? $this->update_error->get_error_code() : null, |
1766 | 1783 | 'status' => $this->update_status, |
| 1784 | 'saved_value' => $this->value(), |
1767 | 1785 | ); |
1768 | 1786 | |
1769 | 1787 | return $data; |