Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion js/customize-setting-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ wp.customize.settingValidation = (function( $, api ) {
validationMessageElement.slideDown( 'fast' );
}

control.container.toggleClass( 'customize-setting-invalid', 0 === validationMessages.length );
control.container.toggleClass( 'customize-setting-invalid', 0 !== validationMessages.length );
validationMessageElement.empty().append( $.trim(
self.validationMessageTemplate( { messages: validationMessages } )
) );
Expand Down Expand Up @@ -179,6 +179,31 @@ wp.customize.settingValidation = (function( $, api ) {
// @todo Also display response.message somewhere.
};

/**
* Apply saved sanitized values from server to settings in JS client, if different.
*
* @param {object} response
* @param {object} response.sanitized_setting_values
*/
self.afterSaveSuccess = function( response ) {
var wasSaved;
if ( ! response.sanitized_setting_values ) {
return;
}

wasSaved = api.state( 'saved' ).get();

_.each( response.sanitized_setting_values, function( value, id ) {
var setting = api( id );
if ( setting ) {
setting.set( value );
setting._dirty = false;
}
} );

api.state( 'saved' ).set( wasSaved );
};

api.bind( 'add', function( setting ) {
self.setupSettingForValidationMessage( setting );
} );
Expand All @@ -191,6 +216,9 @@ wp.customize.settingValidation = (function( $, api ) {
api.bind( 'error', function( response ) {
self.afterSaveFailure( response );
} );
api.bind( 'saved', function( response ) {
self.afterSaveSuccess( response );
} );

return self;

Expand Down
64 changes: 64 additions & 0 deletions php/class-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ class Plugin extends Plugin_Base {
*/
public $invalid_settings = array();

/**
* Sanitized values of saved settings.
*
* @var array
*/
public $saved_setting_values = array();

/**
* Class constructor.
*/
Expand All @@ -44,6 +51,11 @@ public function init() {
// Priority is set to 100 so that plugins can attach validation-sanitization filters at default priority of 10.
add_action( 'customize_validate_settings', array( $this, 'validate_settings' ), 100 );

// Priority set to 1000 in case a plugin dynamically adds a setting just in time.
add_action( 'customize_save', array( $this, '_add_actions_for_flagging_saved_settings' ), 1000 );

add_action( 'customize_save_after', array( $this, 'gather_saved_setting_values' ) );

add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ), 1 );
}

Expand Down Expand Up @@ -140,6 +152,55 @@ public function validate_settings( \WP_Customize_Manager $wp_customize ) {
wp_send_json_error( $response );
}

/**
* Keep track of which settings were actually saved.
*
* Note that the footwork with id_bases is needed because there is no
* action for customize_save_{$setting_id}.
*
* @access private
* @param \WP_Customize_Manager $wp_customize Customize manager.
* @action customize_save
*/
public function _add_actions_for_flagging_saved_settings( \WP_Customize_Manager $wp_customize ) {
$seen_id_bases = array();
foreach ( $wp_customize->settings() as $setting ) {
$id_data = $setting->id_data();
if ( ! isset( $seen_id_bases[ $id_data['base'] ] ) ) {
add_action( 'customize_save_' . $id_data['base'], array( $this, '_flag_saved_setting_value' ) );
$seen_id_bases[ $id_data['base'] ] = true;
}
}
}

/**
* Flag which settings were saved.
*
* @access private
* @param \WP_Customize_Setting $setting Saved setting.
* @see \WP_Customize_Setting::save()
* @action customize_save
*/
public function _flag_saved_setting_value( \WP_Customize_Setting $setting ) {
$this->saved_setting_values[ $setting->id ] = null;
}

/**
* Gather the saved setting values.
*
* @param \WP_Customize_Manager $wp_customize Customizer manager.
* @action customize_save_after
*/
public function gather_saved_setting_values( \WP_Customize_Manager $wp_customize ) {
$setting_ids = array_keys( $this->saved_setting_values );
foreach ( $setting_ids as $setting_id ) {
$setting = $wp_customize->get_setting( $setting_id );
if ( $setting ) {
$this->saved_setting_values[ $setting_id ] = $setting->js_value();
}
}
}

/**
* Export any invalid setting data to the Customizer JS client.
*
Expand All @@ -152,6 +213,9 @@ public function filter_customize_save_response( $response ) {
if ( ! empty( $this->invalid_settings ) ) {
$response['invalid_settings'] = $this->invalid_settings;
}
if ( ! empty( $this->saved_setting_values ) ) {
$response['sanitized_setting_values'] = $this->saved_setting_values;
}
return $response;
}

Expand Down
17 changes: 15 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ Core feature plugin for Customizer setting validation, error messaging, and tran
## Description ##

This feature plugin allows setting values to be validated and for any validation errors to block the Customizer from
saving any setting until all are valid. The functionality here will be proposed for inclusion in WordPress Core via
Trac [#34893](https://core.trac.wordpress.org/ticket/34893): Improve Customizer setting validation model.
saving any setting until all are valid. Additionally, once a successful save is performed on the server, any settings
that have resulting PHP-sanitized values which differ from the JS values will be updated on the client to match, while
retaining the non-dirty saved sate.

The functionality here will be proposed for inclusion in WordPress Core via Trac [#34893](https://core.trac.wordpress.org/ticket/34893):
Improve Customizer setting validation model.

See demo video “[Customize Validate Entitled Settings](https://gist.github.com/westonruter/1016332b18ee7946dec3)” plugin which forces the site title,
widget titles, and nav menu item labels to all be populated and to start with an upper-case letter:

[![Play video on YouTube](https://i1.ytimg.com/vi/ZNk6FhtS8TM/hqdefault.jpg)](https://www.youtube.com/watch?v=ZNk6FhtS8TM)

Settings in the Customizer rely on sanitization to ensure that only valid values get persisted to the database.
The sanitization in the Customizer generally allows values to be passed through to be persisted and does not enforce
Expand All @@ -38,11 +47,15 @@ is that some settings would get saved, whereas others would not, and the user wo
and which failed (again, since there is no standard mechanism for showing validation error message).
The Customizer state would only partially get persisted to the database. This isn't good.

Lastly, once the settings are successfully saved, if any of the PHP-sanitization differs in any way from the
JS-sanitization on the client, the difference in value will not be apparent in the Customizer controls.

So this plugin aims to solve both these problems by:

* Validating settings on server before save.
* Displaying validation error messages from server and from JS client.
* Performing transactional/atomic setting saving, rejecting all settings if one is invalid.
* Sync back the PHP-sanitized saved setting values to the JS client and ensure controls are populated with the actual persisted values.

Note that the transactional/atomic saving here in setting validation is not the same as the
[Customizer Transactions proposal](https://make.wordpress.org/core/2015/01/26/customizer-transactions-proposal/),
Expand Down
17 changes: 15 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ Core feature plugin for Customizer setting validation, error messaging, and tran
== Description ==

This feature plugin allows setting values to be validated and for any validation errors to block the Customizer from
saving any setting until all are valid. The functionality here will be proposed for inclusion in WordPress Core via
Trac [#34893](https://core.trac.wordpress.org/ticket/34893): Improve Customizer setting validation model.
saving any setting until all are valid. Additionally, once a successful save is performed on the server, any settings
that have resulting PHP-sanitized values which differ from the JS values will be updated on the client to match, while
retaining the non-dirty saved sate.

The functionality here will be proposed for inclusion in WordPress Core via Trac [#34893](https://core.trac.wordpress.org/ticket/34893):
Improve Customizer setting validation model.

See demo video “[Customize Validate Entitled Settings](https://gist.github.com/westonruter/1016332b18ee7946dec3)” plugin which forces the site title,
widget titles, and nav menu item labels to all be populated and to start with an upper-case letter:

[youtube https://youtu.be/ZNk6FhtS8TM]

Settings in the Customizer rely on sanitization to ensure that only valid values get persisted to the database.
The sanitization in the Customizer generally allows values to be passed through to be persisted and does not enforce
Expand All @@ -35,11 +44,15 @@ is that some settings would get saved, whereas others would not, and the user wo
and which failed (again, since there is no standard mechanism for showing validation error message).
The Customizer state would only partially get persisted to the database. This isn't good.

Lastly, once the settings are successfully saved, if any of the PHP-sanitization differs in any way from the
JS-sanitization on the client, the difference in value will not be apparent in the Customizer controls.

So this plugin aims to solve both these problems by:

* Validating settings on server before save.
* Displaying validation error messages from server and from JS client.
* Performing transactional/atomic setting saving, rejecting all settings if one is invalid.
* Sync back the PHP-sanitized saved setting values to the JS client and ensure controls are populated with the actual persisted values.

Note that the transactional/atomic saving here in setting validation is not the same as the
[Customizer Transactions proposal](https://make.wordpress.org/core/2015/01/26/customizer-transactions-proposal/),
Expand Down