WordPress.org

Make WordPress Core

Ticket #36944: 36944.0.wip.diff

File 36944.0.wip.diff, 13.4 KB (added by westonruter, 6 years ago)
  • src/wp-admin/js/customize-controls.js

    diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
    index 2a5a5b9..92af3d1 100644
     
    15431543
    15441544                                        control.setting = control.settings['default'] || null;
    15451545
     1546                                        // Add setting notifications to the control notification.
    15461547                                        _.each( control.settings, function( setting ) {
    15471548                                                setting.notifications.bind( 'add', function( settingNotification ) {
    1548                                                         var controlNotification = new api.Notification( setting.id + ':' + settingNotification.code, settingNotification );
     1549                                                        var controlNotification, code, params;
     1550                                                        code = setting.id + ':' + settingNotification.code;
     1551                                                        params = _.extend(
     1552                                                                {},
     1553                                                                settingNotification,
     1554                                                                {
     1555                                                                        setting: setting.id
     1556                                                                }
     1557                                                        );
     1558                                                        controlNotification = new api.Notification( code, params );
    15491559                                                        control.notifications.add( controlNotification.code, controlNotification );
    15501560                                                } );
    15511561                                                setting.notifications.bind( 'remove', function( settingNotification ) {
     
    34313441                        },
    34323442
    34333443                        /**
    3434                          * Handle invalid_settings in an error response for the customize-save request.
     3444                         * Handle setting_validities in an error response for the customize-save request.
    34353445                         *
    34363446                         * Add notifications to the settings and focus on the first control that has an invalid setting.
    34373447                         *
    34383448                         * @since 4.6.0
    34393449                         * @private
    34403450                         *
    3441                          * @param {object} response
    3442                          * @param {object} response.invalid_settings
     3451                         * @param {object}  options
     3452                         * @param {object}  options.settingValidities
     3453                         * @param {boolean} [options.focusInvalidControl=false]
    34433454                         * @returns {void}
    34443455                         */
    3445                         _handleInvalidSettingsError: function( response ) {
    3446                                 var invalidControls = [], wasFocused = false;
    3447                                 if ( _.isEmpty( response.invalid_settings ) ) {
    3448                                         return;
    3449                                 }
     3456                        _handleSettingValidities: function( options ) {
     3457                                var invalidSettingControls, invalidSettings = [], wasFocused = false;
    34503458
    34513459                                // Find the controls that correspond to each invalid setting.
    3452                                 _.each( response.invalid_settings, function( notifications, settingId ) {
     3460                                _.each( options.settingValidities, function( validity, settingId ) {
    34533461                                        var setting = api( settingId );
    34543462                                        if ( setting ) {
    3455                                                 _.each( notifications, function( notificationParams, code ) {
    3456                                                         var notification = new api.Notification( code, notificationParams );
    3457                                                         setting.notifications.add( code, notification );
     3463
     3464                                                // Add notifications for invalidities.
     3465                                                if ( _.isObject( validity ) ) {
     3466                                                        _.each( validity, function( params, code ) {
     3467                                                                var notification = new api.Notification( code, params ), existingNotification, needsReplacement = false;
     3468
     3469                                                                // Remove existing notification if already exists for code but differs in parameters.
     3470                                                                existingNotification = setting.notifications( notification.code );
     3471                                                                if ( existingNotification ) {
     3472                                                                        needsReplacement = ( notification.type !== existingNotification.type ) || ! _.isEqual( notification.data, existingNotification.data );
     3473                                                                }
     3474                                                                if ( needsReplacement ) {
     3475                                                                        setting.notifications.remove( code );
     3476                                                                }
     3477
     3478                                                                if ( ! setting.notifications.has( notification.code ) ) {
     3479                                                                        setting.notifications.add( code, notification );
     3480                                                                }
     3481                                                                invalidSettings.push( setting.id );
     3482                                                        } );
     3483                                                }
     3484
     3485                                                // Remove notification errors that are no longer valid.
     3486                                                setting.notifications.each( function( notification ) {
     3487                                                        if ( 'error' === notification.type && ( true === validity || ! validity[ notification.code ] ) ) {
     3488                                                                setting.notifications.remove( notification.code );
     3489                                                        }
    34583490                                                } );
    34593491                                        }
     3492                                } );
     3493
     3494                                if ( options && options.focusInvalidControl ) {
     3495                                        invalidSettingControls = api.findControlsForSettings( invalidSettings );
    34603496
    3461                                         api.control.each( function( control ) {
    3462                                                 _.each( control.settings, function( controlSetting ) {
    3463                                                         if ( controlSetting.id === settingId ) {
    3464                                                                 invalidControls.push( control );
     3497                                        // Focus on the first control that is inside of an expanded section (one that is visible).
     3498                                        _( _.values( invalidSettingControls ) ).find( function( controls ) {
     3499                                                return _( controls ).find( function( control ) {
     3500                                                        var isExpanded = control.section() && api.section.has( control.section() ) && api.section( control.section() ).expanded();
     3501                                                        if ( isExpanded && control.expanded ) {
     3502                                                                isExpanded = control.expanded();
    34653503                                                        }
     3504                                                        if ( isExpanded ) {
     3505                                                                control.focus();
     3506                                                                wasFocused = true;
     3507                                                        }
     3508                                                        return wasFocused;
    34663509                                                } );
    34673510                                        } );
    3468                                 } );
    34693511
    3470                                 // Focus on the first control that is inside of an expanded section (one that is visible).
    3471                                 _( invalidControls ).find( function( control ) {
    3472                                         var isExpanded = control.section() && api.section.has( control.section() ) && api.section( control.section() ).expanded();
    3473                                         if ( isExpanded && control.expanded ) {
    3474                                                 isExpanded = control.expanded();
    3475                                         }
    3476                                         if ( isExpanded ) {
    3477                                                 control.focus();
    3478                                                 wasFocused = true;
     3512                                        // Focus on the first invalid control.
     3513                                        if ( ! wasFocused && ! _.isEmpty( invalidSettingControls ) ) {
     3514                                                _.values( invalidSettingControls )[0][0].focus();
    34793515                                        }
    3480                                         return wasFocused;
    3481                                 } );
    3482 
    3483                                 // Focus on the first invalid control.
    3484                                 if ( ! wasFocused && invalidControls[0] ) {
    3485                                         invalidControls[0].focus();
    34863516                                }
    34873517                        },
    34883518
     
    34913521                                        processing = api.state( 'processing' ),
    34923522                                        submitWhenDoneProcessing,
    34933523                                        submit,
    3494                                         modifiedWhileSaving = {};
     3524                                        modifiedWhileSaving = {},
     3525                                        invalidSettings = [],
     3526                                        invalidControls;
    34953527
    34963528                                body.addClass( 'saving' );
    34973529
     
    35023534
    35033535                                submit = function () {
    35043536                                        var request, query;
     3537
     3538                                        /*
     3539                                         * Block saving if there are any settings that are marked as
     3540                                         * invalid from the client (not from the server). Focus on
     3541                                         * the control.
     3542                                         */
     3543                                        api.each( function( setting ) {
     3544                                                setting.notifications.each( function( notification ) {
     3545                                                        if ( 'error' === notification.type && ( ! notification.data || ! notification.data.from_server ) ) {
     3546                                                                invalidSettings.push( setting.id );
     3547                                                        }
     3548                                                } );
     3549                                        } );
     3550                                        invalidControls = api.findControlsForSettings( invalidSettings );
     3551                                        if ( ! _.isEmpty( invalidControls ) ) {
     3552                                                _.values( invalidControls )[0][0].focus();
     3553                                                body.removeClass( 'saving' );
     3554                                                api.unbind( 'change', captureSettingModifiedDuringSave );
     3555                                                return;
     3556                                        }
     3557
    35053558                                        query = $.extend( self.query(), {
    35063559                                                nonce:  self.nonce.save
    35073560                                        } );
     
    35123565
    35133566                                        api.trigger( 'save', request );
    35143567
    3515                                         /*
    3516                                          * Remove all setting error notifications prior to save, allowing
    3517                                          * server to respond with fresh validation error notifications.
    3518                                          */
    3519                                         api.each( function( setting ) {
    3520                                                 setting.notifications.each( function( notification ) {
    3521                                                         if ( 'error' === notification.type ) {
    3522                                                                 setting.notifications.remove( notification.code );
    3523                                                         }
    3524                                                 } );
    3525                                         } );
    3526 
    35273568                                        request.always( function () {
    35283569                                                body.removeClass( 'saving' );
    35293570                                                saveBtn.prop( 'disabled', false );
     
    35483589                                                        } );
    35493590                                                }
    35503591
    3551                                                 self._handleInvalidSettingsError( response );
     3592                                                if ( response.setting_validities ) {
     3593                                                        self._handleSettingValidities( {
     3594                                                                settingValidities: response.setting_validities,
     3595                                                                focusInvalidControl: true
     3596                                                        } );
     3597                                                }
    35523598
    35533599                                                api.trigger( 'error', response );
    35543600                                        } );
     
    35643610
    35653611                                                api.previewer.send( 'saved', response );
    35663612
     3613                                                if ( response.setting_validities ) {
     3614                                                        self._handleSettingValidities( {
     3615                                                                settingValidities: response.setting_validities,
     3616                                                                focusInvalidControl: true
     3617                                                        } );
     3618                                                }
     3619
    35673620                                                api.trigger( 'saved', response );
    35683621
    35693622                                                // Restore the global dirty state if any settings were modified during save.
     
    36703723                });
    36713724
    36723725                /**
     3726                 * Find all controls associated with the given settings.
     3727                 *
     3728                 * @since 4.6.0
     3729                 * @param {string[]} settingIds Setting IDs.
     3730                 * @returns {object<string, wp.customize.Control>} Mapping setting ids to arrays of controls.
     3731                 */
     3732                api.findControlsForSettings = function findControlsForSettings( settingIds ) {
     3733                        var controls = {};
     3734                        _.each( _.unique( settingIds ), function( settingId ) {
     3735                                api.control.each( function( control ) {
     3736                                        _.each( control.settings, function( controlSetting ) {
     3737                                                if ( controlSetting.id === settingId ) {
     3738                                                        if ( ! controls[ settingId ] ) {
     3739                                                                controls[ settingId ] = [];
     3740                                                        }
     3741                                                        controls[ settingId ].push( control );
     3742                                                }
     3743                                        } );
     3744                                } );
     3745                        } );
     3746                        return controls;
     3747                },
     3748
     3749                /**
    36733750                 * Sort panels, sections, controls by priorities. Hide empty sections and panels.
    36743751                 *
    36753752                 * @since 4.1.0
  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index f89887e..ac7285a 100644
    final class WP_Customize_Manager { 
    991991         * @since 4.6.0
    992992         * @access public
    993993         * @see WP_REST_Request::has_valid_params()
     994         * @see WP_Customize_Setting::validate()
    994995         *
    995996         * @param array $setting_values Mapping of setting IDs to values to sanitize and validate.
    996          * @return array Empty array if all settings were valid. One or more instances of `WP_Error` if any were invalid.
     997         * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`.
    997998         */
    998999        public function validate_setting_values( $setting_values ) {
    9991000                $validity_errors = array();
    final class WP_Customize_Manager { 
    10061007                        if ( false === $validity || null === $validity ) {
    10071008                                $validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
    10081009                        }
    1009                         if ( is_wp_error( $validity ) ) {
    1010                                 $validity_errors[ $setting_id ] = $validity;
    1011                         }
     1010                        $validity_errors[ $setting_id ] = $validity;
    10121011                }
    10131012                return $validity_errors;
    10141013        }
    final class WP_Customize_Manager { 
    10411040                do_action( 'customize_save_validation_before', $this );
    10421041
    10431042                // Validate settings.
    1044                 $validity_errors = $this->validate_setting_values( $this->unsanitized_post_values() );
    1045                 $invalid_count = count( $validity_errors );
    1046                 if ( $invalid_count > 0 ) {
    1047                         $settings_errors = array();
    1048                         foreach ( $validity_errors as $setting_id => $validity_error ) {
    1049                                 $settings_errors[ $setting_id ] = array();
    1050                                 foreach ( $validity_error->errors as $error_code => $error_messages ) {
    1051                                         $settings_errors[ $setting_id ][ $error_code ] = array(
     1043                $setting_validities = $this->validate_setting_values( $this->unsanitized_post_values() );
     1044                $invalid_setting_count = 0;
     1045                $exported_setting_validities = array();
     1046                foreach ( $setting_validities as $setting_id => $validity ) {
     1047                        if ( is_wp_error( $validity ) ) {
     1048                                $invalid_setting_count += 1;
     1049                                $exported_setting_validities[ $setting_id ] = array();
     1050                                foreach ( $validity->errors as $error_code => $error_messages ) {
     1051                                        $error_data = $validity->get_error_data( $error_code );
     1052                                        if ( is_null( $error_data ) ) {
     1053                                                $error_data = array();
     1054                                        }
     1055                                        $error_data = array_merge(
     1056                                                $error_data,
     1057                                                array(
     1058                                                        'from_server' => true,
     1059                                                )
     1060                                        );
     1061                                        $exported_setting_validities[ $setting_id ][ $error_code ] = array(
    10521062                                                'message' => join( ' ', $error_messages ),
    1053                                                 'data' => $validity_error->get_error_data( $error_code ),
     1063                                                'data' => $error_data,
    10541064                                        );
    10551065                                }
     1066                        } else {
     1067                                $exported_setting_validities[ $setting_id ] = true;
    10561068                        }
     1069                }
     1070                if ( $invalid_setting_count > 0 ) {
    10571071                        $response = array(
    1058                                 'invalid_settings' => $settings_errors,
    1059                                 'message' => sprintf( _n( 'There is %s invalid setting.', 'There are %s invalid settings.', $invalid_count ), number_format_i18n( $invalid_count ) ),
     1072                                'setting_validities' => $exported_setting_validities,
     1073                                'message' => sprintf( _n( 'There is %s invalid setting.', 'There are %s invalid settings.', $invalid_setting_count ), number_format_i18n( $invalid_setting_count ) ),
    10601074                        );
    10611075
    10621076                        /** This filter is documented in wp-includes/class-wp-customize-manager.php */
    final class WP_Customize_Manager { 
    10971111                 */
    10981112                do_action( 'customize_save_after', $this );
    10991113
     1114                $data = array(
     1115                        'setting_validities' => $exported_setting_validities,
     1116                );
     1117
    11001118                /**
    11011119                 * Filters response data for a successful customize_save AJAX request.
    11021120                 *
    final class WP_Customize_Manager { 
    11081126                 *                                   event on `wp.customize`.
    11091127                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
    11101128                 */
    1111                 $response = apply_filters( 'customize_save_response', array(), $this );
     1129                $response = apply_filters( 'customize_save_response', $data, $this );
    11121130                wp_send_json_success( $response );
    11131131        }
    11141132
  • src/wp-includes/customize/class-wp-customize-selective-refresh.php

    diff --git src/wp-includes/customize/class-wp-customize-selective-refresh.php src/wp-includes/customize/class-wp-customize-selective-refresh.php
    index f90f0f9..8caf143 100644
    final class WP_Customize_Selective_Refresh { 
    402402                        $response['errors'] = $this->triggered_errors;
    403403                }
    404404
     405                $response['setting_validities'] = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
     406
    405407                /**
    406408                 * Filters the response from rendering the partials.
    407409                 *