: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'label' => _x( 'Preset', 'Background Preset' ),
'section' => 'background_image',
'default' => _x( 'Default', 'Default Preset' ),
'fill' => __( 'Fill Screen' ),
'fit' => __( 'Fit to Screen' ),
'repeat' => _x( 'Repeat', 'Repeat Image' ),
'custom' => _x( 'Custom', 'Custom Preset' ),
'default' => get_theme_support( 'custom-background', 'default-position-x' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'default' => get_theme_support( 'custom-background', 'default-position-y' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
new WP_Customize_Background_Position_Control(
'label' => __( 'Image Position' ),
'section' => 'background_image',
'x' => 'background_position_x',
'y' => 'background_position_y',
'default' => get_theme_support( 'custom-background', 'default-size' ),
'theme_supports' => 'custom-background',
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'label' => __( 'Image Size' ),
'section' => 'background_image',
'auto' => _x( 'Original', 'Original Size' ),
'contain' => __( 'Fit to Screen' ),
'cover' => __( 'Fill Screen' ),
'default' => get_theme_support( 'custom-background', 'default-repeat' ),
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'theme_supports' => 'custom-background',
'label' => __( 'Repeat Background Image' ),
'section' => 'background_image',
'default' => get_theme_support( 'custom-background', 'default-attachment' ),
'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
'theme_supports' => 'custom-background',
'label' => __( 'Scroll with Page' ),
'section' => 'background_image',
* 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';
* 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.
'title' => __( 'Homepage Settings' ),
'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' ),
'default' => get_option( 'show_on_front' ),
'capability' => 'manage_options',
'label' => __( 'Your homepage displays' ),
'section' => 'static_front_page',
'posts' => __( 'Your latest posts' ),
'page' => __( 'A static page' ),
'capability' => 'manage_options',
'label' => __( 'Homepage' ),
'section' => 'static_front_page',
'type' => 'dropdown-pages',
'allow_addition' => true,
'capability' => 'manage_options',
'label' => __( 'Posts page' ),
'section' => 'static_front_page',
'type' => 'dropdown-pages',
'allow_addition' => true,
$section_description = '<p>';
$section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.' );
$section_description .= sprintf(
' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text"> %3$s</span></a>',
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 .= '</p>';
$section_description .= '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>';
$section_description .= '<ul>';
$section_description .= '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>';
$section_description .= '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>';
$section_description .= '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '</li>';
$section_description .= '</ul>';
if ( 'false' !== wp_get_current_user()->syntax_highlighting ) {
$section_description .= '<p>';
$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 <a href="%1$s" %2$s>user profile%3$s</a> to work in plain text mode.' ),
esc_url( get_edit_profile_url() ),
'class="external-link" target="_blank"',
'<span class="screen-reader-text"> %s</span>',
/* translators: Hidden accessibility text. */
__( '(opens in a new tab)' )
$section_description .= '</p>';
$section_description .= '<p class="section-description-buttons">';
$section_description .= '<button type="button" class="button-link section-description-close">' . __( 'Close' ) . '</button>';
$section_description .= '</p>';
'title' => __( 'Additional CSS' ),
'description_hidden' => true,
'description' => $section_description,
$custom_css_setting = new WP_Customize_Custom_CSS_Setting(
sprintf( 'custom_css[%s]', get_stylesheet() ),
'capability' => 'edit_css',
$this->add_setting( $custom_css_setting );
new WP_Customize_Code_Editor_Control(
'label' => __( 'CSS code' ),
'section' => 'custom_css',
'settings' => array( 'default' => $custom_css_setting->id ),
'code_type' => 'text/css',
'aria-describedby' => 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4',
* Returns whether there are published pages.
* Used as active callback for static front page section and controls.
* @return bool Whether there are published (or to be published) pages.
public function has_published_pages() {
$setting = $this->get_setting( 'nav_menus_created_posts' );
foreach ( $setting->value() as $post_id ) {
if ( 'page' === get_post_type( $post_id ) ) {
* Adds settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
* @see add_dynamic_settings()
public function register_dynamic_settings() {
$setting_ids = array_keys( $this->unsanitized_post_values() );
$this->add_dynamic_settings( $setting_ids );
* Loads themes into the theme browsing/installation UI.
public function handle_load_themes_request() {
check_ajax_referer( 'switch_themes', 'nonce' );
if ( ! current_user_can( 'switch_themes' ) ) {
if ( empty( $_POST['theme_action'] ) ) {
wp_send_json_error( 'missing_theme_action' );
$theme_action = sanitize_key( $_POST['theme_action'] );
// Define query filters based on user input.
if ( ! array_key_exists( 'search', $_POST ) ) {
$args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
if ( ! array_key_exists( 'tags', $_POST ) ) {
$args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
if ( ! array_key_exists( 'page', $_POST ) ) {
$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' ) ) {
// Arguments for all queries.
'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 ) ) {
// 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' ),
$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(
'_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
$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(
'rating' => $theme->rating,
'number' => $theme->num_ratings,
$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';
$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'];
unset( $theme->screenshot_url );
* Filters the theme data loaded in the customizer.
* This allows theme data to be loading from an external source,
* or modification of data loaded from `wp_prepare_themes_for_js()`
* or WordPress.org via `themes_api()`.
* @see wp_prepare_themes_for_js()
* @see WP_Customize_Manager::__construct()
* @param array|stdClass $themes Nested array or object of theme data.
* @param array $args List of arguments, such as page, search term, and tags to query for.
* @param WP_Customize_Manager $manager Instance of Customize manager.
$themes = apply_filters( 'customize_load_themes', $themes, $args, $this );
wp_send_json_success( $themes );
* Callback for validating the header_textcolor value.
* Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
* Returns default text color if hex color is empty.
public function _sanitize_header_textcolor( $color ) {
if ( 'blank' === $color ) {