WordPress 4.3
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-manager.php
1 <?php
2 /**
3  * WordPress Customize Manager classes
4  *
5  * @package WordPress
6  * @subpackage Customize
7  * @since 3.4.0
8  */
9
10 /**
11  * Customize Manager class.
12  *
13  * Bootstraps the Customize experience on the server-side.
14  *
15  * Sets up the theme-switching process if a theme other than the active one is
16  * being previewed and customized.
17  *
18  * Serves as a factory for Customize Controls and Settings, and
19  * instantiates default Customize Controls and Settings.
20  *
21  * @since 3.4.0
22  */
23 final class WP_Customize_Manager {
24         /**
25          * An instance of the theme being previewed.
26          *
27          * @var WP_Theme
28          */
29         protected $theme;
30
31         /**
32          * The directory name of the previously active theme (within the theme_root).
33          *
34          * @var string
35          */
36         protected $original_stylesheet;
37
38         /**
39          * Whether this is a Customizer pageload.
40          *
41          * @var bool
42          */
43         protected $previewing = false;
44
45         /**
46          * Methods and properties deailing with managing widgets in the Customizer.
47          *
48          * @var WP_Customize_Widgets
49          */
50         public $widgets;
51
52         /**
53          * Methods and properties deailing with managing nav menus in the Customizer.
54          *
55          * @var WP_Customize_Nav_Menus
56          */
57         public $nav_menus;
58
59         protected $settings   = array();
60         protected $containers = array();
61         protected $panels     = array();
62         protected $sections   = array();
63         protected $controls   = array();
64
65         protected $nonce_tick;
66
67         protected $customized;
68
69         /**
70          * Panel types that may be rendered from JS templates.
71          *
72          * @since 4.3.0
73          * @access protected
74          * @var array
75          */
76         protected $registered_panel_types = array();
77
78         /**
79          * Section types that may be rendered from JS templates.
80          *
81          * @since 4.3.0
82          * @access protected
83          * @var array
84          */
85         protected $registered_section_types = array();
86
87         /**
88          * Control types that may be rendered from JS templates.
89          *
90          * @since 4.1.0
91          * @access protected
92          * @var array
93          */
94         protected $registered_control_types = array();
95
96         /**
97          * Unsanitized values for Customize Settings parsed from $_POST['customized'].
98          *
99          * @var array
100          */
101         private $_post_values;
102
103         /**
104          * Constructor.
105          *
106          * @since 3.4.0
107          */
108         public function __construct() {
109                 require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
110                 require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
111                 require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
112                 require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
113                 require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
114                 require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
115
116                 $this->widgets = new WP_Customize_Widgets( $this );
117                 $this->nav_menus = new WP_Customize_Nav_Menus( $this );
118
119                 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
120
121                 add_action( 'setup_theme', array( $this, 'setup_theme' ) );
122                 add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
123
124                 // Run wp_redirect_status late to make sure we override the status last.
125                 add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
126
127                 // Do not spawn cron (especially the alternate cron) while running the Customizer.
128                 remove_action( 'init', 'wp_cron' );
129
130                 // Do not run update checks when rendering the controls.
131                 remove_action( 'admin_init', '_maybe_update_core' );
132                 remove_action( 'admin_init', '_maybe_update_plugins' );
133                 remove_action( 'admin_init', '_maybe_update_themes' );
134
135                 add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
136                 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
137
138                 add_action( 'customize_register',                 array( $this, 'register_controls' ) );
139                 add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
140                 add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
141                 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
142         }
143
144         /**
145          * Return true if it's an AJAX request.
146          *
147          * @since 3.4.0
148          * @since 4.2.0 Added `$action` param.
149          * @access public
150          *
151          * @param string|null $action Whether the supplied AJAX action is being run.
152          * @return bool True if it's an AJAX request, false otherwise.
153          */
154         public function doing_ajax( $action = null ) {
155                 $doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
156                 if ( ! $doing_ajax ) {
157                         return false;
158                 }
159
160                 if ( ! $action ) {
161                         return true;
162                 } else {
163                         /*
164                          * Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need
165                          * to check before admin-ajax.php gets to that point.
166                          */
167                         return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
168                 }
169         }
170
171         /**
172          * Custom wp_die wrapper. Returns either the standard message for UI
173          * or the AJAX message.
174          *
175          * @since 3.4.0
176          *
177          * @param mixed $ajax_message AJAX return
178          * @param mixed $message UI message
179          */
180         protected function wp_die( $ajax_message, $message = null ) {
181                 if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
182                         wp_die( $ajax_message );
183                 }
184
185                 if ( ! $message ) {
186                         $message = __( 'Cheatin&#8217; uh?' );
187                 }
188
189                 wp_die( $message );
190         }
191
192         /**
193          * Return the AJAX wp_die() handler if it's a customized request.
194          *
195          * @since 3.4.0
196          *
197          * @return string
198          */
199         public function wp_die_handler() {
200                 if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
201                         return '_ajax_wp_die_handler';
202                 }
203
204                 return '_default_wp_die_handler';
205         }
206
207         /**
208          * Start preview and customize theme.
209          *
210          * Check if customize query variable exist. Init filters to filter the current theme.
211          *
212          * @since 3.4.0
213          */
214         public function setup_theme() {
215                 send_origin_headers();
216
217                 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
218                 if ( is_admin() && ! $doing_ajax_or_is_customized ) {
219                         auth_redirect();
220                 } elseif ( $doing_ajax_or_is_customized && ! is_user_logged_in() ) {
221                         $this->wp_die( 0 );
222                 }
223
224                 show_admin_bar( false );
225
226                 if ( ! current_user_can( 'customize' ) ) {
227                         $this->wp_die( -1 );
228                 }
229
230                 $this->original_stylesheet = get_stylesheet();
231
232                 $this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
233
234                 if ( $this->is_theme_active() ) {
235                         // Once the theme is loaded, we'll validate it.
236                         add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
237                 } else {
238                         // If the requested theme is not the active theme and the user doesn't have the
239                         // switch_themes cap, bail.
240                         if ( ! current_user_can( 'switch_themes' ) ) {
241                                 $this->wp_die( -1 );
242                         }
243
244                         // If the theme has errors while loading, bail.
245                         if ( $this->theme()->errors() ) {
246                                 $this->wp_die( -1 );
247                         }
248
249                         // If the theme isn't allowed per multisite settings, bail.
250                         if ( ! $this->theme()->is_allowed() ) {
251                                 $this->wp_die( -1 );
252                         }
253                 }
254
255                 $this->start_previewing_theme();
256         }
257
258         /**
259          * Callback to validate a theme once it is loaded
260          *
261          * @since 3.4.0
262          */
263         public function after_setup_theme() {
264                 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_SERVER['customized'] ) );
265                 if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
266                         wp_redirect( 'themes.php?broken=true' );
267                         exit;
268                 }
269         }
270
271         /**
272          * If the theme to be previewed isn't the active theme, add filter callbacks
273          * to swap it out at runtime.
274          *
275          * @since 3.4.0
276          */
277         public function start_previewing_theme() {
278                 // Bail if we're already previewing.
279                 if ( $this->is_preview() ) {
280                         return;
281                 }
282
283                 $this->previewing = true;
284
285                 if ( ! $this->is_theme_active() ) {
286                         add_filter( 'template', array( $this, 'get_template' ) );
287                         add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
288                         add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
289
290                         // @link: https://core.trac.wordpress.org/ticket/20027
291                         add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
292                         add_filter( 'pre_option_template', array( $this, 'get_template' ) );
293
294                         // Handle custom theme roots.
295                         add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
296                         add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
297                 }
298
299                 /**
300                  * Fires once the Customizer theme preview has started.
301                  *
302                  * @since 3.4.0
303                  *
304                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
305                  */
306                 do_action( 'start_previewing_theme', $this );
307         }
308
309         /**
310          * Stop previewing the selected theme.
311          *
312          * Removes filters to change the current theme.
313          *
314          * @since 3.4.0
315          */
316         public function stop_previewing_theme() {
317                 if ( ! $this->is_preview() ) {
318                         return;
319                 }
320
321                 $this->previewing = false;
322
323                 if ( ! $this->is_theme_active() ) {
324                         remove_filter( 'template', array( $this, 'get_template' ) );
325                         remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
326                         remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
327
328                         // @link: https://core.trac.wordpress.org/ticket/20027
329                         remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
330                         remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
331
332                         // Handle custom theme roots.
333                         remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
334                         remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
335                 }
336
337                 /**
338                  * Fires once the Customizer theme preview has stopped.
339                  *
340                  * @since 3.4.0
341                  *
342                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
343                  */
344                 do_action( 'stop_previewing_theme', $this );
345         }
346
347         /**
348          * Get the theme being customized.
349          *
350          * @since 3.4.0
351          *
352          * @return WP_Theme
353          */
354         public function theme() {
355                 if ( ! $this->theme ) {
356                         $this->theme = wp_get_theme();
357                 }
358                 return $this->theme;
359         }
360
361         /**
362          * Get the registered settings.
363          *
364          * @since 3.4.0
365          *
366          * @return array
367          */
368         public function settings() {
369                 return $this->settings;
370         }
371
372         /**
373          * Get the registered controls.
374          *
375          * @since 3.4.0
376          *
377          * @return array
378          */
379         public function controls() {
380                 return $this->controls;
381         }
382
383         /**
384          * Get the registered containers.
385          *
386          * @since 4.0.0
387          *
388          * @return array
389          */
390         public function containers() {
391                 return $this->containers;
392         }
393
394         /**
395          * Get the registered sections.
396          *
397          * @since 3.4.0
398          *
399          * @return array
400          */
401         public function sections() {
402                 return $this->sections;
403         }
404
405         /**
406          * Get the registered panels.
407          *
408          * @since 4.0.0
409          * @access public
410          *
411          * @return array Panels.
412          */
413         public function panels() {
414                 return $this->panels;
415         }
416
417         /**
418          * Checks if the current theme is active.
419          *
420          * @since 3.4.0
421          *
422          * @return bool
423          */
424         public function is_theme_active() {
425                 return $this->get_stylesheet() == $this->original_stylesheet;
426         }
427
428         /**
429          * Register styles/scripts and initialize the preview of each setting
430          *
431          * @since 3.4.0
432          */
433         public function wp_loaded() {
434
435                 /**
436                  * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
437                  *
438                  * @since 3.4.0
439                  *
440                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
441                  */
442                 do_action( 'customize_register', $this );
443
444                 if ( $this->is_preview() && ! is_admin() )
445                         $this->customize_preview_init();
446         }
447
448         /**
449          * Prevents AJAX requests from following redirects when previewing a theme
450          * by issuing a 200 response instead of a 30x.
451          *
452          * Instead, the JS will sniff out the location header.
453          *
454          * @since 3.4.0
455          *
456          * @param $status
457          * @return int
458          */
459         public function wp_redirect_status( $status ) {
460                 if ( $this->is_preview() && ! is_admin() )
461                         return 200;
462
463                 return $status;
464         }
465
466         /**
467          * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
468          * settings for subsequent post_value() lookups.
469          *
470          * @since 4.1.1
471          *
472          * @return array
473          */
474         public function unsanitized_post_values() {
475                 if ( ! isset( $this->_post_values ) ) {
476                         if ( isset( $_POST['customized'] ) ) {
477                                 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
478                         }
479                         if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
480                                 $this->_post_values = array();
481                         }
482                 }
483                 if ( empty( $this->_post_values ) ) {
484                         return array();
485                 } else {
486                         return $this->_post_values;
487                 }
488         }
489
490         /**
491          * Return the sanitized value for a given setting from the request's POST data.
492          *
493          * @since 3.4.0
494          * @since 4.1.1 Introduced 'default' parameter.
495          *
496          * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
497          * @param mixed $default value returned $setting has no post value (added in 4.2.0).
498          * @return string|mixed $post_value Sanitized value or the $default provided
499          */
500         public function post_value( $setting, $default = null ) {
501                 $post_values = $this->unsanitized_post_values();
502                 if ( array_key_exists( $setting->id, $post_values ) ) {
503                         return $setting->sanitize( $post_values[ $setting->id ] );
504                 } else {
505                         return $default;
506                 }
507         }
508
509         /**
510          * Override a setting's (unsanitized) value as found in any incoming $_POST['customized'].
511          *
512          * @since 4.2.0
513          * @access public
514          *
515          * @param string $setting_id ID for the WP_Customize_Setting instance.
516          * @param mixed  $value      Post value.
517          */
518         public function set_post_value( $setting_id, $value ) {
519                 $this->unsanitized_post_values();
520                 $this->_post_values[ $setting_id ] = $value;
521         }
522
523         /**
524          * Print JavaScript settings.
525          *
526          * @since 3.4.0
527          */
528         public function customize_preview_init() {
529                 $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
530
531                 $this->prepare_controls();
532
533                 wp_enqueue_script( 'customize-preview' );
534                 add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
535                 add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
536                 add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
537                 add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
538                 add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
539                 add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
540                 add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
541
542                 foreach ( $this->settings as $setting ) {
543                         $setting->preview();
544                 }
545
546                 /**
547                  * Fires once the Customizer preview has initialized and JavaScript
548                  * settings have been printed.
549                  *
550                  * @since 3.4.0
551                  *
552                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
553                  */
554                 do_action( 'customize_preview_init', $this );
555         }
556
557         /**
558          * Prevent sending a 404 status when returning the response for the customize
559          * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
560          *
561          * @since 4.0.0
562          * @access public
563          */
564         public function customize_preview_override_404_status() {
565                 if ( is_404() ) {
566                         status_header( 200 );
567                 }
568         }
569
570         /**
571          * Print base element for preview frame.
572          *
573          * @since 3.4.0
574          */
575         public function customize_preview_base() {
576                 ?><base href="<?php echo home_url( '/' ); ?>" /><?php
577         }
578
579         /**
580          * Print a workaround to handle HTML5 tags in IE < 9.
581          *
582          * @since 3.4.0
583          */
584         public function customize_preview_html5() { ?>
585                 <!--[if lt IE 9]>
586                 <script type="text/javascript">
587                         var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
588                                 'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
589                                 'output', 'progress', 'section', 'time', 'video' ];
590                         for ( var i = 0; i < e.length; i++ ) {
591                                 document.createElement( e[i] );
592                         }
593                 </script>
594                 <![endif]--><?php
595         }
596
597         /**
598          * Print CSS for loading indicators for the Customizer preview.
599          *
600          * @since 4.2.0
601          * @access public
602          */
603         public function customize_preview_loading_style() {
604                 ?><style>
605                         body.wp-customizer-unloading {
606                                 opacity: 0.25;
607                                 cursor: progress !important;
608                                 -webkit-transition: opacity 0.5s;
609                                 transition: opacity 0.5s;
610                         }
611                         body.wp-customizer-unloading * {
612                                 pointer-events: none !important;
613                         }
614                 </style><?php
615         }
616
617         /**
618          * Print JavaScript settings for preview frame.
619          *
620          * @since 3.4.0
621          */
622         public function customize_preview_settings() {
623                 $settings = array(
624                         'values'  => array(),
625                         'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
626                         'activePanels' => array(),
627                         'activeSections' => array(),
628                         'activeControls' => array(),
629                         'l10n' => array(
630                                 'loading'  => __( 'Loading ...' ),
631                         ),
632                 );
633
634                 if ( 2 == $this->nonce_tick ) {
635                         $settings['nonce'] = array(
636                                 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
637                                 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
638                         );
639                 }
640
641                 foreach ( $this->settings as $id => $setting ) {
642                         if ( $setting->check_capabilities() ) {
643                                 $settings['values'][ $id ] = $setting->js_value();
644                         }
645                 }
646                 foreach ( $this->panels as $panel_id => $panel ) {
647                         if ( $panel->check_capabilities() ) {
648                                 $settings['activePanels'][ $panel_id ] = $panel->active();
649                                 foreach ( $panel->sections as $section_id => $section ) {
650                                         if ( $section->check_capabilities() ) {
651                                                 $settings['activeSections'][ $section_id ] = $section->active();
652                                         }
653                                 }
654                         }
655                 }
656                 foreach ( $this->sections as $id => $section ) {
657                         if ( $section->check_capabilities() ) {
658                                 $settings['activeSections'][ $id ] = $section->active();
659                         }
660                 }
661                 foreach ( $this->controls as $id => $control ) {
662                         if ( $control->check_capabilities() ) {
663                                 $settings['activeControls'][ $id ] = $control->active();
664                         }
665                 }
666
667                 ?>
668                 <script type="text/javascript">
669                         var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
670                 </script>
671                 <?php
672         }
673
674         /**
675          * Prints a signature so we can ensure the Customizer was properly executed.
676          *
677          * @since 3.4.0
678          */
679         public function customize_preview_signature() {
680                 echo 'WP_CUSTOMIZER_SIGNATURE';
681         }
682
683         /**
684          * Removes the signature in case we experience a case where the Customizer was not properly executed.
685          *
686          * @since 3.4.0
687          *
688          * @return mixed
689          */
690         public function remove_preview_signature( $return = null ) {
691                 remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
692
693                 return $return;
694         }
695
696         /**
697          * Is it a theme preview?
698          *
699          * @since 3.4.0
700          *
701          * @return bool True if it's a preview, false if not.
702          */
703         public function is_preview() {
704                 return (bool) $this->previewing;
705         }
706
707         /**
708          * Retrieve the template name of the previewed theme.
709          *
710          * @since 3.4.0
711          *
712          * @return string Template name.
713          */
714         public function get_template() {
715                 return $this->theme()->get_template();
716         }
717
718         /**
719          * Retrieve the stylesheet name of the previewed theme.
720          *
721          * @since 3.4.0
722          *
723          * @return string Stylesheet name.
724          */
725         public function get_stylesheet() {
726                 return $this->theme()->get_stylesheet();
727         }
728
729         /**
730          * Retrieve the template root of the previewed theme.
731          *
732          * @since 3.4.0
733          *
734          * @return string Theme root.
735          */
736         public function get_template_root() {
737                 return get_raw_theme_root( $this->get_template(), true );
738         }
739
740         /**
741          * Retrieve the stylesheet root of the previewed theme.
742          *
743          * @since 3.4.0
744          *
745          * @return string Theme root.
746          */
747         public function get_stylesheet_root() {
748                 return get_raw_theme_root( $this->get_stylesheet(), true );
749         }
750
751         /**
752          * Filter the current theme and return the name of the previewed theme.
753          *
754          * @since 3.4.0
755          *
756          * @param $current_theme {@internal Parameter is not used}
757          * @return string Theme name.
758          */
759         public function current_theme( $current_theme ) {
760                 return $this->theme()->display('Name');
761         }
762
763         /**
764          * Switch the theme and trigger the save() method on each setting.
765          *
766          * @since 3.4.0
767          */
768         public function save() {
769                 if ( ! $this->is_preview() ) {
770                         wp_send_json_error( 'not_preview' );
771                 }
772
773                 $action = 'save-customize_' . $this->get_stylesheet();
774                 if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
775                         wp_send_json_error( 'invalid_nonce' );
776                 }
777
778                 // Do we have to switch themes?
779                 if ( ! $this->is_theme_active() ) {
780                         // Temporarily stop previewing the theme to allow switch_themes()
781                         // to operate properly.
782                         $this->stop_previewing_theme();
783                         switch_theme( $this->get_stylesheet() );
784                         update_option( 'theme_switched_via_customizer', true );
785                         $this->start_previewing_theme();
786                 }
787
788                 /**
789                  * Fires once the theme has switched in the Customizer, but before settings
790                  * have been saved.
791                  *
792                  * @since 3.4.0
793                  *
794                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
795                  */
796                 do_action( 'customize_save', $this );
797
798                 foreach ( $this->settings as $setting ) {
799                         $setting->save();
800                 }
801
802                 /**
803                  * Fires after Customize settings have been saved.
804                  *
805                  * @since 3.6.0
806                  *
807                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
808                  */
809                 do_action( 'customize_save_after', $this );
810
811                 /**
812                  * Filter response data for a successful customize_save AJAX request.
813                  *
814                  * This filter does not apply if there was a nonce or authentication failure.
815                  *
816                  * @since 4.2.0
817                  *
818                  * @param array                $data Additional information passed back to the 'saved'
819                  *                                   event on `wp.customize`.
820                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
821                  */
822                 $response = apply_filters( 'customize_save_response', array(), $this );
823                 wp_send_json_success( $response );
824         }
825
826         /**
827          * Refresh nonces for the current preview.
828          *
829          * @since 4.2.0
830          */
831         public function refresh_nonces() {
832                 if ( ! $this->is_preview() ) {
833                         wp_send_json_error( 'not_preview' );
834                 }
835
836                 $nonces = array(
837                         'save'    => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
838                         'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
839                 );
840
841                 /**
842                  * Filter nonces for a customize_refresh_nonces AJAX request.
843                  *
844                  * @since 4.2.0
845                  *
846                  * @param array                $nonces Array of refreshed nonces for save and
847                  *                                     preview actions.
848                  * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
849                  */
850                 $nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
851                 wp_send_json_success( $nonces );
852         }
853
854         /**
855          * Add a customize setting.
856          *
857          * @since 3.4.0
858          *
859          * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
860          * @param array $args                     Setting arguments; passed to WP_Customize_Setting
861          *                                        constructor.
862          */
863         public function add_setting( $id, $args = array() ) {
864                 if ( $id instanceof WP_Customize_Setting ) {
865                         $setting = $id;
866                 } else {
867                         $setting = new WP_Customize_Setting( $this, $id, $args );
868                 }
869                 $this->settings[ $setting->id ] = $setting;
870         }
871
872         /**
873          * Register any dynamically-created settings, such as those from $_POST['customized']
874          * that have no corresponding setting created.
875          *
876          * This is a mechanism to "wake up" settings that have been dynamically created
877          * on the frontend and have been sent to WordPress in `$_POST['customized']`. When WP
878          * loads, the dynamically-created settings then will get created and previewed
879          * even though they are not directly created statically with code.
880          *
881          * @since 4.2.0
882          *
883          * @param array $setting_ids The setting IDs to add.
884          * @return WP_Customize_Setting The settings added.
885          */
886         public function add_dynamic_settings( $setting_ids ) {
887                 $new_settings = array();
888                 foreach ( $setting_ids as $setting_id ) {
889                         // Skip settings already created
890                         if ( $this->get_setting( $setting_id ) ) {
891                                 continue;
892                         }
893
894                         $setting_args = false;
895                         $setting_class = 'WP_Customize_Setting';
896
897                         /**
898                          * Filter a dynamic setting's constructor args.
899                          *
900                          * For a dynamic setting to be registered, this filter must be employed
901                          * to override the default false value with an array of args to pass to
902                          * the WP_Customize_Setting constructor.
903                          *
904                          * @since 4.2.0
905                          *
906                          * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
907                          * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
908                          */
909                         $setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
910                         if ( false === $setting_args ) {
911                                 continue;
912                         }
913
914                         /**
915                          * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
916                          *
917                          * @since 4.2.0
918                          *
919                          * @param string $setting_class WP_Customize_Setting or a subclass.
920                          * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
921                          * @param array  $setting_args  WP_Customize_Setting or a subclass.
922                          */
923                         $setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
924
925                         $setting = new $setting_class( $this, $setting_id, $setting_args );
926
927                         $this->add_setting( $setting );
928                         $new_settings[] = $setting;
929                 }
930                 return $new_settings;
931         }
932
933         /**
934          * Retrieve a customize setting.
935          *
936          * @since 3.4.0
937          *
938          * @param string $id Customize Setting ID.
939          * @return WP_Customize_Setting|void The setting, if set.
940          */
941         public function get_setting( $id ) {
942                 if ( isset( $this->settings[ $id ] ) ) {
943                         return $this->settings[ $id ];
944                 }
945         }
946
947         /**
948          * Remove a customize setting.
949          *
950          * @since 3.4.0
951          *
952          * @param string $id Customize Setting ID.
953          */
954         public function remove_setting( $id ) {
955                 unset( $this->settings[ $id ] );
956         }
957
958         /**
959          * Add a customize panel.
960          *
961          * @since 4.0.0
962          * @access public
963          *
964          * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
965          * @param array                     $args Optional. Panel arguments. Default empty array.
966          */
967         public function add_panel( $id, $args = array() ) {
968                 if ( $id instanceof WP_Customize_Panel ) {
969                         $panel = $id;
970                 } else {
971                         $panel = new WP_Customize_Panel( $this, $id, $args );
972                 }
973
974                 $this->panels[ $panel->id ] = $panel;
975         }
976
977         /**
978          * Retrieve a customize panel.
979          *
980          * @since 4.0.0
981          * @access public
982          *
983          * @param string $id Panel ID to get.
984          * @return WP_Customize_Panel|void Requested panel instance, if set.
985          */
986         public function get_panel( $id ) {
987                 if ( isset( $this->panels[ $id ] ) ) {
988                         return $this->panels[ $id ];
989                 }
990         }
991
992         /**
993          * Remove a customize panel.
994          *
995          * @since 4.0.0
996          * @access public
997          *
998          * @param string $id Panel ID to remove.
999          */
1000         public function remove_panel( $id ) {
1001                 unset( $this->panels[ $id ] );
1002         }
1003
1004         /**
1005          * Register a customize panel type.
1006          *
1007          * Registered types are eligible to be rendered via JS and created dynamically.
1008          *
1009          * @since 4.3.0
1010          * @access public
1011          *
1012          * @see WP_Customize_Panel
1013          *
1014          * @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
1015          */
1016         public function register_panel_type( $panel ) {
1017                 $this->registered_panel_types[] = $panel;
1018         }
1019
1020         /**
1021          * Render JS templates for all registered panel types.
1022          *
1023          * @since 4.3.0
1024          * @access public
1025          */
1026         public function render_panel_templates() {
1027                 foreach ( $this->registered_panel_types as $panel_type ) {
1028                         $panel = new $panel_type( $this, 'temp', array() );
1029                         $panel->print_template();
1030                 }
1031         }
1032
1033         /**
1034          * Add a customize section.
1035          *
1036          * @since 3.4.0
1037          *
1038          * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
1039          * @param array                       $args Section arguments.
1040          */
1041         public function add_section( $id, $args = array() ) {
1042                 if ( $id instanceof WP_Customize_Section ) {
1043                         $section = $id;
1044                 } else {
1045                         $section = new WP_Customize_Section( $this, $id, $args );
1046                 }
1047                 $this->sections[ $section->id ] = $section;
1048         }
1049
1050         /**
1051          * Retrieve a customize section.
1052          *
1053          * @since 3.4.0
1054          *
1055          * @param string $id Section ID.
1056          * @return WP_Customize_Section|void The section, if set.
1057          */
1058         public function get_section( $id ) {
1059                 if ( isset( $this->sections[ $id ] ) )
1060                         return $this->sections[ $id ];
1061         }
1062
1063         /**
1064          * Remove a customize section.
1065          *
1066          * @since 3.4.0
1067          *
1068          * @param string $id Section ID.
1069          */
1070         public function remove_section( $id ) {
1071                 unset( $this->sections[ $id ] );
1072         }
1073
1074         /**
1075          * Register a customize section type.
1076          *
1077          * Registered types are eligible to be rendered via JS and created dynamically.
1078          *
1079          * @since 4.3.0
1080          * @access public
1081          *
1082          * @see WP_Customize_Section
1083          *
1084          * @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
1085          */
1086         public function register_section_type( $section ) {
1087                 $this->registered_section_types[] = $section;
1088         }
1089
1090         /**
1091          * Render JS templates for all registered section types.
1092          *
1093          * @since 4.3.0
1094          * @access public
1095          */
1096         public function render_section_templates() {
1097                 foreach ( $this->registered_section_types as $section_type ) {
1098                         $section = new $section_type( $this, 'temp', array() );
1099                         $section->print_template();
1100                 }
1101         }
1102
1103         /**
1104          * Add a customize control.
1105          *
1106          * @since 3.4.0
1107          *
1108          * @param WP_Customize_Control|string $id   Customize Control object, or ID.
1109          * @param array                       $args Control arguments; passed to WP_Customize_Control
1110          *                                          constructor.
1111          */
1112         public function add_control( $id, $args = array() ) {
1113                 if ( $id instanceof WP_Customize_Control ) {
1114                         $control = $id;
1115                 } else {
1116                         $control = new WP_Customize_Control( $this, $id, $args );
1117                 }
1118                 $this->controls[ $control->id ] = $control;
1119         }
1120
1121         /**
1122          * Retrieve a customize control.
1123          *
1124          * @since 3.4.0
1125          *
1126          * @param string $id ID of the control.
1127          * @return WP_Customize_Control|void The control object, if set.
1128          */
1129         public function get_control( $id ) {
1130                 if ( isset( $this->controls[ $id ] ) )
1131                         return $this->controls[ $id ];
1132         }
1133
1134         /**
1135          * Remove a customize control.
1136          *
1137          * @since 3.4.0
1138          *
1139          * @param string $id ID of the control.
1140          */
1141         public function remove_control( $id ) {
1142                 unset( $this->controls[ $id ] );
1143         }
1144
1145         /**
1146          * Register a customize control type.
1147          *
1148          * Registered types are eligible to be rendered via JS and created dynamically.
1149          *
1150          * @since 4.1.0
1151          * @access public
1152          *
1153          * @param string $control Name of a custom control which is a subclass of
1154          *                        {@see WP_Customize_Control}.
1155          */
1156         public function register_control_type( $control ) {
1157                 $this->registered_control_types[] = $control;
1158         }
1159
1160         /**
1161          * Render JS templates for all registered control types.
1162          *
1163          * @since 4.1.0
1164          * @access public
1165          */
1166         public function render_control_templates() {
1167                 foreach ( $this->registered_control_types as $control_type ) {
1168                         $control = new $control_type( $this, 'temp', array() );
1169                         $control->print_template();
1170                 }
1171         }
1172
1173         /**
1174          * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
1175          *
1176          * @since 3.4.0
1177          *
1178          * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
1179          * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
1180          * @return int
1181          */
1182         protected function _cmp_priority( $a, $b ) {
1183                 if ( $a->priority === $b->priority ) {
1184                         return $a->instance_number - $a->instance_number;
1185                 } else {
1186                         return $a->priority - $b->priority;
1187                 }
1188         }
1189
1190         /**
1191          * Prepare panels, sections, and controls.
1192          *
1193          * For each, check if required related components exist,
1194          * whether the user has the necessary capabilities,
1195          * and sort by priority.
1196          *
1197          * @since 3.4.0
1198          */
1199         public function prepare_controls() {
1200
1201                 $controls = array();
1202                 uasort( $this->controls, array( $this, '_cmp_priority' ) );
1203
1204                 foreach ( $this->controls as $id => $control ) {
1205                         if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
1206                                 continue;
1207                         }
1208
1209                         $this->sections[ $control->section ]->controls[] = $control;
1210                         $controls[ $id ] = $control;
1211                 }
1212                 $this->controls = $controls;
1213
1214                 // Prepare sections.
1215                 uasort( $this->sections, array( $this, '_cmp_priority' ) );
1216                 $sections = array();
1217
1218                 foreach ( $this->sections as $section ) {
1219                         if ( ! $section->check_capabilities() ) {
1220                                 continue;
1221                         }
1222
1223                         usort( $section->controls, array( $this, '_cmp_priority' ) );
1224
1225                         if ( ! $section->panel ) {
1226                                 // Top-level section.
1227                                 $sections[ $section->id ] = $section;
1228                         } else {
1229                                 // This section belongs to a panel.
1230                                 if ( isset( $this->panels [ $section->panel ] ) ) {
1231                                         $this->panels[ $section->panel ]->sections[ $section->id ] = $section;
1232                                 }
1233                         }
1234                 }
1235                 $this->sections = $sections;
1236
1237                 // Prepare panels.
1238                 uasort( $this->panels, array( $this, '_cmp_priority' ) );
1239                 $panels = array();
1240
1241                 foreach ( $this->panels as $panel ) {
1242                         if ( ! $panel->check_capabilities() ) {
1243                                 continue;
1244                         }
1245
1246                         uasort( $panel->sections, array( $this, '_cmp_priority' ) );
1247                         $panels[ $panel->id ] = $panel;
1248                 }
1249                 $this->panels = $panels;
1250
1251                 // Sort panels and top-level sections together.
1252                 $this->containers = array_merge( $this->panels, $this->sections );
1253                 uasort( $this->containers, array( $this, '_cmp_priority' ) );
1254         }
1255
1256         /**
1257          * Enqueue scripts for customize controls.
1258          *
1259          * @since 3.4.0
1260          */
1261         public function enqueue_control_scripts() {
1262                 foreach ( $this->controls as $control ) {
1263                         $control->enqueue();
1264                 }
1265         }
1266
1267         /**
1268          * Register some default controls.
1269          *
1270          * @since 3.4.0
1271          */
1272         public function register_controls() {
1273
1274                 /* Panel, Section, and Control Types */
1275                 $this->register_panel_type( 'WP_Customize_Panel' );
1276                 $this->register_section_type( 'WP_Customize_Section' );
1277                 $this->register_section_type( 'WP_Customize_Sidebar_Section' );
1278                 $this->register_control_type( 'WP_Customize_Color_Control' );
1279                 $this->register_control_type( 'WP_Customize_Media_Control' );
1280                 $this->register_control_type( 'WP_Customize_Upload_Control' );
1281                 $this->register_control_type( 'WP_Customize_Image_Control' );
1282                 $this->register_control_type( 'WP_Customize_Background_Image_Control' );
1283                 $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
1284                 $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
1285                 $this->register_control_type( 'WP_Customize_Theme_Control' );
1286
1287                 /* Themes */
1288
1289                 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
1290                         'title'      => $this->theme()->display( 'Name' ),
1291                         'capability' => 'switch_themes',
1292                         'priority'   => 0,
1293                 ) ) );
1294
1295                 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
1296                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
1297                         'capability' => 'switch_themes',
1298                 ) ) );
1299
1300                 require_once( ABSPATH . 'wp-admin/includes/theme.php' );
1301
1302                 // Theme Controls.
1303
1304                 // Add a control for the active/original theme.
1305                 if ( ! $this->is_theme_active() ) {
1306                         $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
1307                         $active_theme = current( $themes );
1308                         $active_theme['isActiveTheme'] = true;
1309                         $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
1310                                 'theme'    => $active_theme,
1311                                 'section'  => 'themes',
1312                                 'settings' => 'active_theme',
1313                         ) ) );
1314                 }
1315
1316                 $themes = wp_prepare_themes_for_js();
1317                 foreach ( $themes as $theme ) {
1318                         if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
1319                                 continue;
1320                         }
1321
1322                         $theme_id = 'theme_' . $theme['id'];
1323                         $theme['isActiveTheme'] = false;
1324                         $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
1325                                 'theme'    => $theme,
1326                                 'section'  => 'themes',
1327                                 'settings' => 'active_theme',
1328                         ) ) );
1329                 }
1330
1331                 /* Site Identity */
1332
1333                 $this->add_section( 'title_tagline', array(
1334                         'title'    => __( 'Site Identity' ),
1335                         'priority' => 20,
1336                 ) );
1337
1338                 $this->add_setting( 'blogname', array(
1339                         'default'    => get_option( 'blogname' ),
1340                         'type'       => 'option',
1341                         'capability' => 'manage_options',
1342                 ) );
1343
1344                 $this->add_control( 'blogname', array(
1345                         'label'      => __( 'Site Title' ),
1346                         'section'    => 'title_tagline',
1347                 ) );
1348
1349                 $this->add_setting( 'blogdescription', array(
1350                         'default'    => get_option( 'blogdescription' ),
1351                         'type'       => 'option',
1352                         'capability' => 'manage_options',
1353                 ) );
1354
1355                 $this->add_control( 'blogdescription', array(
1356                         'label'      => __( 'Tagline' ),
1357                         'section'    => 'title_tagline',
1358                 ) );
1359
1360                 $this->add_setting( 'site_icon', array(
1361                         'type'       => 'option',
1362                         'capability' => 'manage_options',
1363                         'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
1364                 ) );
1365
1366                 $this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
1367                         'label'       => __( 'Site Icon' ),
1368                         'description' => __( 'The Site Icon is used as a browser and app icon for your site. Icons must be square, and at least 512px wide and tall.' ),
1369                         'section'     => 'title_tagline',
1370                         'priority'    => 60,
1371                         'height'      => 512,
1372                         'width'       => 512,
1373                 ) ) );
1374
1375                 /* Colors */
1376
1377                 $this->add_section( 'colors', array(
1378                         'title'          => __( 'Colors' ),
1379                         'priority'       => 40,
1380                 ) );
1381
1382                 $this->add_setting( 'header_textcolor', array(
1383                         'theme_supports' => array( 'custom-header', 'header-text' ),
1384                         'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
1385
1386                         'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
1387                         'sanitize_js_callback' => 'maybe_hash_hex_color',
1388                 ) );
1389
1390                 // Input type: checkbox
1391                 // With custom value
1392                 $this->add_control( 'display_header_text', array(
1393                         'settings' => 'header_textcolor',
1394                         'label'    => __( 'Display Header Text' ),
1395                         'section'  => 'title_tagline',
1396                         'type'     => 'checkbox',
1397                         'priority' => 40,
1398                 ) );
1399
1400                 $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
1401                         'label'   => __( 'Header Text Color' ),
1402                         'section' => 'colors',
1403                 ) ) );
1404
1405                 // Input type: Color
1406                 // With sanitize_callback
1407                 $this->add_setting( 'background_color', array(
1408                         'default'        => get_theme_support( 'custom-background', 'default-color' ),
1409                         'theme_supports' => 'custom-background',
1410
1411                         'sanitize_callback'    => 'sanitize_hex_color_no_hash',
1412                         'sanitize_js_callback' => 'maybe_hash_hex_color',
1413                 ) );
1414
1415                 $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
1416                         'label'   => __( 'Background Color' ),
1417                         'section' => 'colors',
1418                 ) ) );
1419
1420
1421                 /* Custom Header */
1422
1423                 $this->add_section( 'header_image', array(
1424                         'title'          => __( 'Header Image' ),
1425                         'theme_supports' => 'custom-header',
1426                         'priority'       => 60,
1427                 ) );
1428
1429                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
1430                         'default'        => get_theme_support( 'custom-header', 'default-image' ),
1431                         'theme_supports' => 'custom-header',
1432                 ) ) );
1433
1434                 $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
1435                         // 'default'        => get_theme_support( 'custom-header', 'default-image' ),
1436                         'theme_supports' => 'custom-header',
1437                 ) ) );
1438
1439                 $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
1440
1441                 /* Custom Background */
1442
1443                 $this->add_section( 'background_image', array(
1444                         'title'          => __( 'Background Image' ),
1445                         'theme_supports' => 'custom-background',
1446                         'priority'       => 80,
1447                 ) );
1448
1449                 $this->add_setting( 'background_image', array(
1450                         'default'        => get_theme_support( 'custom-background', 'default-image' ),
1451                         'theme_supports' => 'custom-background',
1452                 ) );
1453
1454                 $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
1455                         'theme_supports' => 'custom-background',
1456                 ) ) );
1457
1458                 $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
1459
1460                 $this->add_setting( 'background_repeat', array(
1461                         'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
1462                         'theme_supports' => 'custom-background',
1463                 ) );
1464
1465                 $this->add_control( 'background_repeat', array(
1466                         'label'      => __( 'Background Repeat' ),
1467                         'section'    => 'background_image',
1468                         'type'       => 'radio',
1469                         'choices'    => array(
1470                                 'no-repeat'  => __('No Repeat'),
1471                                 'repeat'     => __('Tile'),
1472                                 'repeat-x'   => __('Tile Horizontally'),
1473                                 'repeat-y'   => __('Tile Vertically'),
1474                         ),
1475                 ) );
1476
1477                 $this->add_setting( 'background_position_x', array(
1478                         'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
1479                         'theme_supports' => 'custom-background',
1480                 ) );
1481
1482                 $this->add_control( 'background_position_x', array(
1483                         'label'      => __( 'Background Position' ),
1484                         'section'    => 'background_image',
1485                         'type'       => 'radio',
1486                         'choices'    => array(
1487                                 'left'       => __('Left'),
1488                                 'center'     => __('Center'),
1489                                 'right'      => __('Right'),
1490                         ),
1491                 ) );
1492
1493                 $this->add_setting( 'background_attachment', array(
1494                         'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
1495                         'theme_supports' => 'custom-background',
1496                 ) );
1497
1498                 $this->add_control( 'background_attachment', array(
1499                         'label'      => __( 'Background Attachment' ),
1500                         'section'    => 'background_image',
1501                         'type'       => 'radio',
1502                         'choices'    => array(
1503                                 'scroll'     => __('Scroll'),
1504                                 'fixed'      => __('Fixed'),
1505                         ),
1506                 ) );
1507
1508                 // If the theme is using the default background callback, we can update
1509                 // the background CSS using postMessage.
1510                 if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
1511                         foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
1512                                 $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
1513                         }
1514                 }
1515
1516                 /* Static Front Page */
1517                 // #WP19627
1518
1519                 // Replicate behavior from options-reading.php and hide front page options if there are no pages
1520                 if ( get_pages() ) {
1521                         $this->add_section( 'static_front_page', array(
1522                                 'title'          => __( 'Static Front Page' ),
1523                         //      'theme_supports' => 'static-front-page',
1524                                 'priority'       => 120,
1525                                 'description'    => __( 'Your theme supports a static front page.' ),
1526                         ) );
1527
1528                         $this->add_setting( 'show_on_front', array(
1529                                 'default'        => get_option( 'show_on_front' ),
1530                                 'capability'     => 'manage_options',
1531                                 'type'           => 'option',
1532                         //      'theme_supports' => 'static-front-page',
1533                         ) );
1534
1535                         $this->add_control( 'show_on_front', array(
1536                                 'label'   => __( 'Front page displays' ),
1537                                 'section' => 'static_front_page',
1538                                 'type'    => 'radio',
1539                                 'choices' => array(
1540                                         'posts' => __( 'Your latest posts' ),
1541                                         'page'  => __( 'A static page' ),
1542                                 ),
1543                         ) );
1544
1545                         $this->add_setting( 'page_on_front', array(
1546                                 'type'       => 'option',
1547                                 'capability' => 'manage_options',
1548                         //      'theme_supports' => 'static-front-page',
1549                         ) );
1550
1551                         $this->add_control( 'page_on_front', array(
1552                                 'label'      => __( 'Front page' ),
1553                                 'section'    => 'static_front_page',
1554                                 'type'       => 'dropdown-pages',
1555                         ) );
1556
1557                         $this->add_setting( 'page_for_posts', array(
1558                                 'type'           => 'option',
1559                                 'capability'     => 'manage_options',
1560                         //      'theme_supports' => 'static-front-page',
1561                         ) );
1562
1563                         $this->add_control( 'page_for_posts', array(
1564                                 'label'      => __( 'Posts page' ),
1565                                 'section'    => 'static_front_page',
1566                                 'type'       => 'dropdown-pages',
1567                         ) );
1568                 }
1569         }
1570
1571         /**
1572          * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
1573          *
1574          * @since 4.2.0
1575          * @access public
1576          *
1577          * @see add_dynamic_settings()
1578          */
1579         public function register_dynamic_settings() {
1580                 $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
1581         }
1582
1583         /**
1584          * Callback for validating the header_textcolor value.
1585          *
1586          * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
1587          * Returns default text color if hex color is empty.
1588          *
1589          * @since 3.4.0
1590          *
1591          * @param string $color
1592          * @return mixed
1593          */
1594         public function _sanitize_header_textcolor( $color ) {
1595                 if ( 'blank' === $color )
1596                         return 'blank';
1597
1598                 $color = sanitize_hex_color_no_hash( $color );
1599                 if ( empty( $color ) )
1600                         $color = get_theme_support( 'custom-header', 'default-text-color' );
1601
1602                 return $color;
1603         }
1604 }
1605
1606 /**
1607  * Sanitizes a hex color.
1608  *
1609  * Returns either '', a 3 or 6 digit hex color (with #), or nothing.
1610  * For sanitizing values without a #, see sanitize_hex_color_no_hash().
1611  *
1612  * @since 3.4.0
1613  *
1614  * @param string $color
1615  * @return string|void
1616  */
1617 function sanitize_hex_color( $color ) {
1618         if ( '' === $color )
1619                 return '';
1620
1621         // 3 or 6 hex digits, or the empty string.
1622         if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
1623                 return $color;
1624 }
1625
1626 /**
1627  * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
1628  *
1629  * Saving hex colors without a hash puts the burden of adding the hash on the
1630  * UI, which makes it difficult to use or upgrade to other color types such as
1631  * rgba, hsl, rgb, and html color names.
1632  *
1633  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
1634  *
1635  * @since 3.4.0
1636  *
1637  * @param string $color
1638  * @return string|null
1639  */
1640 function sanitize_hex_color_no_hash( $color ) {
1641         $color = ltrim( $color, '#' );
1642
1643         if ( '' === $color )
1644                 return '';
1645
1646         return sanitize_hex_color( '#' . $color ) ? $color : null;
1647 }
1648
1649 /**
1650  * Ensures that any hex color is properly hashed.
1651  * Otherwise, returns value untouched.
1652  *
1653  * This method should only be necessary if using sanitize_hex_color_no_hash().
1654  *
1655  * @since 3.4.0
1656  *
1657  * @param string $color
1658  * @return string
1659  */
1660 function maybe_hash_hex_color( $color ) {
1661         if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
1662                 return '#' . $unhashed;
1663
1664         return $color;
1665 }