Make WordPress Core


Ignore:
Timestamp:
05/20/2016 09:09:40 PM (9 years ago)
Author:
westonruter
Message:

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 #35210.
See #30937.
Fixes #34893.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/customize/setting.php

    r37350 r37476  
    4343        $this->assertEquals( '', $setting->sanitize_callback );
    4444        $this->assertEquals( '', $setting->sanitize_js_callback );
     45        $this->assertFalse( has_filter( "customize_validate_{$setting->id}" ) );
    4546        $this->assertFalse( has_filter( "customize_sanitize_{$setting->id}" ) );
    4647        $this->assertFalse( has_filter( "customize_sanitize_js_{$setting->id}" ) );
     
    5556            'default' => 'barbar',
    5657            'transport' => 'postMessage',
     58            'validate_callback' => create_function( '$value', 'return $value . ":validate_callback";' ),
    5759            'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ),
    5860            'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ),
     
    6365            $this->assertEquals( $value, $setting->$key );
    6466        }
     67        $this->assertEquals( 10, has_filter( "customize_validate_{$setting->id}", $args['validate_callback'] ) );
    6568        $this->assertEquals( 10, has_filter( "customize_sanitize_{$setting->id}", $args['sanitize_callback'] ) );
    6669        $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$setting->id}" ), $args['sanitize_js_callback'] );
     
    9194    /**
    9295     * Run assertions on non-multidimensional standard settings.
     96     *
     97     * @see WP_Customize_Setting::value()
    9398     */
    9499    function test_preview_standard_types_non_multidimensional() {
     
    168173     *
    169174     * @see WP_Customize_Setting::preview()
     175     * @see WP_Customize_Setting::value()
    170176     */
    171177    function test_preview_standard_types_multidimensional() {
     
    570576        $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' );
    571577    }
     578
     579    /**
     580     * Test js_value and json methods.
     581     *
     582     * @see WP_Customize_Setting::js_value()
     583     * @see WP_Customize_Setting::json()
     584     */
     585    public function test_js_value() {
     586        $default = "\x00";
     587        $args = array(
     588            'type' => 'binary',
     589            'default' => $default,
     590            'transport' => 'postMessage',
     591            'dirty' => true,
     592            'sanitize_js_callback' => create_function( '$value', 'return base64_encode( $value );' ),
     593        );
     594        $setting = new WP_Customize_Setting( $this->manager, 'name', $args );
     595
     596        $this->assertEquals( $default, $setting->value() );
     597        $this->assertEquals( base64_encode( $default ), $setting->js_value() );
     598
     599        $exported = $setting->json();
     600        $this->assertArrayHasKey( 'type', $exported );
     601        $this->assertArrayHasKey( 'value', $exported );
     602        $this->assertArrayHasKey( 'transport', $exported );
     603        $this->assertArrayHasKey( 'dirty', $exported );
     604        $this->assertEquals( $setting->js_value(), $exported['value'] );
     605        $this->assertEquals( $args['type'], $setting->type );
     606        $this->assertEquals( $args['transport'], $setting->transport );
     607        $this->assertEquals( $args['dirty'], $setting->dirty );
     608    }
     609
     610    /**
     611     * Test validate.
     612     *
     613     * @see WP_Customize_Setting::validate()
     614     */
     615    public function test_validate() {
     616        $setting = new WP_Customize_Setting( $this->manager, 'name', array(
     617            'type' => 'key',
     618            'validate_callback' => array( $this, 'filter_validate_for_test_validate' ),
     619        ) );
     620        $validity = $setting->validate( 'BAD!' );
     621        $this->assertInstanceOf( 'WP_Error', $validity );
     622        $this->assertEquals( 'invalid_key', $validity->get_error_code() );
     623    }
     624
     625    /**
     626     * Validate callback.
     627     *
     628     * @see Tests_WP_Customize_Setting::test_validate()
     629     *
     630     * @param WP_Error $validity Validity.
     631     * @param string   $value    Value.
     632     *
     633     * @return WP_Error
     634     */
     635    public function filter_validate_for_test_validate( $validity, $value ) {
     636        $this->assertInstanceOf( 'WP_Error', $validity );
     637        $this->assertInternalType( 'string', $value );
     638        if ( sanitize_key( $value ) !== $value ) {
     639            $validity->add( 'invalid_key', 'Invalid key' );
     640        }
     641        return $validity;
     642    }
    572643}
    573644
Note: See TracChangeset for help on using the changeset viewer.