WP_Widget: Introduce `is_preview()` method.

With the Widget Customizer it's possible that previewed widgets can leak data outside of Customizer, when the widget uses the cache API.
The Customizer calls the regular update callback which should already refresh the cache. Since cache additions aren't blocked yet the cache can be filled with preview data.
To prevent this issue `WP_Widget::is_preview()` will return true, when `$wp_customize->is_preview()` returns true. If `is_preview()` is true, cache additions are suspended via `wp_suspend_cache_addition()`. Make sure your object cache drop-in has implemented `wp_suspend_cache_addition()`.

`is_preview()` can/should also be used inside `WP_Widget::widget()`, see WP_Widget_Recent_Posts or WP_Widget_Recent_Comments for examples.

For more info see IRC logs: http://irclogs.wordpress.org/chanlog.php?channel=wordpress-dev&day=2014-04-02&sort=asc#m824279

props westonruter.
fixes #27538.
Built from https://develop.svn.wordpress.org/trunk@27966


git-svn-id: http://core.svn.wordpress.org/trunk@27796 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Dominik Schilling 2014-04-06 18:48:16 +00:00
parent a11553eafe
commit 598907559c
2 changed files with 67 additions and 14 deletions

View File

@ -660,13 +660,18 @@ class WP_Widget_Recent_Posts extends WP_Widget {
}
function widget($args, $instance) {
$cache = wp_cache_get('widget_recent_posts', 'widget');
$cache = array();
if ( ! $this->is_preview() ) {
$cache = wp_cache_get( 'widget_recent_posts', 'widget' );
}
if ( !is_array($cache) )
if ( ! is_array( $cache ) ) {
$cache = array();
}
if ( ! isset( $args['widget_id'] ) )
if ( ! isset( $args['widget_id'] ) ) {
$args['widget_id'] = $this->id;
}
if ( isset( $cache[ $args['widget_id'] ] ) ) {
echo $cache[ $args['widget_id'] ];
@ -723,8 +728,12 @@ class WP_Widget_Recent_Posts extends WP_Widget {
endif;
$cache[$args['widget_id']] = ob_get_flush();
wp_cache_set('widget_recent_posts', $cache, 'widget');
if ( ! $this->is_preview() ) {
$cache[ $args['widget_id'] ] = ob_get_flush();
wp_cache_set( 'widget_recent_posts', $cache, 'widget' );
} else {
ob_flush();
}
}
function update( $new_instance, $old_instance ) {
@ -807,10 +816,13 @@ class WP_Widget_Recent_Comments extends WP_Widget {
function widget( $args, $instance ) {
global $comments, $comment;
$cache = wp_cache_get('widget_recent_comments', 'widget');
if ( ! is_array( $cache ) )
$cache = array();
if ( ! $this->is_preview() ) {
$cache = wp_cache_get('widget_recent_comments', 'widget');
}
if ( ! is_array( $cache ) ) {
$cache = array();
}
if ( ! isset( $args['widget_id'] ) )
$args['widget_id'] = $this->id;
@ -865,8 +877,11 @@ class WP_Widget_Recent_Comments extends WP_Widget {
$output .= $after_widget;
echo $output;
$cache[$args['widget_id']] = $output;
wp_cache_set('widget_recent_comments', $cache, 'widget');
if ( ! $this->is_preview() ) {
$cache[ $args['widget_id'] ] = $output;
wp_cache_set( 'widget_recent_comments', $cache, 'widget' );
}
}
function update( $new_instance, $old_instance ) {

View File

@ -163,6 +163,21 @@ class WP_Widget {
return array($this, 'form_callback');
}
/**
* Determine if we're in the Customizer; if true, then the object cache gets
* suspended and widgets should check this to decide whether they should
* store anything persistently to the object cache, to transients, or
* anywhere else.
*
* @since 3.9.0
*
* @return bool True if Customizer is on, false if not.
*/
function is_preview() {
global $wp_customize;
return ( isset( $wp_customize ) && $wp_customize->is_preview() ) ;
}
/** Generate the actual widget content.
* Just finds the instance and calls widget().
* Do NOT over-ride this function. */
@ -189,8 +204,21 @@ class WP_Widget {
* @param array $args An array of default widget arguments.
*/
$instance = apply_filters( 'widget_display_callback', $instance, $this, $args );
if ( false !== $instance )
$this->widget($args, $instance);
if ( false === $instance ) {
return;
}
$was_cache_addition_suspended = wp_suspend_cache_addition();
if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
wp_suspend_cache_addition( true );
}
$this->widget( $args, $instance );
if ( $this->is_preview() ) {
wp_suspend_cache_addition( $was_cache_addition_suspended );
}
}
}
@ -241,7 +269,16 @@ class WP_Widget {
$old_instance = isset($all_instances[$number]) ? $all_instances[$number] : array();
$instance = $this->update($new_instance, $old_instance);
$was_cache_addition_suspended = wp_suspend_cache_addition();
if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
wp_suspend_cache_addition( true );
}
$instance = $this->update( $new_instance, $old_instance );
if ( $this->is_preview() ) {
wp_suspend_cache_addition( $was_cache_addition_suspended );
}
/**
* Filter a widget's settings before saving.
@ -257,8 +294,9 @@ class WP_Widget {
* @param WP_Widget $this The current widget instance.
*/
$instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $this );
if ( false !== $instance )
if ( false !== $instance ) {
$all_instances[$number] = $instance;
}
break; // run only once
}