Changeset 37476
- Timestamp:
- 05/20/2016 09:09:40 PM (8 years ago)
- Location:
- trunk
- Files:
-
- 12 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/css/customize-controls.css
r37442 r37476 495 495 .customize-control input[type="tel"], 496 496 .customize-control input[type="url"] { 497 width: 98%;497 width: 100%; 498 498 line-height: 18px; 499 499 margin: 0; … … 621 621 background: #eee; 622 622 border-right: 1px solid #ddd; 623 } 624 625 626 /** 627 * Notifications 628 */ 629 630 #customize-controls .customize-control-notifications-container { /* Scoped to #customize-controls for specificity over notification styles in common.css. */ 631 margin: 4px 0 8px 0; 632 padding: 0; 633 display: none; 634 cursor: default; 635 } 636 637 #customize-controls .customize-control-widget_form.has-error .widget .widget-top, 638 .customize-control-nav_menu_item.has-error .menu-item-bar .menu-item-handle { 639 box-shadow: inset 0 0 0 2px #dc3232; 640 transition: .15s box-shadow linear; 641 } 642 643 .customize-control-notifications-container li.notice { 644 list-style: none; 645 margin: 0 0 6px 0; 646 padding: 4px 8px; 647 } 648 649 .customize-control-notifications-container li.notice:last-child { 650 margin-bottom: 0; 651 } 652 653 #customize-controls .customize-control-nav_menu_item .customize-control-notifications-container { 654 margin-top: 0; 655 } 656 657 #customize-controls .customize-control-widget_form .customize-control-notifications-container { 658 margin-top: 8px; 659 } 660 661 .customize-control-text.has-error input { 662 outline: 2px solid #dc3232; 623 663 } 624 664 -
trunk/src/wp-admin/js/customize-controls.js
r37347 r37476 28 28 this.transport = this.transport || 'refresh'; 29 29 this._dirty = options.dirty || false; 30 this.notifications = new api.Values({ defaultConstructor: api.Notification }); 30 31 31 32 // Whenever the setting's value changes, refresh the preview. … … 1479 1480 control.active = new api.Value(); 1480 1481 control.activeArgumentsQueue = []; 1482 control.notifications = new api.Values({ defaultConstructor: api.Notification }); 1481 1483 1482 1484 control.elements = []; … … 1542 1544 control.setting = control.settings['default'] || null; 1543 1545 1546 _.each( control.settings, function( setting ) { 1547 setting.notifications.bind( 'add', function( settingNotification ) { 1548 var controlNotification = new api.Notification( setting.id + ':' + settingNotification.code, settingNotification ); 1549 control.notifications.add( controlNotification.code, controlNotification ); 1550 } ); 1551 setting.notifications.bind( 'remove', function( settingNotification ) { 1552 control.notifications.remove( setting.id + ':' + settingNotification.code ); 1553 } ); 1554 } ); 1555 1544 1556 control.embed(); 1545 1557 }) ); … … 1548 1560 // After the control is embedded on the page, invoke the "ready" method. 1549 1561 control.deferred.embedded.done( function () { 1562 /* 1563 * Note that this debounced/deferred rendering is needed for two reasons: 1564 * 1) The 'remove' event is triggered just _before_ the notification is actually removed. 1565 * 2) Improve performance when adding/removing multiple notifications at a time. 1566 */ 1567 var debouncedRenderNotifications = _.debounce( function renderNotifications() { 1568 control.renderNotifications(); 1569 } ); 1570 control.notifications.bind( 'add', function( notification ) { 1571 wp.a11y.speak( notification.message, 'assertive' ); 1572 debouncedRenderNotifications(); 1573 } ); 1574 control.notifications.bind( 'remove', debouncedRenderNotifications ); 1575 control.renderNotifications(); 1576 1550 1577 control.ready(); 1551 1578 }); … … 1588 1615 */ 1589 1616 ready: function() {}, 1617 1618 /** 1619 * Get the element inside of a control's container that contains the validation error message. 1620 * 1621 * Control subclasses may override this to return the proper container to render notifications into. 1622 * Injects the notification container for existing controls that lack the necessary container, 1623 * including special handling for nav menu items and widgets. 1624 * 1625 * @since 4.6.0 1626 * @returns {jQuery} Setting validation message element. 1627 * @this {wp.customize.Control} 1628 */ 1629 getNotificationsContainerElement: function() { 1630 var control = this, controlTitle, notificationsContainer; 1631 1632 notificationsContainer = control.container.find( '.customize-control-notifications-container:first' ); 1633 if ( notificationsContainer.length ) { 1634 return notificationsContainer; 1635 } 1636 1637 notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' ); 1638 1639 if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) { 1640 control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer ); 1641 } else if ( control.container.hasClass( 'customize-control-widget_form' ) ) { 1642 control.container.find( '.widget-inside:first' ).prepend( notificationsContainer ); 1643 } else { 1644 controlTitle = control.container.find( '.customize-control-title' ); 1645 if ( controlTitle.length ) { 1646 controlTitle.after( notificationsContainer ); 1647 } else { 1648 control.container.prepend( notificationsContainer ); 1649 } 1650 } 1651 return notificationsContainer; 1652 }, 1653 1654 /** 1655 * Render notifications. 1656 * 1657 * Renders the `control.notifications` into the control's container. 1658 * Control subclasses may override this method to do their own handling 1659 * of rendering notifications. 1660 * 1661 * @since 4.6.0 1662 * @this {wp.customize.Control} 1663 */ 1664 renderNotifications: function() { 1665 var control = this, container, notifications, hasError = false; 1666 container = control.getNotificationsContainerElement(); 1667 if ( ! container || ! container.length ) { 1668 return; 1669 } 1670 notifications = []; 1671 control.notifications.each( function( notification ) { 1672 notifications.push( notification ); 1673 if ( 'error' === notification.type ) { 1674 hasError = true; 1675 } 1676 } ); 1677 1678 if ( 0 === notifications.length ) { 1679 container.stop().slideUp( 'fast' ); 1680 } else { 1681 container.stop().slideDown( 'fast', null, function() { 1682 $( this ).css( 'height', 'auto' ); 1683 } ); 1684 } 1685 1686 if ( ! control.notificationsTemplate ) { 1687 control.notificationsTemplate = wp.template( 'customize-control-notifications' ); 1688 } 1689 1690 control.container.toggleClass( 'has-notifications', 0 !== notifications.length ); 1691 control.container.toggleClass( 'has-error', hasError ); 1692 container.empty().append( $.trim( 1693 control.notificationsTemplate( { notifications: notifications, altNotice: Boolean( control.altNotice ) } ) 1694 ) ); 1695 }, 1590 1696 1591 1697 /** … … 3224 3330 }); 3225 3331 3332 api.settingConstructor = {}; 3226 3333 api.controlConstructor = { 3227 3334 color: api.ColorControl, … … 3324 3431 }, 3325 3432 3433 /** 3434 * Handle invalid_settings in an error response for the customize-save request. 3435 * 3436 * Add notifications to the settings and focus on the first control that has an invalid setting. 3437 * 3438 * @since 4.6.0 3439 * @private 3440 * 3441 * @param {object} response 3442 * @param {object} response.invalid_settings 3443 * @returns {void} 3444 */ 3445 _handleInvalidSettingsError: function( response ) { 3446 var invalidControls = [], wasFocused = false; 3447 if ( _.isEmpty( response.invalid_settings ) ) { 3448 return; 3449 } 3450 3451 // Find the controls that correspond to each invalid setting. 3452 _.each( response.invalid_settings, function( notifications, settingId ) { 3453 var setting = api( settingId ); 3454 if ( setting ) { 3455 _.each( notifications, function( notificationParams, code ) { 3456 var notification = new api.Notification( code, notificationParams ); 3457 setting.notifications.add( code, notification ); 3458 } ); 3459 } 3460 3461 api.control.each( function( control ) { 3462 _.each( control.settings, function( controlSetting ) { 3463 if ( controlSetting.id === settingId ) { 3464 invalidControls.push( control ); 3465 } 3466 } ); 3467 } ); 3468 } ); 3469 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; 3479 } 3480 return wasFocused; 3481 } ); 3482 3483 // Focus on the first invalid control. 3484 if ( ! wasFocused && invalidControls[0] ) { 3485 invalidControls[0].focus(); 3486 } 3487 }, 3488 3326 3489 save: function() { 3327 3490 var self = this, … … 3349 3512 3350 3513 api.trigger( 'save', request ); 3514 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 } ); 3351 3526 3352 3527 request.always( function () { … … 3373 3548 } ); 3374 3549 } 3550 3551 self._handleInvalidSettingsError( response ); 3552 3375 3553 api.trigger( 'error', response ); 3376 3554 } ); … … 3425 3603 // Create Settings 3426 3604 $.each( api.settings.settings, function( id, data ) { 3427 api.create( id, id, data.value, { 3605 var constructor = api.settingConstructor[ data.type ] || api.Setting, 3606 setting; 3607 3608 setting = new constructor( id, data.value, { 3428 3609 transport: data.transport, 3429 3610 previewer: api.previewer, 3430 3611 dirty: !! data.dirty 3431 3612 } ); 3613 api.add( id, setting ); 3432 3614 }); 3433 3615 -
trunk/src/wp-admin/js/customize-widgets.js
r37437 r37476 431 431 control.onChangeExpanded( expanded, args ); 432 432 }); 433 control.altNotice = true; 433 434 434 435 api.Control.prototype.initialize.call( control, id, options ); -
trunk/src/wp-includes/class-wp-customize-manager.php
r37342 r37476 655 655 * 656 656 * @since 3.4.0 657 * @since 4.1.1 Introduced 'default' parameter. 658 * 659 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object 660 * @param mixed $default value returned $setting has no post value (added in 4.2.0). 661 * @return string|mixed $post_value Sanitized value or the $default provided 657 * @since 4.1.1 Introduced `$default` parameter. 658 * @since 4.6.0 Return `$default` when setting post value is invalid. 659 * @see WP_REST_Server::dispatch() 660 * @see WP_Rest_Request::sanitize_params() 661 * @see WP_Rest_Request::has_valid_params() 662 * 663 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object. 664 * @param mixed $default Value returned $setting has no post value (added in 4.2.0) 665 * or the post value is invalid (added in 4.6.0). 666 * @return string|mixed $post_value Sanitized value or the $default provided. 662 667 */ 663 668 public function post_value( $setting, $default = null ) { 664 669 $post_values = $this->unsanitized_post_values(); 665 if ( array_key_exists( $setting->id, $post_values ) ) { 666 return $setting->sanitize( $post_values[ $setting->id ] ); 667 } else { 670 if ( ! array_key_exists( $setting->id, $post_values ) ) { 668 671 return $default; 669 672 } 673 $value = $setting->sanitize( $post_values[ $setting->id ] ); 674 if ( is_null( $value ) || is_wp_error( $value ) ) { 675 return $default; 676 } 677 $valid = $setting->validate( $value ); 678 if ( is_wp_error( $valid ) ) { 679 return $default; 680 } 681 return $value; 670 682 } 671 683 … … 971 983 972 984 /** 985 * Validate setting values. 986 * 987 * Sanitization is applied to the values before being passed for validation. 988 * Validation is skipped for unregistered settings or for values that are 989 * already null since they will be skipped anyway. 990 * 991 * @since 4.6.0 992 * @access public 993 * @see WP_REST_Request::has_valid_params() 994 * 995 * @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 */ 998 public function validate_setting_values( $setting_values ) { 999 $validity_errors = array(); 1000 foreach ( $setting_values as $setting_id => $unsanitized_value ) { 1001 $setting = $this->get_setting( $setting_id ); 1002 if ( ! $setting || is_null( $unsanitized_value ) ) { 1003 continue; 1004 } 1005 $validity = $setting->validate( $setting->sanitize( $unsanitized_value ) ); 1006 if ( false === $validity || null === $validity ) { 1007 $validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); 1008 } 1009 if ( is_wp_error( $validity ) ) { 1010 $validity_errors[ $setting_id ] = $validity; 1011 } 1012 } 1013 return $validity_errors; 1014 } 1015 1016 /** 973 1017 * Switch the theme and trigger the save() method on each setting. 974 1018 * … … 983 1027 if ( ! check_ajax_referer( $action, 'nonce', false ) ) { 984 1028 wp_send_json_error( 'invalid_nonce' ); 1029 } 1030 1031 /** 1032 * Fires before save validation happens. 1033 * 1034 * Plugins can add just-in-time `customize_validate_{$setting_id}` filters 1035 * at this point to catch any settings registered after `customize_register`. 1036 * 1037 * @since 4.6.0 1038 * 1039 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 1040 */ 1041 do_action( 'customize_save_validation_before', $this ); 1042 1043 // 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( 1052 'message' => join( ' ', $error_messages ), 1053 'data' => $validity_error->get_error_data( $error_code ), 1054 ); 1055 } 1056 } 1057 $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 ) ), 1060 ); 1061 1062 /** This filter is documented in wp-includes/class-wp-customize-manager.php */ 1063 $response = apply_filters( 'customize_save_response', $response, $this ); 1064 wp_send_json_error( $response ); 985 1065 } 986 1066 … … 1404 1484 $control->print_template(); 1405 1485 } 1486 ?> 1487 <script type="text/html" id="tmpl-customize-control-notifications"> 1488 <ul> 1489 <# _.each( data.notifications, function( notification ) { #> 1490 <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li> 1491 <# } ); #> 1492 </ul> 1493 </script> 1494 <?php 1406 1495 } 1407 1496 … … 1764 1853 "s[%s] = %s;\n", 1765 1854 wp_json_encode( $setting->id ), 1766 wp_json_encode( array( 1767 'value' => $setting->js_value(), 1768 'transport' => $setting->transport, 1769 'dirty' => $setting->dirty, 1770 ) ) 1855 wp_json_encode( $setting->json() ) 1771 1856 ); 1772 1857 } -
trunk/src/wp-includes/class-wp-customize-setting.php
r37350 r37476 60 60 * @var callback 61 61 */ 62 public $validate_callback = ''; 62 63 public $sanitize_callback = ''; 63 64 public $sanitize_js_callback = ''; … … 143 144 } 144 145 146 if ( $this->validate_callback ) { 147 add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 ); 148 } 145 149 if ( $this->sanitize_callback ) { 146 150 add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 ); … … 465 469 * 466 470 * @since 3.4.0 467 * 468 * @return false|void False if cap check fails or value isn't set. 471 * @since 4.6.0 Return the result of updating the value. 472 * 473 * @return false|void False if cap check fails or value isn't set or is invalid. 469 474 */ 470 475 final public function save() { 471 476 $value = $this->post_value(); 472 477 473 if ( ! $this->check_capabilities() || ! isset( $value ) ) 478 if ( ! $this->check_capabilities() || ! isset( $value ) ) { 474 479 return false; 480 } 475 481 476 482 /** … … 484 490 * @param WP_Customize_Setting $this WP_Customize_Setting instance. 485 491 */ 486 do_action( 'customize_save_' . $this->id_data[ 'base'], $this );492 do_action( 'customize_save_' . $this->id_data['base'], $this ); 487 493 488 494 $this->update( $value ); … … 495 501 * 496 502 * @param mixed $default A default value which is used as a fallback. Default is null. 497 * @return mixed The default value on failure, otherwise the sanitized value.503 * @return mixed The default value on failure, otherwise the sanitized and validated value. 498 504 */ 499 505 final public function post_value( $default = null ) { … … 506 512 * @since 3.4.0 507 513 * 508 * @param string|array $value The value to sanitize.509 * @return string|array|null Null if an input isn't valid, otherwise the sanitized value.514 * @param string|array $value The value to sanitize. 515 * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid. 510 516 */ 511 517 public function sanitize( $value ) { … … 520 526 */ 521 527 return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); 528 } 529 530 /** 531 * Validate an input. 532 * 533 * @since 4.6.0 534 * @access public 535 * @see WP_REST_Request::has_valid_params() 536 * 537 * @param mixed $value Value to validate. 538 * @return true|WP_Error 539 */ 540 public function validate( $value ) { 541 if ( is_wp_error( $value ) ) { 542 return $value; 543 } 544 if ( is_null( $value ) ) { 545 return new WP_Error( 'invalid_value', __( 'Invalid value.' ) ); 546 } 547 548 $validity = new WP_Error(); 549 550 /** 551 * Validate a Customize setting value. 552 * 553 * Plugins should amend the `$validity` object via its `WP_Error::add()` method. 554 * 555 * @since 4.6.0 556 * 557 * @param WP_Error $validity Filtered from `true` to `WP_Error` when invalid. 558 * @param mixed $value Value of the setting. 559 * @param WP_Customize_Setting $this WP_Customize_Setting instance. 560 */ 561 $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this ); 562 563 if ( is_wp_error( $validity ) && empty( $validity->errors ) ) { 564 $validity = true; 565 } 566 return $validity; 522 567 } 523 568 … … 701 746 702 747 /** 748 * Get the data to export to the client via JSON. 749 * 750 * @since 4.6.0 751 * 752 * @return array Array of parameters passed to JavaScript. 753 */ 754 public function json() { 755 return array( 756 'value' => $this->js_value(), 757 'transport' => $this->transport, 758 'dirty' => $this->dirty, 759 'type' => $this->type, 760 ); 761 } 762 763 /** 703 764 * Validate user capabilities whether the theme supports the setting. 704 765 * -
trunk/src/wp-includes/js/customize-base.js
r36583 r37476 756 756 $.extend( api.Messenger.prototype, api.Events ); 757 757 758 /** 759 * Notification. 760 * 761 * @class 762 * @augments wp.customize.Class 763 * @since 4.6.0 764 * 765 * @param {string} code The error code. 766 * @param {object} params Params. 767 * @param {string} params.message The error message. 768 * @param {string} [params.type=error] The notification type. 769 * @param {*} [params.data] Any additional data. 770 */ 771 api.Notification = api.Class.extend({ 772 initialize: function( code, params ) { 773 this.code = code; 774 this.message = params.message; 775 this.type = params.type || 'error'; 776 this.data = params.data || null; 777 } 778 }); 779 758 780 // The main API object is also a collection of all customizer settings. 759 781 api = $.extend( new api.Values(), api ); -
trunk/tests/phpunit/tests/customize/manager.php
r37040 r37476 124 124 $bar_setting = $manager->get_setting( 'bar' ); 125 125 $this->assertEquals( 'post_value_bar_default', $manager->post_value( $bar_setting, 'post_value_bar_default' ), 'Expected post_value($bar_setting, $default) to return $default since no value supplied in $_POST[customized][bar]' ); 126 } 127 128 /** 129 * Test the WP_Customize_Manager::post_value() method for a setting value that fails validation. 130 * 131 * @ticket 34893 132 */ 133 function test_invalid_post_value() { 134 $default_value = 'foo_default'; 135 $setting = $this->manager->add_setting( 'foo', array( 136 'validate_callback' => array( $this, 'filter_customize_validate_foo' ), 137 'sanitize_callback' => array( $this, 'filter_customize_sanitize_foo' ), 138 ) ); 139 $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) ); 140 $this->assertEquals( $default_value, $setting->post_value( $default_value ) ); 141 142 $post_value = 'bar'; 143 $this->manager->set_post_value( 'foo', $post_value ); 144 $this->assertEquals( strtoupper( $post_value ), $this->manager->post_value( $setting, $default_value ) ); 145 $this->assertEquals( strtoupper( $post_value ), $setting->post_value( $default_value ) ); 146 147 $this->manager->set_post_value( 'foo', 'return_wp_error_in_sanitize' ); 148 $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) ); 149 $this->assertEquals( $default_value, $setting->post_value( $default_value ) ); 150 151 $this->manager->set_post_value( 'foo', 'return_null_in_sanitize' ); 152 $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) ); 153 $this->assertEquals( $default_value, $setting->post_value( $default_value ) ); 154 155 $post_value = '<script>evil</script>'; 156 $this->manager->set_post_value( 'foo', $post_value ); 157 $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) ); 158 $this->assertEquals( $default_value, $setting->post_value( $default_value ) ); 159 } 160 161 /** 162 * Filter customize_validate callback. 163 * 164 * @param mixed $value Value. 165 * @return string|WP_Error 166 */ 167 function filter_customize_sanitize_foo( $value ) { 168 if ( 'return_null_in_sanitize' === $value ) { 169 $value = null; 170 } elseif ( is_string( $value ) ) { 171 $value = strtoupper( $value ); 172 if ( false !== stripos( $value, 'return_wp_error_in_sanitize' ) ) { 173 $value = new WP_Error( 'invalid_value_in_sanitize', __( 'Invalid value.' ), array( 'source' => 'filter_customize_sanitize_foo' ) ); 174 } 175 } 176 return $value; 177 } 178 179 /** 180 * Filter customize_validate callback. 181 * 182 * @param WP_Error $validity Validity. 183 * @param mixed $value Value. 184 * @return WP_Error 185 */ 186 function filter_customize_validate_foo( $validity, $value ) { 187 if ( false !== stripos( $value, '<script' ) ) { 188 $validity->add( 'invalid_value_in_validate', __( 'Invalid value.' ), array( 'source' => 'filter_customize_validate_foo' ) ); 189 } 190 return $validity; 191 } 192 193 /** 194 * Test WP_Customize_Manager::validate_setting_values(). 195 * 196 * @see WP_Customize_Manager::validate_setting_values() 197 */ 198 function test_validate_setting_values() { 199 $default_value = 'foo_default'; 200 $setting = $this->manager->add_setting( 'foo', array( 201 'validate_callback' => array( $this, 'filter_customize_validate_foo' ), 202 'sanitize_callback' => array( $this, 'filter_customize_sanitize_foo' ), 203 ) ); 204 205 $post_value = 'bar'; 206 $this->manager->set_post_value( 'foo', $post_value ); 207 $this->assertEmpty( $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ) ); 208 209 $this->manager->set_post_value( 'foo', 'return_wp_error_in_sanitize' ); 210 $invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ); 211 $this->assertCount( 1, $invalid_settings ); 212 $this->assertArrayHasKey( $setting->id, $invalid_settings ); 213 $this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] ); 214 $error = $invalid_settings[ $setting->id ]; 215 $this->assertEquals( 'invalid_value_in_sanitize', $error->get_error_code() ); 216 $this->assertEquals( array( 'source' => 'filter_customize_sanitize_foo' ), $error->get_error_data() ); 217 218 $this->manager->set_post_value( 'foo', 'return_null_in_sanitize' ); 219 $invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ); 220 $this->assertCount( 1, $invalid_settings ); 221 $this->assertArrayHasKey( $setting->id, $invalid_settings ); 222 $this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] ); 223 $this->assertNull( $invalid_settings[ $setting->id ]->get_error_data() ); 224 225 $post_value = '<script>evil</script>'; 226 $this->manager->set_post_value( 'foo', $post_value ); 227 $invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ); 228 $this->assertCount( 1, $invalid_settings ); 229 $this->assertArrayHasKey( $setting->id, $invalid_settings ); 230 $this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] ); 231 $error = $invalid_settings[ $setting->id ]; 232 $this->assertEquals( 'invalid_value_in_validate', $error->get_error_code() ); 233 $this->assertEquals( array( 'source' => 'filter_customize_validate_foo' ), $error->get_error_data() ); 126 234 } 127 235 … … 417 525 $this->assertContains( 'var _wpCustomizeSettings =', $content ); 418 526 $this->assertContains( '"blogname"', $content ); 527 $this->assertContains( '"type":"option"', $content ); 419 528 $this->assertContains( '_wpCustomizeSettings.controls', $content ); 420 529 $this->assertContains( '_wpCustomizeSettings.settings', $content ); -
trunk/tests/phpunit/tests/customize/setting.php
r37350 r37476 43 43 $this->assertEquals( '', $setting->sanitize_callback ); 44 44 $this->assertEquals( '', $setting->sanitize_js_callback ); 45 $this->assertFalse( has_filter( "customize_validate_{$setting->id}" ) ); 45 46 $this->assertFalse( has_filter( "customize_sanitize_{$setting->id}" ) ); 46 47 $this->assertFalse( has_filter( "customize_sanitize_js_{$setting->id}" ) ); … … 55 56 'default' => 'barbar', 56 57 'transport' => 'postMessage', 58 'validate_callback' => create_function( '$value', 'return $value . ":validate_callback";' ), 57 59 'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ), 58 60 'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ), … … 63 65 $this->assertEquals( $value, $setting->$key ); 64 66 } 67 $this->assertEquals( 10, has_filter( "customize_validate_{$setting->id}", $args['validate_callback'] ) ); 65 68 $this->assertEquals( 10, has_filter( "customize_sanitize_{$setting->id}", $args['sanitize_callback'] ) ); 66 69 $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$setting->id}" ), $args['sanitize_js_callback'] ); … … 91 94 /** 92 95 * Run assertions on non-multidimensional standard settings. 96 * 97 * @see WP_Customize_Setting::value() 93 98 */ 94 99 function test_preview_standard_types_non_multidimensional() { … … 168 173 * 169 174 * @see WP_Customize_Setting::preview() 175 * @see WP_Customize_Setting::value() 170 176 */ 171 177 function test_preview_standard_types_multidimensional() { … … 570 576 $this->assertEquals( 'no', $autoload, 'Even though setting1 did not indicate autoload (thus normally true), since another multidimensional option setting of the base did say autoload=false, it should be autoload=no' ); 571 577 } 578 579 /** 580 * Test js_value and json methods. 581 * 582 * @see WP_Customize_Setting::js_value() 583 * @see WP_Customize_Setting::json() 584 */ 585 public function test_js_value() { 586 $default = "\x00"; 587 $args = array( 588 'type' => 'binary', 589 'default' => $default, 590 'transport' => 'postMessage', 591 'dirty' => true, 592 'sanitize_js_callback' => create_function( '$value', 'return base64_encode( $value );' ), 593 ); 594 $setting = new WP_Customize_Setting( $this->manager, 'name', $args ); 595 596 $this->assertEquals( $default, $setting->value() ); 597 $this->assertEquals( base64_encode( $default ), $setting->js_value() ); 598 599 $exported = $setting->json(); 600 $this->assertArrayHasKey( 'type', $exported ); 601 $this->assertArrayHasKey( 'value', $exported ); 602 $this->assertArrayHasKey( 'transport', $exported ); 603 $this->assertArrayHasKey( 'dirty', $exported ); 604 $this->assertEquals( $setting->js_value(), $exported['value'] ); 605 $this->assertEquals( $args['type'], $setting->type ); 606 $this->assertEquals( $args['transport'], $setting->transport ); 607 $this->assertEquals( $args['dirty'], $setting->dirty ); 608 } 609 610 /** 611 * Test validate. 612 * 613 * @see WP_Customize_Setting::validate() 614 */ 615 public function test_validate() { 616 $setting = new WP_Customize_Setting( $this->manager, 'name', array( 617 'type' => 'key', 618 'validate_callback' => array( $this, 'filter_validate_for_test_validate' ), 619 ) ); 620 $validity = $setting->validate( 'BAD!' ); 621 $this->assertInstanceOf( 'WP_Error', $validity ); 622 $this->assertEquals( 'invalid_key', $validity->get_error_code() ); 623 } 624 625 /** 626 * Validate callback. 627 * 628 * @see Tests_WP_Customize_Setting::test_validate() 629 * 630 * @param WP_Error $validity Validity. 631 * @param string $value Value. 632 * 633 * @return WP_Error 634 */ 635 public function filter_validate_for_test_validate( $validity, $value ) { 636 $this->assertInstanceOf( 'WP_Error', $validity ); 637 $this->assertInternalType( 'string', $value ); 638 if ( sanitize_key( $value ) !== $value ) { 639 $validity->add( 'invalid_key', 'Invalid key' ); 640 } 641 return $validity; 642 } 572 643 } 573 644 -
trunk/tests/qunit/fixtures/customize-settings.js
r36532 r37476 113 113 'transport': 'postMessage', 114 114 'value': 'Lorem Ipsum' 115 }, 116 'fixture-setting-abbr': { 117 'transport': 'postMessage', 118 'value': 'NASA', 119 'type': 'abbreviation' 115 120 } 116 121 }, -
trunk/tests/qunit/index.html
r34563 r37476 12 12 <script src="../../src/wp-includes/js/zxcvbn.min.js"></script> 13 13 <script src="../../src/wp-includes/js/wp-util.js"></script> 14 <script src="../../src/wp-includes/js/wp-a11y.js"></script> 14 15 15 16 <!-- QUnit --> … … 126 127 <# } #> 127 128 </li> 129 </script> 130 <script type="text/html" id="tmpl-customize-control-notifications"> 131 <ul> 132 <# _.each( data.notifications, function( notification ) { #> 133 <li data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li> 134 <# } ); #> 135 </ul> 128 136 </script> 129 137 -
trunk/tests/qunit/wp-admin/js/customize-base.js
r30716 r37476 1 /* global wp */1 /* global wp, test, ok, equal, module */ 2 2 3 3 jQuery( function( $ ) { … … 159 159 ok( wasCallbackFired ); 160 160 }); 161 162 module( 'Customize Base: Notification' ); 163 test( 'Notification object exists and has expected properties', function ( assert ) { 164 var notification = new wp.customize.Notification( 'mycode', { 165 'message': 'Hello World', 166 'type': 'update', 167 'data': { 'foo': 'bar' } 168 } ); 169 170 assert.equal( 'mycode', notification.code ); 171 assert.equal( 'Hello World', notification.message ); 172 assert.equal( 'update', notification.type ); 173 assert.deepEqual( { 'foo': 'bar' }, notification.data ); 174 175 notification = new wp.customize.Notification( 'mycode2', { 176 'message': 'Hello Space' 177 } ); 178 assert.equal( 'mycode2', notification.code ); 179 assert.equal( 'Hello Space', notification.message ); 180 assert.equal( 'error', notification.type ); 181 assert.equal( null, notification.data ); 182 } ); 161 183 }); -
trunk/tests/qunit/wp-admin/js/customize-controls.js
r36689 r37476 1 /* global wp */ 1 /* global wp, test, ok, equal, module */ 2 3 wp.customize.settingConstructor.abbreviation = wp.customize.Setting.extend({ 4 validate: function( value ) { 5 return value.toUpperCase(); 6 } 7 }); 2 8 3 9 jQuery( window ).load( function (){ … … 86 92 equal( wp.customize( 'fixture-setting' )(), 'Lorem Ipsum' ); 87 93 } ); 94 test( 'Setting has notifications', function () { 95 var setting = wp.customize( 'fixture-setting' ); 96 ok( setting.notifications.extended( wp.customize.Values ) ); 97 equal( wp.customize.Notification, setting.notifications.prototype.constructor.defaultConstructor ); 98 } ); 99 test( 'Setting constructor object exists', function( assert ) { 100 assert.ok( _.isObject( wp.customize.settingConstructor ) ); 101 } ); 102 test( 'Custom setting constructor is used', function( assert ) { 103 var setting = wp.customize( 'fixture-setting-abbr' ); 104 assert.ok( setting.extended( wp.customize.settingConstructor.abbreviation ) ); 105 setting.set( 'usa' ); 106 assert.equal( 'USA', setting.get() ); 107 } ); 88 108 89 109 module( 'Customizer Control in Fixture' ); … … 99 119 var control = wp.customize.control( 'fixture-control' ); 100 120 equal( control.section(), 'fixture-section' ); 121 } ); 122 test( 'Control has notifications', function ( assert ) { 123 var control = wp.customize.control( 'fixture-control' ), settingNotification, controlOnlyNotification, doneEmbedded; 124 assert.ok( control.notifications.extended( wp.customize.Values ) ); 125 assert.equal( wp.customize.Notification, control.notifications.prototype.constructor.defaultConstructor ); 126 assert.ok( _.isFunction( control.getNotificationsContainerElement ) ); 127 assert.ok( _.isFunction( control.renderNotifications ) ); 128 129 doneEmbedded = assert.async(); 130 control.deferred.embedded.done( function() { 131 var notificationContainerElement; 132 133 assert.equal( 0, _.size( control.notifications._value ) ); 134 assert.equal( 0, _.size( control.settings['default'].notifications._value ) ); 135 136 notificationContainerElement = control.getNotificationsContainerElement(); 137 assert.equal( 1, notificationContainerElement.length ); 138 assert.ok( notificationContainerElement.is( '.customize-control-notifications-container' ) ); 139 assert.equal( 0, notificationContainerElement.find( '> ul > li' ).length ); 140 assert.equal( 'none', notificationContainerElement.css( 'display' ) ); 141 142 settingNotification = new wp.customize.Notification( 'setting_invalidity', 'Invalid setting' ); 143 controlOnlyNotification = new wp.customize.Notification( 'control_invalidity', 'Invalid control' ); 144 control.settings['default'].notifications.add( settingNotification.code, settingNotification ); 145 control.notifications.add( controlOnlyNotification.code, controlOnlyNotification ); 146 147 // Note that renderNotifications is being called manually here since rendering normally happens asynchronously. 148 control.renderNotifications(); 149 150 assert.equal( 2, notificationContainerElement.find( '> ul > li' ).length ); 151 assert.notEqual( 'none', notificationContainerElement.css( 'display' ) ); 152 assert.equal( 2, _.size( control.notifications._value ) ); 153 assert.equal( 1, _.size( control.settings['default'].notifications._value ) ); 154 155 control.notifications.remove( controlOnlyNotification.code ); 156 control.renderNotifications(); 157 assert.equal( 1, notificationContainerElement.find( '> ul > li' ).length ); 158 assert.notEqual( 'none', notificationContainerElement.css( 'display' ) ); 159 160 control.settings['default'].notifications.remove( settingNotification.code ); 161 control.renderNotifications(); 162 assert.equal( 0, notificationContainerElement.find( '> ul > li' ).length ); 163 assert.ok( notificationContainerElement.is( ':animated' ) ); // It is being slid down. 164 notificationContainerElement.stop().hide(); // Clean up. 165 166 doneEmbedded(); 167 } ); 101 168 } ); 102 169
Note: See TracChangeset
for help on using the changeset viewer.