Make WordPress Core

Changeset 59948


Ignore:
Timestamp:
03/06/2025 11:46:47 PM (7 weeks ago)
Author:
joedolson
Message:

Menus: Validate custom links and add accessible error messages.

Add URL validation in the admin navigation menu manager that matches the validation in the customizer when adding custom links. Improve accessibility of both custom link forms by adding aria-invalid and aria-describedby attributes with visible error messages and announcing the error using wp.a11y.speak().

Props joedolson, nikitasolanki1812, akrocks, pathan-amaankhan, rcreators, ironprogrammer, audrasjb, ankit-k-gupta, chaion07, rinkalpagdar, snehapatil02, jainil07, parthvataliya.
Fixes #60619, #60969.

Location:
trunk/src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/lib/nav-menu.js

    r59431 r59948  
    11031103
    11041104            $('#add-custom-links input[type="text"]').on( 'keypress', function(e){
    1105                 $('#customlinkdiv').removeClass('form-invalid');
     1105                $( '#customlinkdiv' ).removeClass( 'form-invalid' );
     1106                $( '#custom-menu-item-url' ).removeAttr( 'aria-invalid' ).removeAttr( 'aria-describedby' );
     1107                $( '#custom-url-error' ).hide();
    11061108
    11071109                if ( e.keyCode === 13 ) {
    11081110                    e.preventDefault();
    11091111                    $( '#submit-customlinkdiv' ).trigger( 'click' );
     1112                }
     1113            });
     1114
     1115            $( '#submit-customlinkdiv' ).on( 'click', function (e) {
     1116                var urlInput = $( '#custom-menu-item-url' ),
     1117                    url = urlInput.val().trim(),
     1118                    errorMessage = $( '#custom-url-error' ),
     1119                    urlWrap = $( '#menu-item-url-wrap' ),
     1120                    urlRegex;
     1121
     1122                // Hide the error message initially
     1123                errorMessage.hide();
     1124                urlWrap.removeClass( 'has-error' );
     1125
     1126                /*
     1127                 * Allow URLs including:
     1128                 * - http://example.com/
     1129                 * - //example.com
     1130                 * - /directory/
     1131                 * - ?query-param
     1132                 * - #target
     1133                 * - mailto:foo@example.com
     1134                 *
     1135                 * Any further validation will be handled on the server when the setting is attempted to be saved,
     1136                 * so this pattern does not need to be complete.
     1137                 */
     1138                urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
     1139                if ( ! urlRegex.test( url ) ) {
     1140                    e.preventDefault();
     1141                    urlInput.addClass( 'form-invalid' )
     1142                        .attr( 'aria-invalid', 'true' )
     1143                        .attr( 'aria-describedby', 'custom-url-error' );
     1144
     1145                    errorMessage.show();
     1146                    var errorText = errorMessage.text();
     1147                    urlWrap.addClass( 'has-error' );
     1148                    // Announce error message via screen reader
     1149                    wp.a11y.speak( errorText, 'assertive' );
    11101150                }
    11111151            });
     
    13901430        addCustomLink : function( processMethod ) {
    13911431            var url = $('#custom-menu-item-url').val().toString(),
    1392                 label = $('#custom-menu-item-name').val();
     1432                label = $('#custom-menu-item-name').val(),
     1433                urlRegex;
    13931434
    13941435            if ( '' !== url ) {
     
    13981439            processMethod = processMethod || api.addMenuItemToBottom;
    13991440
    1400             if ( '' === url || 'https://' == url || 'http://' == url ) {
     1441            /*
     1442             * Allow URLs including:
     1443             * - http://example.com/
     1444             * - //example.com
     1445             * - /directory/
     1446             * - ?query-param
     1447             * - #target
     1448             * - mailto:foo@example.com
     1449             *
     1450             * Any further validation will be handled on the server when the setting is attempted to be saved,
     1451             * so this pattern does not need to be complete.
     1452             */
     1453            urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
     1454            if ( ! urlRegex.test( url ) ) {
    14011455                $('#customlinkdiv').addClass('form-invalid');
    14021456                return false;
  • trunk/src/js/_enqueues/wp/customize/nav-menus.js

    r59224 r59948  
    224224            this.$el.on( 'input', '#custom-menu-item-name.invalid, #custom-menu-item-url.invalid', function() {
    225225                $( this ).removeClass( 'invalid' );
     226                var errorMessageId = $( this ).attr( 'aria-describedby' );
     227                $( '#' + errorMessageId ).hide();
     228                $( this ).removeAttr( 'aria-invalid' ).removeAttr( 'aria-describedby' );
    226229            });
    227230
     
    547550                itemName = $( '#custom-menu-item-name' ),
    548551                itemUrl = $( '#custom-menu-item-url' ),
     552                urlErrorMessage = $( '#custom-url-error' ),
     553                nameErrorMessage = $( '#custom-name-error' ),
    549554                url = itemUrl.val().trim(),
    550                 urlRegex;
     555                urlRegex,
     556                errorText;
    551557
    552558            if ( ! this.currentMenuControl ) {
     
    567573             */
    568574            urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
    569 
    570             if ( '' === itemName.val() ) {
    571                 itemName.addClass( 'invalid' );
     575            if ( ! urlRegex.test( url ) || '' === itemName.val() ) {
     576                if ( ! urlRegex.test( url ) ) {
     577                    itemUrl.addClass( 'invalid' )
     578                        .attr( 'aria-invalid', 'true' )
     579                        .attr( 'aria-describedby', 'custom-url-error' );
     580                    urlErrorMessage.show();
     581                    errorText = urlErrorMessage.text();
     582                    // Announce error message via screen reader
     583                    wp.a11y.speak( errorText, 'assertive' );
     584                }
     585                if ( '' === itemName.val() ) {
     586                    itemName.addClass( 'invalid' )
     587                        .attr( 'aria-invalid', 'true' )
     588                        .attr( 'aria-describedby', 'custom-name-error' );
     589                    nameErrorMessage.show();
     590                    errorText = ( '' === errorText ) ? nameErrorMessage.text() : errorText + nameErrorMessage.text();
     591                    // Announce error message via screen reader
     592                    wp.a11y.speak( errorText, 'assertive' );
     593                }
    572594                return;
    573             } else if ( ! urlRegex.test( url ) ) {
    574                 itemUrl.addClass( 'invalid' );
    575                 return;
    576             }
     595            }
     596
     597            urlErrorMessage.hide();
     598            nameErrorMessage.hide();
     599            itemName.removeClass( 'invalid' )
     600                .removeAttr( 'aria-invalid', 'true' )
     601                .removeAttr( 'aria-describedby', 'custom-name-error' );
     602            itemUrl.removeClass( 'invalid' )
     603                .removeAttr( 'aria-invalid', 'true' )
     604                .removeAttr( 'aria-describedby', 'custom-name-error' );
    577605
    578606            menuItem = {
  • trunk/src/wp-admin/css/nav-menus.css

    r59265 r59948  
    378378/* Add Menu Item Boxes */
    379379.postbox .howto input,
    380 .customlinkdiv .menu-item-textbox {
     380.customlinkdiv .menu-item-textbox,
     381.customlinkdiv .error-message {
    381382    width: 180px;
    382383    float: right;
     384}
     385
     386.customlinkdiv .error-message {
     387    clear: right;
    383388}
    384389
  • trunk/src/wp-admin/includes/nav-menu.php

    r59462 r59948  
    352352                class="code menu-item-textbox form-required" placeholder="https://"
    353353            />
     354            <span id="custom-url-error" class="error-message" style="display: none;"><?php _e( 'Please provide a valid link.' ); ?></span>
    354355        </p>
    355356
  • trunk/src/wp-includes/class-wp-customize-nav-menus.php

    r59825 r59948  
    12691269                    <label class="howto" for="custom-menu-item-url"><?php _e( 'URL' ); ?></label>
    12701270                    <input id="custom-menu-item-url" name="menu-item[-1][menu-item-url]" type="text" class="code menu-item-textbox" placeholder="https://">
     1271                    <span id="custom-url-error" class="error-message" style="display: none;"><?php _e( 'Please provide a valid link.' ); ?></span>
    12711272                </p>
    12721273                <p id="menu-item-name-wrap" class="wp-clearfix">
    12731274                    <label class="howto" for="custom-menu-item-name"><?php _e( 'Link Text' ); ?></label>
    12741275                    <input id="custom-menu-item-name" name="menu-item[-1][menu-item-title]" type="text" class="regular-text menu-item-textbox">
     1276                    <span id="custom-name-error" class="error-message" style="display: none;"><?php _e( 'The link text cannot be empty.' ); ?></span>
    12751277                </p>
    12761278                <p class="button-controls">
Note: See TracChangeset for help on using the changeset viewer.