From 8bc9ad756db46582b9e060af1a634588dcc2f43e Mon Sep 17 00:00:00 2001 From: "Dominik Schilling (ocean90)" Date: Tue, 3 Feb 2015 10:14:28 +0000 Subject: [PATCH] Ensure that `WP_Customize_Setting::value()` returns default value for setting if not dirty. There was regression introduced by #28580 where only changed (dirty) settings now are POST'ed to the Customizer preview. * Allow WP_Customize_Manager::post_value() to accept a second $default argument. * Introduce WP_Customize_Manager::unsanitized_post_values() for accessing previously-private member variable _post_values. * Do require_once instead of require for Customizer classes. * Add unit tests for WP_Customize_Manager and WP_Customize_Setting. props westonruter. fixes #30988. git-svn-id: https://develop.svn.wordpress.org/trunk@31329 602fd350-edb4-49c9-b593-d223f7449a82 --- .../class-wp-customize-manager.php | 58 ++- .../class-wp-customize-setting.php | 27 +- tests/phpunit/tests/customize/manager.php | 75 ++++ tests/phpunit/tests/customize/setting.php | 364 ++++++++++++++++++ 4 files changed, 503 insertions(+), 21 deletions(-) create mode 100644 tests/phpunit/tests/customize/manager.php create mode 100644 tests/phpunit/tests/customize/setting.php diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index b695271d61..4947d274b6 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -63,9 +63,9 @@ final class WP_Customize_Manager { protected $registered_control_types = array(); /** - * $_POST values for Customize Settings. + * Unsanitized values for Customize Settings parsed from $_POST['customized']. * - * @var array + * @var array|false */ private $_post_values; @@ -75,11 +75,11 @@ final class WP_Customize_Manager { * @since 3.4.0 */ public function __construct() { - require( ABSPATH . WPINC . '/class-wp-customize-setting.php' ); - require( ABSPATH . WPINC . '/class-wp-customize-panel.php' ); - require( ABSPATH . WPINC . '/class-wp-customize-section.php' ); - require( ABSPATH . WPINC . '/class-wp-customize-control.php' ); - require( ABSPATH . WPINC . '/class-wp-customize-widgets.php' ); + require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' ); + require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' ); + require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' ); + require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' ); + require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' ); $this->widgets = new WP_Customize_Widgets( $this ); @@ -399,23 +399,45 @@ final class WP_Customize_Manager { } /** - * Decode the $_POST['customized'] values for a specific Customize Setting. + * Parse the incoming $_POST['customized'] JSON data and store the unsanitized + * settings for subsequent post_value() lookups. + * + * @since 4.1.1 + * + * @return array + */ + public function unsanitized_post_values() { + if ( ! isset( $this->_post_values ) ) { + if ( isset( $_POST['customized'] ) ) { + $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true ); + } + if ( empty( $this->_post_values ) ) { // if not isset or of JSON error + $this->_post_values = false; + } + } + if ( empty( $this->_post_values ) ) { + return array(); + } else { + return $this->_post_values; + } + } + + /** + * Return the sanitized value for a given setting from the request's POST data. * * @since 3.4.0 * * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object - * @return string $post_value Sanitized value + * @param mixed $default value returned $setting has no post value (added in 4.2.0). + * @return string|mixed $post_value Sanitized value or the $default provided */ - public function post_value( $setting ) { - if ( ! isset( $this->_post_values ) ) { - if ( isset( $_POST['customized'] ) ) - $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true ); - else - $this->_post_values = false; + public function post_value( $setting, $default = null ) { + $post_values = $this->unsanitized_post_values(); + if ( array_key_exists( $setting->id, $post_values ) ) { + return $setting->sanitize( $post_values[ $setting->id ] ); + } else { + return $default; } - - if ( isset( $this->_post_values[ $setting->id ] ) ) - return $setting->sanitize( $this->_post_values[ $setting->id ] ); } /** diff --git a/src/wp-includes/class-wp-customize-setting.php b/src/wp-includes/class-wp-customize-setting.php index 7a7be45efa..6cbaf3db62 100644 --- a/src/wp-includes/class-wp-customize-setting.php +++ b/src/wp-includes/class-wp-customize-setting.php @@ -100,12 +100,18 @@ class WP_Customize_Setting { add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); } + protected $_original_value; + /** * Handle previewing the setting. * * @since 3.4.0 */ public function preview() { + if ( ! isset( $this->_original_value ) ) { + $this->_original_value = $this->value(); + } + switch( $this->type ) { case 'theme_mod' : add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); @@ -156,7 +162,15 @@ class WP_Customize_Setting { * @return mixed New or old value. */ public function _preview_filter( $original ) { - return $this->multidimensional_replace( $original, $this->id_data[ 'keys' ], $this->post_value() ); + $undefined = new stdClass(); // symbol hack + $post_value = $this->manager->post_value( $this, $undefined ); + if ( $undefined === $post_value ) { + $value = $this->_original_value; + } else { + $value = $post_value; + } + + return $this->multidimensional_replace( $original, $this->id_data['keys'], $value ); } /** @@ -422,8 +436,15 @@ class WP_Customize_Setting { $node = &$node[ $key ]; } - if ( $create && ! isset( $node[ $last ] ) ) - $node[ $last ] = array(); + if ( $create ) { + if ( ! is_array( $node ) ) { + // account for an array overriding a string or object value + $node = array(); + } + if ( ! isset( $node[ $last ] ) ) { + $node[ $last ] = array(); + } + } if ( ! isset( $node[ $last ] ) ) return; diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php new file mode 100644 index 0000000000..41d7b78faa --- /dev/null +++ b/tests/phpunit/tests/customize/manager.php @@ -0,0 +1,75 @@ +manager = $GLOBALS['wp_customize']; + $this->undefined = new stdClass(); + } + + function tearDown() { + parent::tearDown(); + $this->manager = null; + unset( $GLOBALS['wp_customize'] ); + } + + /** + * Instantiate class, set global $wp_customize, and return instance. + * + * @return WP_Customize_Manager + */ + function instantiate() { + $GLOBALS['wp_customize'] = new WP_Customize_Manager(); + return $GLOBALS['wp_customize']; + } + + /** + * Test WP_Customize_Manager::unsanitized_post_values() + * + * @ticket 30988 + */ + function test_unsanitized_post_values() { + $manager = $this->instantiate(); + + $customized = array( + 'foo' => 'bar', + 'baz[quux]' => 123, + ); + $_POST['customized'] = wp_slash( wp_json_encode( $customized ) ); + $post_values = $manager->unsanitized_post_values(); + $this->assertEquals( $customized, $post_values ); + } + + /** + * Test the WP_Customize_Manager::post_value() method + * + * @ticket 30988 + */ + function test_post_value() { + $posted_settings = array( + 'foo' => 'OOF', + ); + $_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) ); + + $manager = $this->instantiate(); + + $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) ); + $foo_setting = $manager->get_setting( 'foo' ); + $this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->value(), 'Expected non-previewed setting to return default when value() method called.' ); + $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]' ); + + $manager->add_setting( 'bar', array( 'default' => 'bar_default' ) ); + $bar_setting = $manager->get_setting( 'bar' ); + $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]' ); + } + +} + diff --git a/tests/phpunit/tests/customize/setting.php b/tests/phpunit/tests/customize/setting.php new file mode 100644 index 0000000000..effaa813e6 --- /dev/null +++ b/tests/phpunit/tests/customize/setting.php @@ -0,0 +1,364 @@ +manager = $GLOBALS['wp_customize']; + $this->undefined = new stdClass(); + } + + function tearDown() { + parent::tearDown(); + $this->manager = null; + unset( $GLOBALS['wp_customize'] ); + } + + function test_constructor_without_args() { + $setting = new WP_Customize_Setting( $this->manager, 'foo' ); + $this->assertEquals( $this->manager, $setting->manager ); + $this->assertEquals( 'foo', $setting->id ); + $this->assertEquals( 'theme_mod', $setting->type ); + $this->assertEquals( 'edit_theme_options', $setting->capability ); + $this->assertEquals( '', $setting->theme_supports ); + $this->assertEquals( '', $setting->default ); + $this->assertEquals( 'refresh', $setting->transport ); + $this->assertEquals( '', $setting->sanitize_callback ); + $this->assertEquals( '', $setting->sanitize_js_callback ); + $this->assertFalse( has_filter( "customize_sanitize_{$setting->id}" ) ); + $this->assertFalse( has_filter( "customize_sanitize_js_{$setting->id}" ) ); + } + + function test_constructor_with_args() { + $args = array( + 'type' => 'option', + 'capability' => 'edit_posts', + 'theme_supports' => 'widgets', + 'default' => 'barbar', + 'transport' => 'postMessage', + 'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ), + 'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ), + ); + $setting = new WP_Customize_Setting( $this->manager, 'bar', $args ); + $this->assertEquals( 'bar', $setting->id ); + foreach ( $args as $key => $value ) { + $this->assertEquals( $value, $setting->$key ); + } + $this->assertEquals( 10, has_filter( "customize_sanitize_{$setting->id}", $args['sanitize_callback'] ) ); + $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$setting->id}" ), $args['sanitize_js_callback'] ); + } + + public $post_data_overrides = array( + 'unset_option_overridden' => 'unset_option_post_override_value', + 'unset_theme_mod_overridden' => 'unset_theme_mod_post_override_value', + 'set_option_overridden' => 'set_option_post_override_value', + 'set_theme_mod_overridden' => 'set_theme_mod_post_override_value', + 'unset_option_multi_overridden[foo]' => 'unset_option_multi_overridden[foo]_post_override_value', + 'unset_theme_mod_multi_overridden[foo]' => 'unset_theme_mod_multi_overridden[foo]_post_override_value', + 'set_option_multi_overridden[foo]' => 'set_option_multi_overridden[foo]_post_override_value', + 'set_theme_mod_multi_overridden[foo]' => 'set_theme_mod_multi_overridden[foo]_post_override_value', + ); + + public $standard_type_configs = array( + 'option' => array( + 'getter' => 'get_option', + 'setter' => 'update_option', + ), + 'theme_mod' => array( + 'getter' => 'get_theme_mod', + 'setter' => 'set_theme_mod', + ), + ); + + /** + * Run assertions on non-multidimensional standard settings. + */ + function test_preview_standard_types_non_multidimensional() { + $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); + + // Try non-multidimensional settings + foreach ( $this->standard_type_configs as $type => $type_options ) { + // Non-multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen) + $name = "unset_{$type}_without_post_value"; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); + $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 ) ); + $this->assertEquals( $default, $setting->value() ); + + // Non-multidimensional: See what effect the preview has on an extant setting (default value should not be seen) + $name = "set_{$type}_without_post_value"; + $default = "default_value_{$name}"; + $initial_value = "initial_value_{$name}"; + call_user_func( $type_options['setter'], $name, $initial_value ); + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) ); + $this->assertEquals( $initial_value, $setting->value() ); + $setting->preview(); + $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) ); + $this->assertEquals( $initial_value, $setting->value() ); + + // @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 + $overridden_value = "overridden_value_$name"; + call_user_func( $type_options['setter'], $name, $overridden_value ); + $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) ); + $this->assertEquals( $initial_value, $setting->value() ); + $this->assertNotEquals( $overridden_value, $setting->value() ); + + // Non-multidimensional: Test unset setting being overridden by a post value + $name = "unset_{$type}_overridden"; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); // activate post_data + $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) ); + $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() ); + + // Non-multidimensional: Test set setting being overridden by a post value + $name = "set_{$type}_overridden"; + $default = "default_value_{$name}"; + $initial_value = "initial_value_{$name}"; + call_user_func( $type_options['setter'], $name, $initial_value ); + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name, $this->undefined ) ); + $this->assertEquals( $initial_value, $setting->value() ); + $setting->preview(); // activate post_data + $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) ); + $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() ); + } + } + + /** + * Run assertions on multidimensional standard settings. + */ + function test_preview_standard_types_multidimensional() { + $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); + + foreach ( $this->standard_type_configs as $type => $type_options ) { + // Multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen) + $base_name = "unset_{$type}_multi"; + $name = $base_name . '[foo]'; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); + $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); + $this->assertArrayHasKey( 'foo', $base_value ); + $this->assertEquals( $default, $base_value['foo'] ); + + // Multidimensional: See what effect the preview has on an extant setting (default value should not be seen) + $base_name = "set_{$type}_multi"; + $name = $base_name . '[foo]'; + $default = "default_value_{$name}"; + $initial_value = "initial_value_{$name}"; + $base_initial_value = array( 'foo' => $initial_value, 'bar' => 'persisted' ); + call_user_func( $type_options['setter'], $base_name, $base_initial_value ); + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $base_value = call_user_func( $type_options['getter'], $base_name, array() ); + $this->assertEquals( $initial_value, $base_value['foo'] ); + $this->assertEquals( $initial_value, $setting->value() ); + $setting->preview(); + $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) + $base_value = call_user_func( $type_options['getter'], $base_name, array() ); + $this->assertEquals( $initial_value, $base_value['foo'] ); + $this->assertEquals( $initial_value, $setting->value() ); + + // Multidimensional: Test unset setting being overridden by a post value + $base_name = "unset_{$type}_multi_overridden"; + $name = $base_name . '[foo]'; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); + $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) + $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); + $this->assertArrayHasKey( 'foo', $base_value ); + $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] ); + + // Multidimemsional: Test set setting being overridden by a post value + $base_name = "set_{$type}_multi_overridden"; + $name = $base_name . '[foo]'; + $default = "default_value_{$name}"; + $initial_value = "initial_value_{$name}"; + $base_initial_value = array( 'foo' => $initial_value, 'bar' => 'persisted' ); + call_user_func( $type_options['setter'], $base_name, $base_initial_value ); + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); + $this->arrayHasKey( 'foo', $base_value ); + $this->arrayHasKey( 'bar', $base_value ); + $this->assertEquals( $base_initial_value['foo'], $base_value['foo'] ); + $this->assertEquals( $base_initial_value['bar'], call_user_func( $type_options['getter'], $base_name, $this->undefined )['bar'] ); + $this->assertEquals( $initial_value, $setting->value() ); + $setting->preview(); + $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) + $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) + $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); + $this->assertArrayHasKey( 'foo', $base_value ); + $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] ); + $this->arrayHasKey( 'bar', call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); + $this->assertEquals( $base_initial_value['bar'], call_user_func( $type_options['getter'], $base_name, $this->undefined )['bar'] ); + } + } + + /** + * @var array storage for saved custom type data that are tested in self::test_preview_custom_type() + */ + protected $custom_type_data_saved; + + /** + * @var array storage for previewed custom type data that are tested in self::test_preview_custom_type() + */ + protected $custom_type_data_previewed; + + function custom_type_getter( $name, $default = null ) { + if ( did_action( "customize_preview_{$name}" ) && array_key_exists( $name, $this->custom_type_data_previewed ) ) { + $value = $this->custom_type_data_previewed[ $name ]; + } else if ( array_key_exists( $name, $this->custom_type_data_saved ) ) { + $value = $this->custom_type_data_saved[ $name ]; + } else { + $value = $default; + } + return $value; + } + + function custom_type_setter( $name, $value ) { + $this->custom_type_data_saved[ $name ] = $value; + } + + function custom_type_value_filter( $default ) { + $name = preg_replace( '/^customize_value_/', '', current_filter() ); + return $this->custom_type_getter( $name, $default ); + } + + /** + * @param WP_Customize_Setting $setting + */ + function custom_type_preview( $setting ) { + $previewed_value = $setting->post_value( $this->undefined ); + if ( $this->undefined !== $previewed_value ) { + $this->custom_type_data_previewed[ $setting->id ] = $previewed_value; + } + } + + function test_preview_custom_type() { + $type = 'custom_type'; + $post_data_overrides = array( + "unset_{$type}_with_post_value" => "unset_{$type}_without_post_value", + "set_{$type}_with_post_value" => "set_{$type}_without_post_value", + ); + $_POST['customized'] = wp_slash( wp_json_encode( $post_data_overrides ) ); + + $this->custom_type_data_saved = array(); + $this->custom_type_data_previewed = array(); + + add_action( "customize_preview_{$type}", array( $this, 'custom_type_preview' ) ); + + // Custom type not existing and no post value override + $name = "unset_{$type}_without_post_value"; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need + add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); + $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); + $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); + $this->assertEquals( 1, did_action( "customize_preview_{$setting->type}" ) ); + $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); // Note: for a non-custom type this is $default + $this->assertEquals( $default, $setting->value() ); // should be same as above + + // Custom type existing and no post value override + $name = "set_{$type}_without_post_value"; + $default = "default_value_{$name}"; + $initial_value = "initial_value_{$name}"; + $this->custom_type_setter( $name, $initial_value ); + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need + add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); + $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); + $this->assertEquals( $initial_value, $setting->value() ); + $setting->preview(); + $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); + $this->assertEquals( 2, did_action( "customize_preview_{$setting->type}" ) ); + $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); // should be same as above + $this->assertEquals( $initial_value, $setting->value() ); // should be same as above + + // Custom type not existing and with a post value override + $name = "unset_{$type}_with_post_value"; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need + add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); + $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); + $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); + $this->assertEquals( 3, did_action( "customize_preview_{$setting->type}" ) ); + $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) ); + $this->assertEquals( $post_data_overrides[ $name ], $setting->value() ); + + // Custom type not existing and with a post value override + $name = "set_{$type}_with_post_value"; + $default = "default_value_{$name}"; + $initial_value = "initial_value_{$name}"; + $this->custom_type_setter( $name, $initial_value ); + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need + add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); + $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); + $this->assertEquals( $initial_value, $setting->value() ); + $setting->preview(); + $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); + $this->assertEquals( 4, did_action( "customize_preview_{$setting->type}" ) ); + $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) ); + $this->assertEquals( $post_data_overrides[ $name ], $setting->value() ); + + unset( $this->custom_type_data_previewed, $this->custom_type_data_saved ); + } + + /** + * Test specific fix for setting's default value not applying on preview window + * + * @ticket 30988 + */ + function test_non_posted_setting_applying_default_value_in_preview() { + $type = 'option'; + $name = 'unset_option_without_post_value'; + $default = "default_value_{$name}"; + $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); + $this->assertEquals( $this->undefined, get_option( $name, $this->undefined ) ); + $this->assertEquals( $default, $setting->value() ); + $setting->preview(); + $this->assertEquals( $default, get_option( $name, $this->undefined ), sprintf( 'Expected get_option(%s) to return setting default: %s.', $name, $default ) ); + $this->assertEquals( $default, $setting->value() ); + } +} +