]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-wp-customize-widgets.php
WordPress 4.4.2
[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 $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                         'type'            => 'widgets',
359                         'title'           => __( 'Widgets' ),
360                         'description'     => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
361                         'priority'        => 110,
362                         'active_callback' => array( $this, 'is_panel_active' ),
363                 ) );
364
365                 foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
366                         if ( empty( $sidebar_widget_ids ) ) {
367                                 $sidebar_widget_ids = array();
368                         }
369
370                         $is_registered_sidebar = is_registered_sidebar( $sidebar_id );
371                         $is_inactive_widgets   = ( 'wp_inactive_widgets' === $sidebar_id );
372                         $is_active_sidebar     = ( $is_registered_sidebar && ! $is_inactive_widgets );
373
374                         // Add setting for managing the sidebar's widgets.
375                         if ( $is_registered_sidebar || $is_inactive_widgets ) {
376                                 $setting_id   = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
377                                 $setting_args = $this->get_setting_args( $setting_id );
378                                 if ( ! $this->manager->get_setting( $setting_id ) ) {
379                                         if ( ! $this->manager->is_theme_active() ) {
380                                                 $setting_args['dirty'] = true;
381                                         }
382                                         $this->manager->add_setting( $setting_id, $setting_args );
383                                 }
384                                 $new_setting_ids[] = $setting_id;
385
386                                 // Add section to contain controls.
387                                 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id );
388                                 if ( $is_active_sidebar ) {
389
390                                         $section_args = array(
391                                                 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],
392                                                 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],
393                                                 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
394                                                 'panel' => 'widgets',
395                                                 'sidebar_id' => $sidebar_id,
396                                         );
397
398                                         /**
399                                          * Filter Customizer widget section arguments for a given sidebar.
400                                          *
401                                          * @since 3.9.0
402                                          *
403                                          * @param array      $section_args Array of Customizer widget section arguments.
404                                          * @param string     $section_id   Customizer section ID.
405                                          * @param int|string $sidebar_id   Sidebar ID.
406                                          */
407                                         $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id );
408
409                                         $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args );
410                                         $this->manager->add_section( $section );
411
412                                         $control = new WP_Widget_Area_Customize_Control( $this->manager, $setting_id, array(
413                                                 'section'    => $section_id,
414                                                 'sidebar_id' => $sidebar_id,
415                                                 'priority'   => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end.
416                                         ) );
417                                         $new_setting_ids[] = $setting_id;
418
419                                         $this->manager->add_control( $control );
420                                 }
421                         }
422
423                         // Add a control for each active widget (located in a sidebar).
424                         foreach ( $sidebar_widget_ids as $i => $widget_id ) {
425
426                                 // Skip widgets that may have gone away due to a plugin being deactivated.
427                                 if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[$widget_id] ) ) {
428                                         continue;
429                                 }
430
431                                 $registered_widget = $wp_registered_widgets[$widget_id];
432                                 $setting_id        = $this->get_setting_id( $widget_id );
433                                 $id_base           = $wp_registered_widget_controls[$widget_id]['id_base'];
434
435                                 $control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array(
436                                         'label'          => $registered_widget['name'],
437                                         'section'        => $section_id,
438                                         'sidebar_id'     => $sidebar_id,
439                                         'widget_id'      => $widget_id,
440                                         'widget_id_base' => $id_base,
441                                         'priority'       => $i,
442                                         'width'          => $wp_registered_widget_controls[$widget_id]['width'],
443                                         'height'         => $wp_registered_widget_controls[$widget_id]['height'],
444                                         'is_wide'        => $this->is_wide_widget( $widget_id ),
445                                 ) );
446                                 $this->manager->add_control( $control );
447                         }
448                 }
449
450                 if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
451                         foreach ( $new_setting_ids as $new_setting_id ) {
452                                 $this->manager->get_setting( $new_setting_id )->preview();
453                         }
454                 }
455
456                 add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
457         }
458
459         /**
460          * Return whether the widgets panel is active, based on whether there are sidebars registered.
461          *
462          * @since 4.4.0
463          * @access public
464          *
465          * @see WP_Customize_Panel::$active_callback
466          *
467          * @global array $wp_registered_sidebars
468          * @return bool Active.
469          */
470         public function is_panel_active() {
471                 global $wp_registered_sidebars;
472                 return ! empty( $wp_registered_sidebars );
473         }
474
475         /**
476          * Covert a widget_id into its corresponding Customizer setting ID (option name).
477          *
478          * @since 3.9.0
479          * @access public
480          *
481          * @param string $widget_id Widget ID.
482          * @return string Maybe-parsed widget ID.
483          */
484         public function get_setting_id( $widget_id ) {
485                 $parsed_widget_id = $this->parse_widget_id( $widget_id );
486                 $setting_id       = sprintf( 'widget_%s', $parsed_widget_id['id_base'] );
487
488                 if ( ! is_null( $parsed_widget_id['number'] ) ) {
489                         $setting_id .= sprintf( '[%d]', $parsed_widget_id['number'] );
490                 }
491                 return $setting_id;
492         }
493
494         /**
495          * Determine whether the widget is considered "wide".
496          *
497          * Core widgets which may have controls wider than 250, but can
498          * still be shown in the narrow Customizer panel. The RSS and Text
499          * widgets in Core, for example, have widths of 400 and yet they
500          * still render fine in the Customizer panel. This method will
501          * return all Core widgets as being not wide, but this can be
502          * overridden with the is_wide_widget_in_customizer filter.
503          *
504          * @since 3.9.0
505          * @access public
506          *
507          * @global $wp_registered_widget_controls
508          *
509          * @param string $widget_id Widget ID.
510          * @return bool Whether or not the widget is a "wide" widget.
511          */
512         public function is_wide_widget( $widget_id ) {
513                 global $wp_registered_widget_controls;
514
515                 $parsed_widget_id = $this->parse_widget_id( $widget_id );
516                 $width            = $wp_registered_widget_controls[$widget_id]['width'];
517                 $is_core          = in_array( $parsed_widget_id['id_base'], $this->core_widget_id_bases );
518                 $is_wide          = ( $width > 250 && ! $is_core );
519
520                 /**
521                  * Filter whether the given widget is considered "wide".
522                  *
523                  * @since 3.9.0
524                  *
525                  * @param bool   $is_wide   Whether the widget is wide, Default false.
526                  * @param string $widget_id Widget ID.
527                  */
528                 return apply_filters( 'is_wide_widget_in_customizer', $is_wide, $widget_id );
529         }
530
531         /**
532          * Covert a widget ID into its id_base and number components.
533          *
534          * @since 3.9.0
535          * @access public
536          *
537          * @param string $widget_id Widget ID.
538          * @return array Array containing a widget's id_base and number components.
539          */
540         public function parse_widget_id( $widget_id ) {
541                 $parsed = array(
542                         'number' => null,
543                         'id_base' => null,
544                 );
545
546                 if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
547                         $parsed['id_base'] = $matches[1];
548                         $parsed['number']  = intval( $matches[2] );
549                 } else {
550                         // likely an old single widget
551                         $parsed['id_base'] = $widget_id;
552                 }
553                 return $parsed;
554         }
555
556         /**
557          * Convert a widget setting ID (option path) to its id_base and number components.
558          *
559          * @since 3.9.0
560          * @access public
561          *
562          * @param string $setting_id Widget setting ID.
563          * @return WP_Error|array Array containing a widget's id_base and number components,
564          *                        or a WP_Error object.
565          */
566         public function parse_widget_setting_id( $setting_id ) {
567                 if ( ! preg_match( '/^(widget_(.+?))(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
568                         return new WP_Error( 'widget_setting_invalid_id' );
569                 }
570
571                 $id_base = $matches[2];
572                 $number  = isset( $matches[3] ) ? intval( $matches[3] ) : null;
573
574                 return compact( 'id_base', 'number' );
575         }
576
577         /**
578          * Call admin_print_styles-widgets.php and admin_print_styles hooks to
579          * allow custom styles from plugins.
580          *
581          * @since 3.9.0
582          * @access public
583          */
584         public function print_styles() {
585                 /** This action is documented in wp-admin/admin-header.php */
586                 do_action( 'admin_print_styles-widgets.php' );
587
588                 /** This action is documented in wp-admin/admin-header.php */
589                 do_action( 'admin_print_styles' );
590         }
591
592         /**
593          * Call admin_print_scripts-widgets.php and admin_print_scripts hooks to
594          * allow custom scripts from plugins.
595          *
596          * @since 3.9.0
597          * @access public
598          */
599         public function print_scripts() {
600                 /** This action is documented in wp-admin/admin-header.php */
601                 do_action( 'admin_print_scripts-widgets.php' );
602
603                 /** This action is documented in wp-admin/admin-header.php */
604                 do_action( 'admin_print_scripts' );
605         }
606
607         /**
608          * Enqueue scripts and styles for Customizer panel and export data to JavaScript.
609          *
610          * @since 3.9.0
611          * @access public
612          *
613          * @global WP_Scripts $wp_scripts
614          * @global array $wp_registered_sidebars
615          * @global array $wp_registered_widgets
616          */
617         public function enqueue_scripts() {
618                 global $wp_scripts, $wp_registered_sidebars, $wp_registered_widgets;
619
620                 wp_enqueue_style( 'customize-widgets' );
621                 wp_enqueue_script( 'customize-widgets' );
622
623                 /** This action is documented in wp-admin/admin-header.php */
624                 do_action( 'admin_enqueue_scripts', 'widgets.php' );
625
626                 /*
627                  * Export available widgets with control_tpl removed from model
628                  * since plugins need templates to be in the DOM.
629                  */
630                 $available_widgets = array();
631
632                 foreach ( $this->get_available_widgets() as $available_widget ) {
633                         unset( $available_widget['control_tpl'] );
634                         $available_widgets[] = $available_widget;
635                 }
636
637                 $widget_reorder_nav_tpl = sprintf(
638                         '<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>',
639                         __( 'Move to another area&hellip;' ),
640                         __( 'Move down' ),
641                         __( 'Move up' )
642                 );
643
644                 $move_widget_area_tpl = str_replace(
645                         array( '{description}', '{btn}' ),
646                         array(
647                                 __( 'Select an area to move this widget into:' ),
648                                 _x( 'Move', 'Move widget' ),
649                         ),
650                         '<div class="move-widget-area">
651                                 <p class="description">{description}</p>
652                                 <ul class="widget-area-select">
653                                         <% _.each( sidebars, function ( sidebar ){ %>
654                                                 <li class="" data-id="<%- sidebar.id %>" title="<%- sidebar.description %>" tabindex="0"><%- sidebar.name %></li>
655                                         <% }); %>
656                                 </ul>
657                                 <div class="move-widget-actions">
658                                         <button class="move-widget-btn button-secondary" type="button">{btn}</button>
659                                 </div>
660                         </div>'
661                 );
662
663                 $settings = array(
664                         'nonce'                => wp_create_nonce( 'update-widget' ),
665                         'registeredSidebars'   => array_values( $wp_registered_sidebars ),
666                         'registeredWidgets'    => $wp_registered_widgets,
667                         'availableWidgets'     => $available_widgets, // @todo Merge this with registered_widgets
668                         'l10n' => array(
669                                 'saveBtnLabel'     => __( 'Apply' ),
670                                 'saveBtnTooltip'   => __( 'Save and preview changes before publishing them.' ),
671                                 'removeBtnLabel'   => __( 'Remove' ),
672                                 'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ),
673                                 'error'            => __( 'An error has occurred. Please reload the page and try again.' ),
674                                 'widgetMovedUp'    => __( 'Widget moved up' ),
675                                 'widgetMovedDown'  => __( 'Widget moved down' ),
676                                 'noAreasRendered'  => __( 'There are no widget areas currently rendered in the preview. Navigate in the preview to a template that makes use of a widget area in order to access its widgets here.' ),
677                                 'reorderModeOn'    => __( 'Reorder mode enabled' ),
678                                 'reorderModeOff'   => __( 'Reorder mode closed' ),
679                                 'reorderLabelOn'   => esc_attr__( 'Reorder widgets' ),
680                                 'reorderLabelOff'  => esc_attr__( 'Close reorder mode' ),
681                         ),
682                         'tpl' => array(
683                                 'widgetReorderNav' => $widget_reorder_nav_tpl,
684                                 'moveWidgetArea'   => $move_widget_area_tpl,
685                         ),
686                 );
687
688                 foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
689                         unset( $registered_widget['callback'] ); // may not be JSON-serializeable
690                 }
691
692                 $wp_scripts->add_data(
693                         'customize-widgets',
694                         'data',
695                         sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) )
696                 );
697         }
698
699         /**
700          * Render the widget form control templates into the DOM.
701          *
702          * @since 3.9.0
703          * @access public
704          */
705         public function output_widget_control_templates() {
706                 ?>
707                 <div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
708                 <div id="available-widgets">
709                         <div class="customize-section-title">
710                                 <button class="customize-section-back" tabindex="-1">
711                                         <span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
712                                 </button>
713                                 <h3>
714                                         <span class="customize-action"><?php
715                                                 /* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
716                                                 echo sprintf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'widgets' )->title ) );
717                                         ?></span>
718                                         <?php _e( 'Add a Widget' ); ?>
719                                 </h3>
720                         </div>
721                         <div id="available-widgets-filter">
722                                 <label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
723                                 <input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets&hellip;' ) ?>" />
724                         </div>
725                         <div id="available-widgets-list">
726                         <?php foreach ( $this->get_available_widgets() as $available_widget ): ?>
727                                 <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">
728                                         <?php echo $available_widget['control_tpl']; ?>
729                                 </div>
730                         <?php endforeach; ?>
731                         </div><!-- #available-widgets-list -->
732                 </div><!-- #available-widgets -->
733                 </div><!-- #widgets-left -->
734                 <?php
735         }
736
737         /**
738          * Call admin_print_footer_scripts and admin_print_scripts hooks to
739          * allow custom scripts from plugins.
740          *
741          * @since 3.9.0
742          * @access public
743          */
744         public function print_footer_scripts() {
745                 /** This action is documented in wp-admin/admin-footer.php */
746                 do_action( 'admin_print_footer_scripts' );
747
748                 /** This action is documented in wp-admin/admin-footer.php */
749                 do_action( 'admin_footer-widgets.php' );
750         }
751
752         /**
753          * Get common arguments to supply when constructing a Customizer setting.
754          *
755          * @since 3.9.0
756          * @access public
757          *
758          * @param string $id        Widget setting ID.
759          * @param array  $overrides Array of setting overrides.
760          * @return array Possibly modified setting arguments.
761          */
762         public function get_setting_args( $id, $overrides = array() ) {
763                 $args = array(
764                         'type'       => 'option',
765                         'capability' => 'edit_theme_options',
766                         'transport'  => 'refresh',
767                         'default'    => array(),
768                 );
769
770                 if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
771                         $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
772                         $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
773                 } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
774                         $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
775                         $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
776                 }
777
778                 $args = array_merge( $args, $overrides );
779
780                 /**
781                  * Filter the common arguments supplied when constructing a Customizer setting.
782                  *
783                  * @since 3.9.0
784                  *
785                  * @see WP_Customize_Setting
786                  *
787                  * @param array  $args Array of Customizer setting arguments.
788                  * @param string $id   Widget setting ID.
789                  */
790                 return apply_filters( 'widget_customizer_setting_args', $args, $id );
791         }
792
793         /**
794          * Make sure that sidebar widget arrays only ever contain widget IDS.
795          *
796          * Used as the 'sanitize_callback' for each $sidebars_widgets setting.
797          *
798          * @since 3.9.0
799          * @access public
800          *
801          * @param array $widget_ids Array of widget IDs.
802          * @return array Array of sanitized widget IDs.
803          */
804         public function sanitize_sidebar_widgets( $widget_ids ) {
805                 $widget_ids = array_map( 'strval', (array) $widget_ids );
806                 $sanitized_widget_ids = array();
807                 foreach ( $widget_ids as $widget_id ) {
808                         $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
809                 }
810                 return $sanitized_widget_ids;
811         }
812
813         /**
814          * Build up an index of all available widgets for use in Backbone models.
815          *
816          * @since 3.9.0
817          * @access public
818          *
819          * @global array $wp_registered_widgets
820          * @global array $wp_registered_widget_controls
821          * @staticvar array $available_widgets
822          *
823          * @see wp_list_widgets()
824          *
825          * @return array List of available widgets.
826          */
827         public function get_available_widgets() {
828                 static $available_widgets = array();
829                 if ( ! empty( $available_widgets ) ) {
830                         return $available_widgets;
831                 }
832
833                 global $wp_registered_widgets, $wp_registered_widget_controls;
834                 require_once ABSPATH . '/wp-admin/includes/widgets.php'; // for next_widget_id_number()
835
836                 $sort = $wp_registered_widgets;
837                 usort( $sort, array( $this, '_sort_name_callback' ) );
838                 $done = array();
839
840                 foreach ( $sort as $widget ) {
841                         if ( in_array( $widget['callback'], $done, true ) ) { // We already showed this multi-widget
842                                 continue;
843                         }
844
845                         $sidebar = is_active_widget( $widget['callback'], $widget['id'], false, false );
846                         $done[]  = $widget['callback'];
847
848                         if ( ! isset( $widget['params'][0] ) ) {
849                                 $widget['params'][0] = array();
850                         }
851
852                         $available_widget = $widget;
853                         unset( $available_widget['callback'] ); // not serializable to JSON
854
855                         $args = array(
856                                 'widget_id'   => $widget['id'],
857                                 'widget_name' => $widget['name'],
858                                 '_display'    => 'template',
859                         );
860
861                         $is_disabled     = false;
862                         $is_multi_widget = ( isset( $wp_registered_widget_controls[$widget['id']]['id_base'] ) && isset( $widget['params'][0]['number'] ) );
863                         if ( $is_multi_widget ) {
864                                 $id_base            = $wp_registered_widget_controls[$widget['id']]['id_base'];
865                                 $args['_temp_id']   = "$id_base-__i__";
866                                 $args['_multi_num'] = next_widget_id_number( $id_base );
867                                 $args['_add']       = 'multi';
868                         } else {
869                                 $args['_add'] = 'single';
870
871                                 if ( $sidebar && 'wp_inactive_widgets' !== $sidebar ) {
872                                         $is_disabled = true;
873                                 }
874                                 $id_base = $widget['id'];
875                         }
876
877                         $list_widget_controls_args = wp_list_widget_controls_dynamic_sidebar( array( 0 => $args, 1 => $widget['params'][0] ) );
878                         $control_tpl = $this->get_widget_control( $list_widget_controls_args );
879
880                         // The properties here are mapped to the Backbone Widget model.
881                         $available_widget = array_merge( $available_widget, array(
882                                 'temp_id'      => isset( $args['_temp_id'] ) ? $args['_temp_id'] : null,
883                                 'is_multi'     => $is_multi_widget,
884                                 'control_tpl'  => $control_tpl,
885                                 'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
886                                 'is_disabled'  => $is_disabled,
887                                 'id_base'      => $id_base,
888                                 'transport'    => 'refresh',
889                                 'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
890                                 'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
891                                 'is_wide'      => $this->is_wide_widget( $widget['id'] ),
892                         ) );
893
894                         $available_widgets[] = $available_widget;
895                 }
896
897                 return $available_widgets;
898         }
899
900         /**
901          * Naturally order available widgets by name.
902          *
903          * @since 3.9.0
904          * @access protected
905          *
906          * @param array $widget_a The first widget to compare.
907          * @param array $widget_b The second widget to compare.
908          * @return int Reorder position for the current widget comparison.
909          */
910         protected function _sort_name_callback( $widget_a, $widget_b ) {
911                 return strnatcasecmp( $widget_a['name'], $widget_b['name'] );
912         }
913
914         /**
915          * Get the widget control markup.
916          *
917          * @since 3.9.0
918          * @access public
919          *
920          * @param array $args Widget control arguments.
921          * @return string Widget control form HTML markup.
922          */
923         public function get_widget_control( $args ) {
924                 $args[0]['before_form'] = '<div class="form">';
925                 $args[0]['after_form'] = '</div><!-- .form -->';
926                 $args[0]['before_widget_content'] = '<div class="widget-content">';
927                 $args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
928                 ob_start();
929                 call_user_func_array( 'wp_widget_control', $args );
930                 $control_tpl = ob_get_clean();
931                 return $control_tpl;
932         }
933
934         /**
935          * Get the widget control markup parts.
936          *
937          * @since 4.4.0
938          * @access public
939          *
940          * @param array $args Widget control arguments.
941          * @return array {
942          *     @type string $control  Markup for widget control wrapping form.
943          *     @type string $content  The contents of the widget form itself.
944          * }
945          */
946         public function get_widget_control_parts( $args ) {
947                 $args[0]['before_widget_content'] = '<div class="widget-content">';
948                 $args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
949                 $control_markup = $this->get_widget_control( $args );
950
951                 $content_start_pos = strpos( $control_markup, $args[0]['before_widget_content'] );
952                 $content_end_pos = strrpos( $control_markup, $args[0]['after_widget_content'] );
953
954                 $control = substr( $control_markup, 0, $content_start_pos + strlen( $args[0]['before_widget_content'] ) );
955                 $control .= substr( $control_markup, $content_end_pos );
956                 $content = trim( substr(
957                         $control_markup,
958                         $content_start_pos + strlen( $args[0]['before_widget_content'] ),
959                         $content_end_pos - $content_start_pos - strlen( $args[0]['before_widget_content'] )
960                 ) );
961
962                 return compact( 'control', 'content' );
963         }
964
965         /**
966          * Add hooks for the Customizer preview.
967          *
968          * @since 3.9.0
969          * @access public
970          */
971         public function customize_preview_init() {
972                 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
973                 add_action( 'wp_print_styles',    array( $this, 'print_preview_css' ), 1 );
974                 add_action( 'wp_footer',          array( $this, 'export_preview_data' ), 20 );
975         }
976
977         /**
978          * Refresh nonce for widget updates.
979          *
980          * @since 4.2.0
981          * @access public
982          *
983          * @param  array $nonces Array of nonces.
984          * @return array $nonces Array of nonces.
985          */
986         public function refresh_nonces( $nonces ) {
987                 $nonces['update-widget'] = wp_create_nonce( 'update-widget' );
988                 return $nonces;
989         }
990
991         /**
992          * When previewing, make sure the proper previewing widgets are used.
993          *
994          * Because wp_get_sidebars_widgets() gets called early at init
995          * (via wp_convert_widget_settings()) and can set global variable
996          * $_wp_sidebars_widgets to the value of get_option( 'sidebars_widgets' )
997          * before the Customizer preview filter is added, we have to reset
998          * it after the filter has been added.
999          *
1000          * @since 3.9.0
1001          * @access public
1002          *
1003          * @param array $sidebars_widgets List of widgets for the current sidebar.
1004          * @return array
1005          */
1006         public function preview_sidebars_widgets( $sidebars_widgets ) {
1007                 $sidebars_widgets = get_option( 'sidebars_widgets' );
1008
1009                 unset( $sidebars_widgets['array_version'] );
1010                 return $sidebars_widgets;
1011         }
1012
1013         /**
1014          * Enqueue scripts for the Customizer preview.
1015          *
1016          * @since 3.9.0
1017          * @access public
1018          */
1019         public function customize_preview_enqueue() {
1020                 wp_enqueue_script( 'customize-preview-widgets' );
1021         }
1022
1023         /**
1024          * Insert default style for highlighted widget at early point so theme
1025          * stylesheet can override.
1026          *
1027          * @since 3.9.0
1028          * @access public
1029          *
1030          * @action wp_print_styles
1031          */
1032         public function print_preview_css() {
1033                 ?>
1034                 <style>
1035                 .widget-customizer-highlighted-widget {
1036                         outline: none;
1037                         -webkit-box-shadow: 0 0 2px rgba(30,140,190,0.8);
1038                         box-shadow: 0 0 2px rgba(30,140,190,0.8);
1039                         position: relative;
1040                         z-index: 1;
1041                 }
1042                 </style>
1043                 <?php
1044         }
1045
1046         /**
1047          * At the very end of the page, at the very end of the wp_footer,
1048          * communicate the sidebars that appeared on the page.
1049          *
1050          * @since 3.9.0
1051          * @access public
1052      *
1053          * @global array $wp_registered_sidebars
1054          * @global array $wp_registered_widgets
1055          */
1056         public function export_preview_data() {
1057                 global $wp_registered_sidebars, $wp_registered_widgets;
1058                 // Prepare Customizer settings to pass to JavaScript.
1059                 $settings = array(
1060                         'renderedSidebars'   => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
1061                         'renderedWidgets'    => array_fill_keys( array_keys( $this->rendered_widgets ), true ),
1062                         'registeredSidebars' => array_values( $wp_registered_sidebars ),
1063                         'registeredWidgets'  => $wp_registered_widgets,
1064                         'l10n'               => array(
1065                                 'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
1066                         ),
1067                 );
1068                 foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
1069                         unset( $registered_widget['callback'] ); // may not be JSON-serializeable
1070                 }
1071
1072                 ?>
1073                 <script type="text/javascript">
1074                         var _wpWidgetCustomizerPreviewSettings = <?php echo wp_json_encode( $settings ); ?>;
1075                 </script>
1076                 <?php
1077         }
1078
1079         /**
1080          * Keep track of the widgets that were rendered.
1081          *
1082          * @since 3.9.0
1083          * @access public
1084          *
1085          * @param array $widget Rendered widget to tally.
1086          */
1087         public function tally_rendered_widgets( $widget ) {
1088                 $this->rendered_widgets[ $widget['id'] ] = true;
1089         }
1090
1091         /**
1092          * Determine if a widget is rendered on the page.
1093          *
1094          * @since 4.0.0
1095          * @access public
1096          *
1097          * @param string $widget_id Widget ID to check.
1098          * @return bool Whether the widget is rendered.
1099          */
1100         public function is_widget_rendered( $widget_id ) {
1101                 return in_array( $widget_id, $this->rendered_widgets );
1102         }
1103
1104         /**
1105          * Determine if a sidebar is rendered on the page.
1106          *
1107          * @since 4.0.0
1108          * @access public
1109          *
1110          * @param string $sidebar_id Sidebar ID to check.
1111          * @return bool Whether the sidebar is rendered.
1112          */
1113         public function is_sidebar_rendered( $sidebar_id ) {
1114                 return in_array( $sidebar_id, $this->rendered_sidebars );
1115         }
1116
1117         /**
1118          * Tally the sidebars rendered via is_active_sidebar().
1119          *
1120          * Keep track of the times that is_active_sidebar() is called
1121          * in the template, and assume that this means that the sidebar
1122          * would be rendered on the template if there were widgets
1123          * populating it.
1124          *
1125          * @since 3.9.0
1126          * @access public
1127          *
1128          * @param bool   $is_active  Whether the sidebar is active.
1129          * @param string $sidebar_id Sidebar ID.
1130          * @return bool
1131          */
1132         public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
1133                 if ( is_registered_sidebar( $sidebar_id ) ) {
1134                         $this->rendered_sidebars[] = $sidebar_id;
1135                 }
1136                 /*
1137                  * We may need to force this to true, and also force-true the value
1138                  * for 'dynamic_sidebar_has_widgets' if we want to ensure that there
1139                  * is an area to drop widgets into, if the sidebar is empty.
1140                  */
1141                 return $is_active;
1142         }
1143
1144         /**
1145          * Tally the sidebars rendered via dynamic_sidebar().
1146          *
1147          * Keep track of the times that dynamic_sidebar() is called in the template,
1148          * and assume this means the sidebar would be rendered on the template if
1149          * there were widgets populating it.
1150          *
1151          * @since 3.9.0
1152          * @access public
1153          *
1154          * @param bool   $has_widgets Whether the current sidebar has widgets.
1155          * @param string $sidebar_id  Sidebar ID.
1156          * @return bool
1157          */
1158         public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
1159                 if ( is_registered_sidebar( $sidebar_id ) ) {
1160                         $this->rendered_sidebars[] = $sidebar_id;
1161                 }
1162
1163                 /*
1164                  * We may need to force this to true, and also force-true the value
1165                  * for 'is_active_sidebar' if we want to ensure there is an area to
1166                  * drop widgets into, if the sidebar is empty.
1167                  */
1168                 return $has_widgets;
1169         }
1170
1171         /**
1172          * Get MAC for a serialized widget instance string.
1173          *
1174          * Allows values posted back from JS to be rejected if any tampering of the
1175          * data has occurred.
1176          *
1177          * @since 3.9.0
1178          * @access protected
1179          *
1180          * @param string $serialized_instance Widget instance.
1181          * @return string MAC for serialized widget instance.
1182          */
1183         protected function get_instance_hash_key( $serialized_instance ) {
1184                 return wp_hash( $serialized_instance );
1185         }
1186
1187         /**
1188          * Sanitize a widget instance.
1189          *
1190          * Unserialize the JS-instance for storing in the options. It's important
1191          * that this filter only get applied to an instance once.
1192          *
1193          * @since 3.9.0
1194          * @access public
1195          *
1196          * @param array $value Widget instance to sanitize.
1197          * @return array|void Sanitized widget instance.
1198          */
1199         public function sanitize_widget_instance( $value ) {
1200                 if ( $value === array() ) {
1201                         return $value;
1202                 }
1203
1204                 if ( empty( $value['is_widget_customizer_js_value'] )
1205                         || empty( $value['instance_hash_key'] )
1206                         || empty( $value['encoded_serialized_instance'] ) )
1207                 {
1208                         return;
1209                 }
1210
1211                 $decoded = base64_decode( $value['encoded_serialized_instance'], true );
1212                 if ( false === $decoded ) {
1213                         return;
1214                 }
1215
1216                 if ( ! hash_equals( $this->get_instance_hash_key( $decoded ), $value['instance_hash_key'] ) ) {
1217                         return;
1218                 }
1219
1220                 $instance = unserialize( $decoded );
1221                 if ( false === $instance ) {
1222                         return;
1223                 }
1224
1225                 return $instance;
1226         }
1227
1228         /**
1229          * Convert widget instance into JSON-representable format.
1230          *
1231          * @since 3.9.0
1232          * @access public
1233          *
1234          * @param array $value Widget instance to convert to JSON.
1235          * @return array JSON-converted widget instance.
1236          */
1237         public function sanitize_widget_js_instance( $value ) {
1238                 if ( empty( $value['is_widget_customizer_js_value'] ) ) {
1239                         $serialized = serialize( $value );
1240
1241                         $value = array(
1242                                 'encoded_serialized_instance'   => base64_encode( $serialized ),
1243                                 'title'                         => empty( $value['title'] ) ? '' : $value['title'],
1244                                 'is_widget_customizer_js_value' => true,
1245                                 'instance_hash_key'             => $this->get_instance_hash_key( $serialized ),
1246                         );
1247                 }
1248                 return $value;
1249         }
1250
1251         /**
1252          * Strip out widget IDs for widgets which are no longer registered.
1253          *
1254          * One example where this might happen is when a plugin orphans a widget
1255          * in a sidebar upon deactivation.
1256          *
1257          * @since 3.9.0
1258          * @access public
1259          *
1260          * @global array $wp_registered_widgets
1261          *
1262          * @param array $widget_ids List of widget IDs.
1263          * @return array Parsed list of widget IDs.
1264          */
1265         public function sanitize_sidebar_widgets_js_instance( $widget_ids ) {
1266                 global $wp_registered_widgets;
1267                 $widget_ids = array_values( array_intersect( $widget_ids, array_keys( $wp_registered_widgets ) ) );
1268                 return $widget_ids;
1269         }
1270
1271         /**
1272          * Find and invoke the widget update and control callbacks.
1273          *
1274          * Requires that $_POST be populated with the instance data.
1275          *
1276          * @since 3.9.0
1277          * @access public
1278          *
1279          * @global array $wp_registered_widget_updates
1280          * @global array $wp_registered_widget_controls
1281          *
1282          * @param  string $widget_id Widget ID.
1283          * @return WP_Error|array Array containing the updated widget information.
1284          *                        A WP_Error object, otherwise.
1285          */
1286         public function call_widget_update( $widget_id ) {
1287                 global $wp_registered_widget_updates, $wp_registered_widget_controls;
1288
1289                 $setting_id = $this->get_setting_id( $widget_id );
1290
1291                 /*
1292                  * Make sure that other setting changes have previewed since this widget
1293                  * may depend on them (e.g. Menus being present for Custom Menu widget).
1294                  */
1295                 if ( ! did_action( 'customize_preview_init' ) ) {
1296                         foreach ( $this->manager->settings() as $setting ) {
1297                                 if ( $setting->id !== $setting_id ) {
1298                                         $setting->preview();
1299                                 }
1300                         }
1301                 }
1302
1303                 $this->start_capturing_option_updates();
1304                 $parsed_id   = $this->parse_widget_id( $widget_id );
1305                 $option_name = 'widget_' . $parsed_id['id_base'];
1306
1307                 /*
1308                  * If a previously-sanitized instance is provided, populate the input vars
1309                  * with its values so that the widget update callback will read this instance
1310                  */
1311                 $added_input_vars = array();
1312                 if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
1313                         $sanitized_widget_setting = json_decode( $this->get_post_value( 'sanitized_widget_setting' ), true );
1314                         if ( false === $sanitized_widget_setting ) {
1315                                 $this->stop_capturing_option_updates();
1316                                 return new WP_Error( 'widget_setting_malformed' );
1317                         }
1318
1319                         $instance = $this->sanitize_widget_instance( $sanitized_widget_setting );
1320                         if ( is_null( $instance ) ) {
1321                                 $this->stop_capturing_option_updates();
1322                                 return new WP_Error( 'widget_setting_unsanitized' );
1323                         }
1324
1325                         if ( ! is_null( $parsed_id['number'] ) ) {
1326                                 $value = array();
1327                                 $value[$parsed_id['number']] = $instance;
1328                                 $key = 'widget-' . $parsed_id['id_base'];
1329                                 $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
1330                                 $added_input_vars[] = $key;
1331                         } else {
1332                                 foreach ( $instance as $key => $value ) {
1333                                         $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
1334                                         $added_input_vars[] = $key;
1335                                 }
1336                         }
1337                 }
1338
1339                 // Invoke the widget update callback.
1340                 foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1341                         if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) {
1342                                 ob_start();
1343                                 call_user_func_array( $control['callback'], $control['params'] );
1344                                 ob_end_clean();
1345                                 break;
1346                         }
1347                 }
1348
1349                 // Clean up any input vars that were manually added
1350                 foreach ( $added_input_vars as $key ) {
1351                         unset( $_POST[ $key ] );
1352                         unset( $_REQUEST[ $key ] );
1353                 }
1354
1355                 // Make sure the expected option was updated.
1356                 if ( 0 !== $this->count_captured_options() ) {
1357                         if ( $this->count_captured_options() > 1 ) {
1358                                 $this->stop_capturing_option_updates();
1359                                 return new WP_Error( 'widget_setting_too_many_options' );
1360                         }
1361
1362                         $updated_option_name = key( $this->get_captured_options() );
1363                         if ( $updated_option_name !== $option_name ) {
1364                                 $this->stop_capturing_option_updates();
1365                                 return new WP_Error( 'widget_setting_unexpected_option' );
1366                         }
1367                 }
1368
1369                 // Obtain the widget instance.
1370                 $option = $this->get_captured_option( $option_name );
1371                 if ( null !== $parsed_id['number'] ) {
1372                         $instance = $option[ $parsed_id['number'] ];
1373                 } else {
1374                         $instance = $option;
1375                 }
1376
1377                 /*
1378                  * Override the incoming $_POST['customized'] for a newly-created widget's
1379                  * setting with the new $instance so that the preview filter currently
1380                  * in place from WP_Customize_Setting::preview() will use this value
1381                  * instead of the default widget instance value (an empty array).
1382                  */
1383                 $this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance ) );
1384
1385                 // Obtain the widget control with the updated instance in place.
1386                 ob_start();
1387                 $form = $wp_registered_widget_controls[ $widget_id ];
1388                 if ( $form ) {
1389                         call_user_func_array( $form['callback'], $form['params'] );
1390                 }
1391                 $form = ob_get_clean();
1392
1393                 $this->stop_capturing_option_updates();
1394
1395                 return compact( 'instance', 'form' );
1396         }
1397
1398         /**
1399          * Update widget settings asynchronously.
1400          *
1401          * Allows the Customizer to update a widget using its form, but return the new
1402          * instance info via Ajax instead of saving it to the options table.
1403          *
1404          * Most code here copied from wp_ajax_save_widget()
1405          *
1406          * @since 3.9.0
1407          * @access public
1408          *
1409          * @see wp_ajax_save_widget()
1410          *
1411          */
1412         public function wp_ajax_update_widget() {
1413
1414                 if ( ! is_user_logged_in() ) {
1415                         wp_die( 0 );
1416                 }
1417
1418                 check_ajax_referer( 'update-widget', 'nonce' );
1419
1420                 if ( ! current_user_can( 'edit_theme_options' ) ) {
1421                         wp_die( -1 );
1422                 }
1423
1424                 if ( empty( $_POST['widget-id'] ) ) {
1425                         wp_send_json_error( 'missing_widget-id' );
1426                 }
1427
1428                 /** This action is documented in wp-admin/includes/ajax-actions.php */
1429                 do_action( 'load-widgets.php' );
1430
1431                 /** This action is documented in wp-admin/includes/ajax-actions.php */
1432                 do_action( 'widgets.php' );
1433
1434                 /** This action is documented in wp-admin/widgets.php */
1435                 do_action( 'sidebar_admin_setup' );
1436
1437                 $widget_id = $this->get_post_value( 'widget-id' );
1438                 $parsed_id = $this->parse_widget_id( $widget_id );
1439                 $id_base = $parsed_id['id_base'];
1440
1441                 $is_updating_widget_template = (
1442                         isset( $_POST[ 'widget-' . $id_base ] )
1443                         &&
1444                         is_array( $_POST[ 'widget-' . $id_base ] )
1445                         &&
1446                         preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) )
1447                 );
1448                 if ( $is_updating_widget_template ) {
1449                         wp_send_json_error( 'template_widget_not_updatable' );
1450                 }
1451
1452                 $updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
1453                 if ( is_wp_error( $updated_widget ) ) {
1454                         wp_send_json_error( $updated_widget->get_error_code() );
1455                 }
1456
1457                 $form = $updated_widget['form'];
1458                 $instance = $this->sanitize_widget_js_instance( $updated_widget['instance'] );
1459
1460                 wp_send_json_success( compact( 'form', 'instance' ) );
1461         }
1462
1463         /***************************************************************************
1464          * Option Update Capturing
1465          ***************************************************************************/
1466
1467         /**
1468          * List of captured widget option updates.
1469          *
1470          * @since 3.9.0
1471          * @access protected
1472          * @var array $_captured_options Values updated while option capture is happening.
1473          */
1474         protected $_captured_options = array();
1475
1476         /**
1477          * Whether option capture is currently happening.
1478          *
1479          * @since 3.9.0
1480          * @access protected
1481          * @var bool $_is_current Whether option capture is currently happening or not.
1482          */
1483         protected $_is_capturing_option_updates = false;
1484
1485         /**
1486          * Determine whether the captured option update should be ignored.
1487          *
1488          * @since 3.9.0
1489          * @access protected
1490          *
1491          * @param string $option_name Option name.
1492          * @return bool Whether the option capture is ignored.
1493          */
1494         protected function is_option_capture_ignored( $option_name ) {
1495                 return ( 0 === strpos( $option_name, '_transient_' ) );
1496         }
1497
1498         /**
1499          * Retrieve captured widget option updates.
1500          *
1501          * @since 3.9.0
1502          * @access protected
1503          *
1504          * @return array Array of captured options.
1505          */
1506         protected function get_captured_options() {
1507                 return $this->_captured_options;
1508         }
1509
1510         /**
1511          * Get the option that was captured from being saved.
1512          *
1513          * @since 4.2.0
1514          * @access protected
1515          *
1516          * @param string $option_name Option name.
1517          * @param mixed  $default     Optional. Default value to return if the option does not exist.
1518          * @return mixed Value set for the option.
1519          */
1520         protected function get_captured_option( $option_name, $default = false ) {
1521                 if ( array_key_exists( $option_name, $this->_captured_options ) ) {
1522                         $value = $this->_captured_options[ $option_name ];
1523                 } else {
1524                         $value = $default;
1525                 }
1526                 return $value;
1527         }
1528
1529         /**
1530          * Get the number of captured widget option updates.
1531          *
1532          * @since 3.9.0
1533          * @access protected
1534          *
1535          * @return int Number of updated options.
1536          */
1537         protected function count_captured_options() {
1538                 return count( $this->_captured_options );
1539         }
1540
1541         /**
1542          * Start keeping track of changes to widget options, caching new values.
1543          *
1544          * @since 3.9.0
1545          * @access protected
1546          */
1547         protected function start_capturing_option_updates() {
1548                 if ( $this->_is_capturing_option_updates ) {
1549                         return;
1550                 }
1551
1552                 $this->_is_capturing_option_updates = true;
1553
1554                 add_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
1555         }
1556
1557         /**
1558          * Pre-filter captured option values before updating.
1559          *
1560          * @since 3.9.0
1561          * @access public
1562          *
1563          * @param mixed  $new_value   The new option value.
1564          * @param string $option_name Name of the option.
1565          * @param mixed  $old_value   The old option value.
1566          * @return mixed Filtered option value.
1567          */
1568         public function capture_filter_pre_update_option( $new_value, $option_name, $old_value ) {
1569                 if ( $this->is_option_capture_ignored( $option_name ) ) {
1570                         return;
1571                 }
1572
1573                 if ( ! isset( $this->_captured_options[ $option_name ] ) ) {
1574                         add_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
1575                 }
1576
1577                 $this->_captured_options[ $option_name ] = $new_value;
1578
1579                 return $old_value;
1580         }
1581
1582         /**
1583          * Pre-filter captured option values before retrieving.
1584          *
1585          * @since 3.9.0
1586          * @access public
1587          *
1588          * @param mixed $value Value to return instead of the option value.
1589          * @return mixed Filtered option value.
1590          */
1591         public function capture_filter_pre_get_option( $value ) {
1592                 $option_name = preg_replace( '/^pre_option_/', '', current_filter() );
1593
1594                 if ( isset( $this->_captured_options[ $option_name ] ) ) {
1595                         $value = $this->_captured_options[ $option_name ];
1596
1597                         /** This filter is documented in wp-includes/option.php */
1598                         $value = apply_filters( 'option_' . $option_name, $value );
1599                 }
1600
1601                 return $value;
1602         }
1603
1604         /**
1605          * Undo any changes to the options since options capture began.
1606          *
1607          * @since 3.9.0
1608          * @access protected
1609          */
1610         protected function stop_capturing_option_updates() {
1611                 if ( ! $this->_is_capturing_option_updates ) {
1612                         return;
1613                 }
1614
1615                 remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
1616
1617                 foreach ( array_keys( $this->_captured_options ) as $option_name ) {
1618                         remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
1619                 }
1620
1621                 $this->_captured_options = array();
1622                 $this->_is_capturing_option_updates = false;
1623         }
1624
1625         /**
1626          * @since 3.9.0
1627          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1628          */
1629         public function setup_widget_addition_previews() {
1630                 _deprecated_function( __METHOD__, '4.2.0' );
1631         }
1632
1633         /**
1634          * @since 3.9.0
1635          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1636          */
1637         public function prepreview_added_sidebars_widgets() {
1638                 _deprecated_function( __METHOD__, '4.2.0' );
1639         }
1640
1641         /**
1642          * @since 3.9.0
1643          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1644          */
1645         public function prepreview_added_widget_instance() {
1646                 _deprecated_function( __METHOD__, '4.2.0' );
1647         }
1648
1649         /**
1650          * @since 3.9.0
1651          * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
1652          */
1653         public function remove_prepreview_filters() {
1654                 _deprecated_function( __METHOD__, '4.2.0' );
1655         }
1656 }