: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
return '_default_wp_die_handler';
* Starts preview and customize theme.
* Check if customize query variable exist. Init filters to filter the active theme.
* @global string $pagenow The filename of the current screen.
public function setup_theme() {
// Check permissions for customize.php access since this method is called before customize.php can run any code.
if ( 'customize.php' === $pagenow && ! current_user_can( 'customize' ) ) {
if ( ! is_user_logged_in() ) {
'<h1>' . __( 'You need a higher level of permission.' ) . '</h1>' .
'<p>' . __( 'Sorry, you are not allowed to customize this site.' ) . '</p>',
// If a changeset was provided is invalid.
if ( isset( $this->_changeset_uuid ) && false !== $this->_changeset_uuid && ! wp_is_uuid( $this->_changeset_uuid ) ) {
$this->wp_die( -1, __( 'Invalid changeset UUID' ) );
* Clear incoming post data if the user lacks a CSRF token (nonce). Note that the customizer
* application will inject the customize_preview_nonce query parameter into all Ajax requests.
* For similar behavior elsewhere in WordPress, see rest_cookie_check_errors() which logs out
* a user when a valid nonce isn't present.
check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce', false )
check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce', false )
check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'customize_preview_nonce', false )
if ( ! current_user_can( 'customize' ) || ! $has_post_data_nonce ) {
unset( $_POST['customized'] );
unset( $_REQUEST['customized'] );
* If unauthenticated then require a valid changeset UUID to load the preview.
* In this way, the UUID serves as a secret key. If the messenger channel is present,
* then send unauthenticated code to prompt re-auth.
if ( ! current_user_can( 'customize' ) && ! $this->changeset_post_id() ) {
$this->wp_die( $this->messenger_channel ? 0 : -1, __( 'Non-existent changeset UUID.' ) );
if ( ! headers_sent() ) {
// Hide the admin bar if we're embedded in the customizer iframe.
if ( $this->messenger_channel ) {
if ( $this->is_theme_active() ) {
// Once the theme is loaded, we'll validate it.
add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
* If the requested theme is not the active theme and the user doesn't have
* the switch_themes cap, bail.
if ( ! current_user_can( 'switch_themes' ) ) {
$this->wp_die( -1, __( 'Sorry, you are not allowed to edit theme options on this site.' ) );
// If the theme has errors while loading, bail.
if ( $this->theme()->errors() ) {
$this->wp_die( -1, $this->theme()->errors()->get_error_message() );
// If the theme isn't allowed per multisite settings, bail.
if ( ! $this->theme()->is_allowed() ) {
$this->wp_die( -1, __( 'The requested theme does not exist.' ) );
// Make sure changeset UUID is established immediately after the theme is loaded.
add_action( 'after_setup_theme', array( $this, 'establish_loaded_changeset' ), 5 );
* Import theme starter content for fresh installations when landing in the customizer.
* Import starter content at after_setup_theme:100 so that any
* add_theme_support( 'starter-content' ) calls will have been made.
if ( get_option( 'fresh_site' ) && 'customize.php' === $pagenow ) {
add_action( 'after_setup_theme', array( $this, 'import_theme_starter_content' ), 100 );
$this->start_previewing_theme();
* Establishes the loaded changeset.
* This method runs right at after_setup_theme and applies the 'customize_changeset_branching' filter to determine
* whether concurrent changesets are allowed. Then if the Customizer is not initialized with a `changeset_uuid` param,
* this method will determine which UUID should be used. If changeset branching is disabled, then the most saved
* changeset will be loaded by default. Otherwise, if there are no existing saved changesets or if changeset branching is
* enabled, then a new UUID will be generated.
* @global string $pagenow The filename of the current screen.
public function establish_loaded_changeset() {
if ( empty( $this->_changeset_uuid ) ) {
if ( ! $this->branching() && $this->is_theme_active() ) {
$unpublished_changeset_posts = $this->get_changeset_posts(
'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ),
'exclude_restore_dismissed' => false,
$unpublished_changeset_post = array_shift( $unpublished_changeset_posts );
if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) {
$changeset_uuid = $unpublished_changeset_post->post_name;
// If no changeset UUID has been set yet, then generate a new one.
if ( empty( $changeset_uuid ) ) {
$changeset_uuid = wp_generate_uuid4();
$this->_changeset_uuid = $changeset_uuid;
if ( is_admin() && 'customize.php' === $pagenow ) {
$this->set_changeset_lock( $this->changeset_post_id() );
* Callback to validate a theme once it is loaded
public function after_setup_theme() {
$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
wp_redirect( 'themes.php?broken=true' );
* If the theme to be previewed isn't the active theme, add filter callbacks
* to swap it out at runtime.
public function start_previewing_theme() {
// Bail if we're already previewing.
if ( $this->is_preview() ) {
$this->previewing = true;
if ( ! $this->is_theme_active() ) {
add_filter( 'template', array( $this, 'get_template' ) );
add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
// @link: https://core.trac.wordpress.org/ticket/20027
add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
add_filter( 'pre_option_template', array( $this, 'get_template' ) );
// Handle custom theme roots.
add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
* Fires once the Customizer theme preview has started.
* @param WP_Customize_Manager $manager WP_Customize_Manager instance.
do_action( 'start_previewing_theme', $this );
* Stops previewing the selected theme.
* Removes filters to change the active theme.
public function stop_previewing_theme() {
if ( ! $this->is_preview() ) {
$this->previewing = false;
if ( ! $this->is_theme_active() ) {
remove_filter( 'template', array( $this, 'get_template' ) );
remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
// @link: https://core.trac.wordpress.org/ticket/20027
remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
// Handle custom theme roots.
remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
* Fires once the Customizer theme preview has stopped.
* @param WP_Customize_Manager $manager WP_Customize_Manager instance.
do_action( 'stop_previewing_theme', $this );
* Gets whether settings are or will be previewed.
* @see WP_Customize_Setting::preview()
public function settings_previewed() {
return $this->settings_previewed;
* Gets whether data from a changeset's autosaved revision should be loaded if it exists.
* @see WP_Customize_Manager::changeset_data()
* @return bool Is using autosaved changeset revision.
public function autosaved() {
* Whether the changeset branching is allowed.
* @see WP_Customize_Manager::establish_loaded_changeset()
* @return bool Is changeset branching.
public function branching() {
* Filters whether or not changeset branching is allowed.
* By default in core, when changeset branching is not allowed, changesets will operate
* linearly in that only one saved changeset will exist at a time (with a 'draft' or
* 'future' status). This makes the Customizer operate in a way that is similar to going to
* "edit" to one existing post: all users will be making changes to the same post, and autosave
* revisions will be made for that post.
* By contrast, when changeset branching is allowed, then the model is like users going
* to "add new" for a page and each user makes changes independently of each other since
* they are all operating on their own separate pages, each getting their own separate
* initial auto-drafts and then once initially saved, autosave revisions on top of that
* Since linear changesets are deemed to be more suitable for the majority of WordPress users,
* they are the default. For WordPress sites that have heavy site management in the Customizer
* by multiple users then branching changesets should be enabled by means of this filter.
* @param bool $allow_branching Whether branching is allowed. If `false`, the default,
* then only one saved changeset exists at a time.
* @param WP_Customize_Manager $wp_customize Manager instance.
$this->branching = apply_filters( 'customize_changeset_branching', $this->branching, $this );
* Gets the changeset UUID.
* @see WP_Customize_Manager::establish_loaded_changeset()
public function changeset_uuid() {
if ( empty( $this->_changeset_uuid ) ) {
$this->establish_loaded_changeset();
return $this->_changeset_uuid;
* Gets the theme being customized.
public function theme() {
$this->theme = wp_get_theme();
* Gets the registered settings.
public function settings() {
* Gets the registered controls.
public function controls() {
* Gets the registered containers.
public function containers() {
return $this->containers;
* Gets the registered sections.
public function sections() {
* Gets the registered panels.
public function panels() {
* Checks if the current theme is active.
public function is_theme_active() {
return $this->get_stylesheet() === $this->original_stylesheet;
* Registers styles/scripts and initialize the preview of each setting
public function wp_loaded() {
* Unconditionally register core types for panels, sections, and controls
* in case plugin unhooks all customize_register actions.
$this->register_panel_type( 'WP_Customize_Panel' );
$this->register_panel_type( 'WP_Customize_Themes_Panel' );
$this->register_section_type( 'WP_Customize_Section' );
$this->register_section_type( 'WP_Customize_Sidebar_Section' );
$this->register_section_type( 'WP_Customize_Themes_Section' );
$this->register_control_type( 'WP_Customize_Color_Control' );
$this->register_control_type( 'WP_Customize_Media_Control' );
$this->register_control_type( 'WP_Customize_Upload_Control' );
$this->register_control_type( 'WP_Customize_Image_Control' );
$this->register_control_type( 'WP_Customize_Background_Image_Control' );
$this->register_control_type( 'WP_Customize_Background_Position_Control' );
$this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
$this->register_control_type( 'WP_Customize_Site_Icon_Control' );
$this->register_control_type( 'WP_Customize_Theme_Control' );
$this->register_control_type( 'WP_Customize_Code_Editor_Control' );
$this->register_control_type( 'WP_Customize_Date_Time_Control' );
* Fires once WordPress has loaded, allowing scripts and styles to be initialized.
* @param WP_Customize_Manager $manager WP_Customize_Manager instance.
do_action( 'customize_register', $this );
if ( $this->settings_previewed() ) {
foreach ( $this->settings as $setting ) {
if ( $this->is_preview() && ! is_admin() ) {
$this->customize_preview_init();
* Prevents Ajax requests from following redirects when previewing a theme
* by issuing a 200 response instead of a 30x.
* Instead, the JS will sniff out the location header.
* @param int $status Status.
public function wp_redirect_status( $status ) {
_deprecated_function( __FUNCTION__, '4.7.0' );
if ( $this->is_preview() && ! is_admin() ) {
* Finds the changeset post ID for a given changeset UUID.
* @param string $uuid Changeset UUID.
* @return int|null Returns post ID on success and null on failure.
public function find_changeset_post_id( $uuid ) {
$cache_group = 'customize_changeset_post';
$changeset_post_id = wp_cache_get( $uuid, $cache_group );
if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) {
return $changeset_post_id;
$changeset_post_query = new WP_Query(
'post_type' => 'customize_changeset',
'post_status' => get_post_stati(),