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