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