WordPress.org

Make WordPress Core

Ticket #30988: 30988.4.diff

File 30988.4.diff, 27.3 KB (added by westonruter, 6 years ago)

Additional changes: https://github.com/xwp/wordpress-develop/compare/2da22194...2fb9fd4

  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index c9dc52d..1ec56f5 100644
    final class WP_Customize_Manager { 
    6363        protected $registered_control_types = array();
    6464
    6565        /**
    66          * $_POST values for Customize Settings.
     66         * Unsanitized values for Customize Settings parsed from $_POST['customized'].
    6767         *
    68          * @var array
     68         * @var array|false
    6969         */
    7070        private $_post_values;
    7171
    final class WP_Customize_Manager { 
    7575         * @since 3.4.0
    7676         */
    7777        public function __construct() {
    78                 require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
    79                 require( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
    80                 require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
    81                 require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
    82                 require( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
     78                require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
     79                require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
     80                require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
     81                require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
     82                require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
    8383
    8484                $this->widgets = new WP_Customize_Widgets( $this );
    8585
    final class WP_Customize_Manager { 
    399399        }
    400400
    401401        /**
    402          * Decode the $_POST['customized'] values for a specific Customize Setting.
     402         * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
     403         * settings for subsequent post_value() lookups.
    403404         *
    404          * @since 3.4.0
     405         * @since 4.1.1
    405406         *
    406          * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
    407          * @return string $post_value Sanitized value
     407         * @return array
    408408         */
    409         public function post_value( $setting ) {
     409        private function unsanitized_post_values() {
    410410                if ( ! isset( $this->_post_values ) ) {
    411                         if ( isset( $_POST['customized'] ) )
     411                        if ( isset( $_POST['customized'] ) ) {
    412412                                $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
    413                         else
     413                        }
     414                        if ( empty( $this->_post_values ) ) { // if not isset or of JSON error
    414415                                $this->_post_values = false;
     416                        }
     417                }
     418                if ( empty( $this->_post_values ) ) {
     419                        return array();
     420                } else {
     421                        return $this->_post_values;
    415422                }
     423        }
    416424
    417                 if ( isset( $this->_post_values[ $setting->id ] ) )
    418                         return $setting->sanitize( $this->_post_values[ $setting->id ] );
     425        /**
     426         * Return the sanitized value for a given setting from the request's POST data.
     427         *
     428         * @since 3.4.0
     429         *
     430         * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
     431         * @param mixed $default value returned $setting has no post value (added in 4.2.0).
     432         * @return string|mixed $post_value Sanitized value or the $default provided
     433         */
     434        public function post_value( $setting, $default = null ) {
     435                $post_values = $this->unsanitized_post_values();
     436                if ( array_key_exists( $setting->id, $post_values ) ) {
     437                        return $setting->sanitize( $post_values[ $setting->id ] );
     438                } else {
     439                        return $default;
     440                }
    419441        }
    420442
    421443        /**
  • src/wp-includes/class-wp-customize-setting.php

    diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php
    index 7a7be45..adfea11 100644
    class WP_Customize_Setting { 
    100100                        add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
    101101        }
    102102
     103        protected $_original_value;
     104
    103105        /**
    104106         * Handle previewing the setting.
    105107         *
    106108         * @since 3.4.0
    107109         */
    108110        public function preview() {
     111
     112                if ( ! isset( $this->_original_value ) ) {
     113                        $this->_original_value = $this->value();
     114                }
     115
    109116                switch( $this->type ) {
    110117                        case 'theme_mod' :
    111118                                add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
    class WP_Customize_Setting { 
    156163         * @return mixed New or old value.
    157164         */
    158165        public function _preview_filter( $original ) {
    159                 return $this->multidimensional_replace( $original, $this->id_data[ 'keys' ], $this->post_value() );
     166                $undefined = new stdClass(); // symbol hack
     167                $post_value = $this->manager->post_value( $this, $undefined );
     168                if ( $undefined === $post_value ) {
     169                        $value = $this->_original_value;
     170                } else {
     171                        $value = $post_value;
     172                }
     173                $replaced = $this->multidimensional_replace( $original, $this->id_data['keys'], $value );
     174                return $replaced;
    160175        }
    161176
    162177        /**
    class WP_Customize_Setting { 
    422437                        $node = &$node[ $key ];
    423438                }
    424439
    425                 if ( $create && ! isset( $node[ $last ] ) )
    426                         $node[ $last ] = array();
     440                if ( $create ) {
     441                        if ( ! is_array( $node ) ) {
     442                                // account for an array overriding a string or object value
     443                                $node = array();
     444                        }
     445                        if ( ! isset( $node[ $last ] ) ) {
     446                                $node[ $last ] = array();
     447                        }
     448                }
    427449
    428450                if ( ! isset( $node[ $last ] ) )
    429451                        return;
  • new file tests/phpunit/tests/customize/manager.php

    diff --git tests/phpunit/tests/customize/manager.php tests/phpunit/tests/customize/manager.php
    new file mode 100644
    index 0000000..d8f1f26
    - +  
     1<?php
     2
     3/**
     4 * Tests for the WP_Customize_Manager class.
     5 *
     6 * @group customize
     7 */
     8class Tests_WP_Customize_Manager extends WP_UnitTestCase {
     9
     10        function setUp() {
     11                parent::setUp();
     12                require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
     13                $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok
     14                $this->manager = $GLOBALS['wp_customize'];
     15                $this->undefined = new stdClass();
     16        }
     17
     18        function tearDown() {
     19                parent::tearDown();
     20                $this->manager = null;
     21                unset( $GLOBALS['wp_customize'] );
     22        }
     23
     24        /**
     25         * Instantiate class, set global $wp_customize, and return instance.
     26         *
     27         * @return WP_Customize_Manager
     28         */
     29        function instantiate() {
     30                $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok
     31                return $GLOBALS['wp_customize'];
     32        }
     33
     34        /**
     35         * Test the WP_Customize_Manager::post_value() method
     36         *
     37         * @ticket 30936
     38         */
     39        function test_post_value() {
     40                $posted_settings = array(
     41                        'foo' => 'OOF',
     42                );
     43                $_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) );
     44
     45                $manager = $this->instantiate();
     46
     47                $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) ); // @todo this should return the setting instance
     48                $foo_setting = $manager->get_setting( 'foo' );
     49                $this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->value(), 'Expected non-previewed setting to return default when value() method called.' );
     50                $this->assertEquals( $posted_settings['foo'], $manager->post_value( $foo_setting, 'post_value_foo_default' ), 'Expected post_value($foo_setting) to return value supplied in $_POST[customized][foo]' );
     51
     52                $manager->add_setting( 'bar', array( 'default' => 'bar_default' ) );
     53                $bar_setting = $manager->get_setting( 'bar' );
     54                $this->assertEquals( 'post_value_bar_default', $manager->post_value( $bar_setting, 'post_value_bar_default' ), 'Expected post_value($bar_setting, $default) to return $default since no value supplied in $_POST[customized][bar]' );
     55        }
     56
     57}
     58
  • new file tests/phpunit/tests/customize/setting.php

    diff --git tests/phpunit/tests/customize/setting.php tests/phpunit/tests/customize/setting.php
    new file mode 100644
    index 0000000..625335f
    - +  
     1<?php
     2
     3/**
     4 * Tests for the WP_Customize_Setting class.
     5 *
     6 * @group customize
     7 */
     8class Tests_WP_Customize_Setting extends WP_UnitTestCase {
     9
     10        /**
     11         * @var WP_Customize_Manager
     12         */
     13        protected $manager;
     14
     15        /**
     16         * @var stdClass an instance which serves as a symbol to do identity checks with
     17         */
     18        public $undefined;
     19
     20        function setUp() {
     21                parent::setUp();
     22                require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
     23                $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok
     24                $this->manager = $GLOBALS['wp_customize'];
     25                $this->undefined = new stdClass();
     26        }
     27
     28        function tearDown() {
     29                parent::tearDown();
     30                $this->manager = null;
     31                unset( $GLOBALS['wp_customize'] );
     32        }
     33
     34        function test_construct() {
     35                $foo = new WP_Customize_Setting( $this->manager, 'foo' );
     36                $this->assertEquals( $this->manager, $foo->manager );
     37                $this->assertEquals( 'foo', $foo->id );
     38                $this->assertEquals( 'theme_mod', $foo->type );
     39                $this->assertEquals( 'edit_theme_options', $foo->capability );
     40                $this->assertEquals( '', $foo->theme_supports );
     41                $this->assertEquals( '', $foo->default );
     42                $this->assertEquals( 'refresh', $foo->transport );
     43                $this->assertEquals( '', $foo->sanitize_callback );
     44                $this->assertEquals( '', $foo->sanitize_js_callback );
     45                $this->assertFalse( has_filter( "customize_sanitize_{$foo->id}" ) );
     46                $this->assertFalse( has_filter( "customize_sanitize_js_{$foo->id}" ) );
     47
     48                $args = array(
     49                        'type' => 'option',
     50                        'capability' => 'edit_posts',
     51                        'theme_supports' => 'widgets',
     52                        'default' => 'barbar',
     53                        'transport' => 'postMessage',
     54                        'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ),
     55                        'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ),
     56                );
     57                $bar = new WP_Customize_Setting( $this->manager, 'bar', $args );
     58                $this->assertEquals( 'bar', $bar->id );
     59                foreach ( $args as $key => $value ) {
     60                        $this->assertEquals( $value, $bar->$key );
     61                }
     62                $this->assertEquals( 10, has_filter( "customize_sanitize_{$bar->id}", $args['sanitize_callback'] ) );
     63                $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$bar->id}" ), $args['sanitize_js_callback'] );
     64        }
     65
     66        public $post_data_overrides = array(
     67                'unset_option_overridden' => 'unset_option_post_override_value',
     68                'unset_theme_mod_overridden' => 'unset_theme_mod_post_override_value',
     69                'set_option_overridden' => 'set_option_post_override_value',
     70                'set_theme_mod_overridden' => 'set_theme_mod_post_override_value',
     71                'unset_option_multi_overridden[foo]' => 'unset_option_multi_overridden[foo]_post_override_value',
     72                'unset_theme_mod_multi_overridden[foo]' => 'unset_theme_mod_multi_overridden[foo]_post_override_value',
     73                'set_option_multi_overridden[foo]' => 'set_option_multi_overridden[foo]_post_override_value',
     74                'set_theme_mod_multi_overridden[foo]' => 'set_theme_mod_multi_overridden[foo]_post_override_value',
     75        );
     76
     77        public $standard_type_configs = array(
     78                'option' => array(
     79                        'getter' => 'get_option',
     80                        'setter' => 'update_option',
     81                ),
     82                'theme_mod' => array(
     83                        'getter' => 'get_theme_mod',
     84                        'setter' => 'set_theme_mod',
     85                ),
     86        );
     87
     88        /**
     89         * Run assertions on non-multidimensional standard settings
     90         */
     91        function test_preview_standard_types_non_multidimensional() {
     92
     93                // @todo this is hacky. The manager should provide a mechanism to override the post_values
     94                $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) );
     95
     96                // Try non-multidimensional settings
     97                foreach ( $this->standard_type_configs as $type => $type_options ) {
     98                        // Non-multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen)
     99                        $name = "unset_{$type}_without_post_value";
     100                        $default = "default_value_{$name}";
     101                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     102                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) );
     103                        $this->assertEquals( $default, $setting->value() );
     104                        $setting->preview();
     105                        $this->assertEquals( $default, call_user_func( $type_options['getter'], $name, $this->undefined ), sprintf( 'Expected %s(%s) to return setting default: %s.', $type_options['getter'], $name, $default ) );
     106                        $this->assertEquals( $default, $setting->value() );
     107
     108                        // Non-multidimensional: See what effect the preview has on an extant setting (default value should not be seen)
     109                        $name = "set_{$type}_without_post_value";
     110                        $default = "default_value_{$name}";
     111                        $initial_value = "initial_value_{$name}";
     112                        call_user_func( $type_options['setter'], $name, $initial_value );
     113                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     114                        $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) );
     115                        $this->assertEquals( $initial_value, $setting->value() );
     116                        $setting->preview();
     117                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
     118                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     119                        $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) );
     120                        $this->assertEquals( $initial_value, $setting->value() );
     121
     122                        // @todo What if we call the setter after preview() is called? If no post_value, should the new set value be stored? If that happens, then the following 3 assertions should be inverted
     123                        $overridden_value = "overridden_value_$name";
     124                        call_user_func( $type_options['setter'], $name, $overridden_value );
     125                        $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) );
     126                        $this->assertEquals( $initial_value, $setting->value() );
     127                        $this->assertNotEquals( $overridden_value, $setting->value() );
     128
     129                        // Non-multidimensional: Test unset setting being overridden by a post value
     130                        $name = "unset_{$type}_overridden";
     131                        $default = "default_value_{$name}";
     132                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     133                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) );
     134                        $this->assertEquals( $default, $setting->value() );
     135                        $setting->preview(); // activate post_data
     136                        $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) );
     137                        $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() );
     138
     139                        // Non-multidimensional: Test set setting being overridden by a post value
     140                        $name = "set_{$type}_overridden";
     141                        $default = "default_value_{$name}";
     142                        $initial_value = "initial_value_{$name}";
     143                        call_user_func( $type_options['setter'], $name, $initial_value );
     144                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     145                        $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name, $this->undefined ) );
     146                        $this->assertEquals( $initial_value, $setting->value() );
     147                        $setting->preview(); // activate post_data
     148                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
     149                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     150                        $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) );
     151                        $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() );
     152                }
     153        }
     154
     155        /**
     156         * Run assertions on multidimensional standard settings
     157         */
     158        function test_preview_standard_types_multidimensional() {
     159                // @todo this is hacky. The manager should provide a mechanism to override the post_values
     160                $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) );
     161
     162                foreach ( $this->standard_type_configs as $type => $type_options ) {
     163                        // Multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen)
     164                        $base_name = "unset_{$type}_multi";
     165                        $name = $base_name . '[foo]';
     166                        $default = "default_value_{$name}";
     167                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     168                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) );
     169                        $this->assertEquals( $default, $setting->value() );
     170                        $setting->preview();
     171                        $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined );
     172                        $this->assertArrayHasKey( 'foo', $base_value );
     173                        $this->assertEquals( $default, $base_value['foo'] );
     174
     175                        // Multidimensional: See what effect the preview has on an extant setting (default value should not be seen)
     176                        $base_name = "set_{$type}_multi";
     177                        $name = $base_name . '[foo]';
     178                        $default = "default_value_{$name}";
     179                        $initial_value = "initial_value_{$name}";
     180                        $base_initial_value = array( 'foo' => $initial_value, 'bar' => 'persisted' );
     181                        call_user_func( $type_options['setter'], $base_name, $base_initial_value );
     182                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     183                        $base_value = call_user_func( $type_options['getter'], $base_name, array() );
     184                        $this->assertEquals( $initial_value, $base_value['foo'] );
     185                        $this->assertEquals( $initial_value, $setting->value() );
     186                        $setting->preview();
     187                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
     188                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     189                        $base_value = call_user_func( $type_options['getter'], $base_name, array() );
     190                        $this->assertEquals( $initial_value, $base_value['foo'] );
     191                        $this->assertEquals( $initial_value, $setting->value() );
     192
     193                        // Multidimensional: Test unset setting being overridden by a post value
     194                        $base_name = "unset_{$type}_multi_overridden";
     195                        $name = $base_name . '[foo]';
     196                        $default = "default_value_{$name}";
     197                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     198                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) );
     199                        $this->assertEquals( $default, $setting->value() );
     200                        $setting->preview();
     201                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
     202                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     203                        $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined );
     204                        $this->assertArrayHasKey( 'foo', $base_value );
     205                        $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] );
     206
     207                        // Multidimemsional: Test set setting being overridden by a post value
     208                        $base_name = "set_{$type}_multi_overridden";
     209                        $name = $base_name . '[foo]';
     210                        $default = "default_value_{$name}";
     211                        $initial_value = "initial_value_{$name}";
     212                        $base_initial_value = array( 'foo' => $initial_value, 'bar' => 'persisted' );
     213                        call_user_func( $type_options['setter'], $base_name, $base_initial_value );
     214                        $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     215                        $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined );
     216                        $this->arrayHasKey( 'foo', $base_value );
     217                        $this->arrayHasKey( 'bar', $base_value );
     218                        $this->assertEquals( $base_initial_value['foo'], $base_value['foo'] );
     219                        $this->assertEquals( $base_initial_value['bar'], call_user_func( $type_options['getter'], $base_name, $this->undefined )['bar'] );
     220                        $this->assertEquals( $initial_value, $setting->value() );
     221                        $setting->preview();
     222                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
     223                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     224                        $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined );
     225                        $this->assertArrayHasKey( 'foo', $base_value );
     226                        $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] );
     227                        $this->arrayHasKey( 'bar', call_user_func( $type_options['getter'], $base_name, $this->undefined ) );
     228                        $this->assertEquals( $base_initial_value['bar'], call_user_func( $type_options['getter'], $base_name, $this->undefined )['bar'] );
     229                }
     230        }
     231
     232        /**
     233         * @var array storage for custom types that
     234         */
     235        protected $custom_type_data_saved;
     236
     237        protected $custom_type_data_previewed;
     238
     239        function custom_type_getter( $name, $default = null ) {
     240                if ( did_action( "customize_preview_{$name}" ) && array_key_exists( $name, $this->custom_type_data_previewed ) ) {
     241                        $value = $this->custom_type_data_previewed[ $name ];
     242                } else if ( array_key_exists( $name, $this->custom_type_data_saved ) ) {
     243                        $value = $this->custom_type_data_saved[ $name ];
     244                } else {
     245                        $value = $default;
     246                }
     247                return $value;
     248        }
     249
     250        function custom_type_setter( $name, $value ) {
     251                $this->custom_type_data_saved[ $name ] = $value;
     252        }
     253
     254        function custom_type_value_filter( $default ) {
     255                $name = preg_replace( '/^customize_value_/', '', current_filter() );
     256                return $this->custom_type_getter( $name, $default );
     257        }
     258
     259        /**
     260         * @var WP_Customize_Setting $setting
     261         */
     262        function custom_type_preview( $setting ) {
     263                $previewed_value = $setting->post_value( $this->undefined );
     264                if ( $this->undefined !== $previewed_value ) {
     265                        $this->custom_type_data_previewed[ $setting->id ] = $previewed_value;
     266                }
     267        }
     268
     269        function test_preview_custom_type() {
     270                $type = 'custom_type';
     271                $post_data_overrides = array(
     272                        "unset_{$type}_with_post_value" => "unset_{$type}_without_post_value",
     273                        "set_{$type}_with_post_value" => "set_{$type}_without_post_value",
     274                );
     275                $_POST['customized'] = wp_slash( wp_json_encode( $post_data_overrides ) );
     276
     277                $this->custom_type_data_saved = array();
     278                $this->custom_type_data_previewed = array();
     279
     280                add_action( "customize_preview_{$type}", array( $this, custom_type_preview ) );
     281
     282                // Custom type not existing and no post value override
     283                $name = "unset_{$type}_without_post_value";
     284                $default = "default_value_{$name}";
     285                $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     286                // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need
     287
     288                add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) );
     289                $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) );
     290                $this->assertEquals( $default, $setting->value() );
     291                $setting->preview();
     292                $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
     293                $this->assertEquals( 1, did_action( "customize_preview_{$setting->type}" ) );
     294                $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); // Note: for a non-custom type this is $default
     295                $this->assertEquals( $default, $setting->value() ); // should be same as above
     296
     297                // Custom type existing and no post value override
     298                $name = "set_{$type}_without_post_value";
     299                $default = "default_value_{$name}";
     300                $initial_value = "initial_value_{$name}";
     301                $this->custom_type_setter( $name, $initial_value );
     302                $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     303                // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need
     304                add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) );
     305                $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) );
     306                $this->assertEquals( $initial_value, $setting->value() );
     307                $setting->preview();
     308                $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
     309                $this->assertEquals( 2, did_action( "customize_preview_{$setting->type}" ) );
     310                $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); // should be same as above
     311                $this->assertEquals( $initial_value, $setting->value() ); // should be same as above
     312
     313                // Custom type not existing and with a post value override
     314                $name = "unset_{$type}_with_post_value";
     315                $default = "default_value_{$name}";
     316                $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     317                // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need
     318                add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) );
     319                $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) );
     320                $this->assertEquals( $default, $setting->value() );
     321                $setting->preview();
     322                $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
     323                $this->assertEquals( 3, did_action( "customize_preview_{$setting->type}" ) );
     324                $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) );
     325                $this->assertEquals( $post_data_overrides[ $name ], $setting->value() );
     326
     327                // Custom type not existing and with a post value override
     328                $name = "set_{$type}_with_post_value";
     329                $default = "default_value_{$name}";
     330                $initial_value = "initial_value_{$name}";
     331                $this->custom_type_setter( $name, $initial_value );
     332                $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     333                // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need
     334                add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) );
     335                $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) );
     336                $this->assertEquals( $initial_value, $setting->value() );
     337                $setting->preview();
     338                $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
     339                $this->assertEquals( 4, did_action( "customize_preview_{$setting->type}" ) );
     340                $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) );
     341                $this->assertEquals( $post_data_overrides[ $name ], $setting->value() );
     342
     343                unset( $this->custom_type_data_previewed, $this->custom_type_data_saved );
     344        }
     345
     346        /**
     347         * Test specific fix for setting's default value not applying on preview window
     348         *
     349         * @ticket 30988
     350         */
     351        function test_non_posted_setting_applying_default_value_in_preview() {
     352                $type = 'option';
     353                $name = 'unset_option_without_post_value';
     354                $default = "default_value_{$name}";
     355                $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) );
     356                $this->assertEquals( $this->undefined, get_option( $name, $this->undefined ) );
     357                $this->assertEquals( $default, $setting->value() );
     358                $setting->preview();
     359                $this->assertEquals( $default, get_option( $name, $this->undefined ), sprintf( 'Expected get_option(%s) to return setting default: %s.', $name, $default ) );
     360                $this->assertEquals( $default, $setting->value() );
     361        }
     362
     363        // @todo function test_save() {
     364        // @todo test do_action( 'customize_save_' . $this->id_data[ 'base' ], $this );
     365        // @todo test_post_value()
     366        // @todo test_sanitize( $value )
     367        // @todo apply_filters( "customize_sanitize_{$this->id}", $value, $this );
     368        // @todo function update( $value )
     369        // @todo test_value()
     370        // @todo test customize_value_{$name} filter
     371        // @todo test_js_value()
     372        // @todo test apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
     373        // @todo test_check_capabilities() {
     374
     375        // @todo final protected function multidimensional( &$root, $keys, $create = false )
     376        // @todo final protected function multidimensional_replace( $root, $keys, $value )
     377        // @todo final protected function multidimensional_get( $root, $keys, $default = null ) {
     378        // @todo final protected function multidimensional_isset( $root, $keys )
     379}
     380