<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[37476] trunk: Customize: Add setting validation model and control notifications to augment setting sanitization.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/37476">37476</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/37476","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>westonruter</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-05-20 21:09:40 +0000 (Fri, 20 May 2016)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Customize: Add setting validation model and control notifications to augment setting sanitization.
When a setting is invalid, not only will it be blocked from being saved but all other settings will be blocked as well. This ensures that Customizer saves aren't partial but are more transactional. User will be displayed the error in a notification so that they can fix and re-attempt saving.
PHP changes:
* Introduces `WP_Customize_Setting::validate()`, `WP_Customize_Setting::$validate_callback`, and the `customize_validate_{$setting_id}` filter.
* Introduces `WP_Customize_Manager::validate_setting_values()` to do validation (and sanitization) for the setting values supplied, returning a list of `WP_Error` instances for invalid settings.
* Attempting to save settings that are invalid will result in the save being blocked entirely, with the errors being sent in the `customize_save_response`. Modifies `WP_Customize_Manager::save()` to check all settings for validity issues prior to calling their `save` methods.
* Introduces `WP_Customize_Setting::json()` for parity with the other Customizer classes. This includes exporting of the `type`.
* Modifies `WP_Customize_Manager::post_value()` to apply `validate` after `sanitize`, and if validation fails, to return the `$default`.
* Introduces `customize_save_validation_before` action which fires right before the validation checks are made prior to saving.
JS changes:
* Introduces `wp.customize.Notification` in JS which to represent `WP_Error` instances returned from the server when setting validation fails.
* Introduces `wp.customize.Setting.prototype.notifications`.
* Introduces `wp.customize.Control.prototype.notifications`, which are synced with a control's settings' notifications.
* Introduces `wp.customize.Control.prototype.renderNotifications()` to re-render a control's notifications in its notification area. This is called automatically when the notifications collection changes.
* Introduces `wp.customize.settingConstructor`, allowing custom setting types to be used in the same way that custom controls, panels, and sections can be made.
* Injects a notification area into existing controls which is populated in response to the control's `notifications` collection changing. A custom control can customize the placement of the notification area by overriding the new `getNotificationsContainerElement` method.
* When a save fails due to setting invalidity, the invalidity errors will be added to the settings to then populate in the controls' notification areas, and the first such invalid control will be focused.
Props westonruter, celloexpressions, mrahmadawais.
See <a href="https://core.trac.wordpress.org/ticket/35210">#35210</a>.
See <a href="https://core.trac.wordpress.org/ticket/30937">#30937</a>.
Fixes <a href="https://core.trac.wordpress.org/ticket/34893">#34893</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadmincsscustomizecontrolscss">trunk/src/wp-admin/css/customize-controls.css</a></li>
<li><a href="#trunksrcwpadminjscustomizecontrolsjs">trunk/src/wp-admin/js/customize-controls.js</a></li>
<li><a href="#trunksrcwpadminjscustomizewidgetsjs">trunk/src/wp-admin/js/customize-widgets.js</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizemanagerphp">trunk/src/wp-includes/class-wp-customize-manager.php</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizesettingphp">trunk/src/wp-includes/class-wp-customize-setting.php</a></li>
<li><a href="#trunksrcwpincludesjscustomizebasejs">trunk/src/wp-includes/js/customize-base.js</a></li>
<li><a href="#trunktestsphpunittestscustomizemanagerphp">trunk/tests/phpunit/tests/customize/manager.php</a></li>
<li><a href="#trunktestsphpunittestscustomizesettingphp">trunk/tests/phpunit/tests/customize/setting.php</a></li>
<li><a href="#trunktestsqunitfixturescustomizesettingsjs">trunk/tests/qunit/fixtures/customize-settings.js</a></li>
<li><a href="#trunktestsqunitindexhtml">trunk/tests/qunit/index.html</a></li>
<li><a href="#trunktestsqunitwpadminjscustomizebasejs">trunk/tests/qunit/wp-admin/js/customize-base.js</a></li>
<li><a href="#trunktestsqunitwpadminjscustomizecontrolsjs">trunk/tests/qunit/wp-admin/js/customize-controls.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadmincsscustomizecontrolscss"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/css/customize-controls.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/css/customize-controls.css 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/src/wp-admin/css/customize-controls.css 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -494,7 +494,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> .customize-control input[type="search"],
</span><span class="cx" style="display: block; padding: 0 10px"> .customize-control input[type="tel"],
</span><span class="cx" style="display: block; padding: 0 10px"> .customize-control input[type="url"] {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- width: 98%;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ width: 100%;
</ins><span class="cx" style="display: block; padding: 0 10px"> line-height: 18px;
</span><span class="cx" style="display: block; padding: 0 10px"> margin: 0;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -622,6 +622,46 @@
</span><span class="cx" style="display: block; padding: 0 10px"> border-right: 1px solid #ddd;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Notifications
+ */
+
+#customize-controls .customize-control-notifications-container { /* Scoped to #customize-controls for specificity over notification styles in common.css. */
+ margin: 4px 0 8px 0;
+ padding: 0;
+ display: none;
+ cursor: default;
+}
+
+#customize-controls .customize-control-widget_form.has-error .widget .widget-top,
+.customize-control-nav_menu_item.has-error .menu-item-bar .menu-item-handle {
+ box-shadow: inset 0 0 0 2px #dc3232;
+ transition: .15s box-shadow linear;
+}
+
+.customize-control-notifications-container li.notice {
+ list-style: none;
+ margin: 0 0 6px 0;
+ padding: 4px 8px;
+}
+
+.customize-control-notifications-container li.notice:last-child {
+ margin-bottom: 0;
+}
+
+#customize-controls .customize-control-nav_menu_item .customize-control-notifications-container {
+ margin-top: 0;
+}
+
+#customize-controls .customize-control-widget_form .customize-control-notifications-container {
+ margin-top: 8px;
+}
+
+.customize-control-text.has-error input {
+ outline: 2px solid #dc3232;
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /* Style for custom settings */
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="trunksrcwpadminjscustomizecontrolsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/js/customize-controls.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/js/customize-controls.js 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/src/wp-admin/js/customize-controls.js 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -27,6 +27,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> this.id = id;
</span><span class="cx" style="display: block; padding: 0 10px"> this.transport = this.transport || 'refresh';
</span><span class="cx" style="display: block; padding: 0 10px"> this._dirty = options.dirty || false;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ this.notifications = new api.Values({ defaultConstructor: api.Notification });
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Whenever the setting's value changes, refresh the preview.
</span><span class="cx" style="display: block; padding: 0 10px"> this.bind( this.preview );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1478,6 +1479,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> control.priority = new api.Value();
</span><span class="cx" style="display: block; padding: 0 10px"> control.active = new api.Value();
</span><span class="cx" style="display: block; padding: 0 10px"> control.activeArgumentsQueue = [];
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ control.notifications = new api.Values({ defaultConstructor: api.Notification });
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> control.elements = [];
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1541,12 +1543,37 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> control.setting = control.settings['default'] || null;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ _.each( control.settings, function( setting ) {
+ setting.notifications.bind( 'add', function( settingNotification ) {
+ var controlNotification = new api.Notification( setting.id + ':' + settingNotification.code, settingNotification );
+ control.notifications.add( controlNotification.code, controlNotification );
+ } );
+ setting.notifications.bind( 'remove', function( settingNotification ) {
+ control.notifications.remove( setting.id + ':' + settingNotification.code );
+ } );
+ } );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> control.embed();
</span><span class="cx" style="display: block; padding: 0 10px"> }) );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // After the control is embedded on the page, invoke the "ready" method.
</span><span class="cx" style="display: block; padding: 0 10px"> control.deferred.embedded.done( function () {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+ * Note that this debounced/deferred rendering is needed for two reasons:
+ * 1) The 'remove' event is triggered just _before_ the notification is actually removed.
+ * 2) Improve performance when adding/removing multiple notifications at a time.
+ */
+ var debouncedRenderNotifications = _.debounce( function renderNotifications() {
+ control.renderNotifications();
+ } );
+ control.notifications.bind( 'add', function( notification ) {
+ wp.a11y.speak( notification.message, 'assertive' );
+ debouncedRenderNotifications();
+ } );
+ control.notifications.bind( 'remove', debouncedRenderNotifications );
+ control.renderNotifications();
+
</ins><span class="cx" style="display: block; padding: 0 10px"> control.ready();
</span><span class="cx" style="display: block; padding: 0 10px"> });
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1589,6 +1616,85 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ready: function() {},
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get the element inside of a control's container that contains the validation error message.
+ *
+ * Control subclasses may override this to return the proper container to render notifications into.
+ * Injects the notification container for existing controls that lack the necessary container,
+ * including special handling for nav menu items and widgets.
+ *
+ * @since 4.6.0
+ * @returns {jQuery} Setting validation message element.
+ * @this {wp.customize.Control}
+ */
+ getNotificationsContainerElement: function() {
+ var control = this, controlTitle, notificationsContainer;
+
+ notificationsContainer = control.container.find( '.customize-control-notifications-container:first' );
+ if ( notificationsContainer.length ) {
+ return notificationsContainer;
+ }
+
+ notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' );
+
+ if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) {
+ control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer );
+ } else if ( control.container.hasClass( 'customize-control-widget_form' ) ) {
+ control.container.find( '.widget-inside:first' ).prepend( notificationsContainer );
+ } else {
+ controlTitle = control.container.find( '.customize-control-title' );
+ if ( controlTitle.length ) {
+ controlTitle.after( notificationsContainer );
+ } else {
+ control.container.prepend( notificationsContainer );
+ }
+ }
+ return notificationsContainer;
+ },
+
+ /**
+ * Render notifications.
+ *
+ * Renders the `control.notifications` into the control's container.
+ * Control subclasses may override this method to do their own handling
+ * of rendering notifications.
+ *
+ * @since 4.6.0
+ * @this {wp.customize.Control}
+ */
+ renderNotifications: function() {
+ var control = this, container, notifications, hasError = false;
+ container = control.getNotificationsContainerElement();
+ if ( ! container || ! container.length ) {
+ return;
+ }
+ notifications = [];
+ control.notifications.each( function( notification ) {
+ notifications.push( notification );
+ if ( 'error' === notification.type ) {
+ hasError = true;
+ }
+ } );
+
+ if ( 0 === notifications.length ) {
+ container.stop().slideUp( 'fast' );
+ } else {
+ container.stop().slideDown( 'fast', null, function() {
+ $( this ).css( 'height', 'auto' );
+ } );
+ }
+
+ if ( ! control.notificationsTemplate ) {
+ control.notificationsTemplate = wp.template( 'customize-control-notifications' );
+ }
+
+ control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
+ control.container.toggleClass( 'has-error', hasError );
+ container.empty().append( $.trim(
+ control.notificationsTemplate( { notifications: notifications, altNotice: Boolean( control.altNotice ) } )
+ ) );
+ },
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Normal controls do not expand, so just expand its parent
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param {Object} [params]
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3223,6 +3329,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> });
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ api.settingConstructor = {};
</ins><span class="cx" style="display: block; padding: 0 10px"> api.controlConstructor = {
</span><span class="cx" style="display: block; padding: 0 10px"> color: api.ColorControl,
</span><span class="cx" style="display: block; padding: 0 10px"> media: api.MediaControl,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3323,6 +3430,62 @@
</span><span class="cx" style="display: block; padding: 0 10px"> };
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+ * Handle invalid_settings in an error response for the customize-save request.
+ *
+ * Add notifications to the settings and focus on the first control that has an invalid setting.
+ *
+ * @since 4.6.0
+ * @private
+ *
+ * @param {object} response
+ * @param {object} response.invalid_settings
+ * @returns {void}
+ */
+ _handleInvalidSettingsError: function( response ) {
+ var invalidControls = [], wasFocused = false;
+ if ( _.isEmpty( response.invalid_settings ) ) {
+ return;
+ }
+
+ // Find the controls that correspond to each invalid setting.
+ _.each( response.invalid_settings, function( notifications, settingId ) {
+ var setting = api( settingId );
+ if ( setting ) {
+ _.each( notifications, function( notificationParams, code ) {
+ var notification = new api.Notification( code, notificationParams );
+ setting.notifications.add( code, notification );
+ } );
+ }
+
+ api.control.each( function( control ) {
+ _.each( control.settings, function( controlSetting ) {
+ if ( controlSetting.id === settingId ) {
+ invalidControls.push( control );
+ }
+ } );
+ } );
+ } );
+
+ // Focus on the first control that is inside of an expanded section (one that is visible).
+ _( invalidControls ).find( function( control ) {
+ var isExpanded = control.section() && api.section.has( control.section() ) && api.section( control.section() ).expanded();
+ if ( isExpanded && control.expanded ) {
+ isExpanded = control.expanded();
+ }
+ if ( isExpanded ) {
+ control.focus();
+ wasFocused = true;
+ }
+ return wasFocused;
+ } );
+
+ // Focus on the first invalid control.
+ if ( ! wasFocused && invalidControls[0] ) {
+ invalidControls[0].focus();
+ }
+ },
+
</ins><span class="cx" style="display: block; padding: 0 10px"> save: function() {
</span><span class="cx" style="display: block; padding: 0 10px"> var self = this,
</span><span class="cx" style="display: block; padding: 0 10px"> processing = api.state( 'processing' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3349,6 +3512,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> api.trigger( 'save', request );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+ * Remove all setting error notifications prior to save, allowing
+ * server to respond with fresh validation error notifications.
+ */
+ api.each( function( setting ) {
+ setting.notifications.each( function( notification ) {
+ if ( 'error' === notification.type ) {
+ setting.notifications.remove( notification.code );
+ }
+ } );
+ } );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> request.always( function () {
</span><span class="cx" style="display: block; padding: 0 10px"> body.removeClass( 'saving' );
</span><span class="cx" style="display: block; padding: 0 10px"> saveBtn.prop( 'disabled', false );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3372,6 +3547,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> self.preview.iframe.show();
</span><span class="cx" style="display: block; padding: 0 10px"> } );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ self._handleInvalidSettingsError( response );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> api.trigger( 'error', response );
</span><span class="cx" style="display: block; padding: 0 10px"> } );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3424,11 +3602,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Create Settings
</span><span class="cx" style="display: block; padding: 0 10px"> $.each( api.settings.settings, function( id, data ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- api.create( id, id, data.value, {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ var constructor = api.settingConstructor[ data.type ] || api.Setting,
+ setting;
+
+ setting = new constructor( id, data.value, {
</ins><span class="cx" style="display: block; padding: 0 10px"> transport: data.transport,
</span><span class="cx" style="display: block; padding: 0 10px"> previewer: api.previewer,
</span><span class="cx" style="display: block; padding: 0 10px"> dirty: !! data.dirty
</span><span class="cx" style="display: block; padding: 0 10px"> } );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ api.add( id, setting );
</ins><span class="cx" style="display: block; padding: 0 10px"> });
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Create Panels
</span></span></pre></div>
<a id="trunksrcwpadminjscustomizewidgetsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/js/customize-widgets.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/js/customize-widgets.js 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/src/wp-admin/js/customize-widgets.js 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -430,6 +430,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> args = $.extend( {}, control.defaultExpandedArguments, args );
</span><span class="cx" style="display: block; padding: 0 10px"> control.onChangeExpanded( expanded, args );
</span><span class="cx" style="display: block; padding: 0 10px"> });
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ control.altNotice = true;
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> api.Control.prototype.initialize.call( control, id, options );
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-manager.php 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/src/wp-includes/class-wp-customize-manager.php 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -654,19 +654,31 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * Return the sanitized value for a given setting from the request's POST data.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 4.1.1 Introduced 'default' parameter.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.1.1 Introduced `$default` parameter.
+ * @since 4.6.0 Return `$default` when setting post value is invalid.
+ * @see WP_REST_Server::dispatch()
+ * @see WP_Rest_Request::sanitize_params()
+ * @see WP_Rest_Request::has_valid_params()
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
- * @param mixed $default value returned $setting has no post value (added in 4.2.0).
- * @return string|mixed $post_value Sanitized value or the $default provided
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object.
+ * @param mixed $default Value returned $setting has no post value (added in 4.2.0)
+ * or the post value is invalid (added in 4.6.0).
+ * @return string|mixed $post_value Sanitized value or the $default provided.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function post_value( $setting, $default = null ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $post_values = $this->unsanitized_post_values();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( array_key_exists( $setting->id, $post_values ) ) {
- return $setting->sanitize( $post_values[ $setting->id ] );
- } else {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! array_key_exists( $setting->id, $post_values ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> return $default;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $value = $setting->sanitize( $post_values[ $setting->id ] );
+ if ( is_null( $value ) || is_wp_error( $value ) ) {
+ return $default;
+ }
+ $valid = $setting->validate( $value );
+ if ( is_wp_error( $valid ) ) {
+ return $default;
+ }
+ return $value;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -970,6 +982,38 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Validate setting values.
+ *
+ * Sanitization is applied to the values before being passed for validation.
+ * Validation is skipped for unregistered settings or for values that are
+ * already null since they will be skipped anyway.
+ *
+ * @since 4.6.0
+ * @access public
+ * @see WP_REST_Request::has_valid_params()
+ *
+ * @param array $setting_values Mapping of setting IDs to values to sanitize and validate.
+ * @return array Empty array if all settings were valid. One or more instances of `WP_Error` if any were invalid.
+ */
+ public function validate_setting_values( $setting_values ) {
+ $validity_errors = array();
+ foreach ( $setting_values as $setting_id => $unsanitized_value ) {
+ $setting = $this->get_setting( $setting_id );
+ if ( ! $setting || is_null( $unsanitized_value ) ) {
+ continue;
+ }
+ $validity = $setting->validate( $setting->sanitize( $unsanitized_value ) );
+ if ( false === $validity || null === $validity ) {
+ $validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
+ }
+ if ( is_wp_error( $validity ) ) {
+ $validity_errors[ $setting_id ] = $validity;
+ }
+ }
+ return $validity_errors;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Switch the theme and trigger the save() method on each setting.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -984,6 +1028,42 @@
</span><span class="cx" style="display: block; padding: 0 10px"> wp_send_json_error( 'invalid_nonce' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+ * Fires before save validation happens.
+ *
+ * Plugins can add just-in-time `customize_validate_{$setting_id}` filters
+ * at this point to catch any settings registered after `customize_register`.
+ *
+ * @since 4.6.0
+ *
+ * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+ */
+ do_action( 'customize_save_validation_before', $this );
+
+ // Validate settings.
+ $validity_errors = $this->validate_setting_values( $this->unsanitized_post_values() );
+ $invalid_count = count( $validity_errors );
+ if ( $invalid_count > 0 ) {
+ $settings_errors = array();
+ foreach ( $validity_errors as $setting_id => $validity_error ) {
+ $settings_errors[ $setting_id ] = array();
+ foreach ( $validity_error->errors as $error_code => $error_messages ) {
+ $settings_errors[ $setting_id ][ $error_code ] = array(
+ 'message' => join( ' ', $error_messages ),
+ 'data' => $validity_error->get_error_data( $error_code ),
+ );
+ }
+ }
+ $response = array(
+ 'invalid_settings' => $settings_errors,
+ 'message' => sprintf( _n( 'There is %s invalid setting.', 'There are %s invalid settings.', $invalid_count ), number_format_i18n( $invalid_count ) ),
+ );
+
+ /** This filter is documented in wp-includes/class-wp-customize-manager.php */
+ $response = apply_filters( 'customize_save_response', $response, $this );
+ wp_send_json_error( $response );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Do we have to switch themes?
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! $this->is_theme_active() ) {
</span><span class="cx" style="display: block; padding: 0 10px"> // Temporarily stop previewing the theme to allow switch_themes()
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1403,6 +1483,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $control->print_template();
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ ?>
+ <script type="text/html" id="tmpl-customize-control-notifications">
+ <ul>
+ <# _.each( data.notifications, function( notification ) { #>
+ <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li>
+ <# } ); #>
+ </ul>
+ </script>
+ <?php
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1763,11 +1852,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> printf(
</span><span class="cx" style="display: block; padding: 0 10px"> "s[%s] = %s;\n",
</span><span class="cx" style="display: block; padding: 0 10px"> wp_json_encode( $setting->id ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- wp_json_encode( array(
- 'value' => $setting->js_value(),
- 'transport' => $setting->transport,
- 'dirty' => $setting->dirty,
- ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ wp_json_encode( $setting->json() )
</ins><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizesettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-customize-setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-customize-setting.php 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/src/wp-includes/class-wp-customize-setting.php 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -59,6 +59,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @var callback
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public $validate_callback = '';
</ins><span class="cx" style="display: block; padding: 0 10px"> public $sanitize_callback = '';
</span><span class="cx" style="display: block; padding: 0 10px"> public $sanitize_js_callback = '';
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -142,6 +143,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $this->validate_callback ) {
+ add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( $this->sanitize_callback ) {
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -464,14 +468,16 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * the value of the setting.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.6.0 Return the result of updating the value.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @return false|void False if cap check fails or value isn't set.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @return false|void False if cap check fails or value isn't set or is invalid.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> final public function save() {
</span><span class="cx" style="display: block; padding: 0 10px"> $value = $this->post_value();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( ! $this->check_capabilities() || ! isset( $value ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! $this->check_capabilities() || ! isset( $value ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> return false;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires when the WP_Customize_Setting::save() method is called.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -483,7 +489,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param WP_Customize_Setting $this WP_Customize_Setting instance.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- do_action( 'customize_save_' . $this->id_data[ 'base' ], $this );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ do_action( 'customize_save_' . $this->id_data['base'], $this );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $this->update( $value );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -494,7 +500,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param mixed $default A default value which is used as a fallback. Default is null.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @return mixed The default value on failure, otherwise the sanitized value.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @return mixed The default value on failure, otherwise the sanitized and validated value.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> final public function post_value( $default = null ) {
</span><span class="cx" style="display: block; padding: 0 10px"> return $this->manager->post_value( $this, $default );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -505,8 +511,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param string|array $value The value to sanitize.
- * @return string|array|null Null if an input isn't valid, otherwise the sanitized value.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param string|array $value The value to sanitize.
+ * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function sanitize( $value ) {
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -522,6 +528,45 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Validate an input.
+ *
+ * @since 4.6.0
+ * @access public
+ * @see WP_REST_Request::has_valid_params()
+ *
+ * @param mixed $value Value to validate.
+ * @return true|WP_Error
+ */
+ public function validate( $value ) {
+ if ( is_wp_error( $value ) ) {
+ return $value;
+ }
+ if ( is_null( $value ) ) {
+ return new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
+ }
+
+ $validity = new WP_Error();
+
+ /**
+ * Validate a Customize setting value.
+ *
+ * Plugins should amend the `$validity` object via its `WP_Error::add()` method.
+ *
+ * @since 4.6.0
+ *
+ * @param WP_Error $validity Filtered from `true` to `WP_Error` when invalid.
+ * @param mixed $value Value of the setting.
+ * @param WP_Customize_Setting $this WP_Customize_Setting instance.
+ */
+ $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this );
+
+ if ( is_wp_error( $validity ) && empty( $validity->errors ) ) {
+ $validity = true;
+ }
+ return $validity;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Get the root value for a setting, especially for multidimensional ones.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.4.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -700,6 +745,22 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get the data to export to the client via JSON.
+ *
+ * @since 4.6.0
+ *
+ * @return array Array of parameters passed to JavaScript.
+ */
+ public function json() {
+ return array(
+ 'value' => $this->js_value(),
+ 'transport' => $this->transport,
+ 'dirty' => $this->dirty,
+ 'type' => $this->type,
+ );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Validate user capabilities whether the theme supports the setting.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.4.0
</span></span></pre></div>
<a id="trunksrcwpincludesjscustomizebasejs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/js/customize-base.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/js/customize-base.js 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/src/wp-includes/js/customize-base.js 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -755,6 +755,28 @@
</span><span class="cx" style="display: block; padding: 0 10px"> // Add the Events mixin to api.Messenger.
</span><span class="cx" style="display: block; padding: 0 10px"> $.extend( api.Messenger.prototype, api.Events );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /**
+ * Notification.
+ *
+ * @class
+ * @augments wp.customize.Class
+ * @since 4.6.0
+ *
+ * @param {string} code The error code.
+ * @param {object} params Params.
+ * @param {string} params.message The error message.
+ * @param {string} [params.type=error] The notification type.
+ * @param {*} [params.data] Any additional data.
+ */
+ api.Notification = api.Class.extend({
+ initialize: function( code, params ) {
+ this.code = code;
+ this.message = params.message;
+ this.type = params.type || 'error';
+ this.data = params.data || null;
+ }
+ });
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // The main API object is also a collection of all customizer settings.
</span><span class="cx" style="display: block; padding: 0 10px"> api = $.extend( new api.Values(), api );
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizemanagerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/manager.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/manager.php 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/tests/phpunit/tests/customize/manager.php 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -126,6 +126,114 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Test the WP_Customize_Manager::post_value() method for a setting value that fails validation.
+ *
+ * @ticket 34893
+ */
+ function test_invalid_post_value() {
+ $default_value = 'foo_default';
+ $setting = $this->manager->add_setting( 'foo', array(
+ 'validate_callback' => array( $this, 'filter_customize_validate_foo' ),
+ 'sanitize_callback' => array( $this, 'filter_customize_sanitize_foo' ),
+ ) );
+ $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
+ $this->assertEquals( $default_value, $setting->post_value( $default_value ) );
+
+ $post_value = 'bar';
+ $this->manager->set_post_value( 'foo', $post_value );
+ $this->assertEquals( strtoupper( $post_value ), $this->manager->post_value( $setting, $default_value ) );
+ $this->assertEquals( strtoupper( $post_value ), $setting->post_value( $default_value ) );
+
+ $this->manager->set_post_value( 'foo', 'return_wp_error_in_sanitize' );
+ $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
+ $this->assertEquals( $default_value, $setting->post_value( $default_value ) );
+
+ $this->manager->set_post_value( 'foo', 'return_null_in_sanitize' );
+ $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
+ $this->assertEquals( $default_value, $setting->post_value( $default_value ) );
+
+ $post_value = '<script>evil</script>';
+ $this->manager->set_post_value( 'foo', $post_value );
+ $this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
+ $this->assertEquals( $default_value, $setting->post_value( $default_value ) );
+ }
+
+ /**
+ * Filter customize_validate callback.
+ *
+ * @param mixed $value Value.
+ * @return string|WP_Error
+ */
+ function filter_customize_sanitize_foo( $value ) {
+ if ( 'return_null_in_sanitize' === $value ) {
+ $value = null;
+ } elseif ( is_string( $value ) ) {
+ $value = strtoupper( $value );
+ if ( false !== stripos( $value, 'return_wp_error_in_sanitize' ) ) {
+ $value = new WP_Error( 'invalid_value_in_sanitize', __( 'Invalid value.' ), array( 'source' => 'filter_customize_sanitize_foo' ) );
+ }
+ }
+ return $value;
+ }
+
+ /**
+ * Filter customize_validate callback.
+ *
+ * @param WP_Error $validity Validity.
+ * @param mixed $value Value.
+ * @return WP_Error
+ */
+ function filter_customize_validate_foo( $validity, $value ) {
+ if ( false !== stripos( $value, '<script' ) ) {
+ $validity->add( 'invalid_value_in_validate', __( 'Invalid value.' ), array( 'source' => 'filter_customize_validate_foo' ) );
+ }
+ return $validity;
+ }
+
+ /**
+ * Test WP_Customize_Manager::validate_setting_values().
+ *
+ * @see WP_Customize_Manager::validate_setting_values()
+ */
+ function test_validate_setting_values() {
+ $default_value = 'foo_default';
+ $setting = $this->manager->add_setting( 'foo', array(
+ 'validate_callback' => array( $this, 'filter_customize_validate_foo' ),
+ 'sanitize_callback' => array( $this, 'filter_customize_sanitize_foo' ),
+ ) );
+
+ $post_value = 'bar';
+ $this->manager->set_post_value( 'foo', $post_value );
+ $this->assertEmpty( $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ) );
+
+ $this->manager->set_post_value( 'foo', 'return_wp_error_in_sanitize' );
+ $invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
+ $this->assertCount( 1, $invalid_settings );
+ $this->assertArrayHasKey( $setting->id, $invalid_settings );
+ $this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] );
+ $error = $invalid_settings[ $setting->id ];
+ $this->assertEquals( 'invalid_value_in_sanitize', $error->get_error_code() );
+ $this->assertEquals( array( 'source' => 'filter_customize_sanitize_foo' ), $error->get_error_data() );
+
+ $this->manager->set_post_value( 'foo', 'return_null_in_sanitize' );
+ $invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
+ $this->assertCount( 1, $invalid_settings );
+ $this->assertArrayHasKey( $setting->id, $invalid_settings );
+ $this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] );
+ $this->assertNull( $invalid_settings[ $setting->id ]->get_error_data() );
+
+ $post_value = '<script>evil</script>';
+ $this->manager->set_post_value( 'foo', $post_value );
+ $invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
+ $this->assertCount( 1, $invalid_settings );
+ $this->assertArrayHasKey( $setting->id, $invalid_settings );
+ $this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] );
+ $error = $invalid_settings[ $setting->id ];
+ $this->assertEquals( 'invalid_value_in_validate', $error->get_error_code() );
+ $this->assertEquals( array( 'source' => 'filter_customize_validate_foo' ), $error->get_error_data() );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Test WP_Customize_Manager::set_post_value().
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @see WP_Customize_Manager::set_post_value()
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -416,6 +524,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertContains( 'var _wpCustomizeSettings =', $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertContains( '"blogname"', $content );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertContains( '"type":"option"', $content );
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->assertContains( '_wpCustomizeSettings.controls', $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertContains( '_wpCustomizeSettings.settings', $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertContains( '</script>', $content );
</span></span></pre></div>
<a id="trunktestsphpunittestscustomizesettingphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/customize/setting.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/customize/setting.php 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/tests/phpunit/tests/customize/setting.php 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -42,6 +42,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( 'refresh', $setting->transport );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( '', $setting->sanitize_callback );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( '', $setting->sanitize_js_callback );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertFalse( has_filter( "customize_validate_{$setting->id}" ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->assertFalse( has_filter( "customize_sanitize_{$setting->id}" ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertFalse( has_filter( "customize_sanitize_js_{$setting->id}" ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( false, $setting->dirty );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -54,6 +55,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'theme_supports' => 'widgets',
</span><span class="cx" style="display: block; padding: 0 10px"> 'default' => 'barbar',
</span><span class="cx" style="display: block; padding: 0 10px"> 'transport' => 'postMessage',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'validate_callback' => create_function( '$value', 'return $value . ":validate_callback";' ),
</ins><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ),
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -62,6 +64,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( $args as $key => $value ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( $value, $setting->$key );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertEquals( 10, has_filter( "customize_validate_{$setting->id}", $args['validate_callback'] ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( 10, has_filter( "customize_sanitize_{$setting->id}", $args['sanitize_callback'] ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$setting->id}" ), $args['sanitize_js_callback'] );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -90,6 +93,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Run assertions on non-multidimensional standard settings.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *
+ * @see WP_Customize_Setting::value()
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function test_preview_standard_types_non_multidimensional() {
</span><span class="cx" style="display: block; padding: 0 10px"> $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -167,6 +172,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * Run assertions on multidimensional standard settings.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @see WP_Customize_Setting::preview()
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @see WP_Customize_Setting::value()
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function test_preview_standard_types_multidimensional() {
</span><span class="cx" style="display: block; padding: 0 10px"> $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -569,5 +575,70 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $autoload = $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $id_base ) );
</span><span class="cx" style="display: block; padding: 0 10px"> $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' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * Test js_value and json methods.
+ *
+ * @see WP_Customize_Setting::js_value()
+ * @see WP_Customize_Setting::json()
+ */
+ public function test_js_value() {
+ $default = "\x00";
+ $args = array(
+ 'type' => 'binary',
+ 'default' => $default,
+ 'transport' => 'postMessage',
+ 'dirty' => true,
+ 'sanitize_js_callback' => create_function( '$value', 'return base64_encode( $value );' ),
+ );
+ $setting = new WP_Customize_Setting( $this->manager, 'name', $args );
+
+ $this->assertEquals( $default, $setting->value() );
+ $this->assertEquals( base64_encode( $default ), $setting->js_value() );
+
+ $exported = $setting->json();
+ $this->assertArrayHasKey( 'type', $exported );
+ $this->assertArrayHasKey( 'value', $exported );
+ $this->assertArrayHasKey( 'transport', $exported );
+ $this->assertArrayHasKey( 'dirty', $exported );
+ $this->assertEquals( $setting->js_value(), $exported['value'] );
+ $this->assertEquals( $args['type'], $setting->type );
+ $this->assertEquals( $args['transport'], $setting->transport );
+ $this->assertEquals( $args['dirty'], $setting->dirty );
+ }
+
+ /**
+ * Test validate.
+ *
+ * @see WP_Customize_Setting::validate()
+ */
+ public function test_validate() {
+ $setting = new WP_Customize_Setting( $this->manager, 'name', array(
+ 'type' => 'key',
+ 'validate_callback' => array( $this, 'filter_validate_for_test_validate' ),
+ ) );
+ $validity = $setting->validate( 'BAD!' );
+ $this->assertInstanceOf( 'WP_Error', $validity );
+ $this->assertEquals( 'invalid_key', $validity->get_error_code() );
+ }
+
+ /**
+ * Validate callback.
+ *
+ * @see Tests_WP_Customize_Setting::test_validate()
+ *
+ * @param WP_Error $validity Validity.
+ * @param string $value Value.
+ *
+ * @return WP_Error
+ */
+ public function filter_validate_for_test_validate( $validity, $value ) {
+ $this->assertInstanceOf( 'WP_Error', $validity );
+ $this->assertInternalType( 'string', $value );
+ if ( sanitize_key( $value ) !== $value ) {
+ $validity->add( 'invalid_key', 'Invalid key' );
+ }
+ return $validity;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunktestsqunitfixturescustomizesettingsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/fixtures/customize-settings.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/fixtures/customize-settings.js 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/tests/qunit/fixtures/customize-settings.js 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -112,6 +112,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'fixture-setting': {
</span><span class="cx" style="display: block; padding: 0 10px"> 'transport': 'postMessage',
</span><span class="cx" style="display: block; padding: 0 10px"> 'value': 'Lorem Ipsum'
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ },
+ 'fixture-setting-abbr': {
+ 'transport': 'postMessage',
+ 'value': 'NASA',
+ 'type': 'abbreviation'
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="cx" style="display: block; padding: 0 10px"> 'theme': {
</span></span></pre></div>
<a id="trunktestsqunitindexhtml"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/index.html</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/index.html 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/tests/qunit/index.html 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11,6 +11,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <script src="../../src/wp-includes/js/wp-backbone.js"></script>
</span><span class="cx" style="display: block; padding: 0 10px"> <script src="../../src/wp-includes/js/zxcvbn.min.js"></script>
</span><span class="cx" style="display: block; padding: 0 10px"> <script src="../../src/wp-includes/js/wp-util.js"></script>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <script src="../../src/wp-includes/js/wp-a11y.js"></script>
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> <!-- QUnit -->
</span><span class="cx" style="display: block; padding: 0 10px"> <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -126,6 +127,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <# } #>
</span><span class="cx" style="display: block; padding: 0 10px"> </li>
</span><span class="cx" style="display: block; padding: 0 10px"> </script>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <script type="text/html" id="tmpl-customize-control-notifications">
+ <ul>
+ <# _.each( data.notifications, function( notification ) { #>
+ <li data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li>
+ <# } ); #>
+ </ul>
+ </script>
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> <!-- Templates for Customizer Menus -->
</span><span class="cx" style="display: block; padding: 0 10px"> <script type="text/html" id="tmpl-customize-control-nav_menu-content">
</span></span></pre></div>
<a id="trunktestsqunitwpadminjscustomizebasejs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/wp-admin/js/customize-base.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/wp-admin/js/customize-base.js 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/tests/qunit/wp-admin/js/customize-base.js 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,4 +1,4 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/* global wp */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global wp, test, ok, equal, module */
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> jQuery( function( $ ) {
</span><span class="cx" style="display: block; padding: 0 10px"> var FooSuperClass, BarSubClass, foo, bar, ConstructorTestClass, newConstructor, constructorTest, $mockElement, mockString,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -158,4 +158,26 @@
</span><span class="cx" style="display: block; padding: 0 10px"> firstValueInstance.set( 'newValue' );
</span><span class="cx" style="display: block; padding: 0 10px"> ok( wasCallbackFired );
</span><span class="cx" style="display: block; padding: 0 10px"> });
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ module( 'Customize Base: Notification' );
+ test( 'Notification object exists and has expected properties', function ( assert ) {
+ var notification = new wp.customize.Notification( 'mycode', {
+ 'message': 'Hello World',
+ 'type': 'update',
+ 'data': { 'foo': 'bar' }
+ } );
+
+ assert.equal( 'mycode', notification.code );
+ assert.equal( 'Hello World', notification.message );
+ assert.equal( 'update', notification.type );
+ assert.deepEqual( { 'foo': 'bar' }, notification.data );
+
+ notification = new wp.customize.Notification( 'mycode2', {
+ 'message': 'Hello Space'
+ } );
+ assert.equal( 'mycode2', notification.code );
+ assert.equal( 'Hello Space', notification.message );
+ assert.equal( 'error', notification.type );
+ assert.equal( null, notification.data );
+ } );
</ins><span class="cx" style="display: block; padding: 0 10px"> });
</span></span></pre></div>
<a id="trunktestsqunitwpadminjscustomizecontrolsjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/wp-admin/js/customize-controls.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/wp-admin/js/customize-controls.js 2016-05-20 20:56:54 UTC (rev 37475)
+++ trunk/tests/qunit/wp-admin/js/customize-controls.js 2016-05-20 21:09:40 UTC (rev 37476)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,5 +1,11 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-/* global wp */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global wp, test, ok, equal, module */
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+wp.customize.settingConstructor.abbreviation = wp.customize.Setting.extend({
+ validate: function( value ) {
+ return value.toUpperCase();
+ }
+});
+
</ins><span class="cx" style="display: block; padding: 0 10px"> jQuery( window ).load( function (){
</span><span class="cx" style="display: block; padding: 0 10px"> 'use strict';
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -85,6 +91,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> test( 'Setting has fixture value', function () {
</span><span class="cx" style="display: block; padding: 0 10px"> equal( wp.customize( 'fixture-setting' )(), 'Lorem Ipsum' );
</span><span class="cx" style="display: block; padding: 0 10px"> } );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ test( 'Setting has notifications', function () {
+ var setting = wp.customize( 'fixture-setting' );
+ ok( setting.notifications.extended( wp.customize.Values ) );
+ equal( wp.customize.Notification, setting.notifications.prototype.constructor.defaultConstructor );
+ } );
+ test( 'Setting constructor object exists', function( assert ) {
+ assert.ok( _.isObject( wp.customize.settingConstructor ) );
+ } );
+ test( 'Custom setting constructor is used', function( assert ) {
+ var setting = wp.customize( 'fixture-setting-abbr' );
+ assert.ok( setting.extended( wp.customize.settingConstructor.abbreviation ) );
+ setting.set( 'usa' );
+ assert.equal( 'USA', setting.get() );
+ } );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> module( 'Customizer Control in Fixture' );
</span><span class="cx" style="display: block; padding: 0 10px"> test( 'Control exists', function () {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -99,7 +119,54 @@
</span><span class="cx" style="display: block; padding: 0 10px"> var control = wp.customize.control( 'fixture-control' );
</span><span class="cx" style="display: block; padding: 0 10px"> equal( control.section(), 'fixture-section' );
</span><span class="cx" style="display: block; padding: 0 10px"> } );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ test( 'Control has notifications', function ( assert ) {
+ var control = wp.customize.control( 'fixture-control' ), settingNotification, controlOnlyNotification, doneEmbedded;
+ assert.ok( control.notifications.extended( wp.customize.Values ) );
+ assert.equal( wp.customize.Notification, control.notifications.prototype.constructor.defaultConstructor );
+ assert.ok( _.isFunction( control.getNotificationsContainerElement ) );
+ assert.ok( _.isFunction( control.renderNotifications ) );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ doneEmbedded = assert.async();
+ control.deferred.embedded.done( function() {
+ var notificationContainerElement;
+
+ assert.equal( 0, _.size( control.notifications._value ) );
+ assert.equal( 0, _.size( control.settings['default'].notifications._value ) );
+
+ notificationContainerElement = control.getNotificationsContainerElement();
+ assert.equal( 1, notificationContainerElement.length );
+ assert.ok( notificationContainerElement.is( '.customize-control-notifications-container' ) );
+ assert.equal( 0, notificationContainerElement.find( '> ul > li' ).length );
+ assert.equal( 'none', notificationContainerElement.css( 'display' ) );
+
+ settingNotification = new wp.customize.Notification( 'setting_invalidity', 'Invalid setting' );
+ controlOnlyNotification = new wp.customize.Notification( 'control_invalidity', 'Invalid control' );
+ control.settings['default'].notifications.add( settingNotification.code, settingNotification );
+ control.notifications.add( controlOnlyNotification.code, controlOnlyNotification );
+
+ // Note that renderNotifications is being called manually here since rendering normally happens asynchronously.
+ control.renderNotifications();
+
+ assert.equal( 2, notificationContainerElement.find( '> ul > li' ).length );
+ assert.notEqual( 'none', notificationContainerElement.css( 'display' ) );
+ assert.equal( 2, _.size( control.notifications._value ) );
+ assert.equal( 1, _.size( control.settings['default'].notifications._value ) );
+
+ control.notifications.remove( controlOnlyNotification.code );
+ control.renderNotifications();
+ assert.equal( 1, notificationContainerElement.find( '> ul > li' ).length );
+ assert.notEqual( 'none', notificationContainerElement.css( 'display' ) );
+
+ control.settings['default'].notifications.remove( settingNotification.code );
+ control.renderNotifications();
+ assert.equal( 0, notificationContainerElement.find( '> ul > li' ).length );
+ assert.ok( notificationContainerElement.is( ':animated' ) ); // It is being slid down.
+ notificationContainerElement.stop().hide(); // Clean up.
+
+ doneEmbedded();
+ } );
+ } );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> module( 'Customizer control without associated settings' );
</span><span class="cx" style="display: block; padding: 0 10px"> test( 'Control can be created without settings', function() {
</span><span class="cx" style="display: block; padding: 0 10px"> var control = new wp.customize.Control( 'settingless', {
</span></span></pre>
</div>
</div>
</body>
</html>