From 5dae1386aa49a60da2f058b1490703eec2980695 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 21 Nov 2015 02:52:27 +0000 Subject: [PATCH] Customize: Ensure that a setting (especially a multidimensional one) can still be previewed when the post value to preview is set after `preview()` is invoked. * Introduce `customize_post_value_set_{$setting_id}` and `customize_post_value_set` actions which are done when `WP_Customize_Manager::set_post_value()` is called. * Clear the `preview_applied` flag for aggregated multidimensional settings when a post value is set. This ensures the new value is used instead of a previously-cached previewed value. * Move `$is_preview` property from subclasses to `WP_Customize_Setting` parent class. * Deferred preview: Ensure that when `preview()` short-circuits due to not being applicable that it will be called again later when the post value is set. * Populate post value for updated-widget with the (unsanitized) JS-value in `WP_Customize_Widgets::call_widget_update()` so that value will be properly sanitized when accessed in `WP_Customize_Manager::post_value()`. Includes unit tests with assertions to check the reported issues and validate the fixes. Fixes defect introduced in [35007]. See #32103. Fixes #34738. Built from https://develop.svn.wordpress.org/trunk@35724 git-svn-id: http://core.svn.wordpress.org/trunk@35688 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-customize-manager.php | 30 +++++++++++++ wp-includes/class-wp-customize-setting.php | 44 ++++++++++++++++++- wp-includes/class-wp-customize-widgets.php | 2 +- ...ass-wp-customize-nav-menu-item-setting.php | 9 ---- .../class-wp-customize-nav-menu-setting.php | 9 ---- wp-includes/version.php | 2 +- 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/wp-includes/class-wp-customize-manager.php b/wp-includes/class-wp-customize-manager.php index c3b1f32509..9f15d31f57 100644 --- a/wp-includes/class-wp-customize-manager.php +++ b/wp-includes/class-wp-customize-manager.php @@ -659,6 +659,36 @@ final class WP_Customize_Manager { public function set_post_value( $setting_id, $value ) { $this->unsanitized_post_values(); $this->_post_values[ $setting_id ] = $value; + + /** + * Announce when a specific setting's unsanitized post value has been set. + * + * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called. + * + * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID. + * + * @since 4.4.0 + * + * @param mixed $value Unsanitized setting post value. + * @param WP_Customize_Manager $this WP_Customize_Manager instance. + */ + do_action( "customize_post_value_set_{$setting_id}", $value, $this ); + + /** + * Announce when any setting's unsanitized post value has been set. + * + * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called. + * + * This is useful for WP_Customize_Setting instances to watch + * in order to update a cached previewed value. + * + * @since 4.4.0 + * + * @param string $setting_id Setting ID. + * @param mixed $value Unsanitized setting post value. + * @param WP_Customize_Manager $this WP_Customize_Manager instance. + */ + do_action( 'customize_post_value_set', $setting_id, $value, $this ); } /** diff --git a/wp-includes/class-wp-customize-setting.php b/wp-includes/class-wp-customize-setting.php index 12f76d4309..434dec7c06 100644 --- a/wp-includes/class-wp-customize-setting.php +++ b/wp-includes/class-wp-customize-setting.php @@ -81,6 +81,15 @@ class WP_Customize_Setting { */ protected $id_data = array(); + /** + * Whether or not preview() was called. + * + * @since 4.4.0 + * @access protected + * @var bool + */ + protected $is_previewed = false; + /** * Cache of multidimensional values to improve performance. * @@ -191,6 +200,8 @@ class WP_Customize_Setting { } if ( ! empty( $this->id_data['keys'] ) ) { + // Note the preview-applied flag is cleared at priority 9 to ensure it is cleared before a deferred-preview runs. + add_action( "customize_post_value_set_{$this->id}", array( $this, '_clear_aggregated_multidimensional_preview_applied_flag' ), 9 ); $this->is_multidimensional_aggregated = true; } } @@ -245,6 +256,12 @@ class WP_Customize_Setting { if ( ! isset( $this->_previewed_blog_id ) ) { $this->_previewed_blog_id = get_current_blog_id(); } + + // Prevent re-previewing an already-previewed setting. + if ( $this->is_previewed ) { + return true; + } + $id_base = $this->id_data['base']; $is_multidimensional = ! empty( $this->id_data['keys'] ); $multidimensional_filter = array( $this, '_multidimensional_preview_filter' ); @@ -273,7 +290,11 @@ class WP_Customize_Setting { $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied. } + // If the setting does not need previewing now, defer to when it has a value to preview. if ( ! $needs_preview ) { + if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) { + add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ); + } return false; } @@ -327,9 +348,28 @@ class WP_Customize_Setting { */ do_action( "customize_preview_{$this->type}", $this ); } + + $this->is_previewed = true; + return true; } + /** + * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated. + * + * This ensures that the new value will get sanitized and used the next time + * that WP_Customize_Setting::_multidimensional_preview_filter() + * is called for this setting. + * + * @since 4.4.0 + * @access private + * @see WP_Customize_Manager::set_post_value() + * @see WP_Customize_Setting::_multidimensional_preview_filter() + */ + final public function _clear_aggregated_multidimensional_preview_applied_flag() { + unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] ); + } + /** * Callback function to filter non-multidimensional theme mods and options. * @@ -369,13 +409,13 @@ class WP_Customize_Setting { * the first setting previewed will be used to apply the values for the others. * * @since 4.4.0 - * @access public + * @access private * * @see WP_Customize_Setting::$aggregated_multidimensionals * @param mixed $original Original root value. * @return mixed New or old value. */ - public function _multidimensional_preview_filter( $original ) { + final public function _multidimensional_preview_filter( $original ) { if ( ! $this->is_current_blog_previewed() ) { return $original; } diff --git a/wp-includes/class-wp-customize-widgets.php b/wp-includes/class-wp-customize-widgets.php index 6ee69421cd..7639d5091c 100644 --- a/wp-includes/class-wp-customize-widgets.php +++ b/wp-includes/class-wp-customize-widgets.php @@ -1380,7 +1380,7 @@ final class WP_Customize_Widgets { * in place from WP_Customize_Setting::preview() will use this value * instead of the default widget instance value (an empty array). */ - $this->manager->set_post_value( $setting_id, $instance ); + $this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance ) ); // Obtain the widget control with the updated instance in place. ob_start(); diff --git a/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php b/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php index 2fa0b5c0d2..073423ecbe 100644 --- a/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php +++ b/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php @@ -119,15 +119,6 @@ class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { */ public $original_nav_menu_term_id; - /** - * Whether or not preview() was called. - * - * @since 4.3.0 - * @access protected - * @var bool - */ - protected $is_previewed = false; - /** * Whether or not update() was called. * diff --git a/wp-includes/customize/class-wp-customize-nav-menu-setting.php b/wp-includes/customize/class-wp-customize-nav-menu-setting.php index 766099e06b..5562a8df52 100644 --- a/wp-includes/customize/class-wp-customize-nav-menu-setting.php +++ b/wp-includes/customize/class-wp-customize-nav-menu-setting.php @@ -88,15 +88,6 @@ class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { */ public $previous_term_id; - /** - * Whether or not preview() was called. - * - * @since 4.3.0 - * @access protected - * @var bool - */ - protected $is_previewed = false; - /** * Whether or not update() was called. * diff --git a/wp-includes/version.php b/wp-includes/version.php index 1f0c8c807f..e63792dbc0 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.4-beta4-35723'; +$wp_version = '4.4-beta4-35724'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.