Make WordPress Core

Ticket #32760: 32760.6.diff

File 32760.6.diff, 17.1 KB (added by westonruter, 9 years ago)

Additional changes: https://github.com/xwp/wordpress-develop/compare/d0ab22ac973906b60c9bd760a83e26bde449c17b...e06bf93b08aac64bb342761db47a751fbd55696f

  • src/wp-admin/css/customize-nav-menus.css

    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 { 
    654654}
    655655
    656656#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 {
    658660        border: 1px solid #f00;
    659661}
    660662
  • src/wp-admin/js/customize-nav-menus.js

    diff --git src/wp-admin/js/customize-nav-menus.js src/wp-admin/js/customize-nav-menus.js
    index ee1e7b9..563f71c 100644
     
    864864                                button.removeClass( 'open' );
    865865                                button.attr( 'aria-expanded', 'false' );
    866866                                content.slideUp( 'fast' );
     867                                content.find( '.menu-name-field' ).removeClass( 'invalid' );
    867868                        }
    868869                }
    869870        });
     
    900901                                        return;
    901902                                }
    902903                                menuId = matches[1];
    903                                 option = new Option( setting().name, menuId );
     904                                option = new Option( displayNavMenuName( setting().name ), menuId );
    904905                                control.container.find( 'select' ).append( option );
    905906                        });
    906907                        api.bind( 'remove', function( setting ) {
     
    926927                                        }
    927928                                        control.container.find( 'option[value=' + menuId + ']' ).remove();
    928929                                } else {
    929                                         control.container.find( 'option[value=' + menuId + ']' ).text( setting().name );
     930                                        control.container.find( 'option[value=' + menuId + ']' ).text( displayNavMenuName( setting().name ) );
    930931                                }
    931932                        });
    932933                }
     
    16361637                 */
    16371638                ready: function() {
    16381639                        var control = this,
    1639                                 menuId = control.params.menu_id;
     1640                                menuId = control.params.menu_id,
     1641                                menu = control.setting(),
     1642                                name;
    16401643
    16411644                        if ( 'undefined' === typeof this.params.menu_id ) {
    16421645                                throw new Error( 'params.menu_id was not defined' );
     
    16671670                        this._setupTitle();
    16681671
    16691672                        // Add menu to Custom Menu widgets.
    1670                         if ( control.setting() ) {
     1673                        if ( menu ) {
     1674                                name = displayNavMenuName( menu.name );
     1675
    16711676                                api.control.each( function( widgetControl ) {
    16721677                                        if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
    16731678                                                return;
    16741679                                        }
    16751680                                        var select = widgetControl.container.find( 'select' );
    16761681                                        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 ) );
    16781683                                        }
    16791684                                } );
    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 ) );
    16811686                        }
    16821687                },
    16831688
     
    17081713                        });
    17091714
    17101715                        control.setting.bind( function( to ) {
     1716                                var name;
    17111717                                if ( false === to ) {
    17121718                                        control._handleDeletion();
    17131719                                } else {
    17141720                                        // Update names in the Custom Menu widgets.
     1721                                        name = displayNavMenuName( to.name );
    17151722                                        api.control.each( function( widgetControl ) {
    17161723                                                if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
    17171724                                                        return;
    17181725                                                }
    17191726                                                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 );
    17211728                                        });
    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 );
    17231730                                }
    17241731                        } );
    17251732
     
    18781885                                        if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) {
    18791886                                                container.find( '.theme-location-set' ).hide();
    18801887                                        } 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 ) );
    18821889                                        }
    18831890                                };
    18841891
     
    19101917                                        return;
    19111918                                }
    19121919
    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 );
    19211927
    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                                }
    19271933
    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                                }
    19331939
    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                                } );
    19401946
    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                                } );
    19481953                        } );
    19491954                },
    19501955
     
    21822187
    21832188                /**
    21842189                 * Create the new menu with the name supplied.
    2185                  *
    2186                  * @returns {boolean}
    21872190                 */
    21882191                submit: function() {
    21892192
     
    21952198                                customizeId,
    21962199                                placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
    21972200
     2201                        if ( ! name ) {
     2202                                nameInput.addClass( 'invalid' );
     2203                                nameInput.focus();
     2204                                return;
     2205                        }
     2206
    21982207                        customizeId = 'nav_menu[' + String( placeholderId ) + ']';
    21992208
    22002209                        // Register the menu control setting.
     
    22202229                                params: {
    22212230                                        id: customizeId,
    22222231                                        panel: 'nav_menus',
    2223                                         title: name,
     2232                                        title: displayNavMenuName( name ),
    22242233                                        customizeAction: api.Menus.data.l10n.customizingMenus,
    22252234                                        type: 'nav_menu',
    22262235                                        priority: 10,
     
    22312240
    22322241                        // Clear name field.
    22332242                        nameInput.val( '' );
     2243                        nameInput.removeClass( 'invalid' );
    22342244
    22352245                        wp.a11y.speak( api.Menus.data.l10n.menuAdded );
    22362246
     
    23002310                var insertedMenuIdMapping = {};
    23012311
    23022312                _( 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;
    23042314                        if ( 'inserted' === update.status ) {
    23052315                                if ( ! update.previous_term_id ) {
    23062316                                        throw new Error( 'Expected previous_term_id' );
     
    23222332                                if ( ! settingValue ) {
    23232333                                        throw new Error( 'Did not expect setting to be empty (deleted).' );
    23242334                                }
    2325                                 settingValue = _.clone( settingValue );
     2335                                settingValue = $.extend( _.clone( settingValue ), update.saved_value );
    23262336
    23272337                                insertedMenuIdMapping[ update.previous_term_id ] = update.term_id;
    23282338                                newCustomizeId = 'nav_menu[' + String( update.term_id ) + ']';
     
    23802390                                }
    23812391
    23822392                                // @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                                }
    23832407                        }
    23842408                } );
    23852409
     
    25272551                return 'nav_menu_item[' + menuItemId + ']';
    25282552        }
    25292553
     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
    25302567})( wp.customize, wp, jQuery );
  • src/wp-includes/class-wp-customize-nav-menus.php

    diff --git src/wp-includes/class-wp-customize-nav-menus.php src/wp-includes/class-wp-customize-nav-menus.php
    index 16531c1..fa496fd 100644
    final class WP_Customize_Nav_Menus { 
    281281                        'itemTypes'            => $this->available_item_types(),
    282282                        'l10n'                 => array(
    283283                                'untitled'          => _x( '(no label)', 'missing menu item navigation label' ),
     284                                'unnamed'           => _x( '(unnamed)', 'Missing menu name.' ),
    284285                                'custom_label'      => __( 'Custom Link' ),
    285286                                /* translators: %s: Current menu location */
    286287                                'menuLocation'      => __( '(Currently set to: %s)' ),
  • src/wp-includes/class-wp-customize-setting.php

    diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php
    index 5dd5293..ce0f711 100644
    class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { 
    11821182                                if ( false === $nav_menu_setting->save() ) {
    11831183                                        $this->update_status = 'error';
    11841184                                        $this->update_error  = new WP_Error( 'nav_menu_setting_failure' );
     1185                                        return;
    11851186                                }
    11861187
    11871188                                if ( $nav_menu_setting->previous_term_id !== intval( $value['nav_menu_term_id'] ) ) {
    class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { 
    12071208                                if ( false === $parent_nav_menu_item_setting->save() ) {
    12081209                                        $this->update_status = 'error';
    12091210                                        $this->update_error  = new WP_Error( 'nav_menu_item_setting_failure' );
     1211                                        return;
    12101212                                }
    12111213
    12121214                                if ( $parent_nav_menu_item_setting->previous_post_id !== intval( $value['menu_item_parent'] ) ) {
    class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { 
    16111613                $value['parent']      = max( 0, intval( $value['parent'] ) );
    16121614                $value['auto_add']    = ! empty( $value['auto_add'] );
    16131615
     1616                if ( '' === $value['name'] ) {
     1617                        $value['name'] = _x( '(unnamed)', 'Missing menu name.' );
     1618                }
     1619
    16141620                /** This filter is documented in wp-includes/class-wp-customize-setting.php */
    16151621                return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
    16161622        }
    class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { 
    16691675                } else {
    16701676                        // Insert or update menu.
    16711677                        $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
     1680                        $menu_id = $is_placeholder ? 0 : $this->term_id;
     1681                        $r = wp_update_nav_menu_object( $menu_id, $menu_data );
     1682                        $original_name = $menu_data['menu-name'];
     1683                        $name_conflict_suffix = 1;
     1684                        while ( is_wp_error( $r ) && 'menu_exists' === $r->get_error_code() ) {
     1685                                $name_conflict_suffix += 1;
     1686                                /* translators: 1: original menu name, 2: duplicate count */
     1687                                $menu_data['menu-name'] = sprintf( __( '%1$s (%2$d)' ), $original_name, $name_conflict_suffix );
     1688                                $r = wp_update_nav_menu_object( $menu_id, $menu_data );
    16741689                        }
    16751690
    1676                         $r = wp_update_nav_menu_object( $is_placeholder ? 0 : $this->term_id, $menu_data );
    16771691                        if ( is_wp_error( $r ) ) {
    16781692                                $this->update_status = 'error';
    16791693                                $this->update_error  = $r;
    class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { 
    17641778                        'previous_term_id' => $this->previous_term_id,
    17651779                        'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
    17661780                        'status'           => $this->update_status,
     1781                        'saved_value'      => 'deleted' === $this->update_status ? null : $this->value(),
    17671782                );
    17681783
    17691784                return $data;
  • tests/phpunit/tests/customize/nav-menu-setting.php

    diff --git tests/phpunit/tests/customize/nav-menu-setting.php tests/phpunit/tests/customize/nav-menu-setting.php
    index d2f5d9c..5d99fd6 100644
    class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase { 
    9494        function test_construct_empty_menus() {
    9595                do_action( 'customize_register', $this->wp_customize );
    9696                $_wp_customize = $this->wp_customize;
    97                 unset($_wp_customize->nav_menus);
     97                unset( $_wp_customize->nav_menus );
    9898
    9999                $exception = null;
    100100                try {
    class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase { 
    310310                $this->assertEquals( 0, $sanitized['parent'] );
    311311                $this->assertEquals( true, $sanitized['auto_add'] );
    312312                $this->assertEqualSets( array( 'name', 'description', 'parent', 'auto_add' ), array_keys( $sanitized ) );
     313
     314                $value['name'] = '    '; // Blank spaces.
     315                $sanitized = $setting->sanitize( $value );
     316                $this->assertEquals( '(unnamed)', $sanitized['name'] );
    313317        }
    314318
    315319        /**
    class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase { 
    360364                $this->assertArrayHasKey( 'previous_term_id', $update_result );
    361365                $this->assertArrayHasKey( 'error', $update_result );
    362366                $this->assertArrayHasKey( 'status', $update_result );
     367                $this->assertArrayHasKey( 'saved_value', $update_result );
     368                $this->assertEquals( $new_value, $update_result['saved_value'] );
    363369
    364370                $this->assertEquals( $menu_id, $update_result['term_id'] );
    365371                $this->assertNull( $update_result['previous_term_id'] );
    class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase { 
    410416                $this->assertArrayHasKey( 'previous_term_id', $update_result );
    411417                $this->assertArrayHasKey( 'error', $update_result );
    412418                $this->assertArrayHasKey( 'status', $update_result );
     419                $this->assertArrayHasKey( 'saved_value', $update_result );
     420                $this->assertEquals( $setting->value(), $update_result['saved_value'] );
    413421
    414422                $this->assertEquals( $menu->term_id, $update_result['term_id'] );
    415423                $this->assertEquals( $menu_id, $update_result['previous_term_id'] );
    class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase { 
    418426        }
    419427
    420428        /**
     429         * Test saving a new name that conflicts with an existing nav menu's name.
     430         *
     431         * @see WP_Customize_Nav_Menu_Setting::update()
     432         */
     433        function test_save_inserted_conflicted_name() {
     434                do_action( 'customize_register', $this->wp_customize );
     435
     436                $menu_name = 'Foo';
     437                wp_update_nav_menu_object( 0, array( 'menu-name' => $menu_name ) );
     438
     439                $menu_id = -123;
     440                $setting_id = "nav_menu[$menu_id]";
     441                $setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
     442                $this->wp_customize->set_post_value( $setting->id, array( 'name' => $menu_name ) );
     443                $setting->save();
     444
     445                $expected_resolved_menu_name = "$menu_name (2)";
     446                $new_menu = wp_get_nav_menu_object( $setting->term_id );
     447                $this->assertEquals( $expected_resolved_menu_name, $new_menu->name );
     448
     449                $save_response = apply_filters( 'customize_save_response', array() );
     450                $this->assertEquals( $expected_resolved_menu_name, $save_response['nav_menu_updates'][0]['saved_value']['name'] );
     451        }
     452
     453        /**
    421454         * Test protected update() method via the save() method, for deleted menu.
    422455         *
    423456         * @see WP_Customize_Nav_Menu_Setting::update()
    class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase { 
    448481                $this->assertArrayHasKey( 'previous_term_id', $update_result );
    449482                $this->assertArrayHasKey( 'error', $update_result );
    450483                $this->assertArrayHasKey( 'status', $update_result );
     484                $this->assertArrayHasKey( 'saved_value', $update_result );
     485                $this->assertNull( $update_result['saved_value'] );
    451486
    452487                $this->assertEquals( $menu_id, $update_result['term_id'] );
    453488                $this->assertNull( $update_result['previous_term_id'] );