5 * Bootstraps the Customize experience on the server-side.
7 * Sets up the theme-switching process if a theme other than the active one is
8 * being previewed and customized.
10 * Serves as a factory for Customize Controls and Settings, and
11 * instantiates default Customize Controls and Settings.
14 * @subpackage Customize
17 final class WP_Customize_Manager {
19 * An instance of the theme being previewed.
26 * The directory name of the previously active theme (within the theme_root).
30 protected $original_stylesheet;
33 * Whether this is a Customizer pageload.
37 protected $previewing = false;
40 * Methods and properties deailing with managing widgets in the Customizer.
42 * @var WP_Customize_Widgets
46 protected $settings = array();
47 protected $containers = array();
48 protected $panels = array();
49 protected $sections = array();
50 protected $controls = array();
52 protected $nonce_tick;
54 protected $customized;
57 * Controls that may be rendered from JS templates.
63 protected $registered_control_types = array();
66 * $_POST values for Customize Settings.
70 private $_post_values;
77 public function __construct() {
78 require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
79 require( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
80 require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
81 require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
82 require( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
84 $this->widgets = new WP_Customize_Widgets( $this );
86 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
88 add_action( 'setup_theme', array( $this, 'setup_theme' ) );
89 add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
91 // Run wp_redirect_status late to make sure we override the status last.
92 add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
94 // Do not spawn cron (especially the alternate cron) while running the Customizer.
95 remove_action( 'init', 'wp_cron' );
97 // Do not run update checks when rendering the controls.
98 remove_action( 'admin_init', '_maybe_update_core' );
99 remove_action( 'admin_init', '_maybe_update_plugins' );
100 remove_action( 'admin_init', '_maybe_update_themes' );
102 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
104 add_action( 'customize_register', array( $this, 'register_controls' ) );
105 add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) );
106 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
110 * Return true if it's an AJAX request.
116 public function doing_ajax() {
117 return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
121 * Custom wp_die wrapper. Returns either the standard message for UI
122 * or the AJAX message.
126 * @param mixed $ajax_message AJAX return
127 * @param mixed $message UI message
129 protected function wp_die( $ajax_message, $message = null ) {
130 if ( $this->doing_ajax() )
131 wp_die( $ajax_message );
134 $message = __( 'Cheatin’ uh?' );
140 * Return the AJAX wp_die() handler if it's a customized request.
146 public function wp_die_handler() {
147 if ( $this->doing_ajax() )
148 return '_ajax_wp_die_handler';
150 return '_default_wp_die_handler';
154 * Start preview and customize theme.
156 * Check if customize query variable exist. Init filters to filter the current theme.
160 public function setup_theme() {
161 send_origin_headers();
163 if ( is_admin() && ! $this->doing_ajax() )
165 elseif ( $this->doing_ajax() && ! is_user_logged_in() )
168 show_admin_bar( false );
170 if ( ! current_user_can( 'customize' ) ) {
174 $this->original_stylesheet = get_stylesheet();
176 $this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
178 if ( $this->is_theme_active() ) {
179 // Once the theme is loaded, we'll validate it.
180 add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
182 // If the requested theme is not the active theme and the user doesn't have the
183 // switch_themes cap, bail.
184 if ( ! current_user_can( 'switch_themes' ) )
187 // If the theme has errors while loading, bail.
188 if ( $this->theme()->errors() )
191 // If the theme isn't allowed per multisite settings, bail.
192 if ( ! $this->theme()->is_allowed() )
196 $this->start_previewing_theme();
200 * Callback to validate a theme once it is loaded
204 public function after_setup_theme() {
205 if ( ! $this->doing_ajax() && ! validate_current_theme() ) {
206 wp_redirect( 'themes.php?broken=true' );
212 * If the theme to be previewed isn't the active theme, add filter callbacks
213 * to swap it out at runtime.
217 public function start_previewing_theme() {
218 // Bail if we're already previewing.
219 if ( $this->is_preview() )
222 $this->previewing = true;
224 if ( ! $this->is_theme_active() ) {
225 add_filter( 'template', array( $this, 'get_template' ) );
226 add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
227 add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
229 // @link: https://core.trac.wordpress.org/ticket/20027
230 add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
231 add_filter( 'pre_option_template', array( $this, 'get_template' ) );
233 // Handle custom theme roots.
234 add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
235 add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
239 * Fires once the Customizer theme preview has started.
243 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
245 do_action( 'start_previewing_theme', $this );
249 * Stop previewing the selected theme.
251 * Removes filters to change the current theme.
255 public function stop_previewing_theme() {
256 if ( ! $this->is_preview() )
259 $this->previewing = false;
261 if ( ! $this->is_theme_active() ) {
262 remove_filter( 'template', array( $this, 'get_template' ) );
263 remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
264 remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
266 // @link: https://core.trac.wordpress.org/ticket/20027
267 remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
268 remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
270 // Handle custom theme roots.
271 remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
272 remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
276 * Fires once the Customizer theme preview has stopped.
280 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
282 do_action( 'stop_previewing_theme', $this );
286 * Get the theme being customized.
292 public function theme() {
297 * Get the registered settings.
303 public function settings() {
304 return $this->settings;
308 * Get the registered controls.
314 public function controls() {
315 return $this->controls;
319 * Get the registered containers.
325 public function containers() {
326 return $this->containers;
330 * Get the registered sections.
336 public function sections() {
337 return $this->sections;
341 * Get the registered panels.
346 * @return array Panels.
348 public function panels() {
349 return $this->panels;
353 * Checks if the current theme is active.
359 public function is_theme_active() {
360 return $this->get_stylesheet() == $this->original_stylesheet;
364 * Register styles/scripts and initialize the preview of each setting
368 public function wp_loaded() {
371 * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
375 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
377 do_action( 'customize_register', $this );
379 if ( $this->is_preview() && ! is_admin() )
380 $this->customize_preview_init();
384 * Prevents AJAX requests from following redirects when previewing a theme
385 * by issuing a 200 response instead of a 30x.
387 * Instead, the JS will sniff out the location header.
394 public function wp_redirect_status( $status ) {
395 if ( $this->is_preview() && ! is_admin() )
402 * Decode the $_POST['customized'] values for a specific Customize Setting.
406 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
407 * @return string $post_value Sanitized value
409 public function post_value( $setting ) {
410 if ( ! isset( $this->_post_values ) ) {
411 if ( isset( $_POST['customized'] ) )
412 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
414 $this->_post_values = false;
417 if ( isset( $this->_post_values[ $setting->id ] ) )
418 return $setting->sanitize( $this->_post_values[ $setting->id ] );
422 * Print JavaScript settings.
426 public function customize_preview_init() {
427 $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
429 $this->prepare_controls();
431 wp_enqueue_script( 'customize-preview' );
432 add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
433 add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
434 add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
435 add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
436 add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
437 add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
439 foreach ( $this->settings as $setting ) {
444 * Fires once the Customizer preview has initialized and JavaScript
445 * settings have been printed.
449 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
451 do_action( 'customize_preview_init', $this );
455 * Prevent sending a 404 status when returning the response for the customize
456 * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
461 public function customize_preview_override_404_status() {
463 status_header( 200 );
468 * Print base element for preview frame.
472 public function customize_preview_base() {
473 ?><base href="<?php echo home_url( '/' ); ?>" /><?php
477 * Print a workaround to handle HTML5 tags in IE < 9
481 public function customize_preview_html5() { ?>
483 <script type="text/javascript">
484 var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
485 'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
486 'output', 'progress', 'section', 'time', 'video' ];
487 for ( var i = 0; i < e.length; i++ ) {
488 document.createElement( e[i] );
495 * Print JavaScript settings for preview frame.
499 public function customize_preview_settings() {
502 'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
503 'activePanels' => array(),
504 'activeSections' => array(),
505 'activeControls' => array(),
508 if ( 2 == $this->nonce_tick ) {
509 $settings['nonce'] = array(
510 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
511 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
515 foreach ( $this->settings as $id => $setting ) {
516 $settings['values'][ $id ] = $setting->js_value();
518 foreach ( $this->panels as $id => $panel ) {
519 $settings['activePanels'][ $id ] = $panel->active();
520 foreach ( $panel->sections as $id => $section ) {
521 $settings['activeSections'][ $id ] = $section->active();
524 foreach ( $this->sections as $id => $section ) {
525 $settings['activeSections'][ $id ] = $section->active();
527 foreach ( $this->controls as $id => $control ) {
528 $settings['activeControls'][ $id ] = $control->active();
532 <script type="text/javascript">
533 var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
539 * Prints a signature so we can ensure the Customizer was properly executed.
543 public function customize_preview_signature() {
544 echo 'WP_CUSTOMIZER_SIGNATURE';
548 * Removes the signature in case we experience a case where the Customizer was not properly executed.
552 public function remove_preview_signature( $return = null ) {
553 remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
559 * Is it a theme preview?
563 * @return bool True if it's a preview, false if not.
565 public function is_preview() {
566 return (bool) $this->previewing;
570 * Retrieve the template name of the previewed theme.
574 * @return string Template name.
576 public function get_template() {
577 return $this->theme()->get_template();
581 * Retrieve the stylesheet name of the previewed theme.
585 * @return string Stylesheet name.
587 public function get_stylesheet() {
588 return $this->theme()->get_stylesheet();
592 * Retrieve the template root of the previewed theme.
596 * @return string Theme root.
598 public function get_template_root() {
599 return get_raw_theme_root( $this->get_template(), true );
603 * Retrieve the stylesheet root of the previewed theme.
607 * @return string Theme root.
609 public function get_stylesheet_root() {
610 return get_raw_theme_root( $this->get_stylesheet(), true );
614 * Filter the current theme and return the name of the previewed theme.
618 * @param $current_theme {@internal Parameter is not used}
619 * @return string Theme name.
621 public function current_theme( $current_theme ) {
622 return $this->theme()->display('Name');
626 * Switch the theme and trigger the save() method on each setting.
630 public function save() {
631 if ( ! $this->is_preview() )
634 check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
636 // Do we have to switch themes?
637 if ( ! $this->is_theme_active() ) {
638 // Temporarily stop previewing the theme to allow switch_themes()
639 // to operate properly.
640 $this->stop_previewing_theme();
641 switch_theme( $this->get_stylesheet() );
642 update_option( 'theme_switched_via_customizer', true );
643 $this->start_previewing_theme();
647 * Fires once the theme has switched in the Customizer, but before settings
652 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
654 do_action( 'customize_save', $this );
656 foreach ( $this->settings as $setting ) {
661 * Fires after Customize settings have been saved.
665 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
667 do_action( 'customize_save_after', $this );
673 * Add a customize setting.
677 * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
678 * @param array $args Setting arguments; passed to WP_Customize_Setting
681 public function add_setting( $id, $args = array() ) {
682 if ( is_a( $id, 'WP_Customize_Setting' ) )
685 $setting = new WP_Customize_Setting( $this, $id, $args );
687 $this->settings[ $setting->id ] = $setting;
691 * Retrieve a customize setting.
695 * @param string $id Customize Setting ID.
696 * @return WP_Customize_Setting
698 public function get_setting( $id ) {
699 if ( isset( $this->settings[ $id ] ) )
700 return $this->settings[ $id ];
704 * Remove a customize setting.
708 * @param string $id Customize Setting ID.
710 public function remove_setting( $id ) {
711 unset( $this->settings[ $id ] );
715 * Add a customize panel.
720 * @param WP_Customize_Panel|string $id Customize Panel object, or Panel ID.
721 * @param array $args Optional. Panel arguments. Default empty array.
723 public function add_panel( $id, $args = array() ) {
724 if ( is_a( $id, 'WP_Customize_Panel' ) ) {
728 $panel = new WP_Customize_Panel( $this, $id, $args );
731 $this->panels[ $panel->id ] = $panel;
735 * Retrieve a customize panel.
740 * @param string $id Panel ID to get.
741 * @return WP_Customize_Panel Requested panel instance.
743 public function get_panel( $id ) {
744 if ( isset( $this->panels[ $id ] ) ) {
745 return $this->panels[ $id ];
750 * Remove a customize panel.
755 * @param string $id Panel ID to remove.
757 public function remove_panel( $id ) {
758 unset( $this->panels[ $id ] );
762 * Add a customize section.
766 * @param WP_Customize_Section|string $id Customize Section object, or Section ID.
767 * @param array $args Section arguments.
769 public function add_section( $id, $args = array() ) {
770 if ( is_a( $id, 'WP_Customize_Section' ) )
773 $section = new WP_Customize_Section( $this, $id, $args );
775 $this->sections[ $section->id ] = $section;
779 * Retrieve a customize section.
783 * @param string $id Section ID.
784 * @return WP_Customize_Section
786 public function get_section( $id ) {
787 if ( isset( $this->sections[ $id ] ) )
788 return $this->sections[ $id ];
792 * Remove a customize section.
796 * @param string $id Section ID.
798 public function remove_section( $id ) {
799 unset( $this->sections[ $id ] );
803 * Add a customize control.
807 * @param WP_Customize_Control|string $id Customize Control object, or ID.
808 * @param array $args Control arguments; passed to WP_Customize_Control
811 public function add_control( $id, $args = array() ) {
812 if ( is_a( $id, 'WP_Customize_Control' ) )
815 $control = new WP_Customize_Control( $this, $id, $args );
817 $this->controls[ $control->id ] = $control;
821 * Retrieve a customize control.
825 * @param string $id ID of the control.
826 * @return WP_Customize_Control $control The control object.
828 public function get_control( $id ) {
829 if ( isset( $this->controls[ $id ] ) )
830 return $this->controls[ $id ];
834 * Remove a customize control.
838 * @param string $id ID of the control.
840 public function remove_control( $id ) {
841 unset( $this->controls[ $id ] );
845 * Register a customize control type.
847 * Registered types are eligible to be rendered via JS and created dynamically.
852 * @param string $control Name of a custom control which is a subclass of
853 * {@see WP_Customize_Control}.
855 public function register_control_type( $control ) {
856 $this->registered_control_types[] = $control;
860 * Render JS templates for all registered control types.
865 public function render_control_templates() {
866 foreach ( $this->registered_control_types as $control_type ) {
867 $control = new $control_type( $this, 'temp', array() );
868 $control->print_template();
873 * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
877 * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $a Object A.
878 * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $b Object B.
881 protected final function _cmp_priority( $a, $b ) {
882 if ( $a->priority === $b->priority ) {
883 return $a->instance_number - $a->instance_number;
885 return $a->priority - $b->priority;
890 * Prepare panels, sections, and controls.
892 * For each, check if required related components exist,
893 * whether the user has the necessary capabilities,
894 * and sort by priority.
898 public function prepare_controls() {
901 uasort( $this->controls, array( $this, '_cmp_priority' ) );
903 foreach ( $this->controls as $id => $control ) {
904 if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
908 $this->sections[ $control->section ]->controls[] = $control;
909 $controls[ $id ] = $control;
911 $this->controls = $controls;
914 uasort( $this->sections, array( $this, '_cmp_priority' ) );
917 foreach ( $this->sections as $section ) {
918 if ( ! $section->check_capabilities() || ! $section->controls ) {
922 usort( $section->controls, array( $this, '_cmp_priority' ) );
924 if ( ! $section->panel ) {
925 // Top-level section.
926 $sections[ $section->id ] = $section;
928 // This section belongs to a panel.
929 if ( isset( $this->panels [ $section->panel ] ) ) {
930 $this->panels[ $section->panel ]->sections[ $section->id ] = $section;
934 $this->sections = $sections;
937 uasort( $this->panels, array( $this, '_cmp_priority' ) );
940 foreach ( $this->panels as $panel ) {
941 if ( ! $panel->check_capabilities() || ! $panel->sections ) {
945 uasort( $panel->sections, array( $this, '_cmp_priority' ) );
946 $panels[ $panel->id ] = $panel;
948 $this->panels = $panels;
950 // Sort panels and top-level sections together.
951 $this->containers = array_merge( $this->panels, $this->sections );
952 uasort( $this->containers, array( $this, '_cmp_priority' ) );
956 * Enqueue scripts for customize controls.
960 public function enqueue_control_scripts() {
961 foreach ( $this->controls as $control ) {
967 * Register some default controls.
971 public function register_controls() {
973 /* Control Types (custom control classes) */
974 $this->register_control_type( 'WP_Customize_Color_Control' );
975 $this->register_control_type( 'WP_Customize_Upload_Control' );
976 $this->register_control_type( 'WP_Customize_Image_Control' );
977 $this->register_control_type( 'WP_Customize_Background_Image_Control' );
979 /* Site Title & Tagline */
981 $this->add_section( 'title_tagline', array(
982 'title' => __( 'Site Title & Tagline' ),
986 $this->add_setting( 'blogname', array(
987 'default' => get_option( 'blogname' ),
989 'capability' => 'manage_options',
992 $this->add_control( 'blogname', array(
993 'label' => __( 'Site Title' ),
994 'section' => 'title_tagline',
997 $this->add_setting( 'blogdescription', array(
998 'default' => get_option( 'blogdescription' ),
1000 'capability' => 'manage_options',
1003 $this->add_control( 'blogdescription', array(
1004 'label' => __( 'Tagline' ),
1005 'section' => 'title_tagline',
1010 $this->add_section( 'colors', array(
1011 'title' => __( 'Colors' ),
1015 $this->add_setting( 'header_textcolor', array(
1016 'theme_supports' => array( 'custom-header', 'header-text' ),
1017 'default' => get_theme_support( 'custom-header', 'default-text-color' ),
1019 'sanitize_callback' => array( $this, '_sanitize_header_textcolor' ),
1020 'sanitize_js_callback' => 'maybe_hash_hex_color',
1023 // Input type: checkbox
1024 // With custom value
1025 $this->add_control( 'display_header_text', array(
1026 'settings' => 'header_textcolor',
1027 'label' => __( 'Display Header Text' ),
1028 'section' => 'title_tagline',
1029 'type' => 'checkbox',
1032 $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
1033 'label' => __( 'Header Text Color' ),
1034 'section' => 'colors',
1037 // Input type: Color
1038 // With sanitize_callback
1039 $this->add_setting( 'background_color', array(
1040 'default' => get_theme_support( 'custom-background', 'default-color' ),
1041 'theme_supports' => 'custom-background',
1043 'sanitize_callback' => 'sanitize_hex_color_no_hash',
1044 'sanitize_js_callback' => 'maybe_hash_hex_color',
1047 $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
1048 'label' => __( 'Background Color' ),
1049 'section' => 'colors',
1055 $this->add_section( 'header_image', array(
1056 'title' => __( 'Header Image' ),
1057 'theme_supports' => 'custom-header',
1061 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
1062 'default' => get_theme_support( 'custom-header', 'default-image' ),
1063 'theme_supports' => 'custom-header',
1066 $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
1067 // 'default' => get_theme_support( 'custom-header', 'default-image' ),
1068 'theme_supports' => 'custom-header',
1071 $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
1073 /* Custom Background */
1075 $this->add_section( 'background_image', array(
1076 'title' => __( 'Background Image' ),
1077 'theme_supports' => 'custom-background',
1081 $this->add_setting( 'background_image', array(
1082 'default' => get_theme_support( 'custom-background', 'default-image' ),
1083 'theme_supports' => 'custom-background',
1086 $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
1087 'theme_supports' => 'custom-background',
1090 $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
1092 $this->add_setting( 'background_repeat', array(
1093 'default' => get_theme_support( 'custom-background', 'default-repeat' ),
1094 'theme_supports' => 'custom-background',
1097 $this->add_control( 'background_repeat', array(
1098 'label' => __( 'Background Repeat' ),
1099 'section' => 'background_image',
1102 'no-repeat' => __('No Repeat'),
1103 'repeat' => __('Tile'),
1104 'repeat-x' => __('Tile Horizontally'),
1105 'repeat-y' => __('Tile Vertically'),
1109 $this->add_setting( 'background_position_x', array(
1110 'default' => get_theme_support( 'custom-background', 'default-position-x' ),
1111 'theme_supports' => 'custom-background',
1114 $this->add_control( 'background_position_x', array(
1115 'label' => __( 'Background Position' ),
1116 'section' => 'background_image',
1119 'left' => __('Left'),
1120 'center' => __('Center'),
1121 'right' => __('Right'),
1125 $this->add_setting( 'background_attachment', array(
1126 'default' => get_theme_support( 'custom-background', 'default-attachment' ),
1127 'theme_supports' => 'custom-background',
1130 $this->add_control( 'background_attachment', array(
1131 'label' => __( 'Background Attachment' ),
1132 'section' => 'background_image',
1135 'scroll' => __('Scroll'),
1136 'fixed' => __('Fixed'),
1140 // If the theme is using the default background callback, we can update
1141 // the background CSS using postMessage.
1142 if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
1143 foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
1144 $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
1150 $locations = get_registered_nav_menus();
1151 $menus = wp_get_nav_menus();
1152 $num_locations = count( array_keys( $locations ) );
1154 $this->add_section( 'nav', array(
1155 'title' => __( 'Navigation' ),
1156 'theme_supports' => 'menus',
1158 '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.'),
1162 $choices = array( 0 => __( '— Select —' ) );
1163 foreach ( $menus as $menu ) {
1164 $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '…' );
1167 foreach ( $locations as $location => $description ) {
1168 $menu_setting_id = "nav_menu_locations[{$location}]";
1170 $this->add_setting( $menu_setting_id, array(
1171 'sanitize_callback' => 'absint',
1172 'theme_supports' => 'menus',
1175 $this->add_control( $menu_setting_id, array(
1176 'label' => $description,
1179 'choices' => $choices,
1184 /* Static Front Page */
1187 $this->add_section( 'static_front_page', array(
1188 'title' => __( 'Static Front Page' ),
1189 // 'theme_supports' => 'static-front-page',
1191 'description' => __( 'Your theme supports a static front page.' ),
1194 $this->add_setting( 'show_on_front', array(
1195 'default' => get_option( 'show_on_front' ),
1196 'capability' => 'manage_options',
1198 // 'theme_supports' => 'static-front-page',
1201 $this->add_control( 'show_on_front', array(
1202 'label' => __( 'Front page displays' ),
1203 'section' => 'static_front_page',
1206 'posts' => __( 'Your latest posts' ),
1207 'page' => __( 'A static page' ),
1211 $this->add_setting( 'page_on_front', array(
1213 'capability' => 'manage_options',
1214 // 'theme_supports' => 'static-front-page',
1217 $this->add_control( 'page_on_front', array(
1218 'label' => __( 'Front page' ),
1219 'section' => 'static_front_page',
1220 'type' => 'dropdown-pages',
1223 $this->add_setting( 'page_for_posts', array(
1225 'capability' => 'manage_options',
1226 // 'theme_supports' => 'static-front-page',
1229 $this->add_control( 'page_for_posts', array(
1230 'label' => __( 'Posts page' ),
1231 'section' => 'static_front_page',
1232 'type' => 'dropdown-pages',
1237 * Callback for validating the header_textcolor value.
1239 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
1240 * Returns default text color if hex color is empty.
1244 * @param string $color
1247 public function _sanitize_header_textcolor( $color ) {
1248 if ( 'blank' === $color )
1251 $color = sanitize_hex_color_no_hash( $color );
1252 if ( empty( $color ) )
1253 $color = get_theme_support( 'custom-header', 'default-text-color' );
1260 * Sanitizes a hex color.
1262 * Returns either '', a 3 or 6 digit hex color (with #), or null.
1263 * For sanitizing values without a #, see sanitize_hex_color_no_hash().
1267 * @param string $color
1268 * @return string|null
1270 function sanitize_hex_color( $color ) {
1271 if ( '' === $color )
1274 // 3 or 6 hex digits, or the empty string.
1275 if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
1282 * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
1284 * Saving hex colors without a hash puts the burden of adding the hash on the
1285 * UI, which makes it difficult to use or upgrade to other color types such as
1286 * rgba, hsl, rgb, and html color names.
1288 * Returns either '', a 3 or 6 digit hex color (without a #), or null.
1292 * @param string $color
1293 * @return string|null
1295 function sanitize_hex_color_no_hash( $color ) {
1296 $color = ltrim( $color, '#' );
1298 if ( '' === $color )
1301 return sanitize_hex_color( '#' . $color ) ? $color : null;
1305 * Ensures that any hex color is properly hashed.
1306 * Otherwise, returns value untouched.
1308 * This method should only be necessary if using sanitize_hex_color_no_hash().
1312 * @param string $color
1315 function maybe_hash_hex_color( $color ) {
1316 if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
1317 return '#' . $unhashed;