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 * Unsanitized values for Customize Settings parsed from $_POST['customized'].
70 private $_post_values;
77 public function __construct() {
78 require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
79 require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
80 require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
81 require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
82 require_once( 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 * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
403 * settings for subsequent post_value() lookups.
409 public function unsanitized_post_values() {
410 if ( ! isset( $this->_post_values ) ) {
411 if ( isset( $_POST['customized'] ) ) {
412 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
414 if ( empty( $this->_post_values ) ) { // if not isset or of JSON error
415 $this->_post_values = false;
418 if ( empty( $this->_post_values ) ) {
421 return $this->_post_values;
426 * Return the sanitized value for a given setting from the request's POST data.
429 * @since 4.1.1 Introduced 'default' parameter.
431 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
432 * @param mixed $default value returned $setting has no post value (added in 4.2.0).
433 * @return string|mixed $post_value Sanitized value or the $default provided
435 public function post_value( $setting, $default = null ) {
436 $post_values = $this->unsanitized_post_values();
437 if ( array_key_exists( $setting->id, $post_values ) ) {
438 return $setting->sanitize( $post_values[ $setting->id ] );
445 * Print JavaScript settings.
449 public function customize_preview_init() {
450 $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
452 $this->prepare_controls();
454 wp_enqueue_script( 'customize-preview' );
455 add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
456 add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
457 add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
458 add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
459 add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
460 add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
462 foreach ( $this->settings as $setting ) {
467 * Fires once the Customizer preview has initialized and JavaScript
468 * settings have been printed.
472 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
474 do_action( 'customize_preview_init', $this );
478 * Prevent sending a 404 status when returning the response for the customize
479 * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
484 public function customize_preview_override_404_status() {
486 status_header( 200 );
491 * Print base element for preview frame.
495 public function customize_preview_base() {
496 ?><base href="<?php echo home_url( '/' ); ?>" /><?php
500 * Print a workaround to handle HTML5 tags in IE < 9
504 public function customize_preview_html5() { ?>
506 <script type="text/javascript">
507 var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
508 'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
509 'output', 'progress', 'section', 'time', 'video' ];
510 for ( var i = 0; i < e.length; i++ ) {
511 document.createElement( e[i] );
518 * Print JavaScript settings for preview frame.
522 public function customize_preview_settings() {
525 'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
526 'activePanels' => array(),
527 'activeSections' => array(),
528 'activeControls' => array(),
531 if ( 2 == $this->nonce_tick ) {
532 $settings['nonce'] = array(
533 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
534 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
538 foreach ( $this->settings as $id => $setting ) {
539 $settings['values'][ $id ] = $setting->js_value();
541 foreach ( $this->panels as $id => $panel ) {
542 $settings['activePanels'][ $id ] = $panel->active();
543 foreach ( $panel->sections as $id => $section ) {
544 $settings['activeSections'][ $id ] = $section->active();
547 foreach ( $this->sections as $id => $section ) {
548 $settings['activeSections'][ $id ] = $section->active();
550 foreach ( $this->controls as $id => $control ) {
551 $settings['activeControls'][ $id ] = $control->active();
555 <script type="text/javascript">
556 var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
562 * Prints a signature so we can ensure the Customizer was properly executed.
566 public function customize_preview_signature() {
567 echo 'WP_CUSTOMIZER_SIGNATURE';
571 * Removes the signature in case we experience a case where the Customizer was not properly executed.
575 public function remove_preview_signature( $return = null ) {
576 remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
582 * Is it a theme preview?
586 * @return bool True if it's a preview, false if not.
588 public function is_preview() {
589 return (bool) $this->previewing;
593 * Retrieve the template name of the previewed theme.
597 * @return string Template name.
599 public function get_template() {
600 return $this->theme()->get_template();
604 * Retrieve the stylesheet name of the previewed theme.
608 * @return string Stylesheet name.
610 public function get_stylesheet() {
611 return $this->theme()->get_stylesheet();
615 * Retrieve the template root of the previewed theme.
619 * @return string Theme root.
621 public function get_template_root() {
622 return get_raw_theme_root( $this->get_template(), true );
626 * Retrieve the stylesheet root of the previewed theme.
630 * @return string Theme root.
632 public function get_stylesheet_root() {
633 return get_raw_theme_root( $this->get_stylesheet(), true );
637 * Filter the current theme and return the name of the previewed theme.
641 * @param $current_theme {@internal Parameter is not used}
642 * @return string Theme name.
644 public function current_theme( $current_theme ) {
645 return $this->theme()->display('Name');
649 * Switch the theme and trigger the save() method on each setting.
653 public function save() {
654 if ( ! $this->is_preview() )
657 check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
659 // Do we have to switch themes?
660 if ( ! $this->is_theme_active() ) {
661 // Temporarily stop previewing the theme to allow switch_themes()
662 // to operate properly.
663 $this->stop_previewing_theme();
664 switch_theme( $this->get_stylesheet() );
665 update_option( 'theme_switched_via_customizer', true );
666 $this->start_previewing_theme();
670 * Fires once the theme has switched in the Customizer, but before settings
675 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
677 do_action( 'customize_save', $this );
679 foreach ( $this->settings as $setting ) {
684 * Fires after Customize settings have been saved.
688 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
690 do_action( 'customize_save_after', $this );
696 * Add a customize setting.
700 * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
701 * @param array $args Setting arguments; passed to WP_Customize_Setting
704 public function add_setting( $id, $args = array() ) {
705 if ( is_a( $id, 'WP_Customize_Setting' ) )
708 $setting = new WP_Customize_Setting( $this, $id, $args );
710 $this->settings[ $setting->id ] = $setting;
714 * Retrieve a customize setting.
718 * @param string $id Customize Setting ID.
719 * @return WP_Customize_Setting
721 public function get_setting( $id ) {
722 if ( isset( $this->settings[ $id ] ) )
723 return $this->settings[ $id ];
727 * Remove a customize setting.
731 * @param string $id Customize Setting ID.
733 public function remove_setting( $id ) {
734 unset( $this->settings[ $id ] );
738 * Add a customize panel.
743 * @param WP_Customize_Panel|string $id Customize Panel object, or Panel ID.
744 * @param array $args Optional. Panel arguments. Default empty array.
746 public function add_panel( $id, $args = array() ) {
747 if ( is_a( $id, 'WP_Customize_Panel' ) ) {
751 $panel = new WP_Customize_Panel( $this, $id, $args );
754 $this->panels[ $panel->id ] = $panel;
758 * Retrieve a customize panel.
763 * @param string $id Panel ID to get.
764 * @return WP_Customize_Panel Requested panel instance.
766 public function get_panel( $id ) {
767 if ( isset( $this->panels[ $id ] ) ) {
768 return $this->panels[ $id ];
773 * Remove a customize panel.
778 * @param string $id Panel ID to remove.
780 public function remove_panel( $id ) {
781 unset( $this->panels[ $id ] );
785 * Add a customize section.
789 * @param WP_Customize_Section|string $id Customize Section object, or Section ID.
790 * @param array $args Section arguments.
792 public function add_section( $id, $args = array() ) {
793 if ( is_a( $id, 'WP_Customize_Section' ) )
796 $section = new WP_Customize_Section( $this, $id, $args );
798 $this->sections[ $section->id ] = $section;
802 * Retrieve a customize section.
806 * @param string $id Section ID.
807 * @return WP_Customize_Section
809 public function get_section( $id ) {
810 if ( isset( $this->sections[ $id ] ) )
811 return $this->sections[ $id ];
815 * Remove a customize section.
819 * @param string $id Section ID.
821 public function remove_section( $id ) {
822 unset( $this->sections[ $id ] );
826 * Add a customize control.
830 * @param WP_Customize_Control|string $id Customize Control object, or ID.
831 * @param array $args Control arguments; passed to WP_Customize_Control
834 public function add_control( $id, $args = array() ) {
835 if ( is_a( $id, 'WP_Customize_Control' ) )
838 $control = new WP_Customize_Control( $this, $id, $args );
840 $this->controls[ $control->id ] = $control;
844 * Retrieve a customize control.
848 * @param string $id ID of the control.
849 * @return WP_Customize_Control $control The control object.
851 public function get_control( $id ) {
852 if ( isset( $this->controls[ $id ] ) )
853 return $this->controls[ $id ];
857 * Remove a customize control.
861 * @param string $id ID of the control.
863 public function remove_control( $id ) {
864 unset( $this->controls[ $id ] );
868 * Register a customize control type.
870 * Registered types are eligible to be rendered via JS and created dynamically.
875 * @param string $control Name of a custom control which is a subclass of
876 * {@see WP_Customize_Control}.
878 public function register_control_type( $control ) {
879 $this->registered_control_types[] = $control;
883 * Render JS templates for all registered control types.
888 public function render_control_templates() {
889 foreach ( $this->registered_control_types as $control_type ) {
890 $control = new $control_type( $this, 'temp', array() );
891 $control->print_template();
896 * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
900 * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $a Object A.
901 * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $b Object B.
904 protected final function _cmp_priority( $a, $b ) {
905 if ( $a->priority === $b->priority ) {
906 return $a->instance_number - $a->instance_number;
908 return $a->priority - $b->priority;
913 * Prepare panels, sections, and controls.
915 * For each, check if required related components exist,
916 * whether the user has the necessary capabilities,
917 * and sort by priority.
921 public function prepare_controls() {
924 uasort( $this->controls, array( $this, '_cmp_priority' ) );
926 foreach ( $this->controls as $id => $control ) {
927 if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
931 $this->sections[ $control->section ]->controls[] = $control;
932 $controls[ $id ] = $control;
934 $this->controls = $controls;
937 uasort( $this->sections, array( $this, '_cmp_priority' ) );
940 foreach ( $this->sections as $section ) {
941 if ( ! $section->check_capabilities() || ! $section->controls ) {
945 usort( $section->controls, array( $this, '_cmp_priority' ) );
947 if ( ! $section->panel ) {
948 // Top-level section.
949 $sections[ $section->id ] = $section;
951 // This section belongs to a panel.
952 if ( isset( $this->panels [ $section->panel ] ) ) {
953 $this->panels[ $section->panel ]->sections[ $section->id ] = $section;
957 $this->sections = $sections;
960 uasort( $this->panels, array( $this, '_cmp_priority' ) );
963 foreach ( $this->panels as $panel ) {
964 if ( ! $panel->check_capabilities() || ! $panel->sections ) {
968 uasort( $panel->sections, array( $this, '_cmp_priority' ) );
969 $panels[ $panel->id ] = $panel;
971 $this->panels = $panels;
973 // Sort panels and top-level sections together.
974 $this->containers = array_merge( $this->panels, $this->sections );
975 uasort( $this->containers, array( $this, '_cmp_priority' ) );
979 * Enqueue scripts for customize controls.
983 public function enqueue_control_scripts() {
984 foreach ( $this->controls as $control ) {
990 * Register some default controls.
994 public function register_controls() {
996 /* Control Types (custom control classes) */
997 $this->register_control_type( 'WP_Customize_Color_Control' );
998 $this->register_control_type( 'WP_Customize_Upload_Control' );
999 $this->register_control_type( 'WP_Customize_Image_Control' );
1000 $this->register_control_type( 'WP_Customize_Background_Image_Control' );
1002 /* Site Title & Tagline */
1004 $this->add_section( 'title_tagline', array(
1005 'title' => __( 'Site Title & Tagline' ),
1009 $this->add_setting( 'blogname', array(
1010 'default' => get_option( 'blogname' ),
1012 'capability' => 'manage_options',
1015 $this->add_control( 'blogname', array(
1016 'label' => __( 'Site Title' ),
1017 'section' => 'title_tagline',
1020 $this->add_setting( 'blogdescription', array(
1021 'default' => get_option( 'blogdescription' ),
1023 'capability' => 'manage_options',
1026 $this->add_control( 'blogdescription', array(
1027 'label' => __( 'Tagline' ),
1028 'section' => 'title_tagline',
1033 $this->add_section( 'colors', array(
1034 'title' => __( 'Colors' ),
1038 $this->add_setting( 'header_textcolor', array(
1039 'theme_supports' => array( 'custom-header', 'header-text' ),
1040 'default' => get_theme_support( 'custom-header', 'default-text-color' ),
1042 'sanitize_callback' => array( $this, '_sanitize_header_textcolor' ),
1043 'sanitize_js_callback' => 'maybe_hash_hex_color',
1046 // Input type: checkbox
1047 // With custom value
1048 $this->add_control( 'display_header_text', array(
1049 'settings' => 'header_textcolor',
1050 'label' => __( 'Display Header Text' ),
1051 'section' => 'title_tagline',
1052 'type' => 'checkbox',
1055 $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
1056 'label' => __( 'Header Text Color' ),
1057 'section' => 'colors',
1060 // Input type: Color
1061 // With sanitize_callback
1062 $this->add_setting( 'background_color', array(
1063 'default' => get_theme_support( 'custom-background', 'default-color' ),
1064 'theme_supports' => 'custom-background',
1066 'sanitize_callback' => 'sanitize_hex_color_no_hash',
1067 'sanitize_js_callback' => 'maybe_hash_hex_color',
1070 $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
1071 'label' => __( 'Background Color' ),
1072 'section' => 'colors',
1078 $this->add_section( 'header_image', array(
1079 'title' => __( 'Header Image' ),
1080 'theme_supports' => 'custom-header',
1084 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
1085 'default' => get_theme_support( 'custom-header', 'default-image' ),
1086 'theme_supports' => 'custom-header',
1089 $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
1090 // 'default' => get_theme_support( 'custom-header', 'default-image' ),
1091 'theme_supports' => 'custom-header',
1094 $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
1096 /* Custom Background */
1098 $this->add_section( 'background_image', array(
1099 'title' => __( 'Background Image' ),
1100 'theme_supports' => 'custom-background',
1104 $this->add_setting( 'background_image', array(
1105 'default' => get_theme_support( 'custom-background', 'default-image' ),
1106 'theme_supports' => 'custom-background',
1109 $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
1110 'theme_supports' => 'custom-background',
1113 $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
1115 $this->add_setting( 'background_repeat', array(
1116 'default' => get_theme_support( 'custom-background', 'default-repeat' ),
1117 'theme_supports' => 'custom-background',
1120 $this->add_control( 'background_repeat', array(
1121 'label' => __( 'Background Repeat' ),
1122 'section' => 'background_image',
1125 'no-repeat' => __('No Repeat'),
1126 'repeat' => __('Tile'),
1127 'repeat-x' => __('Tile Horizontally'),
1128 'repeat-y' => __('Tile Vertically'),
1132 $this->add_setting( 'background_position_x', array(
1133 'default' => get_theme_support( 'custom-background', 'default-position-x' ),
1134 'theme_supports' => 'custom-background',
1137 $this->add_control( 'background_position_x', array(
1138 'label' => __( 'Background Position' ),
1139 'section' => 'background_image',
1142 'left' => __('Left'),
1143 'center' => __('Center'),
1144 'right' => __('Right'),
1148 $this->add_setting( 'background_attachment', array(
1149 'default' => get_theme_support( 'custom-background', 'default-attachment' ),
1150 'theme_supports' => 'custom-background',
1153 $this->add_control( 'background_attachment', array(
1154 'label' => __( 'Background Attachment' ),
1155 'section' => 'background_image',
1158 'scroll' => __('Scroll'),
1159 'fixed' => __('Fixed'),
1163 // If the theme is using the default background callback, we can update
1164 // the background CSS using postMessage.
1165 if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
1166 foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
1167 $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
1173 $locations = get_registered_nav_menus();
1174 $menus = wp_get_nav_menus();
1175 $num_locations = count( array_keys( $locations ) );
1177 $this->add_section( 'nav', array(
1178 'title' => __( 'Navigation' ),
1179 'theme_supports' => 'menus',
1181 '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.'),
1185 $choices = array( 0 => __( '— Select —' ) );
1186 foreach ( $menus as $menu ) {
1187 $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '…' );
1190 foreach ( $locations as $location => $description ) {
1191 $menu_setting_id = "nav_menu_locations[{$location}]";
1193 $this->add_setting( $menu_setting_id, array(
1194 'sanitize_callback' => 'absint',
1195 'theme_supports' => 'menus',
1198 $this->add_control( $menu_setting_id, array(
1199 'label' => $description,
1202 'choices' => $choices,
1207 /* Static Front Page */
1210 $this->add_section( 'static_front_page', array(
1211 'title' => __( 'Static Front Page' ),
1212 // 'theme_supports' => 'static-front-page',
1214 'description' => __( 'Your theme supports a static front page.' ),
1217 $this->add_setting( 'show_on_front', array(
1218 'default' => get_option( 'show_on_front' ),
1219 'capability' => 'manage_options',
1221 // 'theme_supports' => 'static-front-page',
1224 $this->add_control( 'show_on_front', array(
1225 'label' => __( 'Front page displays' ),
1226 'section' => 'static_front_page',
1229 'posts' => __( 'Your latest posts' ),
1230 'page' => __( 'A static page' ),
1234 $this->add_setting( 'page_on_front', array(
1236 'capability' => 'manage_options',
1237 // 'theme_supports' => 'static-front-page',
1240 $this->add_control( 'page_on_front', array(
1241 'label' => __( 'Front page' ),
1242 'section' => 'static_front_page',
1243 'type' => 'dropdown-pages',
1246 $this->add_setting( 'page_for_posts', array(
1248 'capability' => 'manage_options',
1249 // 'theme_supports' => 'static-front-page',
1252 $this->add_control( 'page_for_posts', array(
1253 'label' => __( 'Posts page' ),
1254 'section' => 'static_front_page',
1255 'type' => 'dropdown-pages',
1260 * Callback for validating the header_textcolor value.
1262 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
1263 * Returns default text color if hex color is empty.
1267 * @param string $color
1270 public function _sanitize_header_textcolor( $color ) {
1271 if ( 'blank' === $color )
1274 $color = sanitize_hex_color_no_hash( $color );
1275 if ( empty( $color ) )
1276 $color = get_theme_support( 'custom-header', 'default-text-color' );
1283 * Sanitizes a hex color.
1285 * Returns either '', a 3 or 6 digit hex color (with #), or null.
1286 * For sanitizing values without a #, see sanitize_hex_color_no_hash().
1290 * @param string $color
1291 * @return string|null
1293 function sanitize_hex_color( $color ) {
1294 if ( '' === $color )
1297 // 3 or 6 hex digits, or the empty string.
1298 if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
1305 * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
1307 * Saving hex colors without a hash puts the burden of adding the hash on the
1308 * UI, which makes it difficult to use or upgrade to other color types such as
1309 * rgba, hsl, rgb, and html color names.
1311 * Returns either '', a 3 or 6 digit hex color (without a #), or null.
1315 * @param string $color
1316 * @return string|null
1318 function sanitize_hex_color_no_hash( $color ) {
1319 $color = ltrim( $color, '#' );
1321 if ( '' === $color )
1324 return sanitize_hex_color( '#' . $color ) ? $color : null;
1328 * Ensures that any hex color is properly hashed.
1329 * Otherwise, returns value untouched.
1331 * This method should only be necessary if using sanitize_hex_color_no_hash().
1335 * @param string $color
1338 function maybe_hash_hex_color( $color ) {
1339 if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
1340 return '#' . $unhashed;