Improving Setting Validation in the Customizer

In #34893 and the accompanying Customize Setting Validation feature plugin I’ve suggested improvements to the Customizer setting validation model. More can be read about the proposal in that ticket description and plugin readme, but the short of it is that settings in the Customizer generally undergo clean-up sanitization but lack a robust system for pass/fail validation. Here is a video demo depicting what I think validation should look like in the Customizer:

Normally the Customizer just sanitizes values by attempting to coerce them and clean them up into something that can be safely used (e.g. stripping tags). As for validation, and while I believe this is relatively unusual to encounter, you can also do strict validation of a setting by blocking it from being saved: this is done by returning null from WP_Customize_Setting::sanitize() (often via  WP_Customize_Setting::$sanitize_callback). This is the behavior for setting the background_color: if the value is not a valid hex code, it will not save. The problem here is that there is no feedback to the user that the save was blocked. If user tries to enter “blue” as a color instead of a hex code, they will not get informed that this is invalid: it will be as if they never tried to save any change at all. Incidentally, this is also the case for widgets, where the WP_Widget::update() normally just sanitizes the instance arrays, though it can return false to block an update, with likewise no indication that the update was blocked.

Additionally, in the current validation system, if you have made 10 changes in your Customizer session (transaction), but half of them get blocked from saving due to being invalid, then you have an only partially-saved Customizer state. This is a really bad experience for sites that make heavy use of the Customizer beyond just using it to tweak some colors.

So to summarize the suggested improvements to validation in the customizer:

  1. All modified settings should be validated up-front before any of them are saved.
  2. If any setting is invalid, the Customizer save request should be rejected: a save thus becomes transactional with all the settings left dirty to try saving again.
  3. Validation error messages should be displayed to the user, prompting them to fix their mistake and try again.

All of the above is implemented in the Customize Setting Validation feature plugin, and I’d love your feedback on it. One thing in particular I need feedback on is how specifically a setting should go about performing validation on the server. As noted above, the WP_Customize_Setting::sanitize() method can currently do both sanitization (returning a cleaned-up value) and validation (returning null to block saving if value is beyond repair). In the current feature plugin, I’m proposing:

  1. Continue to use the same sanitize methods/callbacks/filters.
  2. Introduce a new $strict param to indicate that validation is being performed.
  3. Allow these functions to fail validation by returning WP_Error instances with information about what failed in addition to null as they can today.

Update 2016-06-26: The following examples differ from what has been committed to core A follow-up post will detail the new APIs for validation.

How the changes would look for WP_Customize_Setting::sanitize():

/**
 * Sanitize an input.
 *
 * @since 3.4.0
 * @since 4.6.0 Added `$strict` param to indicate validation is being performed.
 *
 * @param string|array $value  The value to sanitize.
 * @param bool         $strict Whether validation is being performed.
 * @return string|array|null|WP_Error Null or WP_Error if an input isn't valid, otherwise the sanitized value.
 */
public function sanitize( $value, $strict = false )

And for the customize_sanitize_{$id} filter:

/**
 * Filter a Customize setting value.
 *
 * @since 3.4.0
 * @since 4.6.0 Added `$strict` param to indicate validation is being performed.
 *
 * @param mixed                $value  Value of the setting.
 * @param bool                 $strict Whether validation is being performed.
 * @param WP_Customize_Setting $this   WP_Customize_Setting instance.
 */
return apply_filters( "customize_sanitize_{$this->id}", $value, $this, $strict );

This should be nicely backwards-compatible, and allow for the sanitize/validate logic to be kept in the same function since they are very closely related. But that raises some questions:

  1. Should there be a separate WP_Customize_Setting::validate() method as well that by default calls WP_Customize_Setting::sanitize() with the $strict param set to true?
  2. Should validation only be applied when saving the setting while mere sanitization is required for previewing a value change?

On the second point here, my thought is that it may often be the desire for looser cleanup sanitization to be applied to a value during data entry. Consider a blob of text displayed on the page which forbids the use of markup. As I do data entry I may like to see what I am entering being displayed in the preview with the tags stripped (normal behavior today) whereas when I try saving I can be alerted that saving the value with markup is not allowed. This would provide the user with feedback as to why the markup is mysteriously not appearing in the preview. (As for providing this validation feedback while the user entering data, this should be done via JS, or the validation state be determined by performing validation in an update-transaction request for transactions.)

I’ll also note that REST API supports both sanitization and validation for request args, but these are handled in two separate callbacks. The way it works is that any registered sanitize_callbacks are applied first, followed by setting of default values, and then the validate_callbacks are run. See WP_REST_Server::dispatch(). This works fine, but I think we could have more flexibility if the sanitization and validation logic could be in the same method. Perhaps you want to run sanitization before validation (sanitize by stripping tags and validate to ensure the value is not empty), or be more strict by running validation before sanitization (reject any text that contains markup entirely).

So, for you all who develop settings in the Customizer, what are your thoughts on how validation should behave, and how validation should relate to sanitization in the context of the Customizer? What do you think of the API changes I’ve proposed?

#4-6, #customize