WordPress 4.0
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-manager.php
1 <?php
2 /**
3  * Customize Manager.
4  *
5  * Bootstraps the Customize experience on the server-side.
6  *
7  * Sets up the theme-switching process if a theme other than the active one is
8  * being previewed and customized.
9  *
10  * Serves as a factory for Customize Controls and Settings, and
11  * instantiates default Customize Controls and Settings.
12  *
13  * @package WordPress
14  * @subpackage Customize
15  * @since 3.4.0
16  */
17 final class WP_Customize_Manager {
18         /**
19          * An instance of the theme being previewed.
20          *
21          * @var WP_Theme
22          */
23         protected $theme;
24
25         /**
26          * The directory name of the previously active theme (within the theme_root).
27          *
28          * @var string
29          */
30         protected $original_stylesheet;
31
32         /**
33          * Whether this is a Customizer pageload.
34          *
35          * @var boolean
36          */
37         protected $previewing = false;
38
39         /**
40          * Methods and properties deailing with managing widgets in the customizer.
41          *
42          * @var WP_Customize_Widgets
43          */
44         public $widgets;
45
46         protected $settings   = array();
47         protected $containers = array();
48         protected $panels     = array();
49         protected $sections   = array();
50         protected $controls   = array();
51
52         protected $nonce_tick;
53
54         protected $customized;
55
56         /**
57          * $_POST values for Customize Settings.
58          *
59          * @var array
60          */
61         private $_post_values;
62
63         /**
64          * Constructor.
65          *
66          * @since 3.4.0
67          */
68         public function __construct() {
69                 require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
70                 require( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
71                 require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
72                 require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
73                 require( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
74
75                 $this->widgets = new WP_Customize_Widgets( $this );
76
77                 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
78
79                 add_action( 'setup_theme',  array( $this, 'setup_theme' ) );
80                 add_action( 'wp_loaded',    array( $this, 'wp_loaded' ) );
81
82                 // Run wp_redirect_status late to make sure we override the status last.
83                 add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
84
85                 // Do not spawn cron (especially the alternate cron) while running the customizer.
86                 remove_action( 'init', 'wp_cron' );
87
88                 // Do not run update checks when rendering the controls.
89                 remove_action( 'admin_init', '_maybe_update_core' );
90                 remove_action( 'admin_init', '_maybe_update_plugins' );
91                 remove_action( 'admin_init', '_maybe_update_themes' );
92
93                 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
94
95                 add_action( 'customize_register',                 array( $this, 'register_controls' ) );
96                 add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
97                 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
98         }
99
100         /**
101          * Return true if it's an AJAX request.
102          *
103          * @since 3.4.0
104          *
105          * @return bool
106          */
107         public function doing_ajax() {
108                 return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
109         }
110
111         /**
112          * Custom wp_die wrapper. Returns either the standard message for UI
113          * or the AJAX message.
114          *
115          * @since 3.4.0
116          *
117          * @param mixed $ajax_message AJAX return
118          * @param mixed $message UI message
119          */
120         protected function wp_die( $ajax_message, $message = null ) {
121                 if ( $this->doing_ajax() )
122                         wp_die( $ajax_message );
123
124                 if ( ! $message )
125                         $message = __( 'Cheatin&#8217; uh?' );
126
127                 wp_die( $message );
128         }
129
130         /**
131          * Return the AJAX wp_die() handler if it's a customized request.
132          *
133          * @since 3.4.0
134          *
135          * @return string
136          */
137         public function wp_die_handler() {
138                 if ( $this->doing_ajax() )
139                         return '_ajax_wp_die_handler';
140
141                 return '_default_wp_die_handler';
142         }
143
144         /**
145          * Start preview and customize theme.
146          *
147          * Check if customize query variable exist. Init filters to filter the current theme.
148          *
149          * @since 3.4.0
150          */
151         public function setup_theme() {
152                 send_origin_headers();
153
154                 if ( is_admin() && ! $this->doing_ajax() )
155                     auth_redirect();
156                 elseif ( $this->doing_ajax() && ! is_user_logged_in() )
157                     $this->wp_die( 0 );
158
159                 show_admin_bar( false );
160
161                 if ( ! current_user_can( 'customize' ) ) {
162                         $this->wp_die( -1 );
163                 }
164
165                 $this->original_stylesheet = get_stylesheet();
166
167                 $this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
168
169                 if ( $this->is_theme_active() ) {
170                         // Once the theme is loaded, we'll validate it.
171                         add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
172                 } else {
173                         // If the requested theme is not the active theme and the user doesn't have the
174                         // switch_themes cap, bail.
175                         if ( ! current_user_can( 'switch_themes' ) )
176                                 $this->wp_die( -1 );
177
178                         // If the theme has errors while loading, bail.
179                         if ( $this->theme()->errors() )
180                                 $this->wp_die( -1 );
181
182                         // If the theme isn't allowed per multisite settings, bail.
183                         if ( ! $this->theme()->is_allowed() )
184                                 $this->wp_die( -1 );
185                 }
186
187                 $this->start_previewing_theme();
188         }
189
190         /**
191          * Callback to validate a theme once it is loaded
192          *
193          * @since 3.4.0
194          */
195         public function after_setup_theme() {
196                 if ( ! $this->doing_ajax() && ! validate_current_theme() ) {
197                         wp_redirect( 'themes.php?broken=true' );
198                         exit;
199                 }
200         }
201
202         /**
203          * If the theme to be previewed isn't the active theme, add filter callbacks
204          * to swap it out at runtime.
205          *
206          * @since 3.4.0
207          */
208         public function start_previewing_theme() {
209                 // Bail if we're already previewing.
210                 if ( $this->is_preview() )
211                         return;
212
213                 $this->previewing = true;
214
215                 if ( ! $this->is_theme_active() ) {
216                         add_filter( 'template', array( $this, 'get_template' ) );
217                         add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
218                         add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
219
220                         // @link: http://core.trac.wordpress.org/ticket/20027
221                         add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
222                         add_filter( 'pre_option_template', array( $this, 'get_template' ) );
223
224                         // Handle custom theme roots.
225                         add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
226                         add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
227                 }
228
229                 /**
230                  * Fires once the Customizer theme preview has started.
231                  *
232                  * @since 3.4.0
233                  *
234                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
235                  */
236                 do_action( 'start_previewing_theme', $this );
237         }
238
239         /**
240          * Stop previewing the selected theme.
241          *
242          * Removes filters to change the current theme.
243          *
244          * @since 3.4.0
245          */
246         public function stop_previewing_theme() {
247                 if ( ! $this->is_preview() )
248                         return;
249
250                 $this->previewing = false;
251
252                 if ( ! $this->is_theme_active() ) {
253                         remove_filter( 'template', array( $this, 'get_template' ) );
254                         remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
255                         remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
256
257                         // @link: http://core.trac.wordpress.org/ticket/20027
258                         remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
259                         remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
260
261                         // Handle custom theme roots.
262                         remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
263                         remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
264                 }
265
266                 /**
267                  * Fires once the Customizer theme preview has stopped.
268                  *
269                  * @since 3.4.0
270                  *
271                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
272                  */
273                 do_action( 'stop_previewing_theme', $this );
274         }
275
276         /**
277          * Get the theme being customized.
278          *
279          * @since 3.4.0
280          *
281          * @return WP_Theme
282          */
283         public function theme() {
284                 return $this->theme;
285         }
286
287         /**
288          * Get the registered settings.
289          *
290          * @since 3.4.0
291          *
292          * @return array
293          */
294         public function settings() {
295                 return $this->settings;
296         }
297
298         /**
299          * Get the registered controls.
300          *
301          * @since 3.4.0
302          *
303          * @return array
304          */
305         public function controls() {
306                 return $this->controls;
307         }
308
309         /**
310          * Get the registered containers.
311          *
312          * @since 4.0.0
313          *
314          * @return array
315          */
316         public function containers() {
317                 return $this->containers;
318         }
319
320         /**
321          * Get the registered sections.
322          *
323          * @since 3.4.0
324          *
325          * @return array
326          */
327         public function sections() {
328                 return $this->sections;
329         }
330
331         /**
332          * Get the registered panels.
333          *
334          * @since 4.0.0
335          * @access public
336          *
337          * @return array Panels.
338          */
339         public function panels() {
340                 return $this->panels;
341         }
342
343         /**
344          * Checks if the current theme is active.
345          *
346          * @since 3.4.0
347          *
348          * @return bool
349          */
350         public function is_theme_active() {
351                 return $this->get_stylesheet() == $this->original_stylesheet;
352         }
353
354         /**
355          * Register styles/scripts and initialize the preview of each setting
356          *
357          * @since 3.4.0
358          */
359         public function wp_loaded() {
360
361                 /**
362                  * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
363                  *
364                  * @since 3.4.0
365                  *
366                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
367                  */
368                 do_action( 'customize_register', $this );
369
370                 if ( $this->is_preview() && ! is_admin() )
371                         $this->customize_preview_init();
372         }
373
374         /**
375          * Prevents AJAX requests from following redirects when previewing a theme
376          * by issuing a 200 response instead of a 30x.
377          *
378          * Instead, the JS will sniff out the location header.
379          *
380          * @since 3.4.0
381          *
382          * @param $status
383          * @return int
384          */
385         public function wp_redirect_status( $status ) {
386                 if ( $this->is_preview() && ! is_admin() )
387                         return 200;
388
389                 return $status;
390         }
391
392         /**
393          * Decode the $_POST['customized'] values for a specific Customize Setting.
394          *
395          * @since 3.4.0
396          *
397          * @param mixed $setting A WP_Customize_Setting derived object
398          * @return string $post_value Sanitized value
399          */
400         public function post_value( $setting ) {
401                 if ( ! isset( $this->_post_values ) ) {
402                         if ( isset( $_POST['customized'] ) )
403                                 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
404                         else
405                                 $this->_post_values = false;
406                 }
407
408                 if ( isset( $this->_post_values[ $setting->id ] ) )
409                         return $setting->sanitize( $this->_post_values[ $setting->id ] );
410         }
411
412         /**
413          * Print javascript settings.
414          *
415          * @since 3.4.0
416          */
417         public function customize_preview_init() {
418                 $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
419
420                 $this->prepare_controls();
421
422                 wp_enqueue_script( 'customize-preview' );
423                 add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
424                 add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
425                 add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
426                 add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
427                 add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
428                 add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
429
430                 foreach ( $this->settings as $setting ) {
431                         $setting->preview();
432                 }
433
434                 /**
435                  * Fires once the Customizer preview has initialized and JavaScript
436                  * settings have been printed.
437                  *
438                  * @since 3.4.0
439                  *
440                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
441                  */
442                 do_action( 'customize_preview_init', $this );
443         }
444
445         /**
446          * Prevent sending a 404 status when returning the response for the customize
447          * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
448          *
449          * @since 4.0.0
450          * @access public
451          */
452         public function customize_preview_override_404_status() {
453                 if ( is_404() ) {
454                         status_header( 200 );
455                 }
456         }
457
458         /**
459          * Print base element for preview frame.
460          *
461          * @since 3.4.0
462          */
463         public function customize_preview_base() {
464                 ?><base href="<?php echo home_url( '/' ); ?>" /><?php
465         }
466
467         /**
468          * Print a workaround to handle HTML5 tags in IE < 9
469          *
470          * @since 3.4.0
471          */
472         public function customize_preview_html5() { ?>
473                 <!--[if lt IE 9]>
474                 <script type="text/javascript">
475                         var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
476                                 'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
477                                 'output', 'progress', 'section', 'time', 'video' ];
478                         for ( var i = 0; i < e.length; i++ ) {
479                                 document.createElement( e[i] );
480                         }
481                 </script>
482                 <![endif]--><?php
483         }
484
485         /**
486          * Print javascript settings for preview frame.
487          *
488          * @since 3.4.0
489          */
490         public function customize_preview_settings() {
491                 $settings = array(
492                         'values'  => array(),
493                         'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
494                         'activeControls' => array(),
495                 );
496
497                 if ( 2 == $this->nonce_tick ) {
498                         $settings['nonce'] = array(
499                                 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
500                                 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
501                         );
502                 }
503
504                 foreach ( $this->settings as $id => $setting ) {
505                         $settings['values'][ $id ] = $setting->js_value();
506                 }
507                 foreach ( $this->controls as $id => $control ) {
508                         $settings['activeControls'][ $id ] = $control->active();
509                 }
510
511                 ?>
512                 <script type="text/javascript">
513                         var _wpCustomizeSettings = <?php echo json_encode( $settings ); ?>;
514                 </script>
515                 <?php
516         }
517
518         /**
519          * Prints a signature so we can ensure the customizer was properly executed.
520          *
521          * @since 3.4.0
522          */
523         public function customize_preview_signature() {
524                 echo 'WP_CUSTOMIZER_SIGNATURE';
525         }
526
527         /**
528          * Removes the signature in case we experience a case where the customizer was not properly executed.
529          *
530          * @since 3.4.0
531          */
532         public function remove_preview_signature( $return = null ) {
533                 remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
534
535                 return $return;
536         }
537
538         /**
539          * Is it a theme preview?
540          *
541          * @since 3.4.0
542          *
543          * @return bool True if it's a preview, false if not.
544          */
545         public function is_preview() {
546                 return (bool) $this->previewing;
547         }
548
549         /**
550          * Retrieve the template name of the previewed theme.
551          *
552          * @since 3.4.0
553          *
554          * @return string Template name.
555          */
556         public function get_template() {
557                 return $this->theme()->get_template();
558         }
559
560         /**
561          * Retrieve the stylesheet name of the previewed theme.
562          *
563          * @since 3.4.0
564          *
565          * @return string Stylesheet name.
566          */
567         public function get_stylesheet() {
568                 return $this->theme()->get_stylesheet();
569         }
570
571         /**
572          * Retrieve the template root of the previewed theme.
573          *
574          * @since 3.4.0
575          *
576          * @return string Theme root.
577          */
578         public function get_template_root() {
579                 return get_raw_theme_root( $this->get_template(), true );
580         }
581
582         /**
583          * Retrieve the stylesheet root of the previewed theme.
584          *
585          * @since 3.4.0
586          *
587          * @return string Theme root.
588          */
589         public function get_stylesheet_root() {
590                 return get_raw_theme_root( $this->get_stylesheet(), true );
591         }
592
593         /**
594          * Filter the current theme and return the name of the previewed theme.
595          *
596          * @since 3.4.0
597          *
598          * @param $current_theme {@internal Parameter is not used}
599          * @return string Theme name.
600          */
601         public function current_theme( $current_theme ) {
602                 return $this->theme()->display('Name');
603         }
604
605         /**
606          * Switch the theme and trigger the save() method on each setting.
607          *
608          * @since 3.4.0
609          */
610         public function save() {
611                 if ( ! $this->is_preview() )
612                         die;
613
614                 check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
615
616                 // Do we have to switch themes?
617                 if ( ! $this->is_theme_active() ) {
618                         // Temporarily stop previewing the theme to allow switch_themes()
619                         // to operate properly.
620                         $this->stop_previewing_theme();
621                         switch_theme( $this->get_stylesheet() );
622                         update_option( 'theme_switched_via_customizer', true );
623                         $this->start_previewing_theme();
624                 }
625
626                 /**
627                  * Fires once the theme has switched in the Customizer, but before settings
628                  * have been saved.
629                  *
630                  * @since 3.4.0
631                  *
632                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
633                  */
634                 do_action( 'customize_save', $this );
635
636                 foreach ( $this->settings as $setting ) {
637                         $setting->save();
638                 }
639
640                 /**
641                  * Fires after Customize settings have been saved.
642                  *
643                  * @since 3.6.0
644                  *
645                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
646                  */
647                 do_action( 'customize_save_after', $this );
648
649                 die;
650         }
651
652         /**
653          * Add a customize setting.
654          *
655          * @since 3.4.0
656          *
657          * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
658          * @param array $args                     Setting arguments; passed to WP_Customize_Setting
659          *                                        constructor.
660          */
661         public function add_setting( $id, $args = array() ) {
662                 if ( is_a( $id, 'WP_Customize_Setting' ) )
663                         $setting = $id;
664                 else
665                         $setting = new WP_Customize_Setting( $this, $id, $args );
666
667                 $this->settings[ $setting->id ] = $setting;
668         }
669
670         /**
671          * Retrieve a customize setting.
672          *
673          * @since 3.4.0
674          *
675          * @param string $id Customize Setting ID.
676          * @return WP_Customize_Setting
677          */
678         public function get_setting( $id ) {
679                 if ( isset( $this->settings[ $id ] ) )
680                         return $this->settings[ $id ];
681         }
682
683         /**
684          * Remove a customize setting.
685          *
686          * @since 3.4.0
687          *
688          * @param string $id Customize Setting ID.
689          */
690         public function remove_setting( $id ) {
691                 unset( $this->settings[ $id ] );
692         }
693
694         /**
695          * Add a customize panel.
696          *
697          * @since 4.0.0
698          * @access public
699          *
700          * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
701          * @param array                     $args Optional. Panel arguments. Default empty array.
702          */
703         public function add_panel( $id, $args = array() ) {
704                 if ( is_a( $id, 'WP_Customize_Panel' ) ) {
705                         $panel = $id;
706                 }
707                 else {
708                         $panel = new WP_Customize_Panel( $this, $id, $args );
709                 }
710
711                 $this->panels[ $panel->id ] = $panel;
712         }
713
714         /**
715          * Retrieve a customize panel.
716          *
717          * @since 4.0.0
718          * @access public
719          *
720          * @param string $id Panel ID to get.
721          * @return WP_Customize_Panel Requested panel instance.
722          */
723         public function get_panel( $id ) {
724                 if ( isset( $this->panels[ $id ] ) ) {
725                         return $this->panels[ $id ];
726                 }
727         }
728
729         /**
730          * Remove a customize panel.
731          *
732          * @since 4.0.0
733          * @access public
734          *
735          * @param string $id Panel ID to remove.
736          */
737         public function remove_panel( $id ) {
738                 unset( $this->panels[ $id ] );
739         }
740
741         /**
742          * Add a customize section.
743          *
744          * @since 3.4.0
745          *
746          * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
747          * @param array                       $args Section arguments.
748          */
749         public function add_section( $id, $args = array() ) {
750                 if ( is_a( $id, 'WP_Customize_Section' ) )
751                         $section = $id;
752                 else
753                         $section = new WP_Customize_Section( $this, $id, $args );
754
755                 $this->sections[ $section->id ] = $section;
756         }
757
758         /**
759          * Retrieve a customize section.
760          *
761          * @since 3.4.0
762          *
763          * @param string $id Section ID.
764          * @return WP_Customize_Section
765          */
766         public function get_section( $id ) {
767                 if ( isset( $this->sections[ $id ] ) )
768                         return $this->sections[ $id ];
769         }
770
771         /**
772          * Remove a customize section.
773          *
774          * @since 3.4.0
775          *
776          * @param string $id Section ID.
777          */
778         public function remove_section( $id ) {
779                 unset( $this->sections[ $id ] );
780         }
781
782         /**
783          * Add a customize control.
784          *
785          * @since 3.4.0
786          *
787          * @param WP_Customize_Control|string $id   Customize Control object, or ID.
788          * @param array                       $args Control arguments; passed to WP_Customize_Control
789          *                                          constructor.
790          */
791         public function add_control( $id, $args = array() ) {
792                 if ( is_a( $id, 'WP_Customize_Control' ) )
793                         $control = $id;
794                 else
795                         $control = new WP_Customize_Control( $this, $id, $args );
796
797                 $this->controls[ $control->id ] = $control;
798         }
799
800         /**
801          * Retrieve a customize control.
802          *
803          * @since 3.4.0
804          *
805          * @param string $id ID of the control.
806          * @return WP_Customize_Control $control The control object.
807          */
808         public function get_control( $id ) {
809                 if ( isset( $this->controls[ $id ] ) )
810                         return $this->controls[ $id ];
811         }
812
813         /**
814          * Remove a customize control.
815          *
816          * @since 3.4.0
817          *
818          * @param string $id ID of the control.
819          */
820         public function remove_control( $id ) {
821                 unset( $this->controls[ $id ] );
822         }
823
824         /**
825          * Helper function to compare two objects by priority.
826          *
827          * @since 3.4.0
828          *
829          * @param object $a Object A.
830          * @param object $b Object B.
831          * @return int
832          */
833         protected final function _cmp_priority( $a, $b ) {
834                 $ap = $a->priority;
835                 $bp = $b->priority;
836
837                 if ( $ap == $bp )
838                         return 0;
839                 return ( $ap > $bp ) ? 1 : -1;
840         }
841
842         /**
843          * Prepare panels, sections, and controls.
844          *
845          * For each, check if required related components exist,
846          * whether the user has the necessary capabilities,
847          * and sort by priority.
848          *
849          * @since 3.4.0
850          */
851         public function prepare_controls() {
852
853                 $this->controls = array_reverse( $this->controls );
854                 $controls = array();
855
856                 foreach ( $this->controls as $id => $control ) {
857                         if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
858                                 continue;
859                         }
860
861                         $this->sections[ $control->section ]->controls[] = $control;
862                         $controls[ $id ] = $control;
863                 }
864                 $this->controls = $controls;
865
866                 // Prepare sections.
867                 // Reversing makes uasort sort by time added when conflicts occur.
868                 $this->sections = array_reverse( $this->sections );
869                 uasort( $this->sections, array( $this, '_cmp_priority' ) );
870                 $sections = array();
871
872                 foreach ( $this->sections as $section ) {
873                         if ( ! $section->check_capabilities() || ! $section->controls ) {
874                                 continue;
875                         }
876
877                         usort( $section->controls, array( $this, '_cmp_priority' ) );
878
879                         if ( ! $section->panel ) {
880                                 // Top-level section.
881                                 $sections[] = $section;
882                         } else {
883                                 // This section belongs to a panel.
884                                 if ( isset( $this->panels [ $section->panel ] ) ) {
885                                         $this->panels[ $section->panel ]->sections[] = $section;
886                                 }
887                         }
888                 }
889                 $this->sections = $sections;
890
891                 // Prepare panels.
892                 // Reversing makes uasort sort by time added when conflicts occur.
893                 $this->panels = array_reverse( $this->panels );
894                 uasort( $this->panels, array( $this, '_cmp_priority' ) );
895                 $panels = array();
896
897                 foreach ( $this->panels as $panel ) {
898                         if ( ! $panel->check_capabilities() || ! $panel->sections ) {
899                                 continue;
900                         }
901
902                         usort( $panel->sections, array( $this, '_cmp_priority' ) );
903                         $panels[] = $panel;
904                 }
905                 $this->panels = $panels;
906
907                 // Sort panels and top-level sections together.
908                 $this->containers = array_merge( $this->panels, $this->sections );
909                 uasort( $this->containers, array( $this, '_cmp_priority' ) );
910         }
911
912         /**
913          * Enqueue scripts for customize controls.
914          *
915          * @since 3.4.0
916          */
917         public function enqueue_control_scripts() {
918                 foreach ( $this->controls as $control ) {
919                         $control->enqueue();
920                 }
921         }
922
923         /**
924          * Register some default controls.
925          *
926          * @since 3.4.0
927          */
928         public function register_controls() {
929
930                 /* Site Title & Tagline */
931
932                 $this->add_section( 'title_tagline', array(
933                         'title'    => __( 'Site Title & Tagline' ),
934                         'priority' => 20,
935                 ) );
936
937                 $this->add_setting( 'blogname', array(
938                         'default'    => get_option( 'blogname' ),
939                         'type'       => 'option',
940                         'capability' => 'manage_options',
941                 ) );
942
943                 $this->add_control( 'blogname', array(
944                         'label'      => __( 'Site Title' ),
945                         'section'    => 'title_tagline',
946                 ) );
947
948                 $this->add_setting( 'blogdescription', array(
949                         'default'    => get_option( 'blogdescription' ),
950                         'type'       => 'option',
951                         'capability' => 'manage_options',
952                 ) );
953
954                 $this->add_control( 'blogdescription', array(
955                         'label'      => __( 'Tagline' ),
956                         'section'    => 'title_tagline',
957                 ) );
958
959                 /* Colors */
960
961                 $this->add_section( 'colors', array(
962                         'title'          => __( 'Colors' ),
963                         'priority'       => 40,
964                 ) );
965
966                 $this->add_setting( 'header_textcolor', array(
967                         'theme_supports' => array( 'custom-header', 'header-text' ),
968                         'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
969
970                         'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
971                         'sanitize_js_callback' => 'maybe_hash_hex_color',
972                 ) );
973
974                 // Input type: checkbox
975                 // With custom value
976                 $this->add_control( 'display_header_text', array(
977                         'settings' => 'header_textcolor',
978                         'label'    => __( 'Display Header Text' ),
979                         'section'  => 'title_tagline',
980                         'type'     => 'checkbox',
981                 ) );
982
983                 $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
984                         'label'   => __( 'Header Text Color' ),
985                         'section' => 'colors',
986                 ) ) );
987
988                 // Input type: Color
989                 // With sanitize_callback
990                 $this->add_setting( 'background_color', array(
991                         'default'        => get_theme_support( 'custom-background', 'default-color' ),
992                         'theme_supports' => 'custom-background',
993
994                         'sanitize_callback'    => 'sanitize_hex_color_no_hash',
995                         'sanitize_js_callback' => 'maybe_hash_hex_color',
996                 ) );
997
998                 $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
999                         'label'   => __( 'Background Color' ),
1000                         'section' => 'colors',
1001                 ) ) );
1002
1003
1004                 /* Custom Header */
1005
1006                 $this->add_section( 'header_image', array(
1007                         'title'          => __( 'Header Image' ),
1008                         'theme_supports' => 'custom-header',
1009                         'priority'       => 60,
1010                 ) );
1011
1012                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
1013                         'default'        => get_theme_support( 'custom-header', 'default-image' ),
1014                         'theme_supports' => 'custom-header',
1015                 ) ) );
1016
1017                 $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
1018                         // 'default'        => get_theme_support( 'custom-header', 'default-image' ),
1019                         'theme_supports' => 'custom-header',
1020                 ) ) );
1021
1022                 $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
1023
1024                 /* Custom Background */
1025
1026                 $this->add_section( 'background_image', array(
1027                         'title'          => __( 'Background Image' ),
1028                         'theme_supports' => 'custom-background',
1029                         'priority'       => 80,
1030                 ) );
1031
1032                 $this->add_setting( 'background_image', array(
1033                         'default'        => get_theme_support( 'custom-background', 'default-image' ),
1034                         'theme_supports' => 'custom-background',
1035                 ) );
1036
1037                 $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
1038                         'theme_supports' => 'custom-background',
1039                 ) ) );
1040
1041                 $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
1042
1043                 $this->add_setting( 'background_repeat', array(
1044                         'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
1045                         'theme_supports' => 'custom-background',
1046                 ) );
1047
1048                 $this->add_control( 'background_repeat', array(
1049                         'label'      => __( 'Background Repeat' ),
1050                         'section'    => 'background_image',
1051                         'type'       => 'radio',
1052                         'choices'    => array(
1053                                 'no-repeat'  => __('No Repeat'),
1054                                 'repeat'     => __('Tile'),
1055                                 'repeat-x'   => __('Tile Horizontally'),
1056                                 'repeat-y'   => __('Tile Vertically'),
1057                         ),
1058                 ) );
1059
1060                 $this->add_setting( 'background_position_x', array(
1061                         'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
1062                         'theme_supports' => 'custom-background',
1063                 ) );
1064
1065                 $this->add_control( 'background_position_x', array(
1066                         'label'      => __( 'Background Position' ),
1067                         'section'    => 'background_image',
1068                         'type'       => 'radio',
1069                         'choices'    => array(
1070                                 'left'       => __('Left'),
1071                                 'center'     => __('Center'),
1072                                 'right'      => __('Right'),
1073                         ),
1074                 ) );
1075
1076                 $this->add_setting( 'background_attachment', array(
1077                         'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
1078                         'theme_supports' => 'custom-background',
1079                 ) );
1080
1081                 $this->add_control( 'background_attachment', array(
1082                         'label'      => __( 'Background Attachment' ),
1083                         'section'    => 'background_image',
1084                         'type'       => 'radio',
1085                         'choices'    => array(
1086                                 'scroll'     => __('Scroll'),
1087                                 'fixed'      => __('Fixed'),
1088                         ),
1089                 ) );
1090
1091                 // If the theme is using the default background callback, we can update
1092                 // the background CSS using postMessage.
1093                 if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
1094                         foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
1095                                 $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
1096                         }
1097                 }
1098
1099                 /* Nav Menus */
1100
1101                 $locations      = get_registered_nav_menus();
1102                 $menus          = wp_get_nav_menus();
1103                 $num_locations  = count( array_keys( $locations ) );
1104
1105                 $this->add_section( 'nav', array(
1106                         'title'          => __( 'Navigation' ),
1107                         'theme_supports' => 'menus',
1108                         'priority'       => 100,
1109                         'description'    => sprintf( _n('Your theme supports %s menu. Select which menu you would like to use.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) ) . "\n\n" . __('You can edit your menu content on the Menus screen in the Appearance section.'),
1110                 ) );
1111
1112                 if ( $menus ) {
1113                         $choices = array( 0 => __( '&mdash; Select &mdash;' ) );
1114                         foreach ( $menus as $menu ) {
1115                                 $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
1116                         }
1117
1118                         foreach ( $locations as $location => $description ) {
1119                                 $menu_setting_id = "nav_menu_locations[{$location}]";
1120
1121                                 $this->add_setting( $menu_setting_id, array(
1122                                         'sanitize_callback' => 'absint',
1123                                         'theme_supports'    => 'menus',
1124                                 ) );
1125
1126                                 $this->add_control( $menu_setting_id, array(
1127                                         'label'   => $description,
1128                                         'section' => 'nav',
1129                                         'type'    => 'select',
1130                                         'choices' => $choices,
1131                                 ) );
1132                         }
1133                 }
1134
1135                 /* Static Front Page */
1136                 // #WP19627
1137
1138                 $this->add_section( 'static_front_page', array(
1139                         'title'          => __( 'Static Front Page' ),
1140                 //      'theme_supports' => 'static-front-page',
1141                         'priority'       => 120,
1142                         'description'    => __( 'Your theme supports a static front page.' ),
1143                 ) );
1144
1145                 $this->add_setting( 'show_on_front', array(
1146                         'default'        => get_option( 'show_on_front' ),
1147                         'capability'     => 'manage_options',
1148                         'type'           => 'option',
1149                 //      'theme_supports' => 'static-front-page',
1150                 ) );
1151
1152                 $this->add_control( 'show_on_front', array(
1153                         'label'   => __( 'Front page displays' ),
1154                         'section' => 'static_front_page',
1155                         'type'    => 'radio',
1156                         'choices' => array(
1157                                 'posts' => __( 'Your latest posts' ),
1158                                 'page'  => __( 'A static page' ),
1159                         ),
1160                 ) );
1161
1162                 $this->add_setting( 'page_on_front', array(
1163                         'type'       => 'option',
1164                         'capability' => 'manage_options',
1165                 //      'theme_supports' => 'static-front-page',
1166                 ) );
1167
1168                 $this->add_control( 'page_on_front', array(
1169                         'label'      => __( 'Front page' ),
1170                         'section'    => 'static_front_page',
1171                         'type'       => 'dropdown-pages',
1172                 ) );
1173
1174                 $this->add_setting( 'page_for_posts', array(
1175                         'type'           => 'option',
1176                         'capability'     => 'manage_options',
1177                 //      'theme_supports' => 'static-front-page',
1178                 ) );
1179
1180                 $this->add_control( 'page_for_posts', array(
1181                         'label'      => __( 'Posts page' ),
1182                         'section'    => 'static_front_page',
1183                         'type'       => 'dropdown-pages',
1184                 ) );
1185         }
1186
1187         /**
1188          * Callback for validating the header_textcolor value.
1189          *
1190          * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
1191          * Returns default text color if hex color is empty.
1192          *
1193          * @since 3.4.0
1194          *
1195          * @param string $color
1196          * @return string
1197          */
1198         public function _sanitize_header_textcolor( $color ) {
1199                 if ( 'blank' === $color )
1200                         return 'blank';
1201
1202                 $color = sanitize_hex_color_no_hash( $color );
1203                 if ( empty( $color ) )
1204                         $color = get_theme_support( 'custom-header', 'default-text-color' );
1205
1206                 return $color;
1207         }
1208 }
1209
1210 /**
1211  * Sanitizes a hex color.
1212  *
1213  * Returns either '', a 3 or 6 digit hex color (with #), or null.
1214  * For sanitizing values without a #, see sanitize_hex_color_no_hash().
1215  *
1216  * @since 3.4.0
1217  *
1218  * @param string $color
1219  * @return string|null
1220  */
1221 function sanitize_hex_color( $color ) {
1222         if ( '' === $color )
1223                 return '';
1224
1225         // 3 or 6 hex digits, or the empty string.
1226         if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
1227                 return $color;
1228
1229         return null;
1230 }
1231
1232 /**
1233  * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
1234  *
1235  * Saving hex colors without a hash puts the burden of adding the hash on the
1236  * UI, which makes it difficult to use or upgrade to other color types such as
1237  * rgba, hsl, rgb, and html color names.
1238  *
1239  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
1240  *
1241  * @since 3.4.0
1242  * @uses sanitize_hex_color()
1243  *
1244  * @param string $color
1245  * @return string|null
1246  */
1247 function sanitize_hex_color_no_hash( $color ) {
1248         $color = ltrim( $color, '#' );
1249
1250         if ( '' === $color )
1251                 return '';
1252
1253         return sanitize_hex_color( '#' . $color ) ? $color : null;
1254 }
1255
1256 /**
1257  * Ensures that any hex color is properly hashed.
1258  * Otherwise, returns value untouched.
1259  *
1260  * This method should only be necessary if using sanitize_hex_color_no_hash().
1261  *
1262  * @since 3.4.0
1263  *
1264  * @param string $color
1265  * @return string
1266  */
1267 function maybe_hash_hex_color( $color ) {
1268         if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
1269                 return '#' . $unhashed;
1270
1271         return $color;
1272 }