<?php
/**
- * Customize Widgets Class
- *
- * Implements widget management in the Customizer.
+ * WordPress Customize Widgets classes
*
* @package WordPress
* @subpackage Customize
* @since 3.9.0
*/
+
+/**
+ * Customize Widgets class.
+ *
+ * Implements widget management in the Customizer.
+ *
+ * @since 3.9.0
+ *
+ * @see WP_Customize_Manager
+ */
final class WP_Customize_Widgets {
/**
'rss', 'search', 'tag_cloud', 'text',
);
- /**
- * @since 3.9.0
- * @access protected
- * @var
- */
- protected $_customized;
-
/**
* @since 3.9.0
* @access protected
* @var array
*/
- protected $_prepreview_added_filters = array();
+ protected $rendered_sidebars = array();
/**
* @since 3.9.0
* @access protected
* @var array
*/
- protected $rendered_sidebars = array();
+ protected $rendered_widgets = array();
/**
* @since 3.9.0
* @access protected
* @var array
*/
- protected $rendered_widgets = array();
+ protected $old_sidebars_widgets = array();
/**
- * @since 3.9.0
+ * Mapping of setting type to setting ID pattern.
+ *
+ * @since 4.2.0
* @access protected
* @var array
*/
- protected $old_sidebars_widgets = array();
+ protected $setting_id_patterns = array(
+ 'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
+ 'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
+ );
/**
* Initial loader.
public function __construct( $manager ) {
$this->manager = $manager;
- add_action( 'after_setup_theme', array( $this, 'setup_widget_addition_previews' ) );
+ add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
+ add_action( 'after_setup_theme', array( $this, 'register_settings' ) );
add_action( 'wp_loaded', array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
add_action( 'customize_controls_init', array( $this, 'customize_controls_init' ) );
add_action( 'customize_register', array( $this, 'schedule_customize_register' ), 1 );
add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_footer_scripts' ) );
add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_widget_control_templates' ) );
add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
+ add_filter( 'customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
add_action( 'dynamic_sidebar', array( $this, 'tally_rendered_widgets' ) );
add_filter( 'is_active_sidebar', array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
}
/**
- * Get an unslashed post value or return a default.
- *
- * @since 3.9.0
+ * Get the widget setting type given a setting ID.
*
+ * @since 4.2.0
* @access protected
*
- * @param string $name Post value.
- * @param mixed $default Default post value.
- * @return mixed Unslashed post value or default value.
+ * @staticvar array $cache
+ *
+ * @param $setting_id Setting ID.
+ * @return string|void Setting type.
*/
- protected function get_post_value( $name, $default = null ) {
- if ( ! isset( $_POST[ $name ] ) ) {
- return $default;
+ protected function get_setting_type( $setting_id ) {
+ static $cache = array();
+ if ( isset( $cache[ $setting_id ] ) ) {
+ return $cache[ $setting_id ];
+ }
+ foreach ( $this->setting_id_patterns as $type => $pattern ) {
+ if ( preg_match( $pattern, $setting_id ) ) {
+ $cache[ $setting_id ] = $type;
+ return $type;
+ }
}
-
- return wp_unslash( $_POST[$name] );
}
/**
- * Set up widget addition previews.
- *
- * Since the widgets get registered on 'widgets_init' before the customizer
- * settings are set up on 'customize_register', we have to filter the options
- * similarly to how the setting previewer will filter the options later.
- *
- * @since 3.9.0
+ * Inspect the incoming customized data for any widget settings, and dynamically add them up-front so widgets will be initialized properly.
*
+ * @since 4.2.0
* @access public
*/
- public function setup_widget_addition_previews() {
- $is_customize_preview = false;
-
- if ( ! empty( $this->manager ) && ! is_admin() && 'on' === $this->get_post_value( 'wp_customize' ) ) {
- $is_customize_preview = check_ajax_referer( 'preview-customize_' . $this->manager->get_stylesheet(), 'nonce', false );
- }
-
- $is_ajax_widget_update = false;
- if ( $this->manager->doing_ajax() && 'update-widget' === $this->get_post_value( 'action' ) ) {
- $is_ajax_widget_update = check_ajax_referer( 'update-widget', 'nonce', false );
- }
-
- $is_ajax_customize_save = false;
- if ( $this->manager->doing_ajax() && 'customize_save' === $this->get_post_value( 'action' ) ) {
- $is_ajax_customize_save = check_ajax_referer( 'save-customize_' . $this->manager->get_stylesheet(), 'nonce', false );
- }
-
- $is_valid_request = ( $is_ajax_widget_update || $is_customize_preview || $is_ajax_customize_save );
- if ( ! $is_valid_request ) {
- return;
- }
-
- // Input from customizer preview.
- if ( isset( $_POST['customized'] ) ) {
- $this->_customized = json_decode( $this->get_post_value( 'customized' ), true );
- } else { // Input from ajax widget update request.
- $this->_customized = array();
- $id_base = $this->get_post_value( 'id_base' );
- $widget_number = $this->get_post_value( 'widget_number', false );
- $option_name = 'widget_' . $id_base;
- $this->_customized[ $option_name ] = array();
- if ( preg_match( '/^[0-9]+$/', $widget_number ) ) {
- $option_name .= '[' . $widget_number . ']';
- $this->_customized[ $option_name ][ $widget_number ] = array();
+ public function register_settings() {
+ $widget_setting_ids = array();
+ $incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
+ foreach ( $incoming_setting_ids as $setting_id ) {
+ if ( ! is_null( $this->get_setting_type( $setting_id ) ) ) {
+ $widget_setting_ids[] = $setting_id;
}
}
+ if ( $this->manager->doing_ajax( 'update-widget' ) && isset( $_REQUEST['widget-id'] ) ) {
+ $widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
+ }
- $function = array( $this, 'prepreview_added_sidebars_widgets' );
-
- $hook = 'option_sidebars_widgets';
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
-
- $hook = 'default_option_sidebars_widgets';
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
-
- $function = array( $this, 'prepreview_added_widget_instance' );
- foreach ( $this->_customized as $setting_id => $value ) {
- if ( preg_match( '/^(widget_.+?)(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
- $option = $matches[1];
-
- $hook = sprintf( 'option_%s', $option );
- if ( ! has_filter( $hook, $function ) ) {
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
- }
-
- $hook = sprintf( 'default_option_%s', $option );
- if ( ! has_filter( $hook, $function ) ) {
- add_filter( $hook, $function );
- $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
- }
+ $settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
- /*
- * Make sure the option is registered so that the update_option()
- * won't fail due to the filters providing a default value, which
- * causes the update_option() to get confused.
- */
- add_option( $option, array() );
+ /*
+ * Preview settings right away so that widgets and sidebars will get registered properly.
+ * But don't do this if a customize_save because this will cause WP to think there is nothing
+ * changed that needs to be saved.
+ */
+ if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
+ foreach ( $settings as $setting ) {
+ $setting->preview();
}
}
}
/**
- * Ensure that newly-added widgets will appear in the widgets_sidebars.
- *
- * This is necessary because the customizer's setting preview filters
- * are added after the widgets_init action, which is too late for the
- * widgets to be set up properly.
+ * Determine the arguments for a dynamically-created setting.
*
- * @since 3.9.0
+ * @since 4.2.0
* @access public
*
- * @param array $sidebars_widgets Associative array of sidebars and their widgets.
- * @return array Filtered array of sidebars and their widgets.
+ * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
+ * @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
+ * @return false|array Setting arguments, false otherwise.
*/
- public function prepreview_added_sidebars_widgets( $sidebars_widgets ) {
- foreach ( $this->_customized as $setting_id => $value ) {
- if ( preg_match( '/^sidebars_widgets\[(.+?)\]$/', $setting_id, $matches ) ) {
- $sidebar_id = $matches[1];
- $sidebars_widgets[ $sidebar_id ] = $value;
- }
+ public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
+ if ( $this->get_setting_type( $setting_id ) ) {
+ $args = $this->get_setting_args( $setting_id );
}
- return $sidebars_widgets;
+ return $args;
}
/**
- * Ensure newly-added widgets have empty instances so they
- * will be recognized.
- *
- * This is necessary because the customizer's setting preview
- * filters are added after the widgets_init action, which is
- * too late for the widgets to be set up properly.
+ * Get an unslashed post value or return a default.
*
* @since 3.9.0
- * @access public
*
- * @param array|bool|mixed $value Widget instance(s), false if open was empty.
- * @return array|mixed Widget instance(s) with additions.
- */
- public function prepreview_added_widget_instance( $value = false ) {
- if ( ! preg_match( '/^(?:default_)?option_(widget_(.+))/', current_filter(), $matches ) ) {
- return $value;
- }
- $id_base = $matches[2];
-
- foreach ( $this->_customized as $setting_id => $setting ) {
- $parsed_setting_id = $this->parse_widget_setting_id( $setting_id );
- if ( is_wp_error( $parsed_setting_id ) || $id_base !== $parsed_setting_id['id_base'] ) {
- continue;
- }
- $widget_number = $parsed_setting_id['number'];
-
- if ( is_null( $widget_number ) ) {
- // Single widget.
- if ( false === $value ) {
- $value = array();
- }
- } else {
- // Multi widget.
- if ( empty( $value ) ) {
- $value = array( '_multiwidget' => 1 );
- }
- if ( ! isset( $value[ $widget_number ] ) ) {
- $value[ $widget_number ] = array();
- }
- }
- }
-
- return $value;
- }
-
- /**
- * Remove pre-preview filters.
- *
- * Removes filters added in setup_widget_addition_previews()
- * to ensure widgets are populating the options during
- * 'widgets_init'.
+ * @access protected
*
- * @since 3.9.0
- * @access public
+ * @param string $name Post value.
+ * @param mixed $default Default post value.
+ * @return mixed Unslashed post value or default value.
*/
- public function remove_prepreview_filters() {
- foreach ( $this->_prepreview_added_filters as $prepreview_added_filter ) {
- remove_filter( $prepreview_added_filter['hook'], $prepreview_added_filter['function'] );
+ protected function get_post_value( $name, $default = null ) {
+ if ( ! isset( $_POST[ $name ] ) ) {
+ return $default;
}
- $this->_prepreview_added_filters = array();
+
+ return wp_unslash( $_POST[ $name ] );
}
/**
* Override sidebars_widgets for theme switch.
*
- * When switching a theme via the customizer, supply any previously-configured
+ * When switching a theme via the Customizer, supply any previously-configured
* sidebars_widgets from the target theme as the initial sidebars_widgets
* setting. Also store the old theme's existing settings so that they can
* be passed along for storing in the sidebars_widgets theme_mod when the
*
* @since 3.9.0
* @access public
+ *
+ * @global array $sidebars_widgets
+ * @global array $_wp_sidebars_widgets
*/
public function override_sidebars_widgets_for_theme_switch() {
global $sidebars_widgets;
$sidebars_widgets = $this->old_sidebars_widgets;
$sidebars_widgets = retrieve_widgets( 'customize' );
add_filter( 'option_sidebars_widgets', array( $this, 'filter_option_sidebars_widgets_for_theme_switch' ), 1 );
+ // reset global cache var used by wp_get_sidebars_widgets()
+ unset( $GLOBALS['_wp_sidebars_widgets'] );
}
/**
- * Filter old_sidebars_widgets_data customizer setting.
+ * Filter old_sidebars_widgets_data Customizer setting.
*
* When switching themes, filter the Customizer setting
* old_sidebars_widgets_data to supply initial $sidebars_widgets before they
* @since 3.9.0
* @access public
*
- * @param array $sidebars_widgets
+ * @param array $old_sidebars_widgets
+ * @return array
*/
public function filter_customize_value_old_sidebars_widgets_data( $old_sidebars_widgets ) {
return $this->old_sidebars_widgets;
* @since 3.9.0
* @access public
*
+ * @global array $sidebars_widgets
+ *
* @param array $sidebars_widgets
+ * @return array
*/
public function filter_option_sidebars_widgets_for_theme_switch( $sidebars_widgets ) {
$sidebars_widgets = $GLOBALS['sidebars_widgets'];
* @access public
*/
public function schedule_customize_register() {
- if ( is_admin() ) { // @todo for some reason, $wp_customize->is_preview() is true here?
+ if ( is_admin() ) {
$this->customize_register();
} else {
add_action( 'wp', array( $this, 'customize_register' ) );
}
/**
- * Register customizer settings and controls for all sidebars and widgets.
+ * Register Customizer settings and controls for all sidebars and widgets.
*
* @since 3.9.0
* @access public
+ *
+ * @global array $wp_registered_widgets
+ * @global array $wp_registered_widget_controls
+ * @global array $wp_registered_sidebars
*/
public function customize_register() {
global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_sidebars;
$sidebars_widgets = array_merge(
array( 'wp_inactive_widgets' => array() ),
- array_fill_keys( array_keys( $GLOBALS['wp_registered_sidebars'] ), array() ),
+ array_fill_keys( array_keys( $wp_registered_sidebars ), array() ),
wp_get_sidebars_widgets()
);
foreach ( array_keys( $wp_registered_widgets ) as $widget_id ) {
$setting_id = $this->get_setting_id( $widget_id );
$setting_args = $this->get_setting_args( $setting_id );
-
- $setting_args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
- $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
-
- $this->manager->add_setting( $setting_id, $setting_args );
-
+ if ( ! $this->manager->get_setting( $setting_id ) ) {
+ $this->manager->add_setting( $setting_id, $setting_args );
+ }
$new_setting_ids[] = $setting_id;
}
$setting_id = 'old_sidebars_widgets_data';
$setting_args = $this->get_setting_args( $setting_id, array(
'type' => 'global_variable',
+ 'dirty' => true,
) );
$this->manager->add_setting( $setting_id, $setting_args );
}
$sidebar_widget_ids = array();
}
- $is_registered_sidebar = isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] );
+ $is_registered_sidebar = isset( $wp_registered_sidebars[ $sidebar_id ] );
$is_inactive_widgets = ( 'wp_inactive_widgets' === $sidebar_id );
$is_active_sidebar = ( $is_registered_sidebar && ! $is_inactive_widgets );
if ( $is_registered_sidebar || $is_inactive_widgets ) {
$setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
$setting_args = $this->get_setting_args( $setting_id );
-
- $setting_args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
- $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
-
- $this->manager->add_setting( $setting_id, $setting_args );
+ if ( ! $this->manager->get_setting( $setting_id ) ) {
+ if ( ! $this->manager->is_theme_active() ) {
+ $setting_args['dirty'] = true;
+ }
+ $this->manager->add_setting( $setting_id, $setting_args );
+ }
$new_setting_ids[] = $setting_id;
// Add section to contain controls.
if ( $is_active_sidebar ) {
$section_args = array(
- 'title' => $GLOBALS['wp_registered_sidebars'][ $sidebar_id ]['name'],
- 'description' => $GLOBALS['wp_registered_sidebars'][ $sidebar_id ]['description'],
+ 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],
+ 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],
'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
'panel' => 'widgets',
+ 'sidebar_id' => $sidebar_id,
);
/**
*/
$section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id );
- $this->manager->add_section( $section_id, $section_args );
+ $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args );
+ $this->manager->add_section( $section );
$control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array(
'section' => $section_id,
foreach ( $sidebar_widget_ids as $i => $widget_id ) {
// Skip widgets that may have gone away due to a plugin being deactivated.
- if ( ! $is_active_sidebar || ! isset( $GLOBALS['wp_registered_widgets'][$widget_id] ) ) {
+ if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[$widget_id] ) ) {
continue;
}
- $registered_widget = $GLOBALS['wp_registered_widgets'][$widget_id];
+ $registered_widget = $wp_registered_widgets[$widget_id];
$setting_id = $this->get_setting_id( $widget_id );
- $id_base = $GLOBALS['wp_registered_widget_controls'][$widget_id]['id_base'];
+ $id_base = $wp_registered_widget_controls[$widget_id]['id_base'];
$control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array(
'label' => $registered_widget['name'],
}
}
- /*
- * We have to register these settings later than customize_preview_init
- * so that other filters have had a chance to run.
- */
- if ( did_action( 'customize_preview_init' ) ) {
+ if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
foreach ( $new_setting_ids as $new_setting_id ) {
$this->manager->get_setting( $new_setting_id )->preview();
}
}
- $this->remove_prepreview_filters();
+
+ add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
}
/**
- * Covert a widget_id into its corresponding customizer setting ID (option name).
+ * Covert a widget_id into its corresponding Customizer setting ID (option name).
*
* @since 3.9.0
* @access public
* Determine whether the widget is considered "wide".
*
* Core widgets which may have controls wider than 250, but can
- * still be shown in the narrow customizer panel. The RSS and Text
+ * still be shown in the narrow Customizer panel. The RSS and Text
* widgets in Core, for example, have widths of 400 and yet they
- * still render fine in the customizer panel. This method will
+ * still render fine in the Customizer panel. This method will
* return all Core widgets as being not wide, but this can be
* overridden with the is_wide_widget_in_customizer filter.
*
* @since 3.9.0
* @access public
*
+ * @global $wp_registered_widget_controls
+ *
* @param string $widget_id Widget ID.
* @return bool Whether or not the widget is a "wide" widget.
*/
}
/**
- * Enqueue scripts and styles for customizer panel and export data to JavaScript.
+ * Enqueue scripts and styles for Customizer panel and export data to JavaScript.
*
* @since 3.9.0
* @access public
+ *
+ * @global WP_Scripts $wp_scripts
+ * @global array $wp_registered_sidebars
+ * @global array $wp_registered_widgets
*/
public function enqueue_scripts() {
+ global $wp_scripts, $wp_registered_sidebars, $wp_registered_widgets;
+
wp_enqueue_style( 'customize-widgets' );
wp_enqueue_script( 'customize-widgets' );
</div>'
);
- global $wp_scripts;
-
$settings = array(
'nonce' => wp_create_nonce( 'update-widget' ),
- 'registeredSidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ),
- 'registeredWidgets' => $GLOBALS['wp_registered_widgets'],
+ 'registeredSidebars' => array_values( $wp_registered_sidebars ),
+ 'registeredWidgets' => $wp_registered_widgets,
'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets
'l10n' => array(
'saveBtnLabel' => __( 'Apply' ),
'removeBtnLabel' => __( 'Remove' ),
'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ),
'error' => __( 'An error has occurred. Please reload the page and try again.' ),
+ 'widgetMovedUp' => __( 'Widget moved up' ),
+ 'widgetMovedDown' => __( 'Widget moved down' ),
),
'tpl' => array(
'widgetReorderNav' => $widget_reorder_nav_tpl,
$wp_scripts->add_data(
'customize-widgets',
'data',
- sprintf( 'var _wpCustomizeWidgetsSettings = %s;', json_encode( $settings ) )
+ sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) )
);
}
?>
<div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
<div id="available-widgets">
+ <div class="customize-section-title">
+ <button class="customize-section-back" tabindex="-1">
+ <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
+ </button>
+ <h3>
+ <span class="customize-action"><?php
+ /* translators: ▸ is the unicode right-pointing triangle, and %s is the section title in the Customizer */
+ echo sprintf( __( 'Customizing ▸ %s' ), esc_html( $this->manager->get_panel( 'widgets' )->title ) );
+ ?></span>
+ <?php _e( 'Add a Widget' ); ?>
+ </h3>
+ </div>
<div id="available-widgets-filter">
<label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
<input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets…' ) ?>" />
</div>
+ <div id="available-widgets-list">
<?php foreach ( $this->get_available_widgets() as $available_widget ): ?>
<div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0">
<?php echo $available_widget['control_tpl']; ?>
</div>
<?php endforeach; ?>
+ </div><!-- #available-widgets-list -->
</div><!-- #available-widgets -->
</div><!-- #widgets-left -->
<?php
'transport' => 'refresh',
'default' => array(),
);
+
+ if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
+ $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
+ $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
+ } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
+ $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
+ $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
+ }
+
$args = array_merge( $args, $overrides );
/**
* @return array Array of sanitized widget IDs.
*/
public function sanitize_sidebar_widgets( $widget_ids ) {
- global $wp_registered_widgets;
-
- $widget_ids = array_map( 'strval', (array) $widget_ids );
+ $widget_ids = array_map( 'strval', (array) $widget_ids );
$sanitized_widget_ids = array();
-
foreach ( $widget_ids as $widget_id ) {
- if ( array_key_exists( $widget_id, $wp_registered_widgets ) ) {
- $sanitized_widget_ids[] = $widget_id;
- }
+ $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
}
return $sanitized_widget_ids;
}
* @since 3.9.0
* @access public
*
+ * @global array $wp_registered_widgets
+ * @global array $wp_registered_widget_controls
+ * @staticvar array $available_widgets
+ *
* @see wp_list_widgets()
*
* @return array List of available widgets.
* Naturally order available widgets by name.
*
* @since 3.9.0
- * @static
* @access protected
*
* @param array $widget_a The first widget to compare.
call_user_func_array( 'wp_widget_control', $args );
$replacements = array(
- '<form action="" method="post">' => '<div class="form">',
+ '<form method="post">' => '<div class="form">',
'</form>' => '</div><!-- .form -->',
);
}
/**
- * Add hooks for the customizer preview.
+ * Add hooks for the Customizer preview.
*
* @since 3.9.0
* @access public
*/
public function customize_preview_init() {
- add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
add_action( 'wp_print_styles', array( $this, 'print_preview_css' ), 1 );
add_action( 'wp_footer', array( $this, 'export_preview_data' ), 20 );
}
+ /**
+ * Refresh nonce for widget updates.
+ *
+ * @since 4.2.0
+ * @access public
+ *
+ * @param array $nonces Array of nonces.
+ * @return array $nonces Array of nonces.
+ */
+ public function refresh_nonces( $nonces ) {
+ $nonces['update-widget'] = wp_create_nonce( 'update-widget' );
+ return $nonces;
+ }
+
/**
* When previewing, make sure the proper previewing widgets are used.
*
* Because wp_get_sidebars_widgets() gets called early at init
* (via wp_convert_widget_settings()) and can set global variable
* $_wp_sidebars_widgets to the value of get_option( 'sidebars_widgets' )
- * before the customizer preview filter is added, we have to reset
+ * before the Customizer preview filter is added, we have to reset
* it after the filter has been added.
*
* @since 3.9.0
* @access public
*
* @param array $sidebars_widgets List of widgets for the current sidebar.
+ * @return array
*/
public function preview_sidebars_widgets( $sidebars_widgets ) {
$sidebars_widgets = get_option( 'sidebars_widgets' );
*
* @since 3.9.0
* @access public
+ *
+ * @global array $wp_registered_sidebars
+ * @global array $wp_registered_widgets
*/
public function export_preview_data() {
-
- // Prepare customizer settings to pass to Javascript.
+ global $wp_registered_sidebars, $wp_registered_widgets;
+ // Prepare Customizer settings to pass to JavaScript.
$settings = array(
'renderedSidebars' => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
'renderedWidgets' => array_fill_keys( array_keys( $this->rendered_widgets ), true ),
- 'registeredSidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ),
- 'registeredWidgets' => $GLOBALS['wp_registered_widgets'],
+ 'registeredSidebars' => array_values( $wp_registered_sidebars ),
+ 'registeredWidgets' => $wp_registered_widgets,
'l10n' => array(
'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
),
?>
<script type="text/javascript">
- var _wpWidgetCustomizerPreviewSettings = <?php echo json_encode( $settings ); ?>;
+ var _wpWidgetCustomizerPreviewSettings = <?php echo wp_json_encode( $settings ); ?>;
</script>
<?php
}
* @since 3.9.0
* @access public
*
+ * @global array $wp_registered_sidebars
+ *
* @param bool $is_active Whether the sidebar is active.
* @param string $sidebar_id Sidebar ID.
+ * @return bool
*/
public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
* @since 3.9.0
* @access public
*
+ * @global array $wp_registered_sidebars
+ *
* @param bool $has_widgets Whether the current sidebar has widgets.
* @param string $sidebar_id Sidebar ID.
+ * @return bool
*/
public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
* @access public
*
* @param array $value Widget instance to sanitize.
- * @return array Sanitized widget instance.
+ * @return array|void Sanitized widget instance.
*/
public function sanitize_widget_instance( $value ) {
if ( $value === array() ) {
|| empty( $value['instance_hash_key'] )
|| empty( $value['encoded_serialized_instance'] ) )
{
- return null;
+ return;
}
$decoded = base64_decode( $value['encoded_serialized_instance'], true );
if ( false === $decoded ) {
- return null;
+ return;
}
- if ( $this->get_instance_hash_key( $decoded ) !== $value['instance_hash_key'] ) {
- return null;
+ if ( ! hash_equals( $this->get_instance_hash_key( $decoded ), $value['instance_hash_key'] ) ) {
+ return;
}
$instance = unserialize( $decoded );
if ( false === $instance ) {
- return null;
+ return;
}
return $instance;
* @since 3.9.0
* @access public
*
+ * @global array $wp_registered_widgets
+ *
* @param array $widget_ids List of widget IDs.
* @return array Parsed list of widget IDs.
*/
* @since 3.9.0
* @access public
*
+ * @global array $wp_registered_widget_updates
+ * @global array $wp_registered_widget_controls
+ *
* @param string $widget_id Widget ID.
* @return WP_Error|array Array containing the updated widget information.
* A WP_Error object, otherwise.
public function call_widget_update( $widget_id ) {
global $wp_registered_widget_updates, $wp_registered_widget_controls;
+ $setting_id = $this->get_setting_id( $widget_id );
+
+ /*
+ * Make sure that other setting changes have previewed since this widget
+ * may depend on them (e.g. Menus being present for Custom Menu widget).
+ */
+ if ( ! did_action( 'customize_preview_init' ) ) {
+ foreach ( $this->manager->settings() as $setting ) {
+ if ( $setting->id !== $setting_id ) {
+ $setting->preview();
+ }
+ }
+ }
+
$this->start_capturing_option_updates();
$parsed_id = $this->parse_widget_id( $widget_id );
$option_name = 'widget_' . $parsed_id['id_base'];
// Clean up any input vars that were manually added
foreach ( $added_input_vars as $key ) {
- unset( $_POST[$key] );
- unset( $_REQUEST[$key] );
+ unset( $_POST[ $key ] );
+ unset( $_REQUEST[ $key ] );
}
// Make sure the expected option was updated.
}
}
+ // Obtain the widget instance.
+ $option = $this->get_captured_option( $option_name );
+ if ( null !== $parsed_id['number'] ) {
+ $instance = $option[ $parsed_id['number'] ];
+ } else {
+ $instance = $option;
+ }
+
+ /*
+ * Override the incoming $_POST['customized'] for a newly-created widget's
+ * setting with the new $instance so that the preview filter currently
+ * 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 );
+
// Obtain the widget control with the updated instance in place.
ob_start();
-
- $form = $wp_registered_widget_controls[$widget_id];
+ $form = $wp_registered_widget_controls[ $widget_id ];
if ( $form ) {
call_user_func_array( $form['callback'], $form['params'] );
}
-
$form = ob_get_clean();
- // Obtain the widget instance.
- $option = get_option( $option_name );
-
- if ( null !== $parsed_id['number'] ) {
- $instance = $option[$parsed_id['number']];
- } else {
- $instance = $option;
- }
-
$this->stop_capturing_option_updates();
return compact( 'instance', 'form' );
wp_die( -1 );
}
- if ( ! isset( $_POST['widget-id'] ) ) {
- wp_send_json_error();
+ if ( empty( $_POST['widget-id'] ) ) {
+ wp_send_json_error( 'missing_widget-id' );
}
/** This action is documented in wp-admin/includes/ajax-actions.php */
$widget_id = $this->get_post_value( 'widget-id' );
$parsed_id = $this->parse_widget_id( $widget_id );
- $id_base = $parsed_id['id_base'];
-
- if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) {
- wp_send_json_error();
+ $id_base = $parsed_id['id_base'];
+
+ $is_updating_widget_template = (
+ isset( $_POST[ 'widget-' . $id_base ] )
+ &&
+ is_array( $_POST[ 'widget-' . $id_base ] )
+ &&
+ preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) )
+ );
+ if ( $is_updating_widget_template ) {
+ wp_send_json_error( 'template_widget_not_updatable' );
}
$updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
if ( is_wp_error( $updated_widget ) ) {
- wp_send_json_error();
+ wp_send_json_error( $updated_widget->get_error_code() );
}
$form = $updated_widget['form'];
* @access protected
*
* @param string $option_name Option name.
- * @return boolean Whether the option capture is ignored.
+ * @return bool Whether the option capture is ignored.
*/
protected function is_option_capture_ignored( $option_name ) {
return ( 0 === strpos( $option_name, '_transient_' ) );
return $this->_captured_options;
}
+ /**
+ * Get the option that was captured from being saved.
+ *
+ * @since 4.2.0
+ * @access protected
+ *
+ * @param string $option_name Option name.
+ * @param mixed $default Optional. Default value to return if the option does not exist.
+ * @return mixed Value set for the option.
+ */
+ protected function get_captured_option( $option_name, $default = false ) {
+ if ( array_key_exists( $option_name, $this->_captured_options ) ) {
+ $value = $this->_captured_options[ $option_name ];
+ } else {
+ $value = $default;
+ }
+ return $value;
+ }
+
/**
* Get the number of captured widget option updates.
*
* @since 3.9.0
* @access public
*
- * @param mixed $new_value
- * @param string $option_name
- * @param mixed $old_value
- * @return mixed
+ * @param mixed $new_value The new option value.
+ * @param string $option_name Name of the option.
+ * @param mixed $old_value The old option value.
+ * @return mixed Filtered option value.
*/
public function capture_filter_pre_update_option( $new_value, $option_name, $old_value ) {
if ( $this->is_option_capture_ignored( $option_name ) ) {
return;
}
- if ( ! isset( $this->_captured_options[$option_name] ) ) {
+ if ( ! isset( $this->_captured_options[ $option_name ] ) ) {
add_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
}
- $this->_captured_options[$option_name] = $new_value;
+ $this->_captured_options[ $option_name ] = $new_value;
return $old_value;
}
* @since 3.9.0
* @access public
*
- * @param mixed $value Option
- * @return mixed
+ * @param mixed $value Value to return instead of the option value.
+ * @return mixed Filtered option value.
*/
public function capture_filter_pre_get_option( $value ) {
$option_name = preg_replace( '/^pre_option_/', '', current_filter() );
- if ( isset( $this->_captured_options[$option_name] ) ) {
- $value = $this->_captured_options[$option_name];
+ if ( isset( $this->_captured_options[ $option_name ] ) ) {
+ $value = $this->_captured_options[ $option_name ];
/** This filter is documented in wp-includes/option.php */
$value = apply_filters( 'option_' . $option_name, $value );
$this->_captured_options = array();
$this->_is_capturing_option_updates = false;
}
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function setup_widget_addition_previews() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function prepreview_added_sidebars_widgets() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function prepreview_added_widget_instance() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
+
+ /**
+ * @since 3.9.0
+ * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
+ */
+ public function remove_prepreview_filters() {
+ _deprecated_function( __METHOD__, '4.2.0' );
+ }
}