WordPress 3.9.1
[autoinstalls/wordpress.git] / wp-includes / widgets.php
1 <?php
2 /**
3  * API for creating dynamic sidebar without hardcoding functionality into
4  * themes. Includes both internal WordPress routines and theme use routines.
5  *
6  * This functionality was found in a plugin before WordPress 2.2 release which
7  * included it in the core from that point on.
8  *
9  * @link http://codex.wordpress.org/Plugins/WordPress_Widgets WordPress Widgets
10  * @link http://codex.wordpress.org/Plugins/WordPress_Widgets_Api Widgets API
11  *
12  * @package WordPress
13  * @subpackage Widgets
14  */
15
16 /**
17  * This class must be extended for each widget and WP_Widget::widget(), WP_Widget::update()
18  * and WP_Widget::form() need to be over-ridden.
19  *
20  * @package WordPress
21  * @subpackage Widgets
22  * @since 2.8.0
23  */
24 class WP_Widget {
25
26         var $id_base;                   // Root id for all widgets of this type.
27         var $name;                              // Name for this widget type.
28         var $widget_options;    // Option array passed to wp_register_sidebar_widget()
29         var $control_options;   // Option array passed to wp_register_widget_control()
30
31         var $number = false;    // Unique ID number of the current instance.
32         var $id = false;                // Unique ID string of the current instance (id_base-number)
33         var $updated = false;   // Set true when we update the data after a POST submit - makes sure we don't do it twice.
34
35         // Member functions that you must over-ride.
36
37         /** Echo the widget content.
38          *
39          * Subclasses should over-ride this function to generate their widget code.
40          *
41          * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget.
42          * @param array $instance The settings for the particular instance of the widget
43          */
44         function widget($args, $instance) {
45                 die('function WP_Widget::widget() must be over-ridden in a sub-class.');
46         }
47
48         /** Update a particular instance.
49          *
50          * This function should check that $new_instance is set correctly.
51          * The newly calculated value of $instance should be returned.
52          * If "false" is returned, the instance won't be saved/updated.
53          *
54          * @param array $new_instance New settings for this instance as input by the user via form()
55          * @param array $old_instance Old settings for this instance
56          * @return array Settings to save or bool false to cancel saving
57          */
58         function update($new_instance, $old_instance) {
59                 return $new_instance;
60         }
61
62         /** Echo the settings update form
63          *
64          * @param array $instance Current settings
65          */
66         function form($instance) {
67                 echo '<p class="no-options-widget">' . __('There are no options for this widget.') . '</p>';
68                 return 'noform';
69         }
70
71         // Functions you'll need to call.
72
73         /**
74          * PHP5 constructor
75          *
76          * @param string $id_base Optional Base ID for the widget, lower case,
77          * if left empty a portion of the widget's class name will be used. Has to be unique.
78          * @param string $name Name for the widget displayed on the configuration page.
79          * @param array $widget_options Optional Passed to wp_register_sidebar_widget()
80          *       - description: shown on the configuration page
81          *       - classname
82          * @param array $control_options Optional Passed to wp_register_widget_control()
83          *       - width: required if more than 250px
84          *       - height: currently not used but may be needed in the future
85          */
86         function __construct( $id_base, $name, $widget_options = array(), $control_options = array() ) {
87                 $this->id_base = empty($id_base) ? preg_replace( '/(wp_)?widget_/', '', strtolower(get_class($this)) ) : strtolower($id_base);
88                 $this->name = $name;
89                 $this->option_name = 'widget_' . $this->id_base;
90                 $this->widget_options = wp_parse_args( $widget_options, array('classname' => $this->option_name) );
91                 $this->control_options = wp_parse_args( $control_options, array('id_base' => $this->id_base) );
92         }
93
94         /**
95          * PHP4 constructor
96          */
97         function WP_Widget( $id_base, $name, $widget_options = array(), $control_options = array() ) {
98                 WP_Widget::__construct( $id_base, $name, $widget_options, $control_options );
99         }
100
101         /**
102          * Constructs name attributes for use in form() fields
103          *
104          * This function should be used in form() methods to create name attributes for fields to be saved by update()
105          *
106          * @param string $field_name Field name
107          * @return string Name attribute for $field_name
108          */
109         function get_field_name($field_name) {
110                 return 'widget-' . $this->id_base . '[' . $this->number . '][' . $field_name . ']';
111         }
112
113         /**
114          * Constructs id attributes for use in form() fields
115          *
116          * This function should be used in form() methods to create id attributes for fields to be saved by update()
117          *
118          * @param string $field_name Field name
119          * @return string ID attribute for $field_name
120          */
121         function get_field_id($field_name) {
122                 return 'widget-' . $this->id_base . '-' . $this->number . '-' . $field_name;
123         }
124
125         // Private Functions. Don't worry about these.
126
127         function _register() {
128                 $settings = $this->get_settings();
129                 $empty = true;
130
131                 if ( is_array($settings) ) {
132                         foreach ( array_keys($settings) as $number ) {
133                                 if ( is_numeric($number) ) {
134                                         $this->_set($number);
135                                         $this->_register_one($number);
136                                         $empty = false;
137                                 }
138                         }
139                 }
140
141                 if ( $empty ) {
142                         // If there are none, we register the widget's existence with a
143                         // generic template
144                         $this->_set(1);
145                         $this->_register_one();
146                 }
147         }
148
149         function _set($number) {
150                 $this->number = $number;
151                 $this->id = $this->id_base . '-' . $number;
152         }
153
154         function _get_display_callback() {
155                 return array($this, 'display_callback');
156         }
157
158         function _get_update_callback() {
159                 return array($this, 'update_callback');
160         }
161
162         function _get_form_callback() {
163                 return array($this, 'form_callback');
164         }
165
166         /**
167          * Determine if we're in the Customizer; if true, then the object cache gets
168          * suspended and widgets should check this to decide whether they should
169          * store anything persistently to the object cache, to transients, or
170          * anywhere else.
171          *
172          * @since 3.9.0
173          *
174          * @return bool True if Customizer is on, false if not.
175          */
176         function is_preview() {
177                 global $wp_customize;
178                 return ( isset( $wp_customize ) && $wp_customize->is_preview() ) ;
179         }
180
181         /** Generate the actual widget content.
182          *      Just finds the instance and calls widget().
183          *      Do NOT over-ride this function. */
184         function display_callback( $args, $widget_args = 1 ) {
185                 if ( is_numeric($widget_args) )
186                         $widget_args = array( 'number' => $widget_args );
187
188                 $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
189                 $this->_set( $widget_args['number'] );
190                 $instance = $this->get_settings();
191
192                 if ( array_key_exists( $this->number, $instance ) ) {
193                         $instance = $instance[$this->number];
194
195                         /**
196                          * Filter the settings for a particular widget instance.
197                          *
198                          * Returning false will effectively short-circuit display of the widget.
199                          *
200                          * @since 2.8.0
201                          *
202                          * @param array     $instance The current widget instance's settings.
203                          * @param WP_Widget $this     The current widget instance.
204                          * @param array     $args     An array of default widget arguments.
205                          */
206                         $instance = apply_filters( 'widget_display_callback', $instance, $this, $args );
207
208                         if ( false === $instance ) {
209                                 return;
210                         }
211
212                         $was_cache_addition_suspended = wp_suspend_cache_addition();
213                         if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
214                                 wp_suspend_cache_addition( true );
215                         }
216
217                         $this->widget( $args, $instance );
218
219                         if ( $this->is_preview() ) {
220                                 wp_suspend_cache_addition( $was_cache_addition_suspended );
221                         }
222                 }
223         }
224
225         /**
226          * Deal with changed settings.
227          *
228          * Do NOT over-ride this function.
229          *
230          * @param mixed $deprecated Not used.
231          */
232         function update_callback( $deprecated = 1 ) {
233                 global $wp_registered_widgets;
234
235                 $all_instances = $this->get_settings();
236
237                 // We need to update the data
238                 if ( $this->updated )
239                         return;
240
241                 $sidebars_widgets = wp_get_sidebars_widgets();
242
243                 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
244                         // Delete the settings for this instance of the widget
245                         if ( isset($_POST['the-widget-id']) )
246                                 $del_id = $_POST['the-widget-id'];
247                         else
248                                 return;
249
250                         if ( isset($wp_registered_widgets[$del_id]['params'][0]['number']) ) {
251                                 $number = $wp_registered_widgets[$del_id]['params'][0]['number'];
252
253                                 if ( $this->id_base . '-' . $number == $del_id )
254                                         unset($all_instances[$number]);
255                         }
256                 } else {
257                         if ( isset($_POST['widget-' . $this->id_base]) && is_array($_POST['widget-' . $this->id_base]) ) {
258                                 $settings = $_POST['widget-' . $this->id_base];
259                         } elseif ( isset($_POST['id_base']) && $_POST['id_base'] == $this->id_base ) {
260                                 $num = $_POST['multi_number'] ? (int) $_POST['multi_number'] : (int) $_POST['widget_number'];
261                                 $settings = array( $num => array() );
262                         } else {
263                                 return;
264                         }
265
266                         foreach ( $settings as $number => $new_instance ) {
267                                 $new_instance = stripslashes_deep($new_instance);
268                                 $this->_set($number);
269
270                                 $old_instance = isset($all_instances[$number]) ? $all_instances[$number] : array();
271
272                                 $was_cache_addition_suspended = wp_suspend_cache_addition();
273                                 if ( $this->is_preview() && ! $was_cache_addition_suspended ) {
274                                         wp_suspend_cache_addition( true );
275                                 }
276
277                                 $instance = $this->update( $new_instance, $old_instance );
278
279                                 if ( $this->is_preview() ) {
280                                         wp_suspend_cache_addition( $was_cache_addition_suspended );
281                                 }
282
283                                 /**
284                                  * Filter a widget's settings before saving.
285                                  *
286                                  * Returning false will effectively short-circuit the widget's ability
287                                  * to update settings.
288                                  *
289                                  * @since 2.8.0
290                                  *
291                                  * @param array     $instance     The current widget instance's settings.
292                                  * @param array     $new_instance Array of new widget settings.
293                                  * @param array     $old_instance Array of old widget settings.
294                                  * @param WP_Widget $this         The current widget instance.
295                                  */
296                                 $instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $this );
297                                 if ( false !== $instance ) {
298                                         $all_instances[$number] = $instance;
299                                 }
300
301                                 break; // run only once
302                         }
303                 }
304
305                 $this->save_settings($all_instances);
306                 $this->updated = true;
307         }
308
309         /**
310          * Generate the control form.
311          *
312          * Do NOT over-ride this function.
313          */
314         function form_callback( $widget_args = 1 ) {
315                 if ( is_numeric($widget_args) )
316                         $widget_args = array( 'number' => $widget_args );
317
318                 $widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
319                 $all_instances = $this->get_settings();
320
321                 if ( -1 == $widget_args['number'] ) {
322                         // We echo out a form where 'number' can be set later
323                         $this->_set('__i__');
324                         $instance = array();
325                 } else {
326                         $this->_set($widget_args['number']);
327                         $instance = $all_instances[ $widget_args['number'] ];
328                 }
329
330                 /**
331                  * Filter the widget instance's settings before displaying the control form.
332                  *
333                  * Returning false effectively short-circuits display of the control form.
334                  *
335                  * @since 2.8.0
336                  *
337                  * @param array     $instance The current widget instance's settings.
338                  * @param WP_Widget $this     The current widget instance.
339                  */
340                 $instance = apply_filters( 'widget_form_callback', $instance, $this );
341
342                 $return = null;
343                 if ( false !== $instance ) {
344                         $return = $this->form($instance);
345
346                         /**
347                          * Fires at the end of the widget control form.
348                          *
349                          * Use this hook to add extra fields to the widget form. The hook
350                          * is only fired if the value passed to the 'widget_form_callback'
351                          * hook is not false.
352                          *
353                          * Note: If the widget has no form, the text echoed from the default
354                          * form method can be hidden using CSS.
355                          *
356                          * @since 2.8.0
357                          *
358                          * @param WP_Widget $this     The widget instance, passed by reference.
359                          * @param null      $return   Return null if new fields are added.
360                          * @param array     $instance An array of the widget's settings.
361                          */
362                         do_action_ref_array( 'in_widget_form', array( &$this, &$return, $instance ) );
363                 }
364                 return $return;
365         }
366
367         /** Helper function: Registers a single instance. */
368         function _register_one($number = -1) {
369                 wp_register_sidebar_widget(     $this->id, $this->name, $this->_get_display_callback(), $this->widget_options, array( 'number' => $number ) );
370                 _register_widget_update_callback( $this->id_base, $this->_get_update_callback(), $this->control_options, array( 'number' => -1 ) );
371                 _register_widget_form_callback( $this->id, $this->name, $this->_get_form_callback(), $this->control_options, array( 'number' => $number ) );
372         }
373
374         function save_settings($settings) {
375                 $settings['_multiwidget'] = 1;
376                 update_option( $this->option_name, $settings );
377         }
378
379         function get_settings() {
380                 $settings = get_option($this->option_name);
381
382                 if ( false === $settings && isset($this->alt_option_name) )
383                         $settings = get_option($this->alt_option_name);
384
385                 if ( !is_array($settings) )
386                         $settings = array();
387
388                 if ( !empty($settings) && !array_key_exists('_multiwidget', $settings) ) {
389                         // old format, convert if single widget
390                         $settings = wp_convert_widget_settings($this->id_base, $this->option_name, $settings);
391                 }
392
393                 unset($settings['_multiwidget'], $settings['__i__']);
394                 return $settings;
395         }
396 }
397
398 /**
399  * Singleton that registers and instantiates WP_Widget classes.
400  *
401  * @package WordPress
402  * @subpackage Widgets
403  * @since 2.8.0
404  */
405 class WP_Widget_Factory {
406         var $widgets = array();
407
408         function WP_Widget_Factory() {
409                 add_action( 'widgets_init', array( $this, '_register_widgets' ), 100 );
410         }
411
412         function register($widget_class) {
413                 $this->widgets[$widget_class] = new $widget_class();
414         }
415
416         function unregister($widget_class) {
417                 if ( isset($this->widgets[$widget_class]) )
418                         unset($this->widgets[$widget_class]);
419         }
420
421         function _register_widgets() {
422                 global $wp_registered_widgets;
423                 $keys = array_keys($this->widgets);
424                 $registered = array_keys($wp_registered_widgets);
425                 $registered = array_map('_get_widget_id_base', $registered);
426
427                 foreach ( $keys as $key ) {
428                         // don't register new widget if old widget with the same id is already registered
429                         if ( in_array($this->widgets[$key]->id_base, $registered, true) ) {
430                                 unset($this->widgets[$key]);
431                                 continue;
432                         }
433
434                         $this->widgets[$key]->_register();
435                 }
436         }
437 }
438
439 /* Global Variables */
440
441 /** @ignore */
442 global $wp_registered_sidebars, $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
443
444 /**
445  * Stores the sidebars, since many themes can have more than one.
446  *
447  * @global array $wp_registered_sidebars
448  * @since 2.2.0
449  */
450 $wp_registered_sidebars = array();
451
452 /**
453  * Stores the registered widgets.
454  *
455  * @global array $wp_registered_widgets
456  * @since 2.2.0
457  */
458 $wp_registered_widgets = array();
459
460 /**
461  * Stores the registered widget control (options).
462  *
463  * @global array $wp_registered_widget_controls
464  * @since 2.2.0
465  */
466 $wp_registered_widget_controls = array();
467 $wp_registered_widget_updates = array();
468
469 /**
470  * Private
471  */
472 $_wp_sidebars_widgets = array();
473
474 /**
475  * Private
476  */
477 $GLOBALS['_wp_deprecated_widgets_callbacks'] = array(
478         'wp_widget_pages',
479         'wp_widget_pages_control',
480         'wp_widget_calendar',
481         'wp_widget_calendar_control',
482         'wp_widget_archives',
483         'wp_widget_archives_control',
484         'wp_widget_links',
485         'wp_widget_meta',
486         'wp_widget_meta_control',
487         'wp_widget_search',
488         'wp_widget_recent_entries',
489         'wp_widget_recent_entries_control',
490         'wp_widget_tag_cloud',
491         'wp_widget_tag_cloud_control',
492         'wp_widget_categories',
493         'wp_widget_categories_control',
494         'wp_widget_text',
495         'wp_widget_text_control',
496         'wp_widget_rss',
497         'wp_widget_rss_control',
498         'wp_widget_recent_comments',
499         'wp_widget_recent_comments_control'
500 );
501
502 /* Template tags & API functions */
503
504 /**
505  * Register a widget
506  *
507  * Registers a WP_Widget widget
508  *
509  * @since 2.8.0
510  *
511  * @see WP_Widget
512  * @see WP_Widget_Factory
513  * @uses WP_Widget_Factory
514  *
515  * @param string $widget_class The name of a class that extends WP_Widget
516  */
517 function register_widget($widget_class) {
518         global $wp_widget_factory;
519
520         $wp_widget_factory->register($widget_class);
521 }
522
523 /**
524  * Unregister a widget
525  *
526  * Unregisters a WP_Widget widget. Useful for unregistering default widgets.
527  * Run within a function hooked to the widgets_init action.
528  *
529  * @since 2.8.0
530  *
531  * @see WP_Widget
532  * @see WP_Widget_Factory
533  * @uses WP_Widget_Factory
534  *
535  * @param string $widget_class The name of a class that extends WP_Widget
536  */
537 function unregister_widget($widget_class) {
538         global $wp_widget_factory;
539
540         $wp_widget_factory->unregister($widget_class);
541 }
542
543 /**
544  * Creates multiple sidebars.
545  *
546  * If you wanted to quickly create multiple sidebars for a theme or internally.
547  * This function will allow you to do so. If you don't pass the 'name' and/or
548  * 'id' in $args, then they will be built for you.
549  *
550  * The default for the name is "Sidebar #", with '#' being replaced with the
551  * number the sidebar is currently when greater than one. If first sidebar, the
552  * name will be just "Sidebar". The default for id is "sidebar-" followed by the
553  * number the sidebar creation is currently at. If the id is provided, and multiple
554  * sidebars are being defined, the id will have "-2" appended, and so on.
555  *
556  * @since 2.2.0
557  *
558  * @see register_sidebar() The second parameter is documented by register_sidebar() and is the same here.
559  * @uses parse_str() Converts a string to an array to be used in the rest of the function.
560  * @uses register_sidebar() Sends single sidebar information [name, id] to this
561  *      function to handle building the sidebar.
562  *
563  * @param int $number Number of sidebars to create.
564  * @param string|array $args Builds Sidebar based off of 'name' and 'id' values.
565  */
566 function register_sidebars($number = 1, $args = array()) {
567         global $wp_registered_sidebars;
568         $number = (int) $number;
569
570         if ( is_string($args) )
571                 parse_str($args, $args);
572
573         for ( $i = 1; $i <= $number; $i++ ) {
574                 $_args = $args;
575
576                 if ( $number > 1 )
577                         $_args['name'] = isset($args['name']) ? sprintf($args['name'], $i) : sprintf(__('Sidebar %d'), $i);
578                 else
579                         $_args['name'] = isset($args['name']) ? $args['name'] : __('Sidebar');
580
581                 // Custom specified ID's are suffixed if they exist already.
582                 // Automatically generated sidebar names need to be suffixed regardless starting at -0
583                 if ( isset($args['id']) ) {
584                         $_args['id'] = $args['id'];
585                         $n = 2; // Start at -2 for conflicting custom ID's
586                         while ( isset($wp_registered_sidebars[$_args['id']]) )
587                                 $_args['id'] = $args['id'] . '-' . $n++;
588                 } else {
589                         $n = count($wp_registered_sidebars);
590                         do {
591                                 $_args['id'] = 'sidebar-' . ++$n;
592                         } while ( isset($wp_registered_sidebars[$_args['id']]) );
593                 }
594                 register_sidebar($_args);
595         }
596 }
597
598 /**
599  * Builds the definition for a single sidebar and returns the ID.
600  *
601  * Accepts either a string or an array and then parses that against a set
602  * of default arguments for the new sidebar. WordPress will automatically
603  * generate a sidebar ID and name based on the current number of registered
604  * sidebars if those arguments are not included.
605  *
606  * When allowing for automatic generation of the name and ID parameters, keep
607  * in mind that the incrementor for your sidebar can change over time depending
608  * on what other plugins and themes are installed.
609  *
610  * If theme support for 'widgets' has not yet been added when this function is
611  * called, it will be automatically enabled through the use of add_theme_support()
612  *
613  * Arguments passed as a string should be separated by '&':
614  *
615  *     e.g. 'name=Sidebar&id=my_prefix_sidebar'
616  *
617  * The same arguments passed as an array:
618  *
619  *     array(
620  *         'name' => 'Sidebar',
621  *         'id'   => 'my_prefix_sidebar',
622  *     )
623  *
624  * Arguments:
625  *     name          - The name or title of the sidebar displayed in the admin dashboard.
626  *     id            - The unique identifier by which the sidebar will be called.
627  *     before_widget - HTML content that will be prepended to each widget's HTML output
628  *                     when assigned to this sidebar.
629  *     after_widget  - HTML content that will be appended to each widget's HTML output
630  *                     when assigned to this sidebar.
631  *     before_title  - HTML content that will be prepended to the sidebar title when displayed.
632  *     after_title   - HTML content that will be appended to the sidebar title when displayed.
633  *
634  * @since 2.2.0
635  * @uses $wp_registered_sidebars Stores the new sidebar in this array by sidebar ID.
636  * @uses add_theme_support() to ensure widget support has been added.
637  *
638  * @param string|array $args Arguments for the sidebar being registered.
639  * @return string Sidebar ID added to $wp_registered_sidebars global.
640  */
641 function register_sidebar($args = array()) {
642         global $wp_registered_sidebars;
643
644         $i = count($wp_registered_sidebars) + 1;
645
646         $defaults = array(
647                 'name' => sprintf(__('Sidebar %d'), $i ),
648                 'id' => "sidebar-$i",
649                 'description' => '',
650                 'class' => '',
651                 'before_widget' => '<li id="%1$s" class="widget %2$s">',
652                 'after_widget' => "</li>\n",
653                 'before_title' => '<h2 class="widgettitle">',
654                 'after_title' => "</h2>\n",
655         );
656
657         $sidebar = wp_parse_args( $args, $defaults );
658
659         $wp_registered_sidebars[$sidebar['id']] = $sidebar;
660
661         add_theme_support('widgets');
662
663         /**
664          * Fires once a sidebar has been registered.
665          *
666          * @since 3.0.0
667          *
668          * @param array $sidebar Parsed arguments for the registered sidebar.
669          */
670         do_action( 'register_sidebar', $sidebar );
671
672         return $sidebar['id'];
673 }
674
675 /**
676  * Removes a sidebar from the list.
677  *
678  * @since 2.2.0
679  *
680  * @uses $wp_registered_sidebars Stores the new sidebar in this array by sidebar ID.
681  *
682  * @param string $name The ID of the sidebar when it was added.
683  */
684 function unregister_sidebar( $name ) {
685         global $wp_registered_sidebars;
686
687         if ( isset( $wp_registered_sidebars[$name] ) )
688                 unset( $wp_registered_sidebars[$name] );
689 }
690
691 /**
692  * Register widget for use in sidebars.
693  *
694  * The default widget option is 'classname' that can be override.
695  *
696  * The function can also be used to unregister widgets when $output_callback
697  * parameter is an empty string.
698  *
699  * @since 2.2.0
700  *
701  * @uses $wp_registered_widgets Uses stored registered widgets.
702  * @uses $wp_register_widget_defaults Retrieves widget defaults.
703  *
704  * @param int|string $id Widget ID.
705  * @param string $name Widget display title.
706  * @param callback $output_callback Run when widget is called.
707  * @param array|string $options Optional. Widget Options.
708  * @param mixed $params,... Widget parameters to add to widget.
709  * @return null Will return if $output_callback is empty after removing widget.
710  */
711 function wp_register_sidebar_widget($id, $name, $output_callback, $options = array()) {
712         global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates, $_wp_deprecated_widgets_callbacks;
713
714         $id = strtolower($id);
715
716         if ( empty($output_callback) ) {
717                 unset($wp_registered_widgets[$id]);
718                 return;
719         }
720
721         $id_base = _get_widget_id_base($id);
722         if ( in_array($output_callback, $_wp_deprecated_widgets_callbacks, true) && !is_callable($output_callback) ) {
723                 if ( isset($wp_registered_widget_controls[$id]) )
724                         unset($wp_registered_widget_controls[$id]);
725
726                 if ( isset($wp_registered_widget_updates[$id_base]) )
727                         unset($wp_registered_widget_updates[$id_base]);
728
729                 return;
730         }
731
732         $defaults = array('classname' => $output_callback);
733         $options = wp_parse_args($options, $defaults);
734         $widget = array(
735                 'name' => $name,
736                 'id' => $id,
737                 'callback' => $output_callback,
738                 'params' => array_slice(func_get_args(), 4)
739         );
740         $widget = array_merge($widget, $options);
741
742         if ( is_callable($output_callback) && ( !isset($wp_registered_widgets[$id]) || did_action( 'widgets_init' ) ) ) {
743
744                 /**
745                  * Fires once for each registered widget.
746                  *
747                  * @since 3.0.0
748                  *
749                  * @param array $widget An array of default widget arguments.
750                  */
751                 do_action( 'wp_register_sidebar_widget', $widget );
752                 $wp_registered_widgets[$id] = $widget;
753         }
754 }
755
756 /**
757  * Retrieve description for widget.
758  *
759  * When registering widgets, the options can also include 'description' that
760  * describes the widget for display on the widget administration panel or
761  * in the theme.
762  *
763  * @since 2.5.0
764  *
765  * @param int|string $id Widget ID.
766  * @return string Widget description, if available. Null on failure to retrieve description.
767  */
768 function wp_widget_description( $id ) {
769         if ( !is_scalar($id) )
770                 return;
771
772         global $wp_registered_widgets;
773
774         if ( isset($wp_registered_widgets[$id]['description']) )
775                 return esc_html( $wp_registered_widgets[$id]['description'] );
776 }
777
778 /**
779  * Retrieve description for a sidebar.
780  *
781  * When registering sidebars a 'description' parameter can be included that
782  * describes the sidebar for display on the widget administration panel.
783  *
784  * @since 2.9.0
785  *
786  * @param int|string $id sidebar ID.
787  * @return string Sidebar description, if available. Null on failure to retrieve description.
788  */
789 function wp_sidebar_description( $id ) {
790         if ( !is_scalar($id) )
791                 return;
792
793         global $wp_registered_sidebars;
794
795         if ( isset($wp_registered_sidebars[$id]['description']) )
796                 return esc_html( $wp_registered_sidebars[$id]['description'] );
797 }
798
799 /**
800  * Remove widget from sidebar.
801  *
802  * @since 2.2.0
803  *
804  * @param int|string $id Widget ID.
805  */
806 function wp_unregister_sidebar_widget($id) {
807
808         /**
809          * Fires just before a widget is removed from a sidebar.
810          *
811          * @since 3.0.0
812          *
813          * @param int $id The widget ID.
814          */
815         do_action( 'wp_unregister_sidebar_widget', $id );
816
817         wp_register_sidebar_widget($id, '', '');
818         wp_unregister_widget_control($id);
819 }
820
821 /**
822  * Registers widget control callback for customizing options.
823  *
824  * The options contains the 'height', 'width', and 'id_base' keys. The 'height'
825  * option is never used. The 'width' option is the width of the fully expanded
826  * control form, but try hard to use the default width. The 'id_base' is for
827  * multi-widgets (widgets which allow multiple instances such as the text
828  * widget), an id_base must be provided. The widget id will end up looking like
829  * {$id_base}-{$unique_number}.
830  *
831  * @since 2.2.0
832  *
833  * @param int|string $id Sidebar ID.
834  * @param string $name Sidebar display name.
835  * @param callback $control_callback Run when sidebar is displayed.
836  * @param array|string $options Optional. Widget options. See above long description.
837  * @param mixed $params,... Optional. Additional parameters to add to widget.
838  */
839 function wp_register_widget_control($id, $name, $control_callback, $options = array()) {
840         global $wp_registered_widget_controls, $wp_registered_widget_updates, $wp_registered_widgets, $_wp_deprecated_widgets_callbacks;
841
842         $id = strtolower($id);
843         $id_base = _get_widget_id_base($id);
844
845         if ( empty($control_callback) ) {
846                 unset($wp_registered_widget_controls[$id]);
847                 unset($wp_registered_widget_updates[$id_base]);
848                 return;
849         }
850
851         if ( in_array($control_callback, $_wp_deprecated_widgets_callbacks, true) && !is_callable($control_callback) ) {
852                 if ( isset($wp_registered_widgets[$id]) )
853                         unset($wp_registered_widgets[$id]);
854
855                 return;
856         }
857
858         if ( isset($wp_registered_widget_controls[$id]) && !did_action( 'widgets_init' ) )
859                 return;
860
861         $defaults = array('width' => 250, 'height' => 200 ); // height is never used
862         $options = wp_parse_args($options, $defaults);
863         $options['width'] = (int) $options['width'];
864         $options['height'] = (int) $options['height'];
865
866         $widget = array(
867                 'name' => $name,
868                 'id' => $id,
869                 'callback' => $control_callback,
870                 'params' => array_slice(func_get_args(), 4)
871         );
872         $widget = array_merge($widget, $options);
873
874         $wp_registered_widget_controls[$id] = $widget;
875
876         if ( isset($wp_registered_widget_updates[$id_base]) )
877                 return;
878
879         if ( isset($widget['params'][0]['number']) )
880                 $widget['params'][0]['number'] = -1;
881
882         unset($widget['width'], $widget['height'], $widget['name'], $widget['id']);
883         $wp_registered_widget_updates[$id_base] = $widget;
884 }
885
886 function _register_widget_update_callback($id_base, $update_callback, $options = array()) {
887         global $wp_registered_widget_updates;
888
889         if ( isset($wp_registered_widget_updates[$id_base]) ) {
890                 if ( empty($update_callback) )
891                         unset($wp_registered_widget_updates[$id_base]);
892                 return;
893         }
894
895         $widget = array(
896                 'callback' => $update_callback,
897                 'params' => array_slice(func_get_args(), 3)
898         );
899
900         $widget = array_merge($widget, $options);
901         $wp_registered_widget_updates[$id_base] = $widget;
902 }
903
904 function _register_widget_form_callback($id, $name, $form_callback, $options = array()) {
905         global $wp_registered_widget_controls;
906
907         $id = strtolower($id);
908
909         if ( empty($form_callback) ) {
910                 unset($wp_registered_widget_controls[$id]);
911                 return;
912         }
913
914         if ( isset($wp_registered_widget_controls[$id]) && !did_action( 'widgets_init' ) )
915                 return;
916
917         $defaults = array('width' => 250, 'height' => 200 );
918         $options = wp_parse_args($options, $defaults);
919         $options['width'] = (int) $options['width'];
920         $options['height'] = (int) $options['height'];
921
922         $widget = array(
923                 'name' => $name,
924                 'id' => $id,
925                 'callback' => $form_callback,
926                 'params' => array_slice(func_get_args(), 4)
927         );
928         $widget = array_merge($widget, $options);
929
930         $wp_registered_widget_controls[$id] = $widget;
931 }
932
933 /**
934  * Remove control callback for widget.
935  *
936  * @since 2.2.0
937  * @uses wp_register_widget_control() Unregisters by using empty callback.
938  *
939  * @param int|string $id Widget ID.
940  */
941 function wp_unregister_widget_control($id) {
942         return wp_register_widget_control($id, '', '');
943 }
944
945 /**
946  * Display dynamic sidebar.
947  *
948  * By default this displays the default sidebar or 'sidebar-1'. If your theme specifies the 'id' or
949  * 'name' parameter for its registered sidebars you can pass an id or name as the $index parameter.
950  * Otherwise, you can pass in a numerical index to display the sidebar at that index.
951  *
952  * @since 2.2.0
953  *
954  * @param int|string $index Optional, default is 1. Index, name or ID of dynamic sidebar.
955  * @return bool True, if widget sidebar was found and called. False if not found or not called.
956  */
957 function dynamic_sidebar($index = 1) {
958         global $wp_registered_sidebars, $wp_registered_widgets;
959
960         if ( is_int($index) ) {
961                 $index = "sidebar-$index";
962         } else {
963                 $index = sanitize_title($index);
964                 foreach ( (array) $wp_registered_sidebars as $key => $value ) {
965                         if ( sanitize_title($value['name']) == $index ) {
966                                 $index = $key;
967                                 break;
968                         }
969                 }
970         }
971
972         $sidebars_widgets = wp_get_sidebars_widgets();
973         if ( empty( $wp_registered_sidebars[ $index ] ) || empty( $sidebars_widgets[ $index ] ) || ! is_array( $sidebars_widgets[ $index ] ) ) {
974                 /** This action is documented in wp-includes/widgets.php */
975                 do_action( 'dynamic_sidebar_before', $index, false );
976                 /** This action is documented in wp-includes/widgets.php */
977                 do_action( 'dynamic_sidebar_after',  $index, false );
978                 /** This filter is documented in wp-includes/widgets.php */
979                 return apply_filters( 'dynamic_sidebar_has_widgets', false, $index );
980         }
981
982         /**
983          * Fires before widgets are rendered in a dynamic sidebar.
984          *
985          * Note: The action also fires for empty sidebars, and on both the front-end
986          * and back-end, including the Inactive Widgets sidebar on the Widgets screen.
987          *
988          * @since 3.9.0
989          *
990          * @param int|string $index       Index, name, or ID of the dynamic sidebar.
991          * @param bool       $has_widgets Whether the sidebar is populated with widgets.
992          *                                Default true.
993          */
994         do_action( 'dynamic_sidebar_before', $index, true );
995         $sidebar = $wp_registered_sidebars[$index];
996
997         $did_one = false;
998         foreach ( (array) $sidebars_widgets[$index] as $id ) {
999
1000                 if ( !isset($wp_registered_widgets[$id]) ) continue;
1001
1002                 $params = array_merge(
1003                         array( array_merge( $sidebar, array('widget_id' => $id, 'widget_name' => $wp_registered_widgets[$id]['name']) ) ),
1004                         (array) $wp_registered_widgets[$id]['params']
1005                 );
1006
1007                 // Substitute HTML id and class attributes into before_widget
1008                 $classname_ = '';
1009                 foreach ( (array) $wp_registered_widgets[$id]['classname'] as $cn ) {
1010                         if ( is_string($cn) )
1011                                 $classname_ .= '_' . $cn;
1012                         elseif ( is_object($cn) )
1013                                 $classname_ .= '_' . get_class($cn);
1014                 }
1015                 $classname_ = ltrim($classname_, '_');
1016                 $params[0]['before_widget'] = sprintf($params[0]['before_widget'], $id, $classname_);
1017
1018                 /**
1019                  * Filter the parameters passed to a widget's display callback.
1020                  *
1021                  * Note: The filter is evaluated on both the front-end and back-end,
1022                  * including for the Inactive Widgets sidebar on the Widgets screen.
1023                  *
1024                  * @since 2.5.0
1025                  *
1026                  * @see register_sidebar()
1027                  *
1028                  * @param array $params {
1029                  *     @type array $args  {
1030                  *         An array of widget display arguments.
1031                  *
1032                  *         @type string $name          Name of the sidebar the widget is assigned to.
1033                  *         @type string $id            ID of the sidebar the widget is assigned to.
1034                  *         @type string $description   The sidebar description.
1035                  *         @type string $class         CSS class applied to the sidebar container.
1036                  *         @type string $before_widget HTML markup to prepend to each widget in the sidebar.
1037                  *         @type string $after_widget  HTML markup to append to each widget in the sidebar.
1038                  *         @type string $before_title  HTML markup to prepend to the widget title when displayed.
1039                  *         @type string $after_title   HTML markup to append to the widget title when displayed.
1040                  *         @type string $widget_id     ID of the widget.
1041                  *         @type string $widget_name   Name of the widget.
1042                  *     }
1043                  *     @type array $widget_args {
1044                  *         An array of multi-widget arguments.
1045                  *
1046                  *         @type int $number Number increment used for multiples of the same widget.
1047                  *     }
1048                  * }
1049                  */
1050                 $params = apply_filters( 'dynamic_sidebar_params', $params );
1051
1052                 $callback = $wp_registered_widgets[$id]['callback'];
1053
1054                 /**
1055                  * Fires before a widget's display callback is called.
1056                  *
1057                  * Note: The action fires on both the front-end and back-end, including
1058                  * for widgets in the Inactive Widgets sidebar on the Widgets screen.
1059                  *
1060                  * The action is not fired for empty sidebars.
1061                  *
1062                  * @since 3.0.0
1063                  *
1064                  * @param array $widget_id {
1065                  *     An associative array of widget arguments.
1066                  *
1067                  *     @type string $name                Name of the widget.
1068                  *     @type string $id                  Widget ID.
1069                  *     @type array|callback $callback    When the hook is fired on the front-end, $callback is an array
1070                  *                                       containing the widget object. Fired on the back-end, $callback
1071                  *                                       is 'wp_widget_control', see $_callback.
1072                  *     @type array          $params      An associative array of multi-widget arguments.
1073                  *     @type string         $classname   CSS class applied to the widget container.
1074                  *     @type string         $description The widget description.
1075                  *     @type array          $_callback   When the hook is fired on the back-end, $_callback is populated
1076                  *                                       with an array containing the widget object, see $callback.
1077                  * }
1078                  */
1079                 do_action( 'dynamic_sidebar', $wp_registered_widgets[ $id ] );
1080
1081                 if ( is_callable($callback) ) {
1082                         call_user_func_array($callback, $params);
1083                         $did_one = true;
1084                 }
1085         }
1086
1087         /**
1088          * Fires after widgets are rendered in a dynamic sidebar.
1089          *
1090          * Note: The action also fires for empty sidebars, and on both the front-end
1091          * and back-end, including the Inactive Widgets sidebar on the Widgets screen.
1092          *
1093          * @since 3.9.0
1094          *
1095          * @param int|string $index       Index, name, or ID of the dynamic sidebar.
1096          * @param bool       $has_widgets Whether the sidebar is populated with widgets.
1097          *                                Default true.
1098          */
1099         do_action( 'dynamic_sidebar_after', $index, true );
1100
1101         /**
1102          * Filter whether a sidebar has widgets.
1103          *
1104          * Note: The filter is also evaluated for empty sidebars, and on both the front-end
1105          * and back-end, including the Inactive Widgets sidebar on the Widgets screen.
1106          *
1107          * @since 3.9.0
1108          *
1109          * @param bool       $did_one Whether at least one widget was rendered in the sidebar.
1110          *                            Default false.
1111          * @param int|string $index   Index, name, or ID of the dynamic sidebar.
1112          */
1113
1114         $did_one = apply_filters( 'dynamic_sidebar_has_widgets', $did_one, $index );
1115
1116         return $did_one;
1117 }
1118
1119 /**
1120  * Whether widget is displayed on the front-end.
1121  *
1122  * Either $callback or $id_base can be used
1123  * $id_base is the first argument when extending WP_Widget class
1124  * Without the optional $widget_id parameter, returns the ID of the first sidebar
1125  * in which the first instance of the widget with the given callback or $id_base is found.
1126  * With the $widget_id parameter, returns the ID of the sidebar where
1127  * the widget with that callback/$id_base AND that ID is found.
1128  *
1129  * NOTE: $widget_id and $id_base are the same for single widgets. To be effective
1130  * this function has to run after widgets have initialized, at action 'init' or later.
1131  *
1132  * @since 2.2.0
1133  *
1134  * @param string $callback Optional, Widget callback to check.
1135  * @param int $widget_id Optional, but needed for checking. Widget ID.
1136  * @param string $id_base Optional, the base ID of a widget created by extending WP_Widget.
1137  * @param bool $skip_inactive Optional, whether to check in 'wp_inactive_widgets'.
1138  * @return mixed false if widget is not active or id of sidebar in which the widget is active.
1139  */
1140 function is_active_widget($callback = false, $widget_id = false, $id_base = false, $skip_inactive = true) {
1141         global $wp_registered_widgets;
1142
1143         $sidebars_widgets = wp_get_sidebars_widgets();
1144
1145         if ( is_array($sidebars_widgets) ) {
1146                 foreach ( $sidebars_widgets as $sidebar => $widgets ) {
1147                         if ( $skip_inactive && 'wp_inactive_widgets' == $sidebar )
1148                                 continue;
1149
1150                         if ( is_array($widgets) ) {
1151                                 foreach ( $widgets as $widget ) {
1152                                         if ( ( $callback && isset($wp_registered_widgets[$widget]['callback']) && $wp_registered_widgets[$widget]['callback'] == $callback ) || ( $id_base && _get_widget_id_base($widget) == $id_base ) ) {
1153                                                 if ( !$widget_id || $widget_id == $wp_registered_widgets[$widget]['id'] )
1154                                                         return $sidebar;
1155                                         }
1156                                 }
1157                         }
1158                 }
1159         }
1160         return false;
1161 }
1162
1163 /**
1164  * Whether the dynamic sidebar is enabled and used by theme.
1165  *
1166  * @since 2.2.0
1167  *
1168  * @return bool True, if using widgets. False, if not using widgets.
1169  */
1170 function is_dynamic_sidebar() {
1171         global $wp_registered_widgets, $wp_registered_sidebars;
1172         $sidebars_widgets = get_option('sidebars_widgets');
1173         foreach ( (array) $wp_registered_sidebars as $index => $sidebar ) {
1174                 if ( count($sidebars_widgets[$index]) ) {
1175                         foreach ( (array) $sidebars_widgets[$index] as $widget )
1176                                 if ( array_key_exists($widget, $wp_registered_widgets) )
1177                                         return true;
1178                 }
1179         }
1180         return false;
1181 }
1182
1183 /**
1184  * Whether a sidebar is in use.
1185  *
1186  * @since 2.8.0
1187  *
1188  * @param mixed $index Sidebar name, id or number to check.
1189  * @return bool true if the sidebar is in use, false otherwise.
1190  */
1191 function is_active_sidebar( $index ) {
1192         $index = ( is_int($index) ) ? "sidebar-$index" : sanitize_title($index);
1193         $sidebars_widgets = wp_get_sidebars_widgets();
1194         $is_active_sidebar = ! empty( $sidebars_widgets[$index] );
1195
1196         /**
1197          * Filter whether a dynamic sidebar is considered "active".
1198          *
1199          * @since 3.9.0
1200          *
1201          * @param bool       $is_active_sidebar Whether or not the sidebar should be considered "active".
1202          *                                      In other words, whether the sidebar contains any widgets.
1203          * @param int|string $index             Index, name, or ID of the dynamic sidebar.
1204          */
1205         return apply_filters( 'is_active_sidebar', $is_active_sidebar, $index );
1206 }
1207
1208 /* Internal Functions */
1209
1210 /**
1211  * Retrieve full list of sidebars and their widgets.
1212  *
1213  * Will upgrade sidebar widget list, if needed. Will also save updated list, if
1214  * needed.
1215  *
1216  * @since 2.2.0
1217  * @access private
1218  *
1219  * @param bool $deprecated Not used (deprecated).
1220  * @return array Upgraded list of widgets to version 3 array format when called from the admin.
1221  */
1222 function wp_get_sidebars_widgets($deprecated = true) {
1223         if ( $deprecated !== true )
1224                 _deprecated_argument( __FUNCTION__, '2.8.1' );
1225
1226         global $_wp_sidebars_widgets, $sidebars_widgets;
1227
1228         // If loading from front page, consult $_wp_sidebars_widgets rather than options
1229         // to see if wp_convert_widget_settings() has made manipulations in memory.
1230         if ( !is_admin() ) {
1231                 if ( empty($_wp_sidebars_widgets) )
1232                         $_wp_sidebars_widgets = get_option('sidebars_widgets', array());
1233
1234                 $sidebars_widgets = $_wp_sidebars_widgets;
1235         } else {
1236                 $sidebars_widgets = get_option('sidebars_widgets', array());
1237         }
1238
1239         if ( is_array( $sidebars_widgets ) && isset($sidebars_widgets['array_version']) )
1240                 unset($sidebars_widgets['array_version']);
1241
1242         /**
1243          * Filter the list of sidebars and their widgets.
1244          *
1245          * @since 2.7.0
1246          *
1247          * @param array $sidebars_widgets An associative array of sidebars and their widgets.
1248          */
1249         $sidebars_widgets = apply_filters( 'sidebars_widgets', $sidebars_widgets );
1250         return $sidebars_widgets;
1251 }
1252
1253 /**
1254  * Set the sidebar widget option to update sidebars.
1255  *
1256  * @since 2.2.0
1257  * @access private
1258  *
1259  * @param array $sidebars_widgets Sidebar widgets and their settings.
1260  */
1261 function wp_set_sidebars_widgets( $sidebars_widgets ) {
1262         if ( !isset( $sidebars_widgets['array_version'] ) )
1263                 $sidebars_widgets['array_version'] = 3;
1264         update_option( 'sidebars_widgets', $sidebars_widgets );
1265 }
1266
1267 /**
1268  * Retrieve default registered sidebars list.
1269  *
1270  * @since 2.2.0
1271  * @access private
1272  *
1273  * @return array
1274  */
1275 function wp_get_widget_defaults() {
1276         global $wp_registered_sidebars;
1277
1278         $defaults = array();
1279
1280         foreach ( (array) $wp_registered_sidebars as $index => $sidebar )
1281                 $defaults[$index] = array();
1282
1283         return $defaults;
1284 }
1285
1286 /**
1287  * Convert the widget settings from single to multi-widget format.
1288  *
1289  * @since 2.8.0
1290  *
1291  * @return array
1292  */
1293 function wp_convert_widget_settings($base_name, $option_name, $settings) {
1294         // This test may need expanding.
1295         $single = $changed = false;
1296         if ( empty($settings) ) {
1297                 $single = true;
1298         } else {
1299                 foreach ( array_keys($settings) as $number ) {
1300                         if ( 'number' == $number )
1301                                 continue;
1302                         if ( !is_numeric($number) ) {
1303                                 $single = true;
1304                                 break;
1305                         }
1306                 }
1307         }
1308
1309         if ( $single ) {
1310                 $settings = array( 2 => $settings );
1311
1312                 // If loading from the front page, update sidebar in memory but don't save to options
1313                 if ( is_admin() ) {
1314                         $sidebars_widgets = get_option('sidebars_widgets');
1315                 } else {
1316                         if ( empty($GLOBALS['_wp_sidebars_widgets']) )
1317                                 $GLOBALS['_wp_sidebars_widgets'] = get_option('sidebars_widgets', array());
1318                         $sidebars_widgets = &$GLOBALS['_wp_sidebars_widgets'];
1319                 }
1320
1321                 foreach ( (array) $sidebars_widgets as $index => $sidebar ) {
1322                         if ( is_array($sidebar) ) {
1323                                 foreach ( $sidebar as $i => $name ) {
1324                                         if ( $base_name == $name ) {
1325                                                 $sidebars_widgets[$index][$i] = "$name-2";
1326                                                 $changed = true;
1327                                                 break 2;
1328                                         }
1329                                 }
1330                         }
1331                 }
1332
1333                 if ( is_admin() && $changed )
1334                         update_option('sidebars_widgets', $sidebars_widgets);
1335         }
1336
1337         $settings['_multiwidget'] = 1;
1338         if ( is_admin() )
1339                 update_option( $option_name, $settings );
1340
1341         return $settings;
1342 }
1343
1344 /**
1345  * Output an arbitrary widget as a template tag.
1346  *
1347  * @since 2.8.0
1348  *
1349  * @param string $widget the widget's PHP class name (see default-widgets.php)
1350  * @param array $instance the widget's instance settings
1351  * @param array $args the widget's sidebar args
1352  * @return void
1353  **/
1354 function the_widget($widget, $instance = array(), $args = array()) {
1355         global $wp_widget_factory;
1356
1357         $widget_obj = $wp_widget_factory->widgets[$widget];
1358         if ( !is_a($widget_obj, 'WP_Widget') )
1359                 return;
1360
1361         $before_widget = sprintf('<div class="widget %s">', $widget_obj->widget_options['classname'] );
1362         $default_args = array( 'before_widget' => $before_widget, 'after_widget' => "</div>", 'before_title' => '<h2 class="widgettitle">', 'after_title' => '</h2>' );
1363
1364         $args = wp_parse_args($args, $default_args);
1365         $instance = wp_parse_args($instance);
1366
1367         /**
1368          * Fires before rendering the requested widget.
1369          *
1370          * @since 3.0.0
1371          *
1372          * @param string $widget   The widget's class name.
1373          * @param array  $instance The current widget instance's settings.
1374          * @param array  $args     An array of the widget's sidebar arguments.
1375          */
1376         do_action( 'the_widget', $widget, $instance, $args );
1377
1378         $widget_obj->_set(-1);
1379         $widget_obj->widget($args, $instance);
1380 }
1381
1382 /**
1383  * Private
1384  */
1385 function _get_widget_id_base($id) {
1386         return preg_replace( '/-[0-9]+$/', '', $id );
1387 }
1388
1389 /**
1390  * Handle sidebars config after theme change
1391  *
1392  * @access private
1393  * @since 3.3.0
1394  */
1395 function _wp_sidebars_changed() {
1396         global $sidebars_widgets;
1397
1398         if ( ! is_array( $sidebars_widgets ) )
1399                 $sidebars_widgets = wp_get_sidebars_widgets();
1400
1401         retrieve_widgets(true);
1402 }
1403
1404 /**
1405  * Look for "lost" widgets, this has to run at least on each theme change.
1406  *
1407  * @since 2.8.0
1408  *
1409  * @param mixed $theme_changed Whether the theme was changed as a boolean. A value
1410  *                             of 'customize' defers updates for the customizer.
1411  * @return array
1412  */
1413 function retrieve_widgets( $theme_changed = false ) {
1414         global $wp_registered_sidebars, $sidebars_widgets, $wp_registered_widgets;
1415
1416         $registered_sidebar_keys = array_keys( $wp_registered_sidebars );
1417         $orphaned = 0;
1418
1419         $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' );
1420         if ( is_array( $old_sidebars_widgets ) ) {
1421                 // time() that sidebars were stored is in $old_sidebars_widgets['time']
1422                 $_sidebars_widgets = $old_sidebars_widgets['data'];
1423
1424                 if ( 'customize' !== $theme_changed ) {
1425                         remove_theme_mod( 'sidebars_widgets' );
1426                 }
1427
1428                 foreach ( $_sidebars_widgets as $sidebar => $widgets ) {
1429                         if ( 'wp_inactive_widgets' == $sidebar || 'orphaned_widgets' == substr( $sidebar, 0, 16 ) )
1430                                 continue;
1431
1432                         if ( !in_array( $sidebar, $registered_sidebar_keys ) ) {
1433                                 $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $widgets;
1434                                 unset( $_sidebars_widgets[$sidebar] );
1435                         }
1436                 }
1437         } else {
1438                 if ( empty( $sidebars_widgets ) )
1439                         return;
1440
1441                 unset( $sidebars_widgets['array_version'] );
1442
1443                 $old = array_keys($sidebars_widgets);
1444                 sort($old);
1445                 sort($registered_sidebar_keys);
1446
1447                 if ( $old == $registered_sidebar_keys )
1448                         return;
1449
1450                 $_sidebars_widgets = array(
1451                         'wp_inactive_widgets' => !empty( $sidebars_widgets['wp_inactive_widgets'] ) ? $sidebars_widgets['wp_inactive_widgets'] : array()
1452                 );
1453
1454                 unset( $sidebars_widgets['wp_inactive_widgets'] );
1455
1456                 foreach ( $wp_registered_sidebars as $id => $settings ) {
1457                         if ( $theme_changed ) {
1458                                 $_sidebars_widgets[$id] = array_shift( $sidebars_widgets );
1459                         } else {
1460                                 // no theme change, grab only sidebars that are currently registered
1461                                 if ( isset( $sidebars_widgets[$id] ) ) {
1462                                         $_sidebars_widgets[$id] = $sidebars_widgets[$id];
1463                                         unset( $sidebars_widgets[$id] );
1464                                 }
1465                         }
1466                 }
1467
1468                 foreach ( $sidebars_widgets as $val ) {
1469                         if ( is_array($val) && ! empty( $val ) )
1470                                 $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $val;
1471                 }
1472         }
1473
1474         // discard invalid, theme-specific widgets from sidebars
1475         $shown_widgets = array();
1476
1477         foreach ( $_sidebars_widgets as $sidebar => $widgets ) {
1478                 if ( !is_array($widgets) )
1479                         continue;
1480
1481                 $_widgets = array();
1482                 foreach ( $widgets as $widget ) {
1483                         if ( isset($wp_registered_widgets[$widget]) )
1484                                 $_widgets[] = $widget;
1485                 }
1486
1487                 $_sidebars_widgets[$sidebar] = $_widgets;
1488                 $shown_widgets = array_merge($shown_widgets, $_widgets);
1489         }
1490
1491         $sidebars_widgets = $_sidebars_widgets;
1492         unset($_sidebars_widgets, $_widgets);
1493
1494         // find hidden/lost multi-widget instances
1495         $lost_widgets = array();
1496         foreach ( $wp_registered_widgets as $key => $val ) {
1497                 if ( in_array($key, $shown_widgets, true) )
1498                         continue;
1499
1500                 $number = preg_replace('/.+?-([0-9]+)$/', '$1', $key);
1501
1502                 if ( 2 > (int) $number )
1503                         continue;
1504
1505                 $lost_widgets[] = $key;
1506         }
1507
1508         $sidebars_widgets['wp_inactive_widgets'] = array_merge($lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets']);
1509         if ( 'customize' !== $theme_changed ) {
1510                 wp_set_sidebars_widgets( $sidebars_widgets );
1511         }
1512
1513         return $sidebars_widgets;
1514 }