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.