: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'instance_number' => 'ASC',
foreach ( $this->panels as $panel ) {
if ( ! $panel->check_capabilities() ) {
$panel->sections = wp_list_sort(
'instance_number' => 'ASC',
$panels[ $panel->id ] = $panel;
// Sort panels and top-level sections together.
$this->containers = array_merge( $this->panels, $this->sections );
$this->containers = wp_list_sort(
'instance_number' => 'ASC',
* Enqueues scripts for customize controls.
public function enqueue_control_scripts() {
foreach ( $this->controls as $control ) {
if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) {
wp_enqueue_script( 'updates' );
'totals' => wp_get_update_data(),
* Determines whether the user agent is iOS.
* @return bool Whether the user agent is iOS.
public function is_ios() {
return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
* Gets the template string for the Customizer pane document title.
* @return string The template string for the document title.
public function get_document_title_template() {
if ( $this->is_theme_active() ) {
/* translators: %s: Document title from the preview. */
$document_title_tmpl = __( 'Customize: %s' );
/* translators: %s: Document title from the preview. */
$document_title_tmpl = __( 'Live Preview: %s' );
$document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
return $document_title_tmpl;
* Sets the initial URL to be previewed.
* @param string $preview_url URL to be previewed.
public function set_preview_url( $preview_url ) {
$preview_url = sanitize_url( $preview_url );
$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
* Gets the initial URL to be previewed.
* @return string URL being previewed.
public function get_preview_url() {
if ( empty( $this->preview_url ) ) {
$preview_url = home_url( '/' );
$preview_url = $this->preview_url;
* Determines whether the admin and the frontend are on different domains.
* @return bool Whether cross-domain.
public function is_cross_domain() {
$admin_origin = wp_parse_url( admin_url() );
$home_origin = wp_parse_url( home_url() );
$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
* Gets URLs allowed to be previewed.
* If the front end and the admin are served from the same domain, load the
* preview over ssl if the Customizer is being loaded over ssl. This avoids
* insecure content warnings. This is not attempted if the admin and front end
* are on different domains to avoid the case where the front end doesn't have
* ssl certs. Domain mapping plugins can allow other urls in these conditions
* using the customize_allowed_urls filter.
* @return array Allowed URLs.
public function get_allowed_urls() {
$allowed_urls = array( home_url( '/' ) );
if ( is_ssl() && ! $this->is_cross_domain() ) {
$allowed_urls[] = home_url( '/', 'https' );
* Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
* @param string[] $allowed_urls An array of allowed URLs.
$allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
* Gets messenger channel.
* @return string Messenger channel.
public function get_messenger_channel() {
return $this->messenger_channel;
* Sets URL to link the user to when closing the Customizer.
* @param string $return_url URL for return link.
public function set_return_url( $return_url ) {
$return_url = sanitize_url( $return_url );
$return_url = remove_query_arg( wp_removable_query_args(), $return_url );
$return_url = wp_validate_redirect( $return_url );
$this->return_url = $return_url;
* Gets URL to link the user to when closing the Customizer.
* @global array $_registered_pages
* @return string URL for link to close Customizer.
public function get_return_url() {
global $_registered_pages;
$referer = wp_get_referer();
$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
if ( $this->return_url ) {
$return_url = $this->return_url;
$return_url_basename = wp_basename( parse_url( $this->return_url, PHP_URL_PATH ) );
$return_url_query = parse_url( $this->return_url, PHP_URL_QUERY );
if ( 'themes.php' === $return_url_basename && $return_url_query ) {
parse_str( $return_url_query, $query_vars );
* If the return URL is a page added by a theme to the Appearance menu via add_submenu_page(),
* verify that it belongs to the active theme, otherwise fall back to the Themes screen.
if ( isset( $query_vars['page'] ) && ! isset( $_registered_pages[ "appearance_page_{$query_vars['page']}" ] ) ) {
$return_url = admin_url( 'themes.php' );
} elseif ( $referer && ! in_array( wp_basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
} elseif ( $this->preview_url ) {
$return_url = $this->preview_url;
$return_url = home_url( '/' );
* Sets the autofocused constructs.
* @param array $autofocus {
* Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
* @type string $control ID for control to be autofocused.
* @type string $section ID for section to be autofocused.
* @type string $panel ID for panel to be autofocused.
public function set_autofocus( $autofocus ) {
$this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
* Gets the autofocused constructs.
* Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
* @type string $control ID for control to be autofocused.
* @type string $section ID for section to be autofocused.
* @type string $panel ID for panel to be autofocused.
public function get_autofocus() {
* Gets nonces for the Customizer.
public function get_nonces() {
'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
'switch_themes' => wp_create_nonce( 'switch_themes' ),
'dismiss_autosave_or_lock' => wp_create_nonce( 'customize_dismiss_autosave_or_lock' ),
'override_lock' => wp_create_nonce( 'customize_override_changeset_lock' ),
'trash' => wp_create_nonce( 'trash_customize_changeset' ),
* Filters nonces for Customizer.
* @param string[] $nonces Array of refreshed nonces for save and
* @param WP_Customize_Manager $manager WP_Customize_Manager instance.
$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
* Prints JavaScript settings for parent window.
public function customize_pane_settings() {
$login_url = add_query_arg(
// Ensure dirty flags are set for modified settings.
foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) {
$setting = $this->get_setting( $setting_id );
$autosave_revision_post = null;
$autosave_autodraft_post = null;
$changeset_post_id = $this->changeset_post_id();
if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) {
if ( $changeset_post_id ) {
if ( is_user_logged_in() ) {
$autosave_revision_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
$autosave_autodraft_posts = $this->get_changeset_posts(
'post_status' => 'auto-draft',
'exclude_restore_dismissed' => true,
if ( ! empty( $autosave_autodraft_posts ) ) {
$autosave_autodraft_post = array_shift( $autosave_autodraft_posts );
$current_user_can_publish = current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts );
// @todo Include all of the status labels here from script-loader.php, and then allow it to be filtered.
$status_choices = array();
if ( $current_user_can_publish ) {
$status_choices[] = array(
'label' => __( 'Publish' ),
$status_choices[] = array(
'label' => __( 'Save Draft' ),
if ( $current_user_can_publish ) {
$status_choices[] = array(
'label' => _x( 'Schedule', 'customizer changeset action/button label' ),
// Prepare Customizer settings to pass to JavaScript.
if ( $changeset_post_id ) {
$changeset_post = get_post( $changeset_post_id );
// Determine initial date to be at present or future, not past.
$current_time = current_time( 'mysql', false );
$initial_date = $current_time;
$initial_date = get_the_time( 'Y-m-d H:i:s', $changeset_post->ID );
if ( $initial_date < $current_time ) {
$initial_date = $current_time;
if ( $this->changeset_post_id() ) {
$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
'uuid' => $this->changeset_uuid(),
'branching' => $this->branching(),
'autosaved' => $this->autosaved(),
'hasAutosaveRevision' => ! empty( $autosave_revision_post ),
'latestAutoDraftUuid' => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null,
'status' => $changeset_post ? $changeset_post->post_status : '',
'currentUserCanPublish' => $current_user_can_publish,
'publishDate' => $initial_date,
'statusChoices' => $status_choices,
'lockUser' => $lock_user_id ? $this->get_lock_user_data( $lock_user_id ) : null,
'initialServerDate' => $current_time,
'dateFormat' => get_option( 'date_format' ),
'timeFormat' => get_option( 'time_format' ),
'initialServerTimestamp' => floor( microtime( true ) * 1000 ),
'initialClientTimestamp' => -1, // To be set with JS below.
'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000,
'keepAliveCheck' => 2500,
'reflowPaneContents' => 100,
'previewFrameSensitivity' => 2000,
'stylesheet' => $this->get_stylesheet(),
'active' => $this->is_theme_active(),
'_canInstall' => current_user_can( 'install_themes' ),
'preview' => sanitize_url( $this->get_preview_url() ),
'return' => sanitize_url( $this->get_return_url() ),
'parent' => sanitize_url( admin_url() ),
'activated' => sanitize_url( home_url( '/' ) ),
'ajax' => sanitize_url( admin_url( 'admin-ajax.php', 'relative' ) ),
'allowed' => array_map( 'sanitize_url', $this->get_allowed_urls() ),
'isCrossDomain' => $this->is_cross_domain(),
'home' => sanitize_url( home_url( '/' ) ),
'login' => sanitize_url( $login_url ),
'mobile' => wp_is_mobile(),
'ios' => $this->is_ios(),
'nonce' => $this->get_nonces(),
'autofocus' => $this->get_autofocus(),
'documentTitleTmpl' => $this->get_document_title_template(),
'previewableDevices' => $this->get_previewable_devices(),
'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ),
/* translators: %d: Number of theme search results, which cannot currently consider singular vs. plural forms. */
'themeSearchResults' => __( '%d themes found' ),
/* translators: %d: Number of themes being displayed, which cannot currently consider singular vs. plural forms. */
'announceThemeCount' => __( 'Displaying %d themes' ),
/* translators: %s: Theme name. */
'announceThemeDetails' => __( 'Showing details for theme: %s' ),
// Temporarily disable installation in Customizer. See #42184.
$filesystem_method = get_filesystem_method();
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
if ( 'direct' !== $filesystem_method && ! $filesystem_credentials_are_stored ) {
$settings['theme']['_filesystemCredentialsNeeded'] = true;
// Prepare Customize Section objects to pass to JavaScript.
foreach ( $this->sections() as $id => $section ) {
if ( $section->check_capabilities() ) {
$settings['sections'][ $id ] = $section->json();
// Prepare Customize Panel objects to pass to JavaScript.
foreach ( $this->panels() as $panel_id => $panel ) {
if ( $panel->check_capabilities() ) {
$settings['panels'][ $panel_id ] = $panel->json();
foreach ( $panel->sections as $section_id => $section ) {
if ( $section->check_capabilities() ) {
$settings['sections'][ $section_id ] = $section->json();
var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
_wpCustomizeSettings.initialClientTimestamp = _.now();
_wpCustomizeSettings.controls = {};
_wpCustomizeSettings.settings = {};
// Serialize settings one by one to improve memory usage.
echo "(function ( s ){\n";
foreach ( $this->settings() as $setting ) {
if ( $setting->check_capabilities() ) {
wp_json_encode( $setting->id ),
wp_json_encode( $setting->json() )