WordPress 4.3
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-widgets.php
1 <?php
2 /**
3  * WordPress Customize Widgets classes
4  *
5  * @package WordPress
6  * @subpackage Customize
7  * @since 3.9.0
8  */
9
10 /**
11  * Customize Widgets class.
12  *
13  * Implements widget management in the Customizer.
14  *
15  * @since 3.9.0
16  *
17  * @see WP_Customize_Manager
18  */
19 final class WP_Customize_Widgets {
20
21         /**
22          * WP_Customize_Manager instance.
23          *
24          * @since 3.9.0
25          * @access public
26          * @var WP_Customize_Manager
27          */
28         public $manager;
29
30         /**
31          * All id_bases for widgets defined in core.
32          *
33          * @since 3.9.0
34          * @access protected
35          * @var array
36          */
37         protected $core_widget_id_bases = array(
38                 'archives', 'calendar', 'categories', 'links', 'meta',
39                 'nav_menu', 'pages', 'recent-comments', 'recent-posts',
40                 'rss', 'search', 'tag_cloud', 'text',
41         );
42
43         /**
44          * @since 3.9.0
45          * @access protected
46          * @var array
47          */
48         protected $rendered_sidebars = array();
49
50         /**
51          * @since 3.9.0
52          * @access protected
53          * @var array
54          */
55         protected $rendered_widgets = array();
56
57         /**
58          * @since 3.9.0
59          * @access protected
60          * @var array
61          */
62         protected $old_sidebars_widgets = array();
63
64         /**
65          * Mapping of setting type to setting ID pattern.
66          *
67          * @since 4.2.0
68          * @access protected
69          * @var array
70          */
71         protected $setting_id_patterns = array(
72                 'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
73                 'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
74         );
75
76         /**
77          * Initial loader.
78          *
79          * @since 3.9.0
80          * @access public
81          *
82          * @param WP_Customize_Manager $manager Customize manager bootstrap instance.
83          */
84         public function __construct( $manager ) {
85                 $this->manager = $manager;
86
87                 add_filter( 'customize_dynamic_setting_args',          array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
88                 add_action( 'after_setup_theme',                       array( $this, 'register_settings' ) );
89                 add_action( 'wp_loaded',                               array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
90                 add_action( 'customize_controls_init',                 array( $this, 'customize_controls_init' ) );
91                 add_action( 'customize_register',                      array( $this, 'schedule_customize_register' ), 1 );
92                 add_action( 'customize_controls_enqueue_scripts',      array( $this, 'enqueue_scripts' ) );
93                 add_action( 'customize_controls_print_styles',         array( $this, 'print_styles' ) );
94                 add_action( 'customize_controls_print_scripts',        array( $this, 'print_scripts' ) );
95                 add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_footer_scripts' ) );
96                 add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_widget_control_templates' ) );
97                 add_action( 'customize_preview_init',                  array( $this, 'customize_preview_init' ) );
98                 add_filter( 'customize_refresh_nonces',                array( $this, 'refresh_nonces' ) );
99
100                 add_action( 'dynamic_sidebar',                         array( $this, 'tally_rendered_widgets' ) );
101                 add_filter( 'is_active_sidebar',                       array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
102                 add_filter( 'dynamic_sidebar_has_widgets',             array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
103         }
104
105         /**
106          * Get the widget setting type given a setting ID.
107          *
108          * @since 4.2.0
109          * @access protected
110          *
111          * @staticvar array $cache
112          *
113          * @param $setting_id Setting ID.
114          * @return string|void Setting type.
115          */
116         protected function get_setting_type( $setting_id ) {
117                 static $cache = array();
118                 if ( isset( $cache[ $setting_id ] ) ) {
119                         return $cache[ $setting_id ];
120                 }
121                 foreach ( $this->setting_id_patterns as $type => $pattern ) {
122                         if ( preg_match( $pattern, $setting_id ) ) {
123                                 $cache[ $setting_id ] = $type;
124                                 return $type;
125                         }
126                 }
127         }
128
129         /**
130          * Inspect the incoming customized data for any widget settings, and dynamically add them up-front so widgets will be initialized properly.
131          *
132          * @since 4.2.0
133          * @access public
134          */
135         public function register_settings() {
136                 $widget_setting_ids = array();
137                 $incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
138                 foreach ( $incoming_setting_ids as $setting_id ) {
139                         if ( ! is_null( $this->get_setting_type( $setting_id ) ) ) {
140                                 $widget_setting_ids[] = $setting_id;
141                         }
142                 }
143                 if ( $this->manager->doing_ajax( 'update-widget' ) && isset( $_REQUEST['widget-id'] ) ) {
144                         $widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
145                 }
146
147                 $settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
148
149                 /*
150                  * Preview settings right away so that widgets and sidebars will get registered properly.
151                  * But don't do this if a customize_save because this will cause WP to think there is nothing
152                  * changed that needs to be saved.
153                  */
154                 if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
155                         foreach ( $settings as $setting ) {
156                                 $setting->preview();
157                         }
158                 }
159         }
160
161         /**
162          * Determine the arguments for a dynamically-created setting.
163          *
164          * @since 4.2.0
165          * @access public
166          *
167          * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
168          * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
169          * @return false|array Setting arguments, false otherwise.
170          */
171         public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
172                 if ( $this->get_setting_type( $setting_id ) ) {
173                         $args = $this->get_setting_args( $setting_id );
174                 }
175                 return $args;
176         }
177
178         /**
179          * Get an unslashed post value or return a default.
180          *
181          * @since 3.9.0
182          *
183          * @access protected
184          *
185          * @param string $name    Post value.
186          * @param mixed  $default Default post value.
187          * @return mixed Unslashed post value or default value.
188          */
189         protected function get_post_value( $name, $default = null ) {
190                 if ( ! isset( $_POST[ $name ] ) ) {
191                         return $default;
192                 }
193
194                 return wp_unslash( $_POST[ $name ] );
195         }
196
197         /**
198          * Override sidebars_widgets for theme switch.
199          *
200          * When switching a theme via the Customizer, supply any previously-configured
201          * sidebars_widgets from the target theme as the initial sidebars_widgets
202          * setting. Also store the old theme's existing settings so that they can
203          * be passed along for storing in the sidebars_widgets theme_mod when the
204          * theme gets switched.
205          *
206          * @since 3.9.0
207          * @access public
208          *
209          * @global array $sidebars_widgets
210          * @global array $_wp_sidebars_widgets
211          */
212         public function override_sidebars_widgets_for_theme_switch() {
213                 global $sidebars_widgets;
214
215                 if ( $this->manager->doing_ajax() || $this->manager->is_theme_active() ) {
216                         return;
217                 }
218
219                 $this->old_sidebars_widgets = wp_get_sidebars_widgets();
220                 add_filter( 'customize_value_old_sidebars_widgets_data', array( $this, 'filter_customize_value_old_sidebars_widgets_data' ) );
221
222                 // retrieve_widgets() looks at the global $sidebars_widgets
223                 $sidebars_widgets = $this->old_sidebars_widgets;
224                 $sidebars_widgets = retrieve_widgets( 'customize' );
225                 add_filter( 'option_sidebars_widgets', array( $this, 'filter_option_sidebars_widgets_for_theme_switch' ), 1 );
226                 // reset global cache var used by wp_get_sidebars_widgets()
227                 unset( $GLOBALS['_wp_sidebars_widgets'] );
228         }
229
230         /**
231          * Filter old_sidebars_widgets_data Customizer setting.
232          *
233          * When switching themes, filter the Customizer setting
234          * old_sidebars_widgets_data to supply initial $sidebars_widgets before they
235          * were overridden by retrieve_widgets(). The value for
236          * old_sidebars_widgets_data gets set in the old theme's sidebars_widgets
237          * theme_mod.
238          *
239          * @see WP_Customize_Widgets::handle_theme_switch()
240          * @since 3.9.0
241          * @access public
242          *
243          * @param array $old_sidebars_widgets
244          * @return array
245          */
246         public function filter_customize_value_old_sidebars_widgets_data( $old_sidebars_widgets ) {
247                 return $this->old_sidebars_widgets;
248         }
249
250         /**
251          * Filter sidebars_widgets option for theme switch.
252          *
253          * When switching themes, the retrieve_widgets() function is run when the
254          * Customizer initializes, and then the new sidebars_widgets here get
255          * supplied as the default value for the sidebars_widgets option.
256          *
257          * @see WP_Customize_Widgets::handle_theme_switch()
258          * @since 3.9.0
259          * @access public
260          *
261          * @global array $sidebars_widgets
262          *
263          * @param array $sidebars_widgets
264          * @return array
265          */
266         public function filter_option_sidebars_widgets_for_theme_switch( $sidebars_widgets ) {
267                 $sidebars_widgets = $GLOBALS['sidebars_widgets'];
268                 $sidebars_widgets['array_version'] = 3;
269                 return $sidebars_widgets;
270         }
271
272         /**
273          * Make sure all widgets get loaded into the Customizer.
274          *
275          * Note: these actions are also fired in wp_ajax_update_widget().
276          *
277          * @since 3.9.0
278          * @access public
279          */
280         public function customize_controls_init() {
281                 /** This action is documented in wp-admin/includes/ajax-actions.php */
282                 do_action( 'load-widgets.php' );
283
284                 /** This action is documented in wp-admin/includes/ajax-actions.php */
285                 do_action( 'widgets.php' );
286
287                 /** This action is documented in wp-admin/widgets.php */
288                 do_action( 'sidebar_admin_setup' );
289         }
290
291         /**
292          * Ensure widgets are available for all types of previews.
293          *
294          * When in preview, hook to 'customize_register' for settings
295          * after WordPress is loaded so that all filters have been
296          * initialized (e.g. Widget Visibility).
297          *
298          * @since 3.9.0
299          * @access public
300          */
301         public function schedule_customize_register() {
302                 if ( is_admin() ) {
303                         $this->customize_register();
304                 } else {
305                         add_action( 'wp', array( $this, 'customize_register' ) );
306                 }
307         }
308
309         /**
310          * Register Customizer settings and controls for all sidebars and widgets.
311          *
312          * @since 3.9.0
313          * @access public
314          *
315          * @global array $wp_registered_widgets
316          * @global array $wp_registered_widget_controls
317          * @global array $wp_registered_sidebars
318          */
319         public function customize_register() {
320                 global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_sidebars;
321
322                 $sidebars_widgets = array_merge(
323                         array( 'wp_inactive_widgets' => array() ),
324                         array_fill_keys( array_keys( $wp_registered_sidebars ), array() ),
325                         wp_get_sidebars_widgets()
326                 );
327
328                 $new_setting_ids = array();
329
330                 /*
331                  * Register a setting for all widgets, including those which are active,
332                  * inactive, and orphaned since a widget may get suppressed from a sidebar
333                  * via a plugin (like Widget Visibility).
334                  */
335                 foreach ( array_keys( $wp_registered_widgets ) as $widget_id ) {
336                         $setting_id   = $this->get_setting_id( $widget_id );
337                         $setting_args = $this->get_setting_args( $setting_id );
338                         if ( ! $this->manager->get_setting( $setting_id ) ) {
339                                 $this->manager->add_setting( $setting_id, $setting_args );
340                         }
341                         $new_setting_ids[] = $setting_id;
342                 }
343
344                 /*
345                  * Add a setting which will be supplied for the theme's sidebars_widgets
346                  * theme_mod when the the theme is switched.
347                  */
348                 if ( ! $this->manager->is_theme_active() ) {
349                         $setting_id = 'old_sidebars_widgets_data';
350                         $setting_args = $this->get_setting_args( $setting_id, array(
351                                 'type' => 'global_variable',
352                                 'dirty' => true,
353                         ) );
354                         $this->manager->add_setting( $setting_id, $setting_args );
355                 }
356
357                 $this->manager->add_panel( 'widgets', array(
358                         'title'       => __( 'Widgets' ),
359                         'description' => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
360                         'priority'    => 110,
361                 ) );
362
363                 foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
364                         if ( empty( $sidebar_widget_ids ) ) {
365                                 $sidebar_widget_ids = array();
366                         }
367
368                         $is_registered_sidebar = isset( $wp_registered_sidebars[ $sidebar_id ] );
369                         $is_inactive_widgets   = ( 'wp_inactive_widgets' === $sidebar_id );
370                         $is_active_sidebar     = ( $is_registered_sidebar && ! $is_inactive_widgets );
371
372                         // Add setting for managing the sidebar's widgets.
373                         if ( $is_registered_sidebar || $is_inactive_widgets ) {
374                                 $setting_id   = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
375                                 $setting_args = $this->get_setting_args( $setting_id );
376                                 if ( ! $this->manager->get_setting( $setting_id ) ) {
377                                         if ( ! $this->manager->is_theme_active() ) {
378                                                 $setting_args['dirty'] = true;
379                                         }
380                                         $this->manager->add_setting( $setting_id, $setting_args );
381                                 }
382                                 $new_setting_ids[] = $setting_id;
383
384                                 // Add section to contain controls.
385                                 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id );
386                                 if ( $is_active_sidebar ) {
387
388                                         $section_args = array(
389                                                 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],
390                                                 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],
391                                                 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
392                                                 'panel' => 'widgets',
393                                                 'sidebar_id' => $sidebar_id,
394                                         );
395
396                                         /**
397                                          * Filter Customizer widget section arguments for a given sidebar.
398                                          *
399                                          * @since 3.9.0
400                                          *
401                                          * @param array      $section_args Array of Customizer widget section arguments.
402                                          * @param string     $section_id   Customizer section ID.
403                                          * @param int|string $sidebar_id   Sidebar ID.
404                                          */
405                                         $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id );
406
407                                         $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args );
408                                         $this->manager->add_section( $section );
409
410                                         $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array(
411                                                 'section'    => $section_id,
412                                                 'sidebar_id' => $sidebar_id,
413                                                 'priority'   => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end.
414                                         ) );
415                                         $new_setting_ids[] = $setting_id;
416
417                                         $this->manager->add_control( $control );
418                                 }
419                         }
420
421                         // Add a control for each active widget (located in a sidebar).
422                         foreach ( $sidebar_widget_ids as $i => $widget_id ) {
423
424                                 // Skip widgets that may have gone away due to a plugin being deactivated.
425                                 if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[$widget_id] ) ) {
426                                         continue;
427                                 }
428
429                                 $registered_widget = $wp_registered_widgets[$widget_id];
430                                 $setting_id        = $this->get_setting_id( $widget_id );
431                                 $id_base           = $wp_registered_widget_controls[$widget_id]['id_base'];
432
433                                 $control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array(
434                                         'label'          => $registered_widget['name'],
435                                         'section'        => $section_id,
436                                         'sidebar_id'     => $sidebar_id,
437                                         'widget_id'      => $widget_id,
438                                         'widget_id_base' => $id_base,
439                                         'priority'       => $i,
440                                         'width'          => $wp_registered_widget_controls[$widget_id]['width'],
441                                         'height'         => $wp_registered_widget_controls[$widget_id]['height'],
442                                         'is_wide'        => $this->is_wide_widget( $widget_id ),
443                                 ) );
444                                 $this->manager->add_control( $control );
445                         }
446                 }
447
448                 if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
449                         foreach ( $new_setting_ids as $new_setting_id ) {
450                                 $this->manager->get_setting( $new_setting_id )->preview();
451                         }
452                 }
453
454                 add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
455         }
456
457         /**
458          * Covert a widget_id into its corresponding Customizer setting ID (option name).
459          *
460          * @since 3.9.0
461          * @access public
462          *
463          * @param string $widget_id Widget ID.
464          * @return string Maybe-parsed widget ID.
465          */
466         public function get_setting_id( $widget_id ) {
467                 $parsed_widget_id = $this->parse_widget_id( $widget_id );
468                 $setting_id       = sprintf( 'widget_%s', $parsed_widget_id['id_base'] );
469
470                 if ( ! is_null( $parsed_widget_id['number'] ) ) {
471                         $setting_id .= sprintf( '[%d]', $parsed_widget_id['number'] );
472                 }
473                 return $setting_id;
474         }
475
476         /**
477          * Determine whether the widget is considered "wide".
478          *
479          * Core widgets which may have controls wider than 250, but can
480          * still be shown in the narrow Customizer panel. The RSS and Text
481          * widgets in Core, for example, have widths of 400 and yet they
482          * still render fine in the Customizer panel. This method will
483          * return all Core widgets as being not wide, but this can be
484          * overridden with the is_wide_widget_in_customizer filter.
485          *
486          * @since 3.9.0
487          * @access public
488          *
489          * @global $wp_registered_widget_controls
490          *
491          * @param string $widget_id Widget ID.
492          * @return bool Whether or not the widget is a "wide" widget.
493          */
494         public function is_wide_widget( $widget_id ) {
495                 global $wp_registered_widget_controls;
496
497                 $parsed_widget_id = $this->parse_widget_id( $widget_id );
498                 $width            = $wp_registered_widget_controls[$widget_id]['width'];
499                 $is_core          = in_array( $parsed_widget_id['id_base'], $this->core_widget_id_bases );
500                 $is_wide          = ( $width > 250 && ! $is_core );
501
502                 /**
503                  * Filter whether the given widget is considered "wide".
504                  *
505                  * @since 3.9.0
506                  *
507                  * @param bool   $is_wide   Whether the widget is wide, Default false.
508                  * @param string $widget_id Widget ID.
509                  */
510                 return apply_filters( 'is_wide_widget_in_customizer', $is_wide, $widget_id );
511         }
512
513         /**
514          * Covert a widget ID into its id_base and number components.
515          *
516          * @since 3.9.0
517          * @access public
518          *
519          * @param string $widget_id Widget ID.
520          * @return array Array containing a widget's id_base and number components.
521          */
522         public function parse_widget_id( $widget_id ) {
523                 $parsed = array(
524                         'number' => null,
525                         'id_base' => null,
526                 );
527
528                 if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
529                         $parsed['id_base'] = $matches[1];
530                         $parsed['number']  = intval( $matches[2] );
531                 } else {
532                         // likely an old single widget
533                         $parsed['id_base'] = $widget_id;
534                 }
535                 return $parsed;
536         }
537
538         /**
539          * Convert a widget setting ID (option path) to its id_base and number components.
540          *
541          * @since 3.9.0
542          * @access public
543          *
544          * @param string $setting_id Widget setting ID.
545          * @return WP_Error|array Array containing a widget's id_base and number components,
546          *                        or a WP_Error object.
547          */
548         public function parse_widget_setting_id( $setting_id ) {
549                 if ( ! preg_match( '/^(widget_(.+?))(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
550                         return new WP_Error( 'widget_setting_invalid_id' );
551                 }
552
553                 $id_base = $matches[2];
554                 $number  = isset( $matches[3] ) ? intval( $matches[3] ) : null;
555
556                 return compact( 'id_base', 'number' );
557         }
558
559         /**
560          * Call admin_print_styles-widgets.php and admin_print_styles hooks to
561          * allow custom styles from plugins.
562          *
563          * @since 3.9.0
564          * @access public
565          */
566         public function print_styles() {
567                 /** This action is documented in wp-admin/admin-header.php */
568                 do_action( 'admin_print_styles-widgets.php' );
569
570                 /** This action is documented in wp-admin/admin-header.php */
571                 do_action( 'admin_print_styles' );
572         }
573
574         /**
575          * Call admin_print_scripts-widgets.php and admin_print_scripts hooks to
576          * allow custom scripts from plugins.
577          *
578          * @since 3.9.0
579          * @access public
580          */
581         public function print_scripts() {
582                 /** This action is documented in wp-admin/admin-header.php */
583                 do_action( 'admin_print_scripts-widgets.php' );
584
585                 /** This action is documented in wp-admin/admin-header.php */
586                 do_action( 'admin_print_scripts' );
587         }
588
589         /**
590          * Enqueue scripts and styles for Customizer panel and export data to JavaScript.
591          *
592          * @since 3.9.0
593          * @access public
594          *
595          * @global WP_Scripts $wp_scripts
596          * @global array $wp_registered_sidebars
597          * @global array $wp_registered_widgets
598          */
599         public function enqueue_scripts() {
600                 global $wp_scripts, $wp_registered_sidebars, $wp_registered_widgets;
601
602                 wp_enqueue_style( 'customize-widgets' );
603                 wp_enqueue_script( 'customize-widgets' );
604
605                 /** This action is documented in wp-admin/admin-header.php */
606                 do_action( 'admin_enqueue_scripts', 'widgets.php' );
607
608                 /*
609                  * Export available widgets with control_tpl removed from model
610                  * since plugins need templates to be in the DOM.
611                  */
612                 $available_widgets = array();
613
614                 foreach ( $this->get_available_widgets() as $available_widget ) {
615                         unset( $available_widget['control_tpl'] );
616                         $available_widgets[] = $available_widget;
617                 }
618
619                 $widget_reorder_nav_tpl = sprintf(
620                         '<div class="widget-reorder-nav"><span class="move-widget" tabindex="0">%1$s</span><span class="move-widget-down" tabindex="0">%2$s</span><span class="move-widget-up" tabindex="0">%3$s</span></div>',
621                         __( 'Move to another area&hellip;' ),
622                         __( 'Move down' ),
623                         __( 'Move up' )
624                 );
625
626                 $move_widget_area_tpl = str_replace(
627                         array( '{description}', '{btn}' ),
628                         array(
629                                 __( 'Select an area to move this widget into:' ),
630                                 _x( 'Move', 'Move widget' ),
631                         ),
632                         '<div class="move-widget-area">
633                                 <p class="description">{description}</p>
634                                 <ul class="widget-area-select">
635                                         <% _.each( sidebars, function ( sidebar ){ %>
636                                                 <li class="" data-id="<%- sidebar.id %>" title="<%- sidebar.description %>" tabindex="0"><%- sidebar.name %></li>
637                                         <% }); %>
638                                 </ul>
639                                 <div class="move-widget-actions">
640                                         <button class="move-widget-btn button-secondary" type="button">{btn}</button>
641                                 </div>
642                         </div>'
643                 );
644
645                 $settings = array(
646                         'nonce'                => wp_create_nonce( 'update-widget' ),
647                         'registeredSidebars'   => array_values( $wp_registered_sidebars ),
648                         'registeredWidgets'    => $wp_registered_widgets,
649                         'availableWidgets'     => $available_widgets, // @todo Merge this with registered_widgets
650                         'l10n' => array(
651                                 'saveBtnLabel'     => __( 'Apply' ),
652                                 'saveBtnTooltip'   => __( 'Save and preview changes before publishing them.' ),
653                                 'removeBtnLabel'   => __( 'Remove' ),
654                                 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ),
655                                 'error'            => __( 'An error has occurred. Please reload the page and try again.' ),
656                                 'widgetMovedUp'    => __( 'Widget moved up' ),
657                                 'widgetMovedDown'  => __( 'Widget moved down' ),
658                         ),
659                         'tpl' => array(
660                                 'widgetReorderNav' => $widget_reorder_nav_tpl,
661                                 'moveWidgetArea'   => $move_widget_area_tpl,
662                         ),
663                 );
664
665                 foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
666                         unset( $registered_widget['callback'] ); // may not be JSON-serializeable
667                 }
668
669                 $wp_scripts->add_data(
670                         'customize-widgets',
671                         'data',
672                         sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) )
673                 );
674         }
675
676         /**
677          * Render the widget form control templates into the DOM.
678          *
679          * @since 3.9.0
680          * @access public
681          */
682         public function output_widget_control_templates() {
683                 ?>
684                 <div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
685                 <div id="available-widgets">
686                         <div class="customize-section-title">
687                                 <button class="customize-section-back" tabindex="-1">
688                                         <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
689                                 </button>
690                                 <h3>
691                                         <span class="customize-action"><?php
692                                                 /* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
693                                                 echo sprintf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'widgets' )->title ) );
694                                         ?></span>
695                                         <?php _e( 'Add a Widget' ); ?>
696                                 </h3>
697                         </div>
698                         <div id="available-widgets-filter">
699                                 <label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
700                                 <input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets&hellip;' ) ?>" />
701                         </div>
702                         <div id="available-widgets-list">
703                         <?php foreach ( $this->get_available_widgets() as $available_widget ): ?>
704                                 <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">
705                                         <?php echo $available_widget['control_tpl']; ?>
706                                 </div>
707                         <?php endforeach; ?>
708                         </div><!-- #available-widgets-list -->
709                 </div><!-- #available-widgets -->
710                 </div><!-- #widgets-left -->
711                 <?php
712         }
713
714         /**
715          * Call admin_print_footer_scripts and admin_print_scripts hooks to
716          * allow custom scripts from plugins.
717          *
718          * @since 3.9.0
719          * @access public
720          */
721         public function print_footer_scripts() {
722                 /** This action is documented in wp-admin/admin-footer.php */
723                 do_action( 'admin_print_footer_scripts' );
724
725                 /** This action is documented in wp-admin/admin-footer.php */
726                 do_action( 'admin_footer-widgets.php' );
727         }
728
729         /**
730          * Get common arguments to supply when constructing a Customizer setting.
731          *
732          * @since 3.9.0
733          * @access public
734          *
735          * @param string $id        Widget setting ID.
736          * @param array  $overrides Array of setting overrides.
737          * @return array Possibly modified setting arguments.
738          */
739         public function get_setting_args( $id, $overrides = array() ) {
740                 $args = array(
741                         'type'       => 'option',
742                         'capability' => 'edit_theme_options',
743                         'transport'  => 'refresh',
744                         'default'    => array(),
745                 );
746
747                 if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
748                         $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
749                         $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
750                 } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
751                         $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
752                         $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
753                 }
754
755                 $args = array_merge( $args, $overrides );
756
757                 /**
758                  * Filter the common arguments supplied when constructing a Customizer setting.
759                  *
760                  * @since 3.9.0
761                  *
762                  * @see WP_Customize_Setting
763                  *
764                  * @param array  $args Array of Customizer setting arguments.
765                  * @param string $id   Widget setting ID.
766                  */
767                 return apply_filters( 'widget_customizer_setting_args', $args, $id );
768         }
769
770         /**
771          * Make sure that sidebar widget arrays only ever contain widget IDS.
772          *
773          * Used as the 'sanitize_callback' for each $sidebars_widgets setting.
774          *
775          * @since 3.9.0
776          * @access public
777          *
778          * @param array $widget_ids Array of widget IDs.
779          * @return array Array of sanitized widget IDs.
780          */
781         public function sanitize_sidebar_widgets( $widget_ids ) {
782                 $widget_ids = array_map( 'strval', (array) $widget_ids );
783                 $sanitized_widget_ids = array();
784                 foreach ( $widget_ids as $widget_id ) {
785                         $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
786                 }
787                 return $sanitized_widget_ids;
788         }
789
790         /**
791          * Build up an index of all available widgets for use in Backbone models.
792          *
793          * @since 3.9.0
794          * @access public
795          *
796          * @global array $wp_registered_widgets
797          * @global array $wp_registered_widget_controls
798          * @staticvar array $available_widgets
799          *
800          * @see wp_list_widgets()
801          *
802          * @return array List of available widgets.
803          */
804         public function get_available_widgets() {
805                 static $available_widgets = array();
806                 if ( ! empty( $available_widgets ) ) {
807                         return $available_widgets;
808                 }
809
810                 global $wp_registered_widgets, $wp_registered_widget_controls;
811                 require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number()
812
813                 $sort = $wp_registered_widgets;
814                 usort( $sort, array( $this, '_sort_name_callback' ) );
815                 $done = array();
816
817                 foreach ( $sort as $widget ) {
818                         if ( in_array( $widget['callback'], $done, true ) ) { // We already showed this multi-widget
819                                 continue;
820                         }
821
822                         $sidebar = is_active_widget( $widget['callback'], $widget['id'], false, false );
823                         $done[]  = $widget['callback'];
824
825                         if ( ! isset( $widget['params'][0] ) ) {
826                                 $widget['params'][0] = array();
827                         }
828
829                         $available_widget = $widget;
830                         unset( $available_widget['callback'] ); // not serializable to JSON
831
832                         $args = array(
833                                 'widget_id'   => $widget['id'],
834                                 'widget_name' => $widget['name'],
835                                 '_display'    => 'template',
836                         );
837
838                         $is_disabled     = false;
839                         $is_multi_widget = ( isset( $wp_registered_widget_controls[$widget['id']]['id_base'] ) && isset( $widget['params'][0]['number'] ) );
840                         if ( $is_multi_widget ) {
841                                 $id_base            = $wp_registered_widget_controls[$widget['id']]['id_base'];
842                                 $args['_temp_id']   = "$id_base-__i__";
843                                 $args['_multi_num'] = next_widget_id_number( $id_base );
844                                 $args['_add']       = 'multi';
845                         } else {
846                                 $args['_add'] = 'single';
847
848                                 if ( $sidebar && 'wp_inactive_widgets' !== $sidebar ) {
849                                         $is_disabled = true;
850                                 }
851                                 $id_base = $widget['id'];
852                         }
853
854                         $list_widget_controls_args = wp_list_widget_controls_dynamic_sidebar( array( 0 => $args, 1 => $widget['params'][0] ) );
855                         $control_tpl = $this->get_widget_control( $list_widget_controls_args );
856
857                         // The properties here are mapped to the Backbone Widget model.
858                         $available_widget = array_merge( $available_widget, array(
859                                 'temp_id'      => isset( $args['_temp_id'] ) ? $args['_temp_id'] : null,
860                                 'is_multi'     => $is_multi_widget,
861                                 'control_tpl'  => $control_tpl,
862                                 'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
863                                 'is_disabled'  => $is_disabled,
864                                 'id_base'      => $id_base,
865                                 'transport'    => 'refresh',
866                                 'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
867                                 'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
868                                 'is_wide'      => $this->is_wide_widget( $widget['id'] ),
869                         ) );
870
871                         $available_widgets[] = $available_widget;
872                 }
873
874                 return $available_widgets;
875         }
876
877         /**
878          * Naturally order available widgets by name.
879          *
880          * @since 3.9.0
881          * @access protected
882          *
883          * @param array $widget_a The first widget to compare.
884          * @param array $widget_b The second widget to compare.
885          * @return int Reorder position for the current widget comparison.
886          */
887         protected function _sort_name_callback( $widget_a, $widget_b ) {
888                 return strnatcasecmp( $widget_a['name'], $widget_b['name'] );
889         }
890
891         /**
892          * Get the widget control markup.
893          *
894          * @since 3.9.0
895          * @access public
896          *
897          * @param array $args Widget control arguments.
898          * @return string Widget control form HTML markup.
899          */
900         public function get_widget_control( $args ) {
901                 ob_start();
902
903                 call_user_func_array( 'wp_widget_control', $args );
904                 $replacements = array(
905                         '<form method="post">' => '<div class="form">',
906                         '</form>' => '</div><!-- .form -->',
907                 );
908
909                 $control_tpl = ob_get_clean();
910
911                 $control_tpl = str_replace( array_keys( $replacements ), array_values( $replacements ), $control_tpl );
912
913                 return $control_tpl;
914         }
915
916         /**
917          * Add hooks for the Customizer preview.
918          *
919          * @since 3.9.0
920          * @access public
921          */
922         public function customize_preview_init() {
923                 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
924                 add_action( 'wp_print_styles',    array( $this, 'print_preview_css' ), 1 );
925                 add_action( 'wp_footer',          array( $this, 'export_preview_data' ), 20 );
926         }
927
928         /**
929          * Refresh nonce for widget updates.
930          *
931          * @since 4.2.0
932          * @access public
933          *
934          * @param  array $nonces Array of nonces.
935          * @return array $nonces Array of nonces.
936          */
937         public function refresh_nonces( $nonces ) {
938                 $nonces['update-widget'] = wp_create_nonce( 'update-widget' );
939                 return $nonces;
940         }
941
942         /**
943          * When previewing, make sure the proper previewing widgets are used.
944          *
945          * Because wp_get_sidebars_widgets() gets called early at init
946          * (via wp_convert_widget_settings()) and can set global variable
947          * $_wp_sidebars_widgets to the value of get_option( 'sidebars_widgets' )
948          * before the Customizer preview filter is added, we have to reset
949          * it after the filter has been added.
950          *
951          * @since 3.9.0
952          * @access public
953          *
954          * @param array $sidebars_widgets List of widgets for the current sidebar.
955          * @return array
956          */
957         public function preview_sidebars_widgets( $sidebars_widgets ) {
958                 $sidebars_widgets = get_option( 'sidebars_widgets' );
959
960                 unset( $sidebars_widgets['array_version'] );
961                 return $sidebars_widgets;
962         }
963
964         /**
965          * Enqueue scripts for the Customizer preview.
966          *
967          * @since 3.9.0
968          * @access public
969          */
970         public function customize_preview_enqueue() {
971                 wp_enqueue_script( 'customize-preview-widgets' );
972         }
973
974         /**
975          * Insert default style for highlighted widget at early point so theme
976          * stylesheet can override.
977          *
978          * @since 3.9.0
979          * @access public
980          *
981          * @action wp_print_styles
982          */
983         public function print_preview_css() {
984                 ?>
985                 <style>
986                 .widget-customizer-highlighted-widget {
987                         outline: none;
988                         -webkit-box-shadow: 0 0 2px rgba(30,140,190,0.8);
989                         box-shadow: 0 0 2px rgba(30,140,190,0.8);
990                         position: relative;
991                         z-index: 1;
992                 }
993                 </style>
994                 <?php
995         }
996
997         /**
998          * At the very end of the page, at the very end of the wp_footer,
999          * communicate the sidebars that appeared on the page.
1000          *
1001          * @since 3.9.0
1002          * @access public
1003      *
1004          * @global array $wp_registered_sidebars
1005          * @global array $wp_registered_widgets
1006          */
1007         public function export_preview_data() {
1008                 global $wp_registered_sidebars, $wp_registered_widgets;
1009                 // Prepare Customizer settings to pass to JavaScript.
1010                 $settings = array(
1011                         'renderedSidebars'   => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
1012                         'renderedWidgets'    => array_fill_keys( array_keys( $this->rendered_widgets ), true ),
1013                         'registeredSidebars' => array_values( $wp_registered_sidebars ),
1014                         'registeredWidgets'  => $wp_registered_widgets,
1015                         'l10n'               => array(
1016                                 'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
1017                         ),
1018                 );
1019                 foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
1020                         unset( $registered_widget['callback'] ); // may not be JSON-serializeable
1021                 }
1022
1023                 ?>
1024                 <script type="text/javascript">
1025                         var _wpWidgetCustomizerPreviewSettings = <?php echo wp_json_encode( $settings ); ?>;
1026                 </script>
1027                 <?php
1028         }
1029
1030         /**
1031          * Keep track of the widgets that were rendered.
1032          *
1033          * @since 3.9.0
1034          * @access public
1035          *
1036          * @param array $widget Rendered widget to tally.
1037          */
1038         public function tally_rendered_widgets( $widget ) {
1039                 $this->rendered_widgets[ $widget['id'] ] = true;
1040         }
1041
1042         /**
1043          * Determine if a widget is rendered on the page.
1044          *
1045          * @since 4.0.0
1046          * @access public
1047          *
1048          * @param string $widget_id Widget ID to check.
1049          * @return bool Whether the widget is rendered.
1050          */
1051         public function is_widget_rendered( $widget_id ) {
1052                 return in_array( $widget_id, $this->rendered_widgets );
1053         }
1054
1055         /**
1056          * Determine if a sidebar is rendered on the page.
1057          *
1058          * @since 4.0.0
1059          * @access public
1060          *
1061          * @param string $sidebar_id Sidebar ID to check.
1062          * @return bool Whether the sidebar is rendered.
1063          */
1064         public function is_sidebar_rendered( $sidebar_id ) {
1065                 return in_array( $sidebar_id, $this->rendered_sidebars );
1066         }
1067
1068         /**
1069          * Tally the sidebars rendered via is_active_sidebar().
1070          *
1071          * Keep track of the times that is_active_sidebar() is called
1072          * in the template, and assume that this means that the sidebar
1073          * would be rendered on the template if there were widgets
1074          * populating it.
1075          *
1076          * @since 3.9.0
1077          * @access public
1078          *
1079          * @global array $wp_registered_sidebars
1080          *
1081          * @param bool   $is_active  Whether the sidebar is active.
1082          * @param string $sidebar_id Sidebar ID.
1083          * @return bool
1084          */
1085         public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
1086                 if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
1087                         $this->rendered_sidebars[] = $sidebar_id;
1088                 }
1089                 /*
1090                  * We may need to force this to true, and also force-true the value
1091                  * for 'dynamic_sidebar_has_widgets' if we want to ensure that there
1092                  * is an area to drop widgets into, if the sidebar is empty.
1093                  */
1094                 return $is_active;
1095         }
1096
1097         /**
1098          * Tally the sidebars rendered via dynamic_sidebar().
1099          *
1100          * Keep track of the times that dynamic_sidebar() is called in the template,
1101          * and assume this means the sidebar would be rendered on the template if
1102          * there were widgets populating it.
1103          *
1104          * @since 3.9.0
1105          * @access public
1106          *
1107          * @global array $wp_registered_sidebars
1108          *
1109          * @param bool   $has_widgets Whether the current sidebar has widgets.
1110          * @param string $sidebar_id  Sidebar ID.
1111          * @return bool
1112          */
1113         public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
1114                 if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
1115                         $this->rendered_sidebars[] = $sidebar_id;
1116                 }
1117
1118                 /*
1119                  * We may need to force this to true, and also force-true the value
1120                  * for 'is_active_sidebar' if we want to ensure there is an area to
1121                  * drop widgets into, if the sidebar is empty.
1122                  */
1123                 return $has_widgets;
1124         }
1125
1126         /**
1127          * Get MAC for a serialized widget instance string.
1128          *
1129          * Allows values posted back from JS to be rejected if any tampering of the
1130          * data has occurred.
1131          *
1132          * @since 3.9.0
1133          * @access protected
1134          *
1135          * @param string $serialized_instance Widget instance.
1136          * @return string MAC for serialized widget instance.
1137          */
1138         protected function get_instance_hash_key( $serialized_instance ) {
1139                 return wp_hash( $serialized_instance );
1140         }
1141
1142         /**
1143          * Sanitize a widget instance.
1144          *
1145          * Unserialize the JS-instance for storing in the options. It's important
1146          * that this filter only get applied to an instance once.
1147          *
1148          * @since 3.9.0
1149          * @access public
1150          *
1151          * @param array $value Widget instance to sanitize.
1152          * @return array|void Sanitized widget instance.
1153          */
1154         public function sanitize_widget_instance( $value ) {
1155                 if ( $value === array() ) {
1156                         return $value;
1157                 }
1158
1159                 if ( empty( $value['is_widget_customizer_js_value'] )
1160                         || empty( $value['instance_hash_key'] )
1161                         || empty( $value['encoded_serialized_instance'] ) )
1162                 {
1163                         return;
1164                 }
1165
1166                 $decoded = base64_decode( $value['encoded_serialized_instance'], true );
1167                 if ( false === $decoded ) {
1168                         return;
1169                 }
1170
1171                 if ( ! hash_equals( $this->get_instance_hash_key( $decoded ), $value['instance_hash_key'] ) ) {
1172                         return;
1173                 }
1174
1175                 $instance = unserialize( $decoded );
1176                 if ( false === $instance ) {
1177                         return;
1178                 }
1179
1180                 return $instance;
1181         }
1182
1183         /**
1184          * Convert widget instance into JSON-representable format.
1185          *
1186          * @since 3.9.0
1187          * @access public
1188          *
1189          * @param array $value Widget instance to convert to JSON.
1190          * @return array JSON-converted widget instance.
1191          */
1192         public function sanitize_widget_js_instance( $value ) {
1193                 if ( empty( $value['is_widget_customizer_js_value'] ) ) {
1194                         $serialized = serialize( $value );
1195
1196                         $value = array(
1197                                 'encoded_serialized_instance'   => base64_encode( $serialized ),
1198                                 'title'                         => empty( $value['title'] ) ? '' : $value['title'],
1199                                 'is_widget_customizer_js_value' => true,
1200                                 'instance_hash_key'             => $this->get_instance_hash_key( $serialized ),
1201                         );
1202                 }
1203                 return $value;
1204         }
1205
1206         /**
1207          * Strip out widget IDs for widgets which are no longer registered.
1208          *
1209          * One example where this might happen is when a plugin orphans a widget
1210          * in a sidebar upon deactivation.
1211          *
1212          * @since 3.9.0
1213          * @access public
1214          *
1215          * @global array $wp_registered_widgets
1216          *
1217          * @param array $widget_ids List of widget IDs.
1218          * @return array Parsed list of widget IDs.
1219          */
1220         public function sanitize_sidebar_widgets_js_instance( $widget_ids ) {
1221                 global $wp_registered_widgets;
1222                 $widget_ids = array_values( array_intersect( $widget_ids, array_keys( $wp_registered_widgets ) ) );
1223                 return $widget_ids;
1224         }
1225
1226         /**
1227          * Find and invoke the widget update and control callbacks.
1228          *
1229          * Requires that $_POST be populated with the instance data.
1230          *
1231          * @since 3.9.0
1232          * @access public
1233          *
1234          * @global array $wp_registered_widget_updates
1235          * @global array $wp_registered_widget_controls
1236          *
1237          * @param  string $widget_id Widget ID.
1238          * @return WP_Error|array Array containing the updated widget information.
1239          *                        A WP_Error object, otherwise.
1240          */
1241         public function call_widget_update( $widget_id ) {
1242                 global $wp_registered_widget_updates, $wp_registered_widget_controls;
1243
1244                 $setting_id = $this->get_setting_id( $widget_id );
1245
1246                 /*
1247                  * Make sure that other setting changes have previewed since this widget
1248                  * may depend on them (e.g. Menus being present for Custom Menu widget).
1249                  */
1250                 if ( ! did_action( 'customize_preview_init' ) ) {
1251                         foreach ( $this->manager->settings() as $setting ) {
1252                                 if ( $setting->id !== $setting_id ) {
1253                                         $setting->preview();
1254                                 }
1255                         }
1256                 }
1257
1258                 $this->start_capturing_option_updates();
1259                 $parsed_id   = $this->parse_widget_id( $widget_id );
1260                 $option_name = 'widget_' . $parsed_id['id_base'];
1261
1262                 /*
1263                  * If a previously-sanitized instance is provided, populate the input vars
1264                  * with its values so that the widget update callback will read this instance
1265                  */
1266                 $added_input_vars = array();
1267                 if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
1268                         $sanitized_widget_setting = json_decode( $this->get_post_value( 'sanitized_widget_setting' ), true );
1269                         if ( false === $sanitized_widget_setting ) {
1270                                 $this->stop_capturing_option_updates();
1271                                 return new WP_Error( 'widget_setting_malformed' );
1272                         }
1273
1274                         $instance = $this->sanitize_widget_instance( $sanitized_widget_setting );
1275                         if ( is_null( $instance ) ) {
1276                                 $this->stop_capturing_option_updates();
1277                                 return new WP_Error( 'widget_setting_unsanitized' );
1278                         }
1279
1280                         if ( ! is_null( $parsed_id['number'] ) ) {
1281                                 $value = array();
1282                                 $value[$parsed_id['number']] = $instance;
1283                                 $key = 'widget-' . $parsed_id['id_base'];
1284                                 $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
1285                                 $added_input_vars[] = $key;
1286                         } else {
1287                                 foreach ( $instance as $key => $value ) {
1288                                         $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
1289                                         $added_input_vars[] = $key;
1290                                 }
1291                         }
1292                 }
1293
1294                 // Invoke the widget update callback.
1295                 foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1296                         if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) {
1297                                 ob_start();
1298                                 call_user_func_array( $control['callback'], $control['params'] );
1299                                 ob_end_clean();
1300                                 break;
1301                         }
1302                 }
1303
1304                 // Clean up any input vars that were manually added
1305                 foreach ( $added_input_vars as $key ) {
1306                         unset( $_POST[ $key ] );
1307                         unset( $_REQUEST[ $key ] );
1308                 }
1309
1310                 // Make sure the expected option was updated.
1311                 if ( 0 !== $this->count_captured_options() ) {
1312                         if ( $this->count_captured_options() > 1 ) {
1313                                 $this->stop_capturing_option_updates();
1314                                 return new WP_Error( 'widget_setting_too_many_options' );
1315                         }
1316
1317                         $updated_option_name = key( $this->get_captured_options() );
1318                         if ( $updated_option_name !== $option_name ) {
1319                                 $this->stop_capturing_option_updates();
1320                                 return new WP_Error( 'widget_setting_unexpected_option' );
1321                         }
1322                 }
1323
1324                 // Obtain the widget instance.
1325                 $option = $this->get_captured_option( $option_name );
1326                 if ( null !== $parsed_id['number'] ) {
1327                         $instance = $option[ $parsed_id['number'] ];
1328                 } else {
1329                         $instance = $option;
1330                 }
1331
1332                 /*
1333                  * Override the incoming $_POST['customized'] for a newly-created widget's
1334                  * setting with the new $instance so that the preview filter currently
1335                  * in place from WP_Customize_Setting::preview() will use this value
1336                  * instead of the default widget instance value (an empty array).
1337                  */
1338                 $this->manager->set_post_value( $setting_id, $instance );
1339
1340                 // Obtain the widget control with the updated instance in place.
1341                 ob_start();
1342                 $form = $wp_registered_widget_controls[ $widget_id ];
1343                 if ( $form ) {
1344                         call_user_func_array( $form['callback'], $form['params'] );
1345                 }
1346                 $form = ob_get_clean();
1347
1348                 $this->stop_capturing_option_updates();
1349
1350                 return compact( 'instance', 'form' );
1351         }
1352
1353         /**
1354          * Update widget settings asynchronously.
1355          *
1356          * Allows the Customizer to update a widget using its form, but return the new
1357          * instance info via Ajax instead of saving it to the options table.
1358          *
1359          * Most code here copied from wp_ajax_save_widget()
1360          *
1361          * @since 3.9.0
1362          * @access public
1363          *
1364          * @see wp_ajax_save_widget()
1365          *
1366          */
1367         public function wp_ajax_update_widget() {
1368
1369                 if ( ! is_user_logged_in() ) {
1370                         wp_die( 0 );
1371                 }
1372
1373                 check_ajax_referer( 'update-widget', 'nonce' );
1374
1375                 if ( ! current_user_can( 'edit_theme_options' ) ) {
1376                         wp_die( -1 );
1377                 }
1378
1379                 if ( empty( $_POST['widget-id'] ) ) {
1380                         wp_send_json_error( 'missing_widget-id' );
1381                 }
1382
1383                 /** This action is documented in wp-admin/includes/ajax-actions.php */
1384                 do_action( 'load-widgets.php' );
1385
1386                 /** This action is documented in wp-admin/includes/ajax-actions.php */
1387                 do_action( 'widgets.php' );
1388
1389                 /** This action is documented in wp-admin/widgets.php */
1390                 do_action( 'sidebar_admin_setup' );
1391
1392                 $widget_id = $this->get_post_value( 'widget-id' );
1393                 $parsed_id = $this->parse_widget_id( $widget_id );
1394                 $id_base = $parsed_id['id_base'];
1395
1396                 $is_updating_widget_template = (
1397                         isset( $_POST[ 'widget-' . $id_base ] )
1398                         &&
1399                         is_array( $_POST[ 'widget-' . $id_base ] )
1400                         &&
1401                         preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) )
1402                 );
1403                 if ( $is_updating_widget_template ) {
1404                         wp_send_json_error( 'template_widget_not_updatable' );
1405                 }
1406
1407                 $updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
1408                 if ( is_wp_error( $updated_widget ) ) {
1409                         wp_send_json_error( $updated_widget->get_error_code() );
1410                 }
1411
1412                 $form = $updated_widget['form'];
1413                 $instance = $this->sanitize_widget_js_instance( $updated_widget['instance'] );
1414
1415                 wp_send_json_success( compact( 'form', 'instance' ) );
1416         }
1417
1418         /***************************************************************************
1419          * Option Update Capturing
1420          ***************************************************************************/
1421
1422         /**
1423          * List of captured widget option updates.
1424          *
1425          * @since 3.9.0
1426          * @access protected
1427          * @var array $_captured_options Values updated while option capture is happening.
1428          */
1429         protected $_captured_options = array();
1430
1431         /**
1432          * Whether option capture is currently happening.
1433          *
1434          * @since 3.9.0
1435          * @access protected
1436          * @var bool $_is_current Whether option capture is currently happening or not.
1437          */
1438         protected $_is_capturing_option_updates = false;
1439
1440         /**
1441          * Determine whether the captured option update should be ignored.
1442          *
1443          * @since 3.9.0
1444          * @access protected
1445          *
1446          * @param string $option_name Option name.
1447          * @return bool Whether the option capture is ignored.
1448          */
1449         protected function is_option_capture_ignored( $option_name ) {
1450                 return ( 0 === strpos( $option_name, '_transient_' ) );
1451         }
1452
1453         /**
1454          * Retrieve captured widget option updates.
1455          *
1456          * @since 3.9.0
1457          * @access protected
1458          *
1459          * @return array Array of captured options.
1460          */
1461         protected function get_captured_options() {
1462                 return $this->_captured_options;
1463         }
1464
1465         /**
1466          * Get the option that was captured from being saved.
1467          *
1468          * @since 4.2.0
1469          * @access protected
1470          *
1471          * @param string $option_name Option name.
1472          * @param mixed  $default     Optional. Default value to return if the option does not exist.
1473          * @return mixed Value set for the option.
1474          */
1475         protected function get_captured_option( $option_name, $default = false ) {
1476                 if ( array_key_exists( $option_name, $this->_captured_options ) ) {
1477                         $value = $this->_captured_options[ $option_name ];
1478                 } else {
1479                         $value = $default;
1480                 }
1481                 return $value;
1482         }
1483
1484         /**
1485          * Get the number of captured widget option updates.
1486          *
1487          * @since 3.9.0
1488          * @access protected
1489          *
1490          * @return int Number of updated options.
1491          */
1492         protected function count_captured_options() {
1493                 return count( $this->_captured_options );
1494         }
1495
1496         /**
1497          * Start keeping track of changes to widget options, caching new values.
1498          *
1499          * @since 3.9.0
1500          * @access protected
1501          */
1502         protected function start_capturing_option_updates() {
1503                 if ( $this->_is_capturing_option_updates ) {
1504                         return;
1505                 }
1506
1507                 $this->_is_capturing_option_updates = true;
1508
1509                 add_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
1510         }
1511
1512         /**
1513          * Pre-filter captured option values before updating.
1514          *
1515          * @since 3.9.0
1516          * @access public
1517          *
1518          * @param mixed  $new_value   The new option value.
1519          * @param string $option_name Name of the option.
1520          * @param mixed  $old_value   The old option value.
1521          * @return mixed Filtered option value.
1522          */
1523         public function capture_filter_pre_update_option( $new_value, $option_name, $old_value ) {
1524                 if ( $this->is_option_capture_ignored( $option_name ) ) {
1525                         return;
1526                 }
1527
1528                 if ( ! isset( $this->_captured_options[ $option_name ] ) ) {
1529                         add_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
1530                 }
1531
1532                 $this->_captured_options[ $option_name ] = $new_value;
1533
1534                 return $old_value;
1535         }
1536
1537         /**
1538          * Pre-filter captured option values before retrieving.
1539          *
1540          * @since 3.9.0
1541          * @access public
1542          *
1543          * @param mixed $value Value to return instead of the option value.
1544          * @return mixed Filtered option value.
1545          */
1546         public function capture_filter_pre_get_option( $value ) {
1547                 $option_name = preg_replace( '/^pre_option_/', '', current_filter() );
1548
1549                 if ( isset( $this->_captured_options[ $option_name ] ) ) {
1550                         $value = $this->_captured_options[ $option_name ];
1551
1552                         /** This filter is documented in wp-includes/option.php */
1553                         $value = apply_filters( 'option_' . $option_name, $value );
1554                 }
1555
1556                 return $value;
1557         }
1558
1559         /**
1560          * Undo any changes to the options since options capture began.
1561          *
1562          * @since 3.9.0
1563          * @access protected
1564          */
1565         protected function stop_capturing_option_updates() {
1566                 if ( ! $this->_is_capturing_option_updates ) {
1567                         return;
1568                 }
1569
1570                 remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
1571
1572                 foreach ( array_keys( $this->_captured_options ) as $option_name ) {
1573                         remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
1574                 }
1575
1576                 $this->_captured_options = array();
1577                 $this->_is_capturing_option_updates = false;
1578         }
1579
1580         /**
1581          * @since 3.9.0
1582          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1583          */
1584         public function setup_widget_addition_previews() {
1585                 _deprecated_function( __METHOD__, '4.2.0' );
1586         }
1587
1588         /**
1589          * @since 3.9.0
1590          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1591          */
1592         public function prepreview_added_sidebars_widgets() {
1593                 _deprecated_function( __METHOD__, '4.2.0' );
1594         }
1595
1596         /**
1597          * @since 3.9.0
1598          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1599          */
1600         public function prepreview_added_widget_instance() {
1601                 _deprecated_function( __METHOD__, '4.2.0' );
1602         }
1603
1604         /**
1605          * @since 3.9.0
1606          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1607          */
1608         public function remove_prepreview_filters() {
1609                 _deprecated_function( __METHOD__, '4.2.0' );
1610         }
1611 }