+/**
+ * Checks an attachment being deleted to see if it's a header or background image.
+ *
+ * If true it removes the theme modification which would be pointing at the deleted
+ * attachment.
+ *
+ * @access private
+ * @since 3.0.0
+ * @since 4.3.0 Also removes `header_image_data`.
+ * @since 4.5.0 Also removes custom logo theme mods.
+ *
+ * @param int $id The attachment id.
+ */
+function _delete_attachment_theme_mod( $id ) {
+ $attachment_image = wp_get_attachment_url( $id );
+ $header_image = get_header_image();
+ $background_image = get_background_image();
+ $custom_logo_id = get_theme_mod( 'custom_logo' );
+
+ if ( $custom_logo_id && $custom_logo_id == $id ) {
+ remove_theme_mod( 'custom_logo' );
+ remove_theme_mod( 'header_text' );
+ }
+
+ if ( $header_image && $header_image == $attachment_image ) {
+ remove_theme_mod( 'header_image' );
+ remove_theme_mod( 'header_image_data' );
+ }
+
+ if ( $background_image && $background_image == $attachment_image ) {
+ remove_theme_mod( 'background_image' );
+ }
+}
+
+/**
+ * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
+ *
+ * See {@see 'after_switch_theme'}.
+ *
+ * @since 3.3.0
+ */
+function check_theme_switched() {
+ if ( $stylesheet = get_option( 'theme_switched' ) ) {
+ $old_theme = wp_get_theme( $stylesheet );
+
+ // Prevent retrieve_widgets() from running since Customizer already called it up front
+ if ( get_option( 'theme_switched_via_customizer' ) ) {
+ remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
+ update_option( 'theme_switched_via_customizer', false );
+ }
+
+ if ( $old_theme->exists() ) {
+ /**
+ * Fires on the first WP load after a theme switch if the old theme still exists.
+ *
+ * This action fires multiple times and the parameters differs
+ * according to the context, if the old theme exists or not.
+ * If the old theme is missing, the parameter will be the slug
+ * of the old theme.
+ *
+ * @since 3.3.0
+ *
+ * @param string $old_name Old theme name.
+ * @param WP_Theme $old_theme WP_Theme instance of the old theme.
+ */
+ do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
+ } else {
+ /** This action is documented in wp-includes/theme.php */
+ do_action( 'after_switch_theme', $stylesheet );
+ }
+ flush_rewrite_rules();
+
+ update_option( 'theme_switched', false );
+ }
+}
+
+/**
+ * Includes and instantiates the WP_Customize_Manager class.
+ *
+ * Loads the Customizer at plugins_loaded when accessing the customize.php admin
+ * page or when any request includes a wp_customize=on param or a customize_changeset
+ * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
+ * WordPress is loading, especially in the Customizer preview
+ * or when making Customizer Ajax requests for widgets or menus.
+ *
+ * @since 3.4.0
+ *
+ * @global WP_Customize_Manager $wp_customize
+ */
+function _wp_customize_include() {
+
+ $is_customize_admin_page = ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) );
+ $should_include = (
+ $is_customize_admin_page
+ ||
+ ( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
+ ||
+ ( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
+ );
+
+ if ( ! $should_include ) {
+ return;
+ }
+
+ /*
+ * Note that wp_unslash() is not being used on the input vars because it is
+ * called before wp_magic_quotes() gets called. Besides this fact, none of
+ * the values should contain any characters needing slashes anyway.
+ */
+ $keys = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel' );
+ $input_vars = array_merge(
+ wp_array_slice_assoc( $_GET, $keys ),
+ wp_array_slice_assoc( $_POST, $keys )
+ );
+
+ $theme = null;
+ $changeset_uuid = null;
+ $messenger_channel = null;
+
+ if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
+ $changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
+ } elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
+ $changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
+ }
+
+ // Note that theme will be sanitized via WP_Theme.
+ if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
+ $theme = $input_vars['theme'];
+ } elseif ( isset( $input_vars['customize_theme'] ) ) {
+ $theme = $input_vars['customize_theme'];
+ }
+ if ( isset( $input_vars['customize_messenger_channel'] ) ) {
+ $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
+ }
+
+ require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
+ $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel' ) );
+}
+
+/**
+ * Publish a snapshot's changes.
+ *
+ * @param string $new_status New post status.
+ * @param string $old_status Old post status.
+ * @param WP_Post $changeset_post Changeset post object.
+ */
+function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
+ global $wp_customize, $wpdb;
+
+ $is_publishing_changeset = (
+ 'customize_changeset' === $changeset_post->post_type
+ &&
+ 'publish' === $new_status
+ &&
+ 'publish' !== $old_status
+ );
+ if ( ! $is_publishing_changeset ) {
+ return;
+ }
+
+ if ( empty( $wp_customize ) ) {
+ require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
+ $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $changeset_post->post_name ) );
+ }
+
+ if ( ! did_action( 'customize_register' ) ) {
+ /*
+ * When running from CLI or Cron, the customize_register action will need
+ * to be triggered in order for core, themes, and plugins to register their
+ * settings. Normally core will add_action( 'customize_register' ) at
+ * priority 10 to register the core settings, and if any themes/plugins
+ * also add_action( 'customize_register' ) at the same priority, they
+ * will have a $wp_customize with those settings registered since they
+ * call add_action() afterward, normally. However, when manually doing
+ * the customize_register action after the setup_theme, then the order
+ * will be reversed for two actions added at priority 10, resulting in
+ * the core settings no longer being available as expected to themes/plugins.
+ * So the following manually calls the method that registers the core
+ * settings up front before doing the action.
+ */
+ remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
+ $wp_customize->register_controls();
+
+ /** This filter is documented in /wp-includes/class-wp-customize-manager.php */
+ do_action( 'customize_register', $wp_customize );
+ }
+ $wp_customize->_publish_changeset_values( $changeset_post->ID ) ;
+
+ /*
+ * Trash the changeset post if revisions are not enabled. Unpublished
+ * changesets by default get garbage collected due to the auto-draft status.
+ * When a changeset post is published, however, it would no longer get cleaned
+ * out. Ths is a problem when the changeset posts are never displayed anywhere,
+ * since they would just be endlessly piling up. So here we use the revisions
+ * feature to indicate whether or not a published changeset should get trashed
+ * and thus garbage collected.
+ */
+ if ( ! wp_revisions_enabled( $changeset_post ) ) {
+ $post = $changeset_post;
+ $post_id = $changeset_post->ID;
+
+ /*
+ * The following re-formulates the logic from wp_trash_post() as done in
+ * wp_publish_post(). The reason for bypassing wp_trash_post() is that it
+ * will mutate the the post_content and the post_name when they should be
+ * untouched.
+ */
+ if ( ! EMPTY_TRASH_DAYS ) {
+ wp_delete_post( $post_id, true );
+ } else {
+ /** This action is documented in wp-includes/post.php */
+ do_action( 'wp_trash_post', $post_id );
+
+ add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
+ add_post_meta( $post_id, '_wp_trash_meta_time', time() );
+
+ $old_status = $post->post_status;
+ $new_status = 'trash';
+ $wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $post->ID ) );
+ clean_post_cache( $post->ID );
+
+ $post->post_status = $new_status;
+ wp_transition_post_status( $new_status, $old_status, $post );
+
+ /** This action is documented in wp-includes/post.php */
+ do_action( 'edit_post', $post->ID, $post );
+
+ /** This action is documented in wp-includes/post.php */
+ do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
+
+ /** This action is documented in wp-includes/post.php */
+ do_action( 'save_post', $post->ID, $post, true );
+
+ /** This action is documented in wp-includes/post.php */
+ do_action( 'wp_insert_post', $post->ID, $post, true );
+
+ /** This action is documented in wp-includes/post.php */
+ do_action( 'trashed_post', $post_id );
+ }
+ }
+}
+
+/**
+ * Filters changeset post data upon insert to ensure post_name is intact.
+ *
+ * This is needed to prevent the post_name from being dropped when the post is
+ * transitioned into pending status by a contributor.
+ *
+ * @since 4.7.0
+ * @see wp_insert_post()
+ *
+ * @param array $post_data An array of slashed post data.
+ * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
+ * @returns array Filtered data.
+ */
+function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
+ if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
+
+ // Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
+ if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
+ $post_data['post_name'] = $supplied_post_data['post_name'];
+ }
+ }
+ return $post_data;
+}
+
+/**
+ * Adds settings for the customize-loader script.
+ *
+ * @since 3.4.0
+ */
+function _wp_customize_loader_settings() {
+ $admin_origin = parse_url( admin_url() );
+ $home_origin = parse_url( home_url() );
+ $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
+
+ $browser = array(
+ 'mobile' => wp_is_mobile(),
+ 'ios' => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
+ );
+
+ $settings = array(
+ 'url' => esc_url( admin_url( 'customize.php' ) ),
+ 'isCrossDomain' => $cross_domain,
+ 'browser' => $browser,
+ 'l10n' => array(
+ 'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
+ 'mainIframeTitle' => __( 'Customizer' ),
+ ),
+ );
+
+ $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
+
+ $wp_scripts = wp_scripts();
+ $data = $wp_scripts->get_data( 'customize-loader', 'data' );
+ if ( $data )
+ $script = "$data\n$script";
+
+ $wp_scripts->add_data( 'customize-loader', 'data', $script );
+}
+
+/**
+ * Returns a URL to load the Customizer.
+ *
+ * @since 3.4.0
+ *
+ * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
+ * The theme's stylesheet will be urlencoded if necessary.
+ * @return string
+ */
+function wp_customize_url( $stylesheet = null ) {
+ $url = admin_url( 'customize.php' );
+ if ( $stylesheet )
+ $url .= '?theme=' . urlencode( $stylesheet );
+ return esc_url( $url );
+}
+
+/**
+ * Prints a script to check whether or not the Customizer is supported,
+ * and apply either the no-customize-support or customize-support class
+ * to the body.
+ *
+ * This function MUST be called inside the body tag.
+ *
+ * Ideally, call this function immediately after the body tag is opened.
+ * This prevents a flash of unstyled content.
+ *
+ * It is also recommended that you add the "no-customize-support" class
+ * to the body tag by default.
+ *
+ * @since 3.4.0
+ * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
+ */
+function wp_customize_support_script() {
+ $admin_origin = parse_url( admin_url() );
+ $home_origin = parse_url( home_url() );
+ $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
+
+ ?>
+ <!--[if lte IE 8]>
+ <script type="text/javascript">
+ document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, '' ) + ' no-customize-support';
+ </script>
+ <![endif]-->
+ <!--[if gte IE 9]><!-->
+ <script type="text/javascript">
+ (function() {
+ var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
+
+ <?php if ( $cross_domain ) : ?>
+ request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
+ <?php else : ?>
+ request = true;
+ <?php endif; ?>
+
+ b[c] = b[c].replace( rcs, ' ' );
+ // The customizer requires postMessage and CORS (if the site is cross domain)
+ b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
+ }());
+ </script>
+ <!--<![endif]-->
+ <?php
+}
+
+/**
+ * Whether the site is being previewed in the Customizer.
+ *
+ * @since 4.0.0
+ *
+ * @global WP_Customize_Manager $wp_customize Customizer instance.
+ *
+ * @return bool True if the site is being previewed in the Customizer, false otherwise.
+ */
+function is_customize_preview() {
+ global $wp_customize;
+
+ return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();
+}