Ticket #36944: 36944.0.wip.diff
File 36944.0.wip.diff, 13.4 KB (added by , 9 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
1543 1543 1544 1544 control.setting = control.settings['default'] || null; 1545 1545 1546 // Add setting notifications to the control notification. 1546 1547 _.each( control.settings, function( setting ) { 1547 1548 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 ); 1549 1559 control.notifications.add( controlNotification.code, controlNotification ); 1550 1560 } ); 1551 1561 setting.notifications.bind( 'remove', function( settingNotification ) { … … 3431 3441 }, 3432 3442 3433 3443 /** 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. 3435 3445 * 3436 3446 * Add notifications to the settings and focus on the first control that has an invalid setting. 3437 3447 * 3438 3448 * @since 4.6.0 3439 3449 * @private 3440 3450 * 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] 3443 3454 * @returns {void} 3444 3455 */ 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; 3450 3458 3451 3459 // Find the controls that correspond to each invalid setting. 3452 _.each( response.invalid_settings, function( notifications, settingId ) {3460 _.each( options.settingValidities, function( validity, settingId ) { 3453 3461 var setting = api( settingId ); 3454 3462 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 } 3458 3490 } ); 3459 3491 } 3492 } ); 3493 3494 if ( options && options.focusInvalidControl ) { 3495 invalidSettingControls = api.findControlsForSettings( invalidSettings ); 3460 3496 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(); 3465 3503 } 3504 if ( isExpanded ) { 3505 control.focus(); 3506 wasFocused = true; 3507 } 3508 return wasFocused; 3466 3509 } ); 3467 3510 } ); 3468 } );3469 3511 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(); 3479 3515 } 3480 return wasFocused;3481 } );3482 3483 // Focus on the first invalid control.3484 if ( ! wasFocused && invalidControls[0] ) {3485 invalidControls[0].focus();3486 3516 } 3487 3517 }, 3488 3518 … … 3491 3521 processing = api.state( 'processing' ), 3492 3522 submitWhenDoneProcessing, 3493 3523 submit, 3494 modifiedWhileSaving = {}; 3524 modifiedWhileSaving = {}, 3525 invalidSettings = [], 3526 invalidControls; 3495 3527 3496 3528 body.addClass( 'saving' ); 3497 3529 … … 3502 3534 3503 3535 submit = function () { 3504 3536 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 3505 3558 query = $.extend( self.query(), { 3506 3559 nonce: self.nonce.save 3507 3560 } ); … … 3512 3565 3513 3566 api.trigger( 'save', request ); 3514 3567 3515 /*3516 * Remove all setting error notifications prior to save, allowing3517 * 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 3527 3568 request.always( function () { 3528 3569 body.removeClass( 'saving' ); 3529 3570 saveBtn.prop( 'disabled', false ); … … 3548 3589 } ); 3549 3590 } 3550 3591 3551 self._handleInvalidSettingsError( response ); 3592 if ( response.setting_validities ) { 3593 self._handleSettingValidities( { 3594 settingValidities: response.setting_validities, 3595 focusInvalidControl: true 3596 } ); 3597 } 3552 3598 3553 3599 api.trigger( 'error', response ); 3554 3600 } ); … … 3564 3610 3565 3611 api.previewer.send( 'saved', response ); 3566 3612 3613 if ( response.setting_validities ) { 3614 self._handleSettingValidities( { 3615 settingValidities: response.setting_validities, 3616 focusInvalidControl: true 3617 } ); 3618 } 3619 3567 3620 api.trigger( 'saved', response ); 3568 3621 3569 3622 // Restore the global dirty state if any settings were modified during save. … … 3670 3723 }); 3671 3724 3672 3725 /** 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 /** 3673 3750 * Sort panels, sections, controls by priorities. Hide empty sections and panels. 3674 3751 * 3675 3752 * @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 { 991 991 * @since 4.6.0 992 992 * @access public 993 993 * @see WP_REST_Request::has_valid_params() 994 * @see WP_Customize_Setting::validate() 994 995 * 995 996 * @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`. 997 998 */ 998 999 public function validate_setting_values( $setting_values ) { 999 1000 $validity_errors = array(); … … final class WP_Customize_Manager { 1006 1007 if ( false === $validity || null === $validity ) { 1007 1008 $validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); 1008 1009 } 1009 if ( is_wp_error( $validity ) ) { 1010 $validity_errors[ $setting_id ] = $validity; 1011 } 1010 $validity_errors[ $setting_id ] = $validity; 1012 1011 } 1013 1012 return $validity_errors; 1014 1013 } … … final class WP_Customize_Manager { 1041 1040 do_action( 'customize_save_validation_before', $this ); 1042 1041 1043 1042 // 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( 1052 1062 'message' => join( ' ', $error_messages ), 1053 'data' => $ validity_error->get_error_data( $error_code ),1063 'data' => $error_data, 1054 1064 ); 1055 1065 } 1066 } else { 1067 $exported_setting_validities[ $setting_id ] = true; 1056 1068 } 1069 } 1070 if ( $invalid_setting_count > 0 ) { 1057 1071 $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 ) ), 1060 1074 ); 1061 1075 1062 1076 /** This filter is documented in wp-includes/class-wp-customize-manager.php */ … … final class WP_Customize_Manager { 1097 1111 */ 1098 1112 do_action( 'customize_save_after', $this ); 1099 1113 1114 $data = array( 1115 'setting_validities' => $exported_setting_validities, 1116 ); 1117 1100 1118 /** 1101 1119 * Filters response data for a successful customize_save AJAX request. 1102 1120 * … … final class WP_Customize_Manager { 1108 1126 * event on `wp.customize`. 1109 1127 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 1110 1128 */ 1111 $response = apply_filters( 'customize_save_response', array(), $this );1129 $response = apply_filters( 'customize_save_response', $data, $this ); 1112 1130 wp_send_json_success( $response ); 1113 1131 } 1114 1132 -
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 { 402 402 $response['errors'] = $this->triggered_errors; 403 403 } 404 404 405 $response['setting_validities'] = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ); 406 405 407 /** 406 408 * Filters the response from rendering the partials. 407 409 *