: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) {
$actual_setting_id = null;
$is_theme_mod_setting = (
isset( $setting_params['value'] )
isset( $setting_params['type'] )
'theme_mod' === $setting_params['type']
preg_match( $namespace_pattern, $raw_setting_id, $matches )
if ( $is_theme_mod_setting ) {
if ( ! isset( $theme_mod_settings[ $matches['stylesheet'] ] ) ) {
$theme_mod_settings[ $matches['stylesheet'] ] = array();
$theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params;
if ( $this->get_stylesheet() === $matches['stylesheet'] ) {
$actual_setting_id = $matches['setting_id'];
$actual_setting_id = $raw_setting_id;
// Keep track of the user IDs for settings actually for this theme.
if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) {
$setting_user_ids[ $actual_setting_id ] = $setting_params['user_id'];
$changeset_setting_values = $this->unsanitized_post_values(
'exclude_post_data' => true,
'exclude_changeset' => false,
$changeset_setting_ids = array_keys( $changeset_setting_values );
$this->add_dynamic_settings( $changeset_setting_ids );
* Fires once the theme has switched in the Customizer, but before settings
* @param WP_Customize_Manager $manager WP_Customize_Manager instance.
do_action( 'customize_save', $this );
* Ensure that all settings will allow themselves to be saved. Note that
* this is safe because the setting would have checked the capability
* when the setting value was written into the changeset. So this is why
* an additional capability check is not required here.
$original_setting_capabilities = array();
foreach ( $changeset_setting_ids as $setting_id ) {
$setting = $this->get_setting( $setting_id );
if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) {
$original_setting_capabilities[ $setting->id ] = $setting->capability;
$setting->capability = 'exist';
$original_user_id = get_current_user_id();
foreach ( $changeset_setting_ids as $setting_id ) {
$setting = $this->get_setting( $setting_id );
* Set the current user to match the user who saved the value into
* the changeset so that any filters that apply during the save
* process will respect the original user's capabilities. This
* will ensure, for example, that KSES won't strip unsafe HTML
* when a scheduled changeset publishes via WP Cron.
if ( isset( $setting_user_ids[ $setting_id ] ) ) {
wp_set_current_user( $setting_user_ids[ $setting_id ] );
wp_set_current_user( $original_user_id );
wp_set_current_user( $original_user_id );
// Update the stashed theme mod settings, removing the active theme's stashed settings, if activated.
if ( did_action( 'switch_theme' ) ) {
$other_theme_mod_settings = $theme_mod_settings;
unset( $other_theme_mod_settings[ $this->get_stylesheet() ] );
$this->update_stashed_theme_mod_settings( $other_theme_mod_settings );
* Fires after Customize settings have been saved.
* @param WP_Customize_Manager $manager WP_Customize_Manager instance.
do_action( 'customize_save_after', $this );
// Restore original capabilities.
foreach ( $original_setting_capabilities as $setting_id => $capability ) {
$setting = $this->get_setting( $setting_id );
$setting->capability = $capability;
// Restore original changeset data.
$this->_changeset_data = $previous_changeset_data;
$this->_changeset_post_id = $previous_changeset_post_id;
$this->_changeset_uuid = $previous_changeset_uuid;
* Convert all autosave revisions into their own auto-drafts so that users can be prompted to
* restore them when a changeset is published, but they had been locked out from including
* their changes in the changeset.
$revisions = wp_get_post_revisions( $changeset_post_id, array( 'check_enabled' => false ) );
foreach ( $revisions as $revision ) {
if ( str_contains( $revision->post_name, "{$changeset_post_id}-autosave" ) ) {
'post_status' => 'auto-draft',
'post_type' => 'customize_changeset',
'post_name' => wp_generate_uuid4(),
clean_post_cache( $revision->ID );
* Updates stashed theme mod settings.
* @param array $inactive_theme_mod_settings Mapping of stylesheet to arrays of theme mod settings.
* @return array|false Returns array of updated stashed theme mods or false if the update failed or there were no changes.
protected function update_stashed_theme_mod_settings( $inactive_theme_mod_settings ) {
$stashed_theme_mod_settings = get_option( 'customize_stashed_theme_mods' );
if ( empty( $stashed_theme_mod_settings ) ) {
$stashed_theme_mod_settings = array();
// Delete any stashed theme mods for the active theme since they would have been loaded and saved upon activation.
unset( $stashed_theme_mod_settings[ $this->get_stylesheet() ] );
// Merge inactive theme mods with the stashed theme mod settings.
foreach ( $inactive_theme_mod_settings as $stylesheet => $theme_mod_settings ) {
if ( ! isset( $stashed_theme_mod_settings[ $stylesheet ] ) ) {
$stashed_theme_mod_settings[ $stylesheet ] = array();
$stashed_theme_mod_settings[ $stylesheet ] = array_merge(
$stashed_theme_mod_settings[ $stylesheet ],
$result = update_option( 'customize_stashed_theme_mods', $stashed_theme_mod_settings, $autoload );
return $stashed_theme_mod_settings;
* Refreshes nonces for the current preview.
public function refresh_nonces() {
if ( ! $this->is_preview() ) {
wp_send_json_error( 'not_preview' );
wp_send_json_success( $this->get_nonces() );
* Deletes a given auto-draft changeset or the autosave revision for a given changeset or delete changeset lock.
public function handle_dismiss_autosave_or_lock_request() {
// Calls to dismiss_user_auto_draft_changesets() and wp_get_post_autosave() require non-zero get_current_user_id().
if ( ! is_user_logged_in() ) {
wp_send_json_error( 'unauthenticated', 401 );
if ( ! $this->is_preview() ) {
wp_send_json_error( 'not_preview', 400 );
if ( ! check_ajax_referer( 'customize_dismiss_autosave_or_lock', 'nonce', false ) ) {
wp_send_json_error( 'invalid_nonce', 403 );
$changeset_post_id = $this->changeset_post_id();
$dismiss_lock = ! empty( $_POST['dismiss_lock'] );
$dismiss_autosave = ! empty( $_POST['dismiss_autosave'] );
if ( empty( $changeset_post_id ) && ! $dismiss_autosave ) {
wp_send_json_error( 'no_changeset_to_dismiss_lock', 404 );
if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) && ! $dismiss_autosave ) {
wp_send_json_error( 'cannot_remove_changeset_lock', 403 );
delete_post_meta( $changeset_post_id, '_edit_lock' );
if ( ! $dismiss_autosave ) {
wp_send_json_success( 'changeset_lock_dismissed' );
if ( $dismiss_autosave ) {
if ( empty( $changeset_post_id ) || 'auto-draft' === get_post_status( $changeset_post_id ) ) {
$dismissed = $this->dismiss_user_auto_draft_changesets();
wp_send_json_success( 'auto_draft_dismissed' );
wp_send_json_error( 'no_auto_draft_to_delete', 404 );
$revision = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) {
wp_send_json_error( 'cannot_delete_autosave_revision', 403 );
if ( ! wp_delete_post( $revision->ID, true ) ) {
wp_send_json_error( 'autosave_revision_deletion_failure', 500 );
wp_send_json_success( 'autosave_revision_deleted' );
wp_send_json_error( 'no_autosave_revision_to_delete', 404 );
wp_send_json_error( 'unknown_error', 500 );
* Adds a customize setting.
* @since 4.5.0 Return added WP_Customize_Setting instance.
* @see WP_Customize_Setting::__construct()
* @link https://developer.wordpress.org/themes/customize-api
* @param WP_Customize_Setting|string $id Customize Setting object, or ID.
* @param array $args Optional. Array of properties for the new Setting object.
* See WP_Customize_Setting::__construct() for information
* on accepted arguments. Default empty array.
* @return WP_Customize_Setting The instance of the setting that was added.
public function add_setting( $id, $args = array() ) {
if ( $id instanceof WP_Customize_Setting ) {
$class = 'WP_Customize_Setting';
/** This filter is documented in wp-includes/class-wp-customize-manager.php */
$args = apply_filters( 'customize_dynamic_setting_args', $args, $id );
/** This filter is documented in wp-includes/class-wp-customize-manager.php */
$class = apply_filters( 'customize_dynamic_setting_class', $class, $id, $args );
$setting = new $class( $this, $id, $args );
$this->settings[ $setting->id ] = $setting;
* Registers any dynamically-created settings, such as those from $_POST['customized']
* that have no corresponding setting created.
* This is a mechanism to "wake up" settings that have been dynamically created
* on the front end and have been sent to WordPress in `$_POST['customized']`. When WP
* loads, the dynamically-created settings then will get created and previewed
* even though they are not directly created statically with code.
* @param array $setting_ids The setting IDs to add.
* @return array The WP_Customize_Setting objects added.
public function add_dynamic_settings( $setting_ids ) {
foreach ( $setting_ids as $setting_id ) {
// Skip settings already created.
if ( $this->get_setting( $setting_id ) ) {
$setting_class = 'WP_Customize_Setting';
* Filters a dynamic setting's constructor args.
* For a dynamic setting to be registered, this filter must be employed
* to override the default false value with an array of args to pass to
* the WP_Customize_Setting constructor.
* @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
* @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
$setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
if ( false === $setting_args ) {
* Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
* @param string $setting_class WP_Customize_Setting or a subclass.
* @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
* @param array $setting_args WP_Customize_Setting or a subclass.
$setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
$setting = new $setting_class( $this, $setting_id, $setting_args );
$this->add_setting( $setting );
$new_settings[] = $setting;
* Retrieves a customize setting.
* @param string $id Customize Setting ID.
* @return WP_Customize_Setting|void The setting, if set.
public function get_setting( $id ) {
if ( isset( $this->settings[ $id ] ) ) {
return $this->settings[ $id ];
* Removes a customize setting.
* Note that removing the setting doesn't destroy the WP_Customize_Setting instance or remove its filters.
* @param string $id Customize Setting ID.
public function remove_setting( $id ) {
unset( $this->settings[ $id ] );
* Adds a customize panel.
* @since 4.5.0 Return added WP_Customize_Panel instance.
* @see WP_Customize_Panel::__construct()
* @param WP_Customize_Panel|string $id Customize Panel object, or ID.
* @param array $args Optional. Array of properties for the new Panel object.
* See WP_Customize_Panel::__construct() for information
* on accepted arguments. Default empty array.
* @return WP_Customize_Panel The instance of the panel that was added.
public function add_panel( $id, $args = array() ) {
if ( $id instanceof WP_Customize_Panel ) {
$panel = new WP_Customize_Panel( $this, $id, $args );
$this->panels[ $panel->id ] = $panel;
* Retrieves a customize panel.
* @param string $id Panel ID to get.
* @return WP_Customize_Panel|void Requested panel instance, if set.
public function get_panel( $id ) {
if ( isset( $this->panels[ $id ] ) ) {
return $this->panels[ $id ];
* Removes a customize panel.
* Note that removing the panel doesn't destroy the WP_Customize_Panel instance or remove its filters.
* @param string $id Panel ID to remove.
public function remove_panel( $id ) {
// Removing core components this way is _doing_it_wrong().
if ( in_array( $id, $this->components, true ) ) {
/* translators: 1: Panel ID, 2: Link to 'customize_loaded_components' filter reference. */
__( 'Removing %1$s manually will cause PHP warnings. Use the %2$s filter instead.' ),
'<a href="%1$s">%2$s</a>',
esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ),
'<code>customize_loaded_components</code>'
unset( $this->panels[ $id ] );
* Registers a customize panel type.
* Registered types are eligible to be rendered via JS and created dynamically.
* @see WP_Customize_Panel
* @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
public function register_panel_type( $panel ) {
$this->registered_panel_types[] = $panel;
* Renders JS templates for all registered panel types.
public function render_panel_templates() {
foreach ( $this->registered_panel_types as $panel_type ) {
$panel = new $panel_type( $this, 'temp', array() );
$panel->print_template();
* Adds a customize section.
* @since 4.5.0 Return added WP_Customize_Section instance.
* @see WP_Customize_Section::__construct()
* @param WP_Customize_Section|string $id Customize Section object, or ID.
* @param array $args Optional. Array of properties for the new Section object.
* See WP_Customize_Section::__construct() for information
* on accepted arguments. Default empty array.
* @return WP_Customize_Section The instance of the section that was added.
public function add_section( $id, $args = array() ) {
if ( $id instanceof WP_Customize_Section ) {
$section = new WP_Customize_Section( $this, $id, $args );
$this->sections[ $section->id ] = $section;