[wp-trac] [WordPress Trac] #43208: Separate setting validation from sanitization

WordPress Trac noreply at wordpress.org
Thu Aug 30 13:25:17 UTC 2018


#43208: Separate setting validation from sanitization
-------------------------------------------------+-------------------------
 Reporter:  flixos90                             |       Owner:  (none)
     Type:  enhancement                          |      Status:  new
 Priority:  normal                               |   Milestone:  Awaiting
                                                 |  Review
Component:  Options, Meta APIs                   |     Version:
 Severity:  normal                               |  Resolution:
 Keywords:  2nd-opinion has-patch needs-unit-    |     Focuses:
  tests                                          |
-------------------------------------------------+-------------------------

Comment (by flixos90):

 [attachment:"43208.6.diff"] ensures the patch applies cleanly again. It
 also changes the `validate_option_{$option}` filter into an action, in
 accordance which how a similar new hook was handled in #40364. An action
 is the proper way to amend a passed-in object. They are passed by
 reference, so filtering them through can only have negative side-effects
 with invalid filter values.

 I also wrote a small test plugin for that, which registers a few different
 options with validation adds an interface to them via an options page, a
 Customizer section, and the REST API settings controller. You can easily
 test the new functionality using it.

 {{{
 <?php
 /*
 Plugin Name: Option Validation Tests
 Plugin URI: https://make.wordpress.org/
 Description: Tests for option validation.
 Version: 1.0
 Author: WordPress Core Contributors
 Author URI: https://make.wordpress.org/
 */

 function test_register_settings() {
         register_setting(
                 'option_validation_tests',
                 'ovt_integer',
                 array(
                         'type'              => 'integer',
                         'description'       => __( 'An integer option.' ),
                         'sanitize_callback' => 'absint',
                         'validate_callback' => function( $errors, $value )
 {
                                 test_validate_setting( $errors, $value,
 array( 'min' => 3 ) );
                         },
                         'show_in_rest'      => true,
                 )
         );

         register_setting(
                 'option_validation_tests',
                 'ovt_email',
                 array(
                         'type'              => 'string',
                         'description'       => __( 'An email option.' ),
                         'sanitize_callback' => 'sanitize_email',
                         'validate_callback' => function( $errors, $value )
 {
                                 test_validate_setting( $errors, $value,
 array( 'email' => true ) );
                         },
                         'show_in_rest'      => true,
                 )
         );

         register_setting(
                 'option_validation_tests',
                 'ovt_bool',
                 array(
                         'type'              => 'boolean',
                         'description'       => __( 'A boolean option.' ),
                         'sanitize_callback' => 'boolval',
                         'validate_callback' => function( $errors, $value )
 {
                                 test_validate_setting( $errors, $value,
 array( 'checked' => true ) );
                         },
                         'show_in_rest'      => true,
                 )
         );

         register_setting(
                 'option_validation_tests',
                 'ovt_enum',
                 array(
                         'type'              => 'string',
                         'description'       => __( 'An enum option.' ),
                         'sanitize_callback' => 'strip_tags',
                         'validate_callback' => function( $errors, $value )
 {
                                 test_validate_setting( $errors, $value,
 array( 'enum' => array( 'value1', 'value2', 'value3' ) ) );
                         },
                         'show_in_rest'      => true,
                 )
         );
 }
 add_action( 'init', 'test_register_settings' );

 function test_validate_setting( $errors, $value, $args = array() ) {
         if ( isset( $args['enum'] ) && ! in_array( $value, $args['enum'],
 true ) ) {
                 $errors->add( 'value_not_in_enum', sprintf( __( 'The value
 %s is not one of the valid choices.' ), esc_html( $value ) ) );
         }

         if ( isset( $args['min'] ) && (float) $value < (float)
 $args['min'] ) {
                 $errors->add( 'value_too_small', sprintf( __( 'The value
 %1$s is smaller than the allowed minimum %2$s.' ), number_format_i18n(
 (float) $value ), number_format_i18n( (float) $args['min'] ) ) );
         }

         if ( isset( $args['max'] ) && (float) $value > (float)
 $args['max'] ) {
                 $errors->add( 'value_too_great', sprintf( __( 'The value
 %1$s is greater than the allowed maximum %2$s.' ), number_format_i18n(
 (float) $value ), number_format_i18n( (float) $args['max'] ) ) );
         }

         if ( isset( $args['email'] ) && $args['email'] && ! is_email(
 $value ) ) {
                 $errors->add( 'value_not_an_email', sprintf( __( 'The
 value %s is not a valid email address.' ), esc_html( $value ) ) );
         }

         if ( isset( $args['checked'] ) && $args['checked'] && ! $value ) {
                 $errors->add( 'value_unchecked', __( 'You must check the
 checkbox.' ) );
         }
 }

 function test_add_settings_fields() {
         add_settings_section( 'general', __( 'General' ), null,
 'option_validation_tests' );

         add_settings_section( 'other', __( 'Other' ), null,
 'option_validation_tests' );

         add_settings_field(
                 'ovt_integer',
                 __( 'Integer Option' ),
                 function() {
                         ?>
                         <input type="number" name="ovt_integer"
 value="<?php echo esc_attr( get_option( 'ovt_integer' ) ); ?>" />
                         <p class="description"><?php esc_html_e( 'Enter a
 value greater than or equal to 3.' ); ?></p>
                         <?php
                 },
                 'option_validation_tests',
                 'general',
                 array( 'label_for' => 'ovt_integer' )
         );

         add_settings_field(
                 'ovt_email',
                 __( 'Email Option' ),
                 function() {
                         ?>
                         <input type="email" name="ovt_email" value="<?php
 echo esc_attr( get_option( 'ovt_email' ) ); ?>" />
                         <p class="description"><?php esc_html_e( 'Enter an
 email address.' ); ?></p>
                         <?php
                 },
                 'option_validation_tests',
                 'general',
                 array( 'label_for' => 'ovt_email' )
         );

         add_settings_field(
                 'ovt_bool',
                 __( 'Boolean Option' ),
                 function() {
                         ?>
                         <input type="checkbox" name="ovt_bool" <?php
 checked( get_option( 'ovt_bool' ) ); ?> />
                         <p class="description"><?php esc_html_e( 'Check
 this checkbox.' ); ?></p>
                         <?php
                 },
                 'option_validation_tests',
                 'general',
                 array( 'label_for' => 'ovt_bool' )
         );

         add_settings_field(
                 'ovt_enum',
                 __( 'Enum Option' ),
                 function() {
                         ?>
                         <input type="text" name="ovt_enum" value="<?php
 echo esc_attr( get_option( 'ovt_enum' ) ); ?>" />
                         <p class="description"><?php esc_html_e( 'Enter
 either value1, value2, or value3.' ); ?></p>
                         <?php
                 },
                 'option_validation_tests',
                 'other',
                 array( 'label_for' => 'ovt_enum' )
         );
 }
 add_action( 'admin_init', 'test_add_settings_fields' );

 function test_register_settings_page() {
         add_options_page( __( 'Option Validation Test' ), __( 'Option
 Validation Test' ), 'manage_options', 'option_validation_tests',
 'test_render_settings_page' );
 }
 add_action( 'admin_menu', 'test_register_settings_page' );

 function test_render_settings_page() {
         ?>
         <div class="wrap">
                 <h1><?php _e( 'Option Validation Test' ); ?></h1>

                 <form action="options.php" method="post"
 novalidate="novalidate">
                         <?php settings_fields( 'option_validation_tests'
 ); ?>
                         <?php do_settings_sections(
 'option_validation_tests' ); ?>
                         <?php submit_button(); ?>
                 </form>
         </div>
         <?php
 }

 function test_customize_register( $wp_customize ) {
         $wp_customize->add_setting( 'ovt_integer', array( 'type' =>
 'option' ) );
         $wp_customize->add_setting( 'ovt_email', array( 'type' => 'option'
 ) );
         $wp_customize->add_setting( 'ovt_bool', array( 'type' => 'option'
 ) );
         $wp_customize->add_setting( 'ovt_enum', array( 'type' => 'option'
 ) );

         $wp_customize->add_section(
                 'option_validation_tests',
                 array(
                         'title'      => __( 'Option Validation Test' ),
                         'capability' => 'manage_options',
                 )
         );

         $wp_customize->add_control(
                 'ovt_integer',
                 array(
                         'section'     => 'option_validation_tests',
                         'type'        => 'number',
                         'label'       => __( 'Integer Option' ),
                         'description' => __( 'Enter a value greater than
 or equal to 3.' ),
                 )
         );

         $wp_customize->add_control(
                 'ovt_email',
                 array(
                         'section'     => 'option_validation_tests',
                         'type'        => 'email',
                         'label'       => __( 'Email Option' ),
                         'description' => __( 'Enter an email address.' ),
                 )
         );

         $wp_customize->add_control(
                 'ovt_bool',
                 array(
                         'section'     => 'option_validation_tests',
                         'type'        => 'checkbox',
                         'label'       => __( 'Boolean Option' ),
                         'description' => __( 'Check this checkbox.' ),
                 )
         );

         $wp_customize->add_control(
                 'ovt_enum',
                 array(
                         'section'     => 'option_validation_tests',
                         'type'        => 'text',
                         'label'       => __( 'Enum Option' ),
                         'description' => __( 'Enter either value1, value2,
 or value3.' ),
                 )
         );
 }
 add_action( 'customize_register', 'test_customize_register' );

 }}}

 Still looking for feedback on this, I consider it a valuable enhancement
 that could make plugin authors' lives a lot easier.

 It also opens up possibilities for further improvements: For example,
 `$wp_customize->add_setting()` calls could automatically happen for
 options that are registered because providing those manually in such a
 case is often redundant. If someone registers a control with an option
 identifier for an option that is registered and no
 `$wp_customize->add_setting()` call is manually made, those could be
 "auto-filled".

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/43208#comment:7>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list