WordPress.org

Make WordPress Core

Ticket #32816: 32816.10000.diff

File 32816.10000.diff, 6.8 KB (added by westonruter, 2 years ago)

Combine PHP validation with JS-based validation for adding new items

  • 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 36138ca2a3..82d27019ae 100644
     
    559559
    560560#custom-menu-item-name.invalid,
    561561#custom-menu-item-url.invalid,
     562.edit-menu-item-url.invalid,
    562563.menu-name-field.invalid,
    563564.menu-name-field.invalid:focus,
    564565#available-menu-items .new-content-item .create-item-input.invalid,
  • 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 2bc192c6a0..3a61060db6 100644
     
    535535                submitLink: function() {
    536536                        var menuItem,
    537537                                itemName = $( '#custom-menu-item-name' ),
    538                                 itemUrl = $( '#custom-menu-item-url' );
     538                                itemUrl = $( '#custom-menu-item-url' ),
     539                                urlRegex,
     540                                urlValue;
    539541
    540542                        if ( ! this.currentMenuControl ) {
    541543                                return;
    542544                        }
    543545
     546                        /*
     547                         * Copyright (c) 2010-2013 Diego Perini, MIT licensed
     548                         * https://gist.github.com/dperini/729294
     549                         * see also https://mathiasbynens.be/demo/url-regex
     550                         * modified to allow protocol-relative URLs
     551                         */
     552                        urlRegex = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;
     553
     554                        urlValue = itemUrl.val();
    544555                        if ( '' === itemName.val() ) {
    545556                                itemName.addClass( 'invalid' );
    546557                                return;
    547                         } else if ( '' === itemUrl.val() || 'http://' === itemUrl.val() ) {
     558                        } else if ( '' === urlValue || 'http://' === urlValue || ! ( '/' === urlValue[0] || urlRegex.test( urlValue ) ) ) {
    548559                                itemUrl.addClass( 'invalid' );
    549560                                return;
    550561                        }
    551562
    552563                        menuItem = {
    553564                                'title': itemName.val(),
    554                                 'url': itemUrl.val(),
     565                                'url': urlValue,
    555566                                'type': 'custom',
    556567                                'type_label': api.Menus.data.l10n.custom_label,
    557568                                'object': 'custom'
     
    13871398                 */
    13881399                _setupUpdateUI: function() {
    13891400                        var control = this,
    1390                                 settingValue = control.setting();
     1401                                settingValue = control.setting(),
     1402                                updateNotifications;
    13911403
    13921404                        control.elements = {};
    13931405                        control.elements.url = new api.Element( control.container.find( '.edit-menu-item-url' ) );
     
    14701482                                        }
    14711483                                }
    14721484                        });
     1485
     1486                        // Style the URL field as invalid when there is an invalid_url notification.
     1487                        updateNotifications = _.debounce( function() {
     1488                                control.elements.url.element.toggleClass( 'invalid', control.setting.notifications.has( 'invalid_url' ) );
     1489                        } );
     1490                        control.setting.notifications.bind( 'add', updateNotifications );
     1491                        control.setting.notifications.bind( 'remove', updateNotifications );
    14731492                },
    14741493
    14751494                /**
  • src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php

    diff --git src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php
    index 33ca5ce150..34f0139294 100644
    class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { 
    641641         * @since 4.3.0
    642642         *
    643643         * @param array $menu_item_value The value to sanitize.
    644          * @return array|false|null Null if an input isn't valid. False if it is marked for deletion.
     644         * @return array|false|null|WP_Error Null if an input isn't valid. False if it is marked for deletion.
    645645         *                          Otherwise the sanitized value.
    646646         */
    647647        public function sanitize( $menu_item_value ) {
    class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { 
    701701                $menu_item_value['attr_title'] = wp_unslash( apply_filters( 'excerpt_save_pre', wp_slash( $menu_item_value['attr_title'] ) ) );
    702702                $menu_item_value['description'] = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $menu_item_value['description'] ) ) );
    703703
    704                 $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
     704                if ( '' !== $menu_item_value['url'] ) {
     705                        $menu_item_value['url'] = esc_url_raw( $menu_item_value['url'] );
     706                        if ( '' === $menu_item_value['url'] ) {
     707                                return new WP_Error( 'invalid_url', __( 'Invalid URL.' ) ); // Fail sanitization if URL is invalid.
     708                        }
     709                }
    705710                if ( 'publish' !== $menu_item_value['status'] ) {
    706711                        $menu_item_value['status'] = 'draft';
    707712                }
  • tests/phpunit/tests/customize/nav-menu-item-setting.php

    diff --git tests/phpunit/tests/customize/nav-menu-item-setting.php tests/phpunit/tests/customize/nav-menu-item-setting.php
    index bcb3dc56de..b5f184405f 100644
    class Test_WP_Customize_Nav_Menu_Item_Setting extends WP_UnitTestCase { 
    472472                $this->assertNull( $setting->sanitize( 'not an array' ) );
    473473                $this->assertNull( $setting->sanitize( 123 ) );
    474474
     475                $valid_urls = array(
     476                        'http://example.com/',
     477                        'https://foo.example.com/hello.html',
     478                        'mailto:nobody@example.com?subject=hi',
     479                        'ftp://example.com/',
     480                        'ftps://example.com/',
     481                        'news://news.server.example/example.group.this',
     482                        'irc://irc.freenode.net/wordpress',
     483                        'gopher://example.com',
     484                        'nntp://news.server.example/example.group.this',
     485                        'feed://example.com/',
     486                        'telnet://example.com',
     487                        'mms://example.com',
     488                        'rtsp://example.com/',
     489                        'svn://develop.svn.wordpress.org/trunk',
     490                        'tel:000-000-000',
     491                        'fax:000-000-000',
     492                        'xmpp:user@host?message',
     493                        'webcal://example.com',
     494                        'urn:org.wordpress',
     495                );
     496                foreach ( $valid_urls as $valid_url ) {
     497                        $url_setting = $setting->sanitize( array( 'url' => $valid_url ) );
     498                        $this->assertInternalType( 'array', $url_setting );
     499                        $this->assertEquals( $valid_url, $url_setting['url'] );
     500                }
     501
     502                $invalid_urls = array(
     503                        'javascript:alert(1)',
     504                        'unknown://something.out-there',
     505                        'smtp://user:pass@mailserver.thing',
     506                );
     507                foreach ( $invalid_urls as $invalid_url ) {
     508                        $url_setting = $setting->sanitize( array( 'url' => $invalid_url ) );
     509                        $this->assertInstanceOf( 'WP_Error', $url_setting );
     510                        $this->assertEquals( 'invalid_url', $url_setting->get_error_code() );
     511                }
     512
    475513                $unsanitized = array(
    476514                        'object_id' => 'bad',
    477515                        'object' => '<b>hello</b>',
    class Test_WP_Customize_Nav_Menu_Item_Setting extends WP_UnitTestCase { 
    479517                        'position' => -123,
    480518                        'type' => 'custom<b>',
    481519                        'title' => '\o/ o\'o Hi<script>unfilteredHtml()</script>',
    482                         'url' => 'javascript:alert(1)',
     520                        'url' => '',
    483521                        'target' => '" onclick="',
    484522                        'attr_title' => '\o/ o\'o <b>bolded</b><script>unfilteredHtml()</script>',
    485523                        'description' => '\o/ o\'o <b>Hello world</b><script>unfilteredHtml()</script>',