From 598907559c1347df971d93dbeaf65c29f1ab9dca Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Sun, 6 Apr 2014 18:48:16 +0000 Subject: [PATCH] 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 --- wp-includes/default-widgets.php | 35 ++++++++++++++++++------- wp-includes/widgets.php | 46 ++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/wp-includes/default-widgets.php b/wp-includes/default-widgets.php index 68048c3173..8a803a4770 100644 --- a/wp-includes/default-widgets.php +++ b/wp-includes/default-widgets.php @@ -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 ) { diff --git a/wp-includes/widgets.php b/wp-includes/widgets.php index 5f2ea1b06a..6295479ca3 100644 --- a/wp-includes/widgets.php +++ b/wp-includes/widgets.php @@ -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 }