array( 'label' => __( 'Enter desktop preview mode' ), 'default' => true, ), 'tablet' => array( 'label' => __( 'Enter tablet preview mode' ), ), 'mobile' => array( 'label' => __( 'Enter mobile preview mode' ), ), ); $devices = apply_filters( 'customize_previewable_devices', $devices ); return $devices; } public function register_controls() { /* Themes (controls are loaded via ajax) */ $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 'title' => $this->theme()->display( 'Name' ), 'description' => ( '
' . __( 'Looking for a theme? You can search or browse the WordPress.org theme directory, install and preview themes, then activate them right here.' ) . '
' . '' . __( 'While previewing a new theme, you can continue to tailor things like widgets and menus, and explore theme-specific options.' ) . '
' ), 'capability' => 'switch_themes', 'priority' => 0, ) ) ); $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 'title' => __( 'Installed themes' ), 'action' => 'installed', 'capability' => 'switch_themes', 'panel' => 'themes', 'priority' => 0, ) ) ); if ( ! is_multisite() ) { $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array( 'title' => __( 'WordPress.org themes' ), 'action' => 'wporg', 'filter_type' => 'remote', 'capability' => 'install_themes', 'panel' => 'themes', 'priority' => 5, ) ) ); } // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 'capability' => 'switch_themes', ) ) ); /* Site Identity */ $this->add_section( 'title_tagline', array( 'title' => __( 'Site Identity' ), 'priority' => 20, ) ); $this->add_setting( 'blogname', array( 'default' => get_option( 'blogname' ), 'type' => 'option', 'capability' => 'manage_options', ) ); $this->add_control( 'blogname', array( 'label' => __( 'Site Title' ), 'section' => 'title_tagline', ) ); $this->add_setting( 'blogdescription', array( 'default' => get_option( 'blogdescription' ), 'type' => 'option', 'capability' => 'manage_options', ) ); $this->add_control( 'blogdescription', array( 'label' => __( 'Tagline' ), 'section' => 'title_tagline', ) ); // Add a setting to hide header text if the theme doesn't support custom headers. if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) { $this->add_setting( 'header_text', array( 'theme_supports' => array( 'custom-logo', 'header-text' ), 'default' => 1, 'sanitize_callback' => 'absint', ) ); $this->add_control( 'header_text', array( 'label' => __( 'Display Site Title and Tagline' ), 'section' => 'title_tagline', 'settings' => 'header_text', 'type' => 'checkbox', ) ); } $this->add_setting( 'site_icon', array( 'type' => 'option', 'capability' => 'manage_options', 'transport' => 'postMessage', // Previewed with JS in the Customizer controls window. ) ); $this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array( 'label' => __( 'Site Icon' ), 'description' => sprintf( /* translators: 1: pixel value for icon size. 2: pixel value for icon size. */ '' . __( 'The Site Icon is what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. It should be square and at least %1$s by %2$s pixels.' ) . '
' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '
'; $width = absint( get_theme_support( 'custom-header', 'width' ) ); $height = absint( get_theme_support( 'custom-header', 'height' ) ); if ( $width && $height ) { $control_description = sprintf( /* translators: 1: .mp4, 2: Header size in pixels. */ __( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends dimensions of %2$s pixels.' ), '.mp4',
sprintf( '%s × %s', $width, $height )
);
} elseif ( $width ) {
$control_description = sprintf(
/* translators: 1: .mp4, 2: Header width in pixels. */
__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a width of %2$s pixels.' ),
'.mp4',
sprintf( '%s', $width )
);
} else {
$control_description = sprintf(
/* translators: 1: .mp4, 2: Header height in pixels. */
__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a height of %2$s pixels.' ),
'.mp4',
sprintf( '%s', $height )
);
}
} else {
$title = __( 'Header Image' );
$description = '';
$control_description = '';
}
$this->add_section(
'header_image',
array(
'title' => $title,
'description' => $description,
'theme_supports' => 'custom-header',
'priority' => 60,
)
);
$this->add_setting(
'header_video',
array(
'theme_supports' => array( 'custom-header', 'video' ),
'transport' => 'postMessage',
'sanitize_callback' => 'absint',
'validate_callback' => array( $this, '_validate_header_video' ),
)
);
$this->add_setting(
'external_header_video',
array(
'theme_supports' => array( 'custom-header', 'video' ),
'transport' => 'postMessage',
'sanitize_callback' => array( $this, '_sanitize_external_header_video' ),
'validate_callback' => array( $this, '_validate_external_header_video' ),
)
);
$this->add_setting(
new WP_Customize_Filter_Setting(
$this,
'header_image',
array(
'default' => sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() ),
'theme_supports' => 'custom-header',
)
)
);
$this->add_setting(
new WP_Customize_Header_Image_Setting(
$this,
'header_image_data',
array(
'theme_supports' => 'custom-header',
)
)
);
/*
* Switch image settings to postMessage when video support is enabled since
* it entails that the_custom_header_markup() will be used, and thus selective
* refresh can be utilized.
*/
if ( current_theme_supports( 'custom-header', 'video' ) ) {
$this->get_setting( 'header_image' )->transport = 'postMessage';
$this->get_setting( 'header_image_data' )->transport = 'postMessage';
}
$this->add_control(
new WP_Customize_Media_Control(
$this,
'header_video',
array(
'theme_supports' => array( 'custom-header', 'video' ),
'label' => __( 'Header Video' ),
'description' => $control_description,
'section' => 'header_image',
'mime_type' => 'video',
'active_callback' => 'is_header_video_active',
)
)
);
$this->add_control(
'external_header_video',
array(
'theme_supports' => array( 'custom-header', 'video' ),
'type' => 'url',
'description' => __( 'Or, enter a YouTube URL:' ),
'section' => 'header_image',
'active_callback' => 'is_header_video_active',
)
);
$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
$this->selective_refresh->add_partial(
'custom_header',
array(
'selector' => '#wp-custom-header',
'render_callback' => 'the_custom_header_markup',
'settings' => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
'container_inclusive' => true,
)
);
/* Custom Background */
$this->add_section(
'background_image',
array(
'title' => __( 'Background Image' ),
'theme_supports' => 'custom-background',
'priority' => 80,
)
);
$this->add_setting(
'background_image',
array(
'default' => get_theme_support( 'custom-background', 'default-image' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
)
);
$this->add_setting(
new WP_Customize_Background_Image_Setting(
$this,
'background_image_thumb',
array(
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
)
)
);
$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
$this->add_setting(
'background_preset',
array(
'default' => get_theme_support( 'custom-background', 'default-preset' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
)
);
$this->add_control(
'background_preset',
array(
'label' => _x( 'Preset', 'Background Preset' ),
'section' => 'background_image',
'type' => 'select',
'choices' => array(
'default' => _x( 'Default', 'Default Preset' ),
'fill' => __( 'Fill Screen' ),
'fit' => __( 'Fit to Screen' ),
'repeat' => _x( 'Repeat', 'Repeat Image' ),
'custom' => _x( 'Custom', 'Custom Preset' ),
),
)
);
$this->add_setting(
'background_position_x',
array(
'default' => get_theme_support( 'custom-background', 'default-position-x' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
)
);
$this->add_setting(
'background_position_y',
array(
'default' => get_theme_support( 'custom-background', 'default-position-y' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
)
);
$this->add_control(
new WP_Customize_Background_Position_Control(
$this,
'background_position',
array(
'label' => __( 'Image Position' ),
'section' => 'background_image',
'settings' => array(
'x' => 'background_position_x',
'y' => 'background_position_y',
),
)
)
);
$this->add_setting(
'background_size',
array(
'default' => get_theme_support( 'custom-background', 'default-size' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
)
);
$this->add_control(
'background_size',
array(
'label' => __( 'Image Size' ),
'section' => 'background_image',
'type' => 'select',
'choices' => array(
'auto' => _x( 'Original', 'Original Size' ),
'contain' => __( 'Fit to Screen' ),
'cover' => __( 'Fill Screen' ),
),
)
);
$this->add_setting(
'background_repeat',
array(
'default' => get_theme_support( 'custom-background', 'default-repeat' ),
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'theme_supports' => 'custom-background',
)
);
$this->add_control(
'background_repeat',
array(
'label' => __( 'Repeat Background Image' ),
'section' => 'background_image',
'type' => 'checkbox',
)
);
$this->add_setting(
'background_attachment',
array(
'default' => get_theme_support( 'custom-background', 'default-attachment' ),
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'theme_supports' => 'custom-background',
)
);
$this->add_control(
'background_attachment',
array(
'label' => __( 'Scroll with Page' ),
'section' => 'background_image',
'type' => 'checkbox',
)
);
/*
* If the theme is using the default background callback, we can update
* the background CSS using postMessage.
*/
if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) {
$this->get_setting( 'background_' . $prop )->transport = 'postMessage';
}
}
/*
* Static Front Page
* See also https://core.trac.wordpress.org/ticket/19627 which introduces the static-front-page theme_support.
* The following replicates behavior from options-reading.php.
*/
$this->add_section(
'static_front_page',
array(
'title' => __( 'Homepage Settings' ),
'priority' => 120,
'description' => __( 'You can choose what’s displayed on the homepage of your site. It can be posts in reverse chronological order (classic blog), or a fixed/static page. To set a static homepage, you first need to create two Pages. One will become the homepage, and the other will be where your posts are displayed.' ),
'active_callback' => array( $this, 'has_published_pages' ),
)
);
$this->add_setting(
'show_on_front',
array(
'default' => get_option( 'show_on_front' ),
'capability' => 'manage_options',
'type' => 'option',
)
);
$this->add_control(
'show_on_front',
array(
'label' => __( 'Your homepage displays' ),
'section' => 'static_front_page',
'type' => 'radio',
'choices' => array(
'posts' => __( 'Your latest posts' ),
'page' => __( 'A static page' ),
),
)
);
$this->add_setting(
'page_on_front',
array(
'type' => 'option',
'capability' => 'manage_options',
)
);
$this->add_control(
'page_on_front',
array(
'label' => __( 'Homepage' ),
'section' => 'static_front_page',
'type' => 'dropdown-pages',
'allow_addition' => true,
)
);
$this->add_setting(
'page_for_posts',
array(
'type' => 'option',
'capability' => 'manage_options',
)
);
$this->add_control(
'page_for_posts',
array(
'label' => __( 'Posts page' ),
'section' => 'static_front_page',
'type' => 'dropdown-pages',
'allow_addition' => true,
)
);
/* Custom CSS */
$section_description = ''; $section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.' ); $section_description .= sprintf( ' %2$s %3$s', esc_url( __( 'https://developer.wordpress.org/advanced-administration/wordpress/css/' ) ), __( 'Learn more about CSS' ), /* translators: Hidden accessibility text. */ __( '(opens in a new tab)' ) ); $section_description .= '
'; $section_description .= '' . __( 'When using a keyboard to navigate:' ) . '
'; $section_description .= ''; $section_description .= sprintf( /* translators: 1: Link to user profile, 2: Additional link attributes, 3: Accessibility text. */ __( 'The edit field automatically highlights code syntax. You can disable this in your user profile%3$s to work in plain text mode.' ), esc_url( get_edit_profile_url() ), 'class="external-link" target="_blank"', sprintf( ' %s', /* translators: Hidden accessibility text. */ __( '(opens in a new tab)' ) ) ); $section_description .= '
'; } $section_description .= ''; $this->add_section( 'custom_css', array( 'title' => __( 'Additional CSS' ), 'priority' => 200, 'description_hidden' => true, 'description' => $section_description, ) ); $custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array( 'capability' => 'edit_css', 'default' => '', ) ); $this->add_setting( $custom_css_setting ); $this->add_control( new WP_Customize_Code_Editor_Control( $this, 'custom_css', array( 'label' => __( 'CSS code' ), 'section' => 'custom_css', 'settings' => array( 'default' => $custom_css_setting->id ), 'code_type' => 'text/css', 'input_attrs' => array( 'aria-describedby' => 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4', ), ) ) ); } public function has_published_pages() { $setting = $this->get_setting( 'nav_menus_created_posts' ); if ( $setting ) { foreach ( $setting->value() as $post_id ) { if ( 'page' === get_post_type( $post_id ) ) { return true; } } } return 0 !== count( get_pages( array( 'number' => 1, 'hierarchical' => 0, ) ) ); } public function register_dynamic_settings() { $setting_ids = array_keys( $this->unsanitized_post_values() ); $this->add_dynamic_settings( $setting_ids ); } public function handle_load_themes_request() { check_ajax_referer( 'switch_themes', 'nonce' ); if ( ! current_user_can( 'switch_themes' ) ) { wp_die( -1 ); } if ( empty( $_POST['theme_action'] ) ) { wp_send_json_error( 'missing_theme_action' ); } $theme_action = sanitize_key( $_POST['theme_action'] ); $themes = array(); $args = array(); // Define query filters based on user input. if ( ! array_key_exists( 'search', $_POST ) ) { $args['search'] = ''; } else { $args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) ); } if ( ! array_key_exists( 'tags', $_POST ) ) { $args['tag'] = ''; } else { $args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) ); } if ( ! array_key_exists( 'page', $_POST ) ) { $args['page'] = 1; } else { $args['page'] = absint( $_POST['page'] ); } require_once ABSPATH . 'wp-admin/includes/theme.php'; if ( 'installed' === $theme_action ) { // Load all installed themes from wp_prepare_themes_for_js(). $themes = array( 'themes' => array() ); foreach ( wp_prepare_themes_for_js() as $theme ) { $theme['type'] = 'installed'; $theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] ); $themes['themes'][] = $theme; } } elseif ( 'wporg' === $theme_action ) { // Load WordPress.org themes from the .org API and normalize data to match installed theme objects. if ( ! current_user_can( 'install_themes' ) ) { wp_die( -1 ); } // Arguments for all queries. $wporg_args = array( 'per_page' => 100, 'fields' => array( 'reviews_url' => true, // Explicitly request the reviews URL to be linked from the customizer. ), ); $args = array_merge( $wporg_args, $args ); if ( '' === $args['search'] && '' === $args['tag'] ) { $args['browse'] = 'new'; // Sort by latest themes by default. } // Load themes from the .org API. $themes = themes_api( 'query_themes', $args ); if ( is_wp_error( $themes ) ) { wp_send_json_error(); } // This list matches the allowed tags in wp-admin/includes/theme-install.php. $themes_allowedtags = array_fill_keys( array( 'a', 'abbr', 'acronym', 'code', 'pre', 'em', 'strong', 'div', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img' ), array() ); $themes_allowedtags['a'] = array_fill_keys( array( 'href', 'title', 'target' ), true ); $themes_allowedtags['acronym']['title'] = true; $themes_allowedtags['abbr']['title'] = true; $themes_allowedtags['img'] = array_fill_keys( array( 'src', 'class', 'alt' ), true ); // Prepare a list of installed themes to check against before the loop. $installed_themes = array(); $wp_themes = wp_get_themes(); foreach ( $wp_themes as $theme ) { $installed_themes[] = $theme->get_stylesheet(); } $update_php = network_admin_url( 'update.php?action=install-theme' ); // Set up properties for themes available on WordPress.org. foreach ( $themes->themes as &$theme ) { $theme->install_url = add_query_arg( array( 'theme' => $theme->slug, '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ), ), $update_php ); $theme->name = wp_kses( $theme->name, $themes_allowedtags ); $theme->version = wp_kses( $theme->version, $themes_allowedtags ); $theme->description = wp_kses( $theme->description, $themes_allowedtags ); $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false, ) ); $theme->num_ratings = number_format_i18n( $theme->num_ratings ); $theme->preview_url = set_url_scheme( $theme->preview_url ); // Handle themes that are already installed as installed themes. if ( in_array( $theme->slug, $installed_themes, true ) ) { $theme->type = 'installed'; } else { $theme->type = $theme_action; } // Set active based on customized theme. $theme->active = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme->slug ); // Map available theme properties to installed theme properties. $theme->id = $theme->slug; $theme->screenshot = array( $theme->screenshot_url ); $theme->authorAndUri = wp_kses( $theme->author['display_name'], $themes_allowedtags ); $theme->compatibleWP = is_wp_version_compatible( $theme->requires ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName $theme->compatiblePHP = is_php_version_compatible( $theme->requires_php ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName if ( isset( $theme->parent ) ) { $theme->parent = $theme->parent['slug']; } else { $theme->parent = false; } unset( $theme->slug ); unset( $theme->screenshot_url ); unset( $theme->author ); } // End foreach(). } // End if(). $themes = apply_filters( 'customize_load_themes', $themes, $args, $this ); wp_send_json_success( $themes ); } public function _sanitize_header_textcolor( $color ) { if ( 'blank' === $color ) { return 'blank'; } $color = sanitize_hex_color_no_hash( $color ); if ( empty( $color ) ) { $color = get_theme_support( 'custom-header', 'default-text-color' ); } return $color; } public function _sanitize_background_setting( $value, $setting ) { if ( 'background_repeat' === $setting->id ) { if ( ! in_array( $value, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background repeat.' ) ); } } elseif ( 'background_attachment' === $setting->id ) { if ( ! in_array( $value, array( 'fixed', 'scroll' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background attachment.' ) ); } } elseif ( 'background_position_x' === $setting->id ) { if ( ! in_array( $value, array( 'left', 'center', 'right' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background position X.' ) ); } } elseif ( 'background_position_y' === $setting->id ) { if ( ! in_array( $value, array( 'top', 'center', 'bottom' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background position Y.' ) ); } } elseif ( 'background_size' === $setting->id ) { if ( ! in_array( $value, array( 'auto', 'contain', 'cover' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) ); } } elseif ( 'background_preset' === $setting->id ) { if ( ! in_array( $value, array( 'default', 'fill', 'fit', 'repeat', 'custom' ), true ) ) { return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) ); } } elseif ( 'background_image' === $setting->id || 'background_image_thumb' === $setting->id ) { $value = empty( $value ) ? '' : sanitize_url( $value ); } else { return new WP_Error( 'unrecognized_setting', __( 'Unrecognized background setting.' ) ); } return $value; } public function export_header_video_settings( $response, $selective_refresh, $partials ) { if ( isset( $partials['custom_header'] ) ) { $response['custom_header_settings'] = get_header_video_settings(); } return $response; } public function _validate_header_video( $validity, $value ) { $video = get_attached_file( absint( $value ) ); if ( $video ) { $size = filesize( $video ); if ( $size > 8 * MB_IN_BYTES ) { $validity->add( 'size_too_large', __( 'This video file is too large to use as a header video. Try a shorter video or optimize the compression settings and re-upload a file that is less than 8MB. Or, upload your video to YouTube and link it with the option below.' ) ); } if ( ! str_ends_with( $video, '.mp4' ) && ! str_ends_with( $video, '.mov' ) ) { // Check for .mp4 or .mov format, which (assuming h.264 encoding) are the only cross-browser-supported formats. $validity->add( 'invalid_file_type', sprintf( /* translators: 1: .mp4, 2: .mov */ __( 'Only %1$s or %2$s files may be used for header video. Please convert your video file and try again, or, upload your video to YouTube and link it with the option below.' ), '.mp4',
'.mov'
)
);
}
}
return $validity;
}
public function _validate_external_header_video( $validity, $value ) {
$video = sanitize_url( $value );
if ( $video ) {
if ( ! preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video ) ) {
$validity->add( 'invalid_url', __( 'Please enter a valid YouTube URL.' ) );
}
}
return $validity;
}
public function _sanitize_external_header_video( $value ) {
return sanitize_url( trim( $value ) );
}
public function _render_custom_logo_partial() {
return get_custom_logo();
}
}