wp/wp-includes/class-wp-customize-manager.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
   238 
   238 
   239 	/**
   239 	/**
   240 	 * Constructor.
   240 	 * Constructor.
   241 	 *
   241 	 *
   242 	 * @since 3.4.0
   242 	 * @since 3.4.0
   243 	 * @since 4.7.0 Added $args param.
   243 	 * @since 4.7.0 Added `$args` parameter.
   244 	 *
   244 	 *
   245 	 * @param array $args {
   245 	 * @param array $args {
   246 	 *     Args.
   246 	 *     Args.
   247 	 *
   247 	 *
   248 	 *     @type null|string|false $changeset_uuid     Changeset UUID, the `post_name` for the customize_changeset post containing the customized state.
   248 	 *     @type null|string|false $changeset_uuid     Changeset UUID, the `post_name` for the customize_changeset post containing the customized state.
   281 		if ( ! isset( $args['messenger_channel'] ) && isset( $_REQUEST['customize_messenger_channel'] ) ) {
   281 		if ( ! isset( $args['messenger_channel'] ) && isset( $_REQUEST['customize_messenger_channel'] ) ) {
   282 			$args['messenger_channel'] = sanitize_key( wp_unslash( $_REQUEST['customize_messenger_channel'] ) );
   282 			$args['messenger_channel'] = sanitize_key( wp_unslash( $_REQUEST['customize_messenger_channel'] ) );
   283 		}
   283 		}
   284 
   284 
   285 		$this->original_stylesheet = get_stylesheet();
   285 		$this->original_stylesheet = get_stylesheet();
   286 		$this->theme = wp_get_theme( 0 === validate_file( $args['theme'] ) ? $args['theme'] : null );
   286 		$this->theme               = wp_get_theme( 0 === validate_file( $args['theme'] ) ? $args['theme'] : null );
   287 		$this->messenger_channel = $args['messenger_channel'];
   287 		$this->messenger_channel   = $args['messenger_channel'];
   288 		$this->_changeset_uuid = $args['changeset_uuid'];
   288 		$this->_changeset_uuid     = $args['changeset_uuid'];
   289 
   289 
   290 		foreach ( array( 'settings_previewed', 'autosaved', 'branching' ) as $key ) {
   290 		foreach ( array( 'settings_previewed', 'autosaved', 'branching' ) as $key ) {
   291 			if ( isset( $args[ $key ] ) ) {
   291 			if ( isset( $args[ $key ] ) ) {
   292 				$this->$key = (bool) $args[ $key ];
   292 				$this->$key = (bool) $args[ $key ];
   293 			}
   293 			}
   315 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php' );
   315 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php' );
   316 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php' );
   316 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php' );
   317 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
   317 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
   318 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-locations-control.php' );
   318 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-locations-control.php' );
   319 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
   319 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
   320 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' ); // @todo Remove in 5.0. See #42364.
   320 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' ); // @todo Remove in a future release. See #42364.
   321 
   321 
   322 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
   322 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
   323 
   323 
   324 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' );
   324 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' );
   325 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
   325 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
   326 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
   326 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
   327 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' );
   327 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' );
   328 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-section.php' ); // @todo Remove in 5.0. See #42364.
   328 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-section.php' ); // @todo Remove in a future release. See #42364.
   329 
   329 
   330 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-custom-css-setting.php' );
   330 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-custom-css-setting.php' );
   331 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
   331 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
   332 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
   332 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
   333 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
   333 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
   344 		 *
   344 		 *
   345 		 * @since 4.4.0
   345 		 * @since 4.4.0
   346 		 *
   346 		 *
   347 		 * @see WP_Customize_Manager::__construct()
   347 		 * @see WP_Customize_Manager::__construct()
   348 		 *
   348 		 *
   349 		 * @param array                $components List of core components to load.
   349 		 * @param string[]             $components Array of core components to load.
   350 		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
   350 		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
   351 		 */
   351 		 */
   352 		$components = apply_filters( 'customize_loaded_components', $this->components, $this );
   352 		$components = apply_filters( 'customize_loaded_components', $this->components, $this );
   353 
   353 
   354 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
   354 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
   363 			require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
   363 			require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
   364 			$this->nav_menus = new WP_Customize_Nav_Menus( $this );
   364 			$this->nav_menus = new WP_Customize_Nav_Menus( $this );
   365 		}
   365 		}
   366 
   366 
   367 		add_action( 'setup_theme', array( $this, 'setup_theme' ) );
   367 		add_action( 'setup_theme', array( $this, 'setup_theme' ) );
   368 		add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
   368 		add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
   369 
   369 
   370 		// Do not spawn cron (especially the alternate cron) while running the Customizer.
   370 		// Do not spawn cron (especially the alternate cron) while running the Customizer.
   371 		remove_action( 'init', 'wp_cron' );
   371 		remove_action( 'init', 'wp_cron' );
   372 
   372 
   373 		// Do not run update checks when rendering the controls.
   373 		// Do not run update checks when rendering the controls.
   374 		remove_action( 'admin_init', '_maybe_update_core' );
   374 		remove_action( 'admin_init', '_maybe_update_core' );
   375 		remove_action( 'admin_init', '_maybe_update_plugins' );
   375 		remove_action( 'admin_init', '_maybe_update_plugins' );
   376 		remove_action( 'admin_init', '_maybe_update_themes' );
   376 		remove_action( 'admin_init', '_maybe_update_themes' );
   377 
   377 
   378 		add_action( 'wp_ajax_customize_save',                     array( $this, 'save' ) );
   378 		add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
   379 		add_action( 'wp_ajax_customize_trash',                    array( $this, 'handle_changeset_trash_request' ) );
   379 		add_action( 'wp_ajax_customize_trash', array( $this, 'handle_changeset_trash_request' ) );
   380 		add_action( 'wp_ajax_customize_refresh_nonces',           array( $this, 'refresh_nonces' ) );
   380 		add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
   381 		add_action( 'wp_ajax_customize_load_themes',              array( $this, 'handle_load_themes_request' ) );
   381 		add_action( 'wp_ajax_customize_load_themes', array( $this, 'handle_load_themes_request' ) );
   382 		add_filter( 'heartbeat_settings',                         array( $this, 'add_customize_screen_to_heartbeat_settings' ) );
   382 		add_filter( 'heartbeat_settings', array( $this, 'add_customize_screen_to_heartbeat_settings' ) );
   383 		add_filter( 'heartbeat_received',                         array( $this, 'check_changeset_lock_with_heartbeat' ), 10, 3 );
   383 		add_filter( 'heartbeat_received', array( $this, 'check_changeset_lock_with_heartbeat' ), 10, 3 );
   384 		add_action( 'wp_ajax_customize_override_changeset_lock',  array( $this, 'handle_override_changeset_lock_request' ) );
   384 		add_action( 'wp_ajax_customize_override_changeset_lock', array( $this, 'handle_override_changeset_lock_request' ) );
   385 		add_action( 'wp_ajax_customize_dismiss_autosave_or_lock', array( $this, 'handle_dismiss_autosave_or_lock_request' ) );
   385 		add_action( 'wp_ajax_customize_dismiss_autosave_or_lock', array( $this, 'handle_dismiss_autosave_or_lock_request' ) );
   386 
   386 
   387 		add_action( 'customize_register',                 array( $this, 'register_controls' ) );
   387 		add_action( 'customize_register', array( $this, 'register_controls' ) );
   388 		add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
   388 		add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
   389 		add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
   389 		add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) );
   390 		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
   390 		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
   391 
   391 
   392 		// Render Common, Panel, Section, and Control templates.
   392 		// Render Common, Panel, Section, and Control templates.
   393 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
   393 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
   394 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
   394 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
   400 		// Export the settings to JS via the _wpCustomizeSettings variable.
   400 		// Export the settings to JS via the _wpCustomizeSettings variable.
   401 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
   401 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
   402 
   402 
   403 		// Add theme update notices.
   403 		// Add theme update notices.
   404 		if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) {
   404 		if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) {
   405 			require_once ABSPATH . '/wp-admin/includes/update.php';
   405 			require_once ABSPATH . 'wp-admin/includes/update.php';
   406 			add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' );
   406 			add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' );
   407 		}
   407 		}
   408 	}
   408 	}
   409 
   409 
   410 	/**
   410 	/**
   456 			wp_print_scripts( array( 'customize-base' ) );
   456 			wp_print_scripts( array( 'customize-base' ) );
   457 
   457 
   458 			$settings = array(
   458 			$settings = array(
   459 				'messengerArgs' => array(
   459 				'messengerArgs' => array(
   460 					'channel' => $this->messenger_channel,
   460 					'channel' => $this->messenger_channel,
   461 					'url' => wp_customize_url(),
   461 					'url'     => wp_customize_url(),
   462 				),
   462 				),
   463 				'error' => $ajax_message,
   463 				'error'         => $ajax_message,
   464 			);
   464 			);
   465 			?>
   465 			?>
   466 			<script>
   466 			<script>
   467 			( function( api, settings ) {
   467 			( function( api, settings ) {
   468 				var preview = new api.Messenger( settings.messengerArgs );
   468 				var preview = new api.Messenger( settings.messengerArgs );
   469 				preview.send( 'iframe-loading-error', settings.error );
   469 				preview.send( 'iframe-loading-error', settings.error );
   470 			} )( wp.customize, <?php echo wp_json_encode( $settings ) ?> );
   470 			} )( wp.customize, <?php echo wp_json_encode( $settings ); ?> );
   471 			</script>
   471 			</script>
   472 			<?php
   472 			<?php
   473 			$message .= ob_get_clean();
   473 			$message .= ob_get_clean();
   474 		}
   474 		}
   475 
   475 
   614 
   614 
   615 		if ( empty( $this->_changeset_uuid ) ) {
   615 		if ( empty( $this->_changeset_uuid ) ) {
   616 			$changeset_uuid = null;
   616 			$changeset_uuid = null;
   617 
   617 
   618 			if ( ! $this->branching() && $this->is_theme_active() ) {
   618 			if ( ! $this->branching() && $this->is_theme_active() ) {
   619 				$unpublished_changeset_posts = $this->get_changeset_posts( array(
   619 				$unpublished_changeset_posts = $this->get_changeset_posts(
   620 					'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ),
   620 					array(
   621 					'exclude_restore_dismissed' => false,
   621 						'post_status'               => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ),
   622 					'author' => 'any',
   622 						'exclude_restore_dismissed' => false,
   623 					'posts_per_page' => 1,
   623 						'author'                    => 'any',
   624 					'order' => 'DESC',
   624 						'posts_per_page'            => 1,
   625 					'orderby' => 'date',
   625 						'order'                     => 'DESC',
   626 				) );
   626 						'orderby'                   => 'date',
   627 				$unpublished_changeset_post = array_shift( $unpublished_changeset_posts );
   627 					)
       
   628 				);
       
   629 				$unpublished_changeset_post  = array_shift( $unpublished_changeset_posts );
   628 				if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) {
   630 				if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) {
   629 					$changeset_uuid = $unpublished_changeset_post->post_name;
   631 					$changeset_uuid = $unpublished_changeset_post->post_name;
   630 				}
   632 				}
   631 			}
   633 			}
   632 
   634 
   965 	 *
   967 	 *
   966 	 * @param string $uuid Changeset UUID.
   968 	 * @param string $uuid Changeset UUID.
   967 	 * @return int|null Returns post ID on success and null on failure.
   969 	 * @return int|null Returns post ID on success and null on failure.
   968 	 */
   970 	 */
   969 	public function find_changeset_post_id( $uuid ) {
   971 	public function find_changeset_post_id( $uuid ) {
   970 		$cache_group = 'customize_changeset_post';
   972 		$cache_group       = 'customize_changeset_post';
   971 		$changeset_post_id = wp_cache_get( $uuid, $cache_group );
   973 		$changeset_post_id = wp_cache_get( $uuid, $cache_group );
   972 		if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) {
   974 		if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) {
   973 			return $changeset_post_id;
   975 			return $changeset_post_id;
   974 		}
   976 		}
   975 
   977 
   976 		$changeset_post_query = new WP_Query( array(
   978 		$changeset_post_query = new WP_Query(
   977 			'post_type' => 'customize_changeset',
   979 			array(
   978 			'post_status' => get_post_stati(),
   980 				'post_type'              => 'customize_changeset',
   979 			'name' => $uuid,
   981 				'post_status'            => get_post_stati(),
   980 			'posts_per_page' => 1,
   982 				'name'                   => $uuid,
   981 			'no_found_rows' => true,
   983 				'posts_per_page'         => 1,
   982 			'cache_results' => true,
   984 				'no_found_rows'          => true,
   983 			'update_post_meta_cache' => false,
   985 				'cache_results'          => true,
   984 			'update_post_term_cache' => false,
   986 				'update_post_meta_cache' => false,
   985 			'lazy_load_term_meta' => false,
   987 				'update_post_term_cache' => false,
   986 		) );
   988 				'lazy_load_term_meta'    => false,
       
   989 			)
       
   990 		);
   987 		if ( ! empty( $changeset_post_query->posts ) ) {
   991 		if ( ! empty( $changeset_post_query->posts ) ) {
   988 			// Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed.
   992 			// Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed.
   989 			$changeset_post_id = $changeset_post_query->posts[0]->ID;
   993 			$changeset_post_id = $changeset_post_query->posts[0]->ID;
   990 			wp_cache_set( $uuid, $changeset_post_id, $cache_group );
   994 			wp_cache_set( $uuid, $changeset_post_id, $cache_group );
   991 			return $changeset_post_id;
   995 			return $changeset_post_id;
  1010 	 * @return WP_Post[] Auto-draft changesets.
  1014 	 * @return WP_Post[] Auto-draft changesets.
  1011 	 */
  1015 	 */
  1012 	protected function get_changeset_posts( $args = array() ) {
  1016 	protected function get_changeset_posts( $args = array() ) {
  1013 		$default_args = array(
  1017 		$default_args = array(
  1014 			'exclude_restore_dismissed' => true,
  1018 			'exclude_restore_dismissed' => true,
  1015 			'posts_per_page' => -1,
  1019 			'posts_per_page'            => -1,
  1016 			'post_type' => 'customize_changeset',
  1020 			'post_type'                 => 'customize_changeset',
  1017 			'post_status' => 'auto-draft',
  1021 			'post_status'               => 'auto-draft',
  1018 			'order' => 'DESC',
  1022 			'order'                     => 'DESC',
  1019 			'orderby' => 'date',
  1023 			'orderby'                   => 'date',
  1020 			'no_found_rows' => true,
  1024 			'no_found_rows'             => true,
  1021 			'cache_results' => true,
  1025 			'cache_results'             => true,
  1022 			'update_post_meta_cache' => false,
  1026 			'update_post_meta_cache'    => false,
  1023 			'update_post_term_cache' => false,
  1027 			'update_post_term_cache'    => false,
  1024 			'lazy_load_term_meta' => false,
  1028 			'lazy_load_term_meta'       => false,
  1025 		);
  1029 		);
  1026 		if ( get_current_user_id() ) {
  1030 		if ( get_current_user_id() ) {
  1027 			$default_args['author'] = get_current_user_id();
  1031 			$default_args['author'] = get_current_user_id();
  1028 		}
  1032 		}
  1029 		$args = array_merge( $default_args, $args );
  1033 		$args = array_merge( $default_args, $args );
  1030 
  1034 
  1031 		if ( ! empty( $args['exclude_restore_dismissed'] ) ) {
  1035 		if ( ! empty( $args['exclude_restore_dismissed'] ) ) {
  1032 			unset( $args['exclude_restore_dismissed'] );
  1036 			unset( $args['exclude_restore_dismissed'] );
  1033 			$args['meta_query'] = array(
  1037 			$args['meta_query'] = array(
  1034 				array(
  1038 				array(
  1035 					'key' => '_customize_restore_dismissed',
  1039 					'key'     => '_customize_restore_dismissed',
  1036 					'compare' => 'NOT EXISTS',
  1040 					'compare' => 'NOT EXISTS',
  1037 				),
  1041 				),
  1038 			);
  1042 			);
  1039 		}
  1043 		}
  1040 
  1044 
  1046 	 *
  1050 	 *
  1047 	 * @since 4.9.0
  1051 	 * @since 4.9.0
  1048 	 * @return int The number of auto-drafts that were dismissed.
  1052 	 * @return int The number of auto-drafts that were dismissed.
  1049 	 */
  1053 	 */
  1050 	protected function dismiss_user_auto_draft_changesets() {
  1054 	protected function dismiss_user_auto_draft_changesets() {
  1051 		$changeset_autodraft_posts = $this->get_changeset_posts( array(
  1055 		$changeset_autodraft_posts = $this->get_changeset_posts(
  1052 			'post_status' => 'auto-draft',
  1056 			array(
  1053 			'exclude_restore_dismissed' => true,
  1057 				'post_status'               => 'auto-draft',
  1054 			'posts_per_page' => -1,
  1058 				'exclude_restore_dismissed' => true,
  1055 		) );
  1059 				'posts_per_page'            => -1,
  1056 		$dismissed = 0;
  1060 			)
       
  1061 		);
       
  1062 		$dismissed                 = 0;
  1057 		foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) {
  1063 		foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) {
  1058 			if ( $autosave_autodraft_post->ID === $this->changeset_post_id() ) {
  1064 			if ( $autosave_autodraft_post->ID === $this->changeset_post_id() ) {
  1059 				continue;
  1065 				continue;
  1060 			}
  1066 			}
  1061 			if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) {
  1067 			if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) {
  1196 
  1202 
  1197 			$changeset_data = $this->get_changeset_post_data( $this->changeset_post_id() );
  1203 			$changeset_data = $this->get_changeset_post_data( $this->changeset_post_id() );
  1198 		}
  1204 		}
  1199 
  1205 
  1200 		$sidebars_widgets = isset( $starter_content['widgets'] ) && ! empty( $this->widgets ) ? $starter_content['widgets'] : array();
  1206 		$sidebars_widgets = isset( $starter_content['widgets'] ) && ! empty( $this->widgets ) ? $starter_content['widgets'] : array();
  1201 		$attachments = isset( $starter_content['attachments'] ) && ! empty( $this->nav_menus ) ? $starter_content['attachments'] : array();
  1207 		$attachments      = isset( $starter_content['attachments'] ) && ! empty( $this->nav_menus ) ? $starter_content['attachments'] : array();
  1202 		$posts = isset( $starter_content['posts'] ) && ! empty( $this->nav_menus ) ? $starter_content['posts'] : array();
  1208 		$posts            = isset( $starter_content['posts'] ) && ! empty( $this->nav_menus ) ? $starter_content['posts'] : array();
  1203 		$options = isset( $starter_content['options'] ) ? $starter_content['options'] : array();
  1209 		$options          = isset( $starter_content['options'] ) ? $starter_content['options'] : array();
  1204 		$nav_menus = isset( $starter_content['nav_menus'] ) && ! empty( $this->nav_menus ) ? $starter_content['nav_menus'] : array();
  1210 		$nav_menus        = isset( $starter_content['nav_menus'] ) && ! empty( $this->nav_menus ) ? $starter_content['nav_menus'] : array();
  1205 		$theme_mods = isset( $starter_content['theme_mods'] ) ? $starter_content['theme_mods'] : array();
  1211 		$theme_mods       = isset( $starter_content['theme_mods'] ) ? $starter_content['theme_mods'] : array();
  1206 
  1212 
  1207 		// Widgets.
  1213 		// Widgets.
  1208 		$max_widget_numbers = array();
  1214 		$max_widget_numbers = array();
  1209 		foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
  1215 		foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
  1210 			$sidebar_widget_ids = array();
  1216 			$sidebar_widget_ids = array();
  1220 					}
  1226 					}
  1221 
  1227 
  1222 					// Find the max widget number for this type.
  1228 					// Find the max widget number for this type.
  1223 					$widget_numbers = array_keys( $settings );
  1229 					$widget_numbers = array_keys( $settings );
  1224 					if ( count( $widget_numbers ) > 0 ) {
  1230 					if ( count( $widget_numbers ) > 0 ) {
  1225 						$widget_numbers[] = 1;
  1231 						$widget_numbers[]               = 1;
  1226 						$max_widget_numbers[ $id_base ] = call_user_func_array( 'max', $widget_numbers );
  1232 						$max_widget_numbers[ $id_base ] = call_user_func_array( 'max', $widget_numbers );
  1227 					} else {
  1233 					} else {
  1228 						$max_widget_numbers[ $id_base ] = 1;
  1234 						$max_widget_numbers[ $id_base ] = 1;
  1229 					}
  1235 					}
  1230 				}
  1236 				}
  1231 				$max_widget_numbers[ $id_base ] += 1;
  1237 				$max_widget_numbers[ $id_base ] += 1;
  1232 
  1238 
  1233 				$widget_id = sprintf( '%s-%d', $id_base, $max_widget_numbers[ $id_base ] );
  1239 				$widget_id  = sprintf( '%s-%d', $id_base, $max_widget_numbers[ $id_base ] );
  1234 				$setting_id = sprintf( 'widget_%s[%d]', $id_base, $max_widget_numbers[ $id_base ] );
  1240 				$setting_id = sprintf( 'widget_%s[%d]', $id_base, $max_widget_numbers[ $id_base ] );
  1235 
  1241 
  1236 				$setting_value = $this->widgets->sanitize_widget_js_instance( $instance );
  1242 				$setting_value = $this->widgets->sanitize_widget_js_instance( $instance );
  1237 				if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
  1243 				if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
  1238 					$this->set_post_value( $setting_id, $setting_value );
  1244 					$this->set_post_value( $setting_id, $setting_value );
  1253 			$starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, $changeset_data['nav_menus_created_posts']['value'] );
  1259 			$starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, $changeset_data['nav_menus_created_posts']['value'] );
  1254 		}
  1260 		}
  1255 
  1261 
  1256 		// Make an index of all the posts needed and what their slugs are.
  1262 		// Make an index of all the posts needed and what their slugs are.
  1257 		$needed_posts = array();
  1263 		$needed_posts = array();
  1258 		$attachments = $this->prepare_starter_content_attachments( $attachments );
  1264 		$attachments  = $this->prepare_starter_content_attachments( $attachments );
  1259 		foreach ( $attachments as $attachment ) {
  1265 		foreach ( $attachments as $attachment ) {
  1260 			$key = 'attachment:' . $attachment['post_name'];
  1266 			$key                  = 'attachment:' . $attachment['post_name'];
  1261 			$needed_posts[ $key ] = true;
  1267 			$needed_posts[ $key ] = true;
  1262 		}
  1268 		}
  1263 		foreach ( array_keys( $posts ) as $post_symbol ) {
  1269 		foreach ( array_keys( $posts ) as $post_symbol ) {
  1264 			if ( empty( $posts[ $post_symbol ]['post_name'] ) && empty( $posts[ $post_symbol ]['post_title'] ) ) {
  1270 			if ( empty( $posts[ $post_symbol ]['post_name'] ) && empty( $posts[ $post_symbol ]['post_title'] ) ) {
  1265 				unset( $posts[ $post_symbol ] );
  1271 				unset( $posts[ $post_symbol ] );
  1285 		$post_types = array_filter( array_merge( array( 'attachment' ), wp_list_pluck( $posts, 'post_type' ) ) );
  1291 		$post_types = array_filter( array_merge( array( 'attachment' ), wp_list_pluck( $posts, 'post_type' ) ) );
  1286 
  1292 
  1287 		// Re-use auto-draft starter content posts referenced in the current customized state.
  1293 		// Re-use auto-draft starter content posts referenced in the current customized state.
  1288 		$existing_starter_content_posts = array();
  1294 		$existing_starter_content_posts = array();
  1289 		if ( ! empty( $starter_content_auto_draft_post_ids ) ) {
  1295 		if ( ! empty( $starter_content_auto_draft_post_ids ) ) {
  1290 			$existing_posts_query = new WP_Query( array(
  1296 			$existing_posts_query = new WP_Query(
  1291 				'post__in' => $starter_content_auto_draft_post_ids,
  1297 				array(
  1292 				'post_status' => 'auto-draft',
  1298 					'post__in'       => $starter_content_auto_draft_post_ids,
  1293 				'post_type' => $post_types,
  1299 					'post_status'    => 'auto-draft',
  1294 				'posts_per_page' => -1,
  1300 					'post_type'      => $post_types,
  1295 			) );
  1301 					'posts_per_page' => -1,
       
  1302 				)
       
  1303 			);
  1296 			foreach ( $existing_posts_query->posts as $existing_post ) {
  1304 			foreach ( $existing_posts_query->posts as $existing_post ) {
  1297 				$post_name = $existing_post->post_name;
  1305 				$post_name = $existing_post->post_name;
  1298 				if ( empty( $post_name ) ) {
  1306 				if ( empty( $post_name ) ) {
  1299 					$post_name = get_post_meta( $existing_post->ID, '_customize_draft_post_name', true );
  1307 					$post_name = get_post_meta( $existing_post->ID, '_customize_draft_post_name', true );
  1300 				}
  1308 				}
  1302 			}
  1310 			}
  1303 		}
  1311 		}
  1304 
  1312 
  1305 		// Re-use non-auto-draft posts.
  1313 		// Re-use non-auto-draft posts.
  1306 		if ( ! empty( $all_post_slugs ) ) {
  1314 		if ( ! empty( $all_post_slugs ) ) {
  1307 			$existing_posts_query = new WP_Query( array(
  1315 			$existing_posts_query = new WP_Query(
  1308 				'post_name__in' => $all_post_slugs,
  1316 				array(
  1309 				'post_status' => array_diff( get_post_stati(), array( 'auto-draft' ) ),
  1317 					'post_name__in'  => $all_post_slugs,
  1310 				'post_type' => 'any',
  1318 					'post_status'    => array_diff( get_post_stati(), array( 'auto-draft' ) ),
  1311 				'posts_per_page' => -1,
  1319 					'post_type'      => 'any',
  1312 			) );
  1320 					'posts_per_page' => -1,
       
  1321 				)
       
  1322 			);
  1313 			foreach ( $existing_posts_query->posts as $existing_post ) {
  1323 			foreach ( $existing_posts_query->posts as $existing_post ) {
  1314 				$key = $existing_post->post_type . ':' . $existing_post->post_name;
  1324 				$key = $existing_post->post_type . ':' . $existing_post->post_name;
  1315 				if ( isset( $needed_posts[ $key ] ) && ! isset( $existing_starter_content_posts[ $key ] ) ) {
  1325 				if ( isset( $needed_posts[ $key ] ) && ! isset( $existing_starter_content_posts[ $key ] ) ) {
  1316 					$existing_starter_content_posts[ $key ] = $existing_post;
  1326 					$existing_starter_content_posts[ $key ] = $existing_post;
  1317 				}
  1327 				}
  1322 		if ( ! empty( $attachments ) ) {
  1332 		if ( ! empty( $attachments ) ) {
  1323 
  1333 
  1324 			$attachment_ids = array();
  1334 			$attachment_ids = array();
  1325 
  1335 
  1326 			foreach ( $attachments as $symbol => $attachment ) {
  1336 			foreach ( $attachments as $symbol => $attachment ) {
  1327 				$file_array = array(
  1337 				$file_array    = array(
  1328 					'name' => $attachment['file_name'],
  1338 					'name' => $attachment['file_name'],
  1329 				);
  1339 				);
  1330 				$file_path = $attachment['file_path'];
  1340 				$file_path     = $attachment['file_path'];
  1331 				$attachment_id = null;
  1341 				$attachment_id = null;
  1332 				$attached_file = null;
  1342 				$attached_file = null;
  1333 				if ( isset( $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ] ) ) {
  1343 				if ( isset( $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ] ) ) {
  1334 					$attachment_post = $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ];
  1344 					$attachment_post = $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ];
  1335 					$attachment_id = $attachment_post->ID;
  1345 					$attachment_id   = $attachment_post->ID;
  1336 					$attached_file = get_attached_file( $attachment_id );
  1346 					$attached_file   = get_attached_file( $attachment_id );
  1337 					if ( empty( $attached_file ) || ! file_exists( $attached_file ) ) {
  1347 					if ( empty( $attached_file ) || ! file_exists( $attached_file ) ) {
  1338 						$attachment_id = null;
  1348 						$attachment_id = null;
  1339 						$attached_file = null;
  1349 						$attached_file = null;
  1340 					} elseif ( $this->get_stylesheet() !== get_post_meta( $attachment_post->ID, '_starter_content_theme', true ) ) {
  1350 					} elseif ( $this->get_stylesheet() !== get_post_meta( $attachment_post->ID, '_starter_content_theme', true ) ) {
  1341 
  1351 
  1348 
  1358 
  1349 				// Insert the attachment auto-draft because it doesn't yet exist or the attached file is gone.
  1359 				// Insert the attachment auto-draft because it doesn't yet exist or the attached file is gone.
  1350 				if ( ! $attachment_id ) {
  1360 				if ( ! $attachment_id ) {
  1351 
  1361 
  1352 					// Copy file to temp location so that original file won't get deleted from theme after sideloading.
  1362 					// Copy file to temp location so that original file won't get deleted from theme after sideloading.
  1353 					$temp_file_name = wp_tempnam( basename( $file_path ) );
  1363 					$temp_file_name = wp_tempnam( wp_basename( $file_path ) );
  1354 					if ( $temp_file_name && copy( $file_path, $temp_file_name ) ) {
  1364 					if ( $temp_file_name && copy( $file_path, $temp_file_name ) ) {
  1355 						$file_array['tmp_name'] = $temp_file_name;
  1365 						$file_array['tmp_name'] = $temp_file_name;
  1356 					}
  1366 					}
  1357 					if ( empty( $file_array['tmp_name'] ) ) {
  1367 					if ( empty( $file_array['tmp_name'] ) ) {
  1358 						continue;
  1368 						continue;
  1432 			$this->set_post_value( $setting_id, array_unique( array_values( $starter_content_auto_draft_post_ids ) ) );
  1442 			$this->set_post_value( $setting_id, array_unique( array_values( $starter_content_auto_draft_post_ids ) ) );
  1433 			$this->pending_starter_content_settings_ids[] = $setting_id;
  1443 			$this->pending_starter_content_settings_ids[] = $setting_id;
  1434 		}
  1444 		}
  1435 
  1445 
  1436 		// Nav menus.
  1446 		// Nav menus.
  1437 		$placeholder_id = -1;
  1447 		$placeholder_id              = -1;
  1438 		$reused_nav_menu_setting_ids = array();
  1448 		$reused_nav_menu_setting_ids = array();
  1439 		foreach ( $nav_menus as $nav_menu_location => $nav_menu ) {
  1449 		foreach ( $nav_menus as $nav_menu_location => $nav_menu ) {
  1440 
  1450 
  1441 			$nav_menu_term_id = null;
  1451 			$nav_menu_term_id    = null;
  1442 			$nav_menu_setting_id = null;
  1452 			$nav_menu_setting_id = null;
  1443 			$matches = array();
  1453 			$matches             = array();
  1444 
  1454 
  1445 			// Look for an existing placeholder menu with starter content to re-use.
  1455 			// Look for an existing placeholder menu with starter content to re-use.
  1446 			foreach ( $changeset_data as $setting_id => $setting_params ) {
  1456 			foreach ( $changeset_data as $setting_id => $setting_params ) {
  1447 				$can_reuse = (
  1457 				$can_reuse = (
  1448 					! empty( $setting_params['starter_content'] )
  1458 					! empty( $setting_params['starter_content'] )
  1450 					! in_array( $setting_id, $reused_nav_menu_setting_ids, true )
  1460 					! in_array( $setting_id, $reused_nav_menu_setting_ids, true )
  1451 					&&
  1461 					&&
  1452 					preg_match( '#^nav_menu\[(?P<nav_menu_id>-?\d+)\]$#', $setting_id, $matches )
  1462 					preg_match( '#^nav_menu\[(?P<nav_menu_id>-?\d+)\]$#', $setting_id, $matches )
  1453 				);
  1463 				);
  1454 				if ( $can_reuse ) {
  1464 				if ( $can_reuse ) {
  1455 					$nav_menu_term_id = intval( $matches['nav_menu_id'] );
  1465 					$nav_menu_term_id              = intval( $matches['nav_menu_id'] );
  1456 					$nav_menu_setting_id = $setting_id;
  1466 					$nav_menu_setting_id           = $setting_id;
  1457 					$reused_nav_menu_setting_ids[] = $setting_id;
  1467 					$reused_nav_menu_setting_ids[] = $setting_id;
  1458 					break;
  1468 					break;
  1459 				}
  1469 				}
  1460 			}
  1470 			}
  1461 
  1471 
  1462 			if ( ! $nav_menu_term_id ) {
  1472 			if ( ! $nav_menu_term_id ) {
  1463 				while ( isset( $changeset_data[ sprintf( 'nav_menu[%d]', $placeholder_id ) ] ) ) {
  1473 				while ( isset( $changeset_data[ sprintf( 'nav_menu[%d]', $placeholder_id ) ] ) ) {
  1464 					$placeholder_id--;
  1474 					$placeholder_id--;
  1465 				}
  1475 				}
  1466 				$nav_menu_term_id = $placeholder_id;
  1476 				$nav_menu_term_id    = $placeholder_id;
  1467 				$nav_menu_setting_id = sprintf( 'nav_menu[%d]', $placeholder_id );
  1477 				$nav_menu_setting_id = sprintf( 'nav_menu[%d]', $placeholder_id );
  1468 			}
  1478 			}
  1469 
  1479 
  1470 			$this->set_post_value( $nav_menu_setting_id, array(
  1480 			$this->set_post_value(
  1471 				'name' => isset( $nav_menu['name'] ) ? $nav_menu['name'] : $nav_menu_location,
  1481 				$nav_menu_setting_id,
  1472 			) );
  1482 				array(
       
  1483 					'name' => isset( $nav_menu['name'] ) ? $nav_menu['name'] : $nav_menu_location,
       
  1484 				)
       
  1485 			);
  1473 			$this->pending_starter_content_settings_ids[] = $nav_menu_setting_id;
  1486 			$this->pending_starter_content_settings_ids[] = $nav_menu_setting_id;
  1474 
  1487 
  1475 			// @todo Add support for menu_item_parent.
  1488 			// @todo Add support for menu_item_parent.
  1476 			$position = 0;
  1489 			$position = 0;
  1477 			foreach ( $nav_menu['items'] as $nav_menu_item ) {
  1490 			foreach ( $nav_menu['items'] as $nav_menu_item ) {
  1483 
  1496 
  1484 				if ( isset( $nav_menu_item['object_id'] ) ) {
  1497 				if ( isset( $nav_menu_item['object_id'] ) ) {
  1485 					if ( 'post_type' === $nav_menu_item['type'] && preg_match( '/^{{(?P<symbol>.+)}}$/', $nav_menu_item['object_id'], $matches ) && isset( $posts[ $matches['symbol'] ] ) ) {
  1498 					if ( 'post_type' === $nav_menu_item['type'] && preg_match( '/^{{(?P<symbol>.+)}}$/', $nav_menu_item['object_id'], $matches ) && isset( $posts[ $matches['symbol'] ] ) ) {
  1486 						$nav_menu_item['object_id'] = $posts[ $matches['symbol'] ]['ID'];
  1499 						$nav_menu_item['object_id'] = $posts[ $matches['symbol'] ]['ID'];
  1487 						if ( empty( $nav_menu_item['title'] ) ) {
  1500 						if ( empty( $nav_menu_item['title'] ) ) {
  1488 							$original_object = get_post( $nav_menu_item['object_id'] );
  1501 							$original_object        = get_post( $nav_menu_item['object_id'] );
  1489 							$nav_menu_item['title'] = $original_object->post_title;
  1502 							$nav_menu_item['title'] = $original_object->post_title;
  1490 						}
  1503 						}
  1491 					} else {
  1504 					} else {
  1492 						continue;
  1505 						continue;
  1493 					}
  1506 					}
  1538 				}
  1551 				}
  1539 			}
  1552 			}
  1540 
  1553 
  1541 			// Handle header image as special case since setting has a legacy format.
  1554 			// Handle header image as special case since setting has a legacy format.
  1542 			if ( 'header_image' === $name ) {
  1555 			if ( 'header_image' === $name ) {
  1543 				$name = 'header_image_data';
  1556 				$name     = 'header_image_data';
  1544 				$metadata = wp_get_attachment_metadata( $value );
  1557 				$metadata = wp_get_attachment_metadata( $value );
  1545 				if ( empty( $metadata ) ) {
  1558 				if ( empty( $metadata ) ) {
  1546 					continue;
  1559 					continue;
  1547 				}
  1560 				}
  1548 				$value = array(
  1561 				$value = array(
  1549 					'attachment_id' => $value,
  1562 					'attachment_id' => $value,
  1550 					'url' => wp_get_attachment_url( $value ),
  1563 					'url'           => wp_get_attachment_url( $value ),
  1551 					'height' => $metadata['height'],
  1564 					'height'        => $metadata['height'],
  1552 					'width' => $metadata['width'],
  1565 					'width'         => $metadata['width'],
  1553 				);
  1566 				);
  1554 			} elseif ( 'background_image' === $name ) {
  1567 			} elseif ( 'background_image' === $name ) {
  1555 				$value = wp_get_attachment_url( $value );
  1568 				$value = wp_get_attachment_url( $value );
  1556 			}
  1569 			}
  1557 
  1570 
  1606 			} elseif ( file_exists( get_template_directory() . '/' . $attachment['file'] ) ) {
  1619 			} elseif ( file_exists( get_template_directory() . '/' . $attachment['file'] ) ) {
  1607 				$file_path = get_template_directory() . '/' . $attachment['file'];
  1620 				$file_path = get_template_directory() . '/' . $attachment['file'];
  1608 			} else {
  1621 			} else {
  1609 				continue;
  1622 				continue;
  1610 			}
  1623 			}
  1611 			$file_name = basename( $attachment['file'] );
  1624 			$file_name = wp_basename( $attachment['file'] );
  1612 
  1625 
  1613 			// Skip file types that are not recognized.
  1626 			// Skip file types that are not recognized.
  1614 			$checked_filetype = wp_check_filetype( $file_name );
  1627 			$checked_filetype = wp_check_filetype( $file_name );
  1615 			if ( empty( $checked_filetype['type'] ) ) {
  1628 			if ( empty( $checked_filetype['type'] ) ) {
  1616 				continue;
  1629 				continue;
  1623 				} else {
  1636 				} else {
  1624 					$attachment['post_name'] = sanitize_title( preg_replace( '/\.\w+$/', '', $file_name ) );
  1637 					$attachment['post_name'] = sanitize_title( preg_replace( '/\.\w+$/', '', $file_name ) );
  1625 				}
  1638 				}
  1626 			}
  1639 			}
  1627 
  1640 
  1628 			$attachment['file_name'] = $file_name;
  1641 			$attachment['file_name']         = $file_name;
  1629 			$attachment['file_path'] = $file_path;
  1642 			$attachment['file_path']         = $file_path;
  1630 			$prepared_attachments[ $symbol ] = $attachment;
  1643 			$prepared_attachments[ $symbol ] = $attachment;
  1631 		}
  1644 		}
  1632 		return $prepared_attachments;
  1645 		return $prepared_attachments;
  1633 	}
  1646 	}
  1634 
  1647 
  1641 
  1654 
  1642 		if ( empty( $this->pending_starter_content_settings_ids ) ) {
  1655 		if ( empty( $this->pending_starter_content_settings_ids ) ) {
  1643 			return;
  1656 			return;
  1644 		}
  1657 		}
  1645 
  1658 
  1646 		$this->save_changeset_post( array(
  1659 		$this->save_changeset_post(
  1647 			'data' => array_fill_keys( $this->pending_starter_content_settings_ids, array( 'starter_content' => true ) ),
  1660 			array(
  1648 			'starter_content' => true,
  1661 				'data'            => array_fill_keys( $this->pending_starter_content_settings_ids, array( 'starter_content' => true ) ),
  1649 		) );
  1662 				'starter_content' => true,
       
  1663 			)
       
  1664 		);
  1650 		$this->saved_starter_content_changeset = true;
  1665 		$this->saved_starter_content_changeset = true;
  1651 
  1666 
  1652 		$this->pending_starter_content_settings_ids = array();
  1667 		$this->pending_starter_content_settings_ids = array();
  1653 	}
  1668 	}
  1654 
  1669 
  1667 	 * state was exclusively sourced from `$_POST['customized']`. Nevertheless,
  1682 	 * state was exclusively sourced from `$_POST['customized']`. Nevertheless,
  1668 	 * the value returned will come from the current changeset post and from the
  1683 	 * the value returned will come from the current changeset post and from the
  1669 	 * incoming post data.
  1684 	 * incoming post data.
  1670 	 *
  1685 	 *
  1671 	 * @since 4.1.1
  1686 	 * @since 4.1.1
  1672 	 * @since 4.7.0 Added $args param and merging with changeset values and stashed theme mods.
  1687 	 * @since 4.7.0 Added `$args` parameter and merging with changeset values and stashed theme mods.
  1673 	 *
  1688 	 *
  1674 	 * @param array $args {
  1689 	 * @param array $args {
  1675 	 *     Args.
  1690 	 *     Args.
  1676 	 *
  1691 	 *
  1677 	 *     @type bool $exclude_changeset Whether the changeset values should also be excluded. Defaults to false.
  1692 	 *     @type bool $exclude_changeset Whether the changeset values should also be excluded. Defaults to false.
  1691 		$values = array();
  1706 		$values = array();
  1692 
  1707 
  1693 		// Let default values be from the stashed theme mods if doing a theme switch and if no changeset is present.
  1708 		// Let default values be from the stashed theme mods if doing a theme switch and if no changeset is present.
  1694 		if ( ! $this->is_theme_active() ) {
  1709 		if ( ! $this->is_theme_active() ) {
  1695 			$stashed_theme_mods = get_option( 'customize_stashed_theme_mods' );
  1710 			$stashed_theme_mods = get_option( 'customize_stashed_theme_mods' );
  1696 			$stylesheet = $this->get_stylesheet();
  1711 			$stylesheet         = $this->get_stylesheet();
  1697 			if ( isset( $stashed_theme_mods[ $stylesheet ] ) ) {
  1712 			if ( isset( $stashed_theme_mods[ $stylesheet ] ) ) {
  1698 				$values = array_merge( $values, wp_list_pluck( $stashed_theme_mods[ $stylesheet ], 'value' ) );
  1713 				$values = array_merge( $values, wp_list_pluck( $stashed_theme_mods[ $stylesheet ], 'value' ) );
  1699 			}
  1714 			}
  1700 		}
  1715 		}
  1701 
  1716 
  1879 	 *
  1894 	 *
  1880 	 * @param array $headers Headers.
  1895 	 * @param array $headers Headers.
  1881 	 * @return array Headers.
  1896 	 * @return array Headers.
  1882 	 */
  1897 	 */
  1883 	public function filter_iframe_security_headers( $headers ) {
  1898 	public function filter_iframe_security_headers( $headers ) {
  1884 		$customize_url = admin_url( 'customize.php' );
  1899 		$headers['X-Frame-Options']         = 'SAMEORIGIN';
  1885 		$headers['X-Frame-Options'] = 'ALLOW-FROM ' . $customize_url;
  1900 		$headers['Content-Security-Policy'] = "frame-ancestors 'self'";
  1886 		$headers['Content-Security-Policy'] = 'frame-ancestors ' . preg_replace( '#^(\w+://[^/]+).+?$#', '$1', $customize_url );
       
  1887 		return $headers;
  1901 		return $headers;
  1888 	}
  1902 	}
  1889 
  1903 
  1890 	/**
  1904 	/**
  1891 	 * Add customize state query params to a given URL if preview is allowed.
  1905 	 * Add customize state query params to a given URL if preview is allowed.
  1897 	 * @param string $url URL.
  1911 	 * @param string $url URL.
  1898 	 * @return string URL.
  1912 	 * @return string URL.
  1899 	 */
  1913 	 */
  1900 	public function add_state_query_params( $url ) {
  1914 	public function add_state_query_params( $url ) {
  1901 		$parsed_original_url = wp_parse_url( $url );
  1915 		$parsed_original_url = wp_parse_url( $url );
  1902 		$is_allowed = false;
  1916 		$is_allowed          = false;
  1903 		foreach ( $this->get_allowed_urls() as $allowed_url ) {
  1917 		foreach ( $this->get_allowed_urls() as $allowed_url ) {
  1904 			$parsed_allowed_url = wp_parse_url( $allowed_url );
  1918 			$parsed_allowed_url = wp_parse_url( $allowed_url );
  1905 			$is_allowed = (
  1919 			$is_allowed         = (
  1906 				$parsed_allowed_url['scheme'] === $parsed_original_url['scheme']
  1920 				$parsed_allowed_url['scheme'] === $parsed_original_url['scheme']
  1907 				&&
  1921 				&&
  1908 				$parsed_allowed_url['host'] === $parsed_original_url['host']
  1922 				$parsed_allowed_url['host'] === $parsed_original_url['host']
  1909 				&&
  1923 				&&
  1910 				0 === strpos( $parsed_original_url['path'], $parsed_allowed_url['path'] )
  1924 				0 === strpos( $parsed_original_url['path'], $parsed_allowed_url['path'] )
  1965 	 * Print CSS for loading indicators for the Customizer preview.
  1979 	 * Print CSS for loading indicators for the Customizer preview.
  1966 	 *
  1980 	 *
  1967 	 * @since 4.2.0
  1981 	 * @since 4.2.0
  1968 	 */
  1982 	 */
  1969 	public function customize_preview_loading_style() {
  1983 	public function customize_preview_loading_style() {
  1970 		?><style>
  1984 		?>
       
  1985 		<style>
  1971 			body.wp-customizer-unloading {
  1986 			body.wp-customizer-unloading {
  1972 				opacity: 0.25;
  1987 				opacity: 0.25;
  1973 				cursor: progress !important;
  1988 				cursor: progress !important;
  1974 				-webkit-transition: opacity 0.5s;
  1989 				-webkit-transition: opacity 0.5s;
  1975 				transition: opacity 0.5s;
  1990 				transition: opacity 0.5s;
  1983 			form.customize-unpreviewable button,
  1998 			form.customize-unpreviewable button,
  1984 			a.customize-unpreviewable,
  1999 			a.customize-unpreviewable,
  1985 			area.customize-unpreviewable {
  2000 			area.customize-unpreviewable {
  1986 				cursor: not-allowed !important;
  2001 				cursor: not-allowed !important;
  1987 			}
  2002 			}
  1988 		</style><?php
  2003 		</style>
       
  2004 		<?php
  1989 	}
  2005 	}
  1990 
  2006 
  1991 	/**
  2007 	/**
  1992 	 * Remove customize_messenger_channel query parameter from the preview window when it is not in an iframe.
  2008 	 * Remove customize_messenger_channel query parameter from the preview window when it is not in an iframe.
  1993 	 *
  2009 	 *
  2029 	 * Print JavaScript settings for preview frame.
  2045 	 * Print JavaScript settings for preview frame.
  2030 	 *
  2046 	 *
  2031 	 * @since 3.4.0
  2047 	 * @since 3.4.0
  2032 	 */
  2048 	 */
  2033 	public function customize_preview_settings() {
  2049 	public function customize_preview_settings() {
  2034 		$post_values = $this->unsanitized_post_values( array( 'exclude_changeset' => true ) );
  2050 		$post_values                 = $this->unsanitized_post_values( array( 'exclude_changeset' => true ) );
  2035 		$setting_validities = $this->validate_setting_values( $post_values );
  2051 		$setting_validities          = $this->validate_setting_values( $post_values );
  2036 		$exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities );
  2052 		$exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities );
  2037 
  2053 
  2038 		// Note that the REQUEST_URI is not passed into home_url() since this breaks subdirectory installations.
  2054 		// Note that the REQUEST_URI is not passed into home_url() since this breaks subdirectory installations.
  2039 		$self_url = empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
  2055 		$self_url           = empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
  2040 		$state_query_params = array(
  2056 		$state_query_params = array(
  2041 			'customize_theme',
  2057 			'customize_theme',
  2042 			'customize_changeset_uuid',
  2058 			'customize_changeset_uuid',
  2043 			'customize_messenger_channel',
  2059 			'customize_messenger_channel',
  2044 		);
  2060 		);
  2045 		$self_url = remove_query_arg( $state_query_params, $self_url );
  2061 		$self_url           = remove_query_arg( $state_query_params, $self_url );
  2046 
  2062 
  2047 		$allowed_urls = $this->get_allowed_urls();
  2063 		$allowed_urls  = $this->get_allowed_urls();
  2048 		$allowed_hosts = array();
  2064 		$allowed_hosts = array();
  2049 		foreach ( $allowed_urls as $allowed_url ) {
  2065 		foreach ( $allowed_urls as $allowed_url ) {
  2050 			$parsed = wp_parse_url( $allowed_url );
  2066 			$parsed = wp_parse_url( $allowed_url );
  2051 			if ( empty( $parsed['host'] ) ) {
  2067 			if ( empty( $parsed['host'] ) ) {
  2052 				continue;
  2068 				continue;
  2057 			}
  2073 			}
  2058 			$allowed_hosts[] = $host;
  2074 			$allowed_hosts[] = $host;
  2059 		}
  2075 		}
  2060 
  2076 
  2061 		$switched_locale = switch_to_locale( get_user_locale() );
  2077 		$switched_locale = switch_to_locale( get_user_locale() );
  2062 		$l10n = array(
  2078 		$l10n            = array(
  2063 			'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
  2079 			'shiftClickToEdit'  => __( 'Shift-click to edit this element.' ),
  2064 			'linkUnpreviewable' => __( 'This link is not live-previewable.' ),
  2080 			'linkUnpreviewable' => __( 'This link is not live-previewable.' ),
  2065 			'formUnpreviewable' => __( 'This form is not live-previewable.' ),
  2081 			'formUnpreviewable' => __( 'This form is not live-previewable.' ),
  2066 		);
  2082 		);
  2067 		if ( $switched_locale ) {
  2083 		if ( $switched_locale ) {
  2068 			restore_previous_locale();
  2084 			restore_previous_locale();
  2069 		}
  2085 		}
  2070 
  2086 
  2071 		$settings = array(
  2087 		$settings = array(
  2072 			'changeset' => array(
  2088 			'changeset'         => array(
  2073 				'uuid' => $this->changeset_uuid(),
  2089 				'uuid'      => $this->changeset_uuid(),
  2074 				'autosaved' => $this->autosaved(),
  2090 				'autosaved' => $this->autosaved(),
  2075 			),
  2091 			),
  2076 			'timeouts' => array(
  2092 			'timeouts'          => array(
  2077 				'selectiveRefresh' => 250,
  2093 				'selectiveRefresh' => 250,
  2078 				'keepAliveSend' => 1000,
  2094 				'keepAliveSend'    => 1000,
  2079 			),
  2095 			),
  2080 			'theme' => array(
  2096 			'theme'             => array(
  2081 				'stylesheet' => $this->get_stylesheet(),
  2097 				'stylesheet' => $this->get_stylesheet(),
  2082 				'active'     => $this->is_theme_active(),
  2098 				'active'     => $this->is_theme_active(),
  2083 			),
  2099 			),
  2084 			'url' => array(
  2100 			'url'               => array(
  2085 				'self' => $self_url,
  2101 				'self'          => $self_url,
  2086 				'allowed' => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
  2102 				'allowed'       => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
  2087 				'allowedHosts' => array_unique( $allowed_hosts ),
  2103 				'allowedHosts'  => array_unique( $allowed_hosts ),
  2088 				'isCrossDomain' => $this->is_cross_domain(),
  2104 				'isCrossDomain' => $this->is_cross_domain(),
  2089 			),
  2105 			),
  2090 			'channel' => $this->messenger_channel,
  2106 			'channel'           => $this->messenger_channel,
  2091 			'activePanels' => array(),
  2107 			'activePanels'      => array(),
  2092 			'activeSections' => array(),
  2108 			'activeSections'    => array(),
  2093 			'activeControls' => array(),
  2109 			'activeControls'    => array(),
  2094 			'settingValidities' => $exported_setting_validities,
  2110 			'settingValidities' => $exported_setting_validities,
  2095 			'nonce' => current_user_can( 'customize' ) ? $this->get_nonces() : array(),
  2111 			'nonce'             => current_user_can( 'customize' ) ? $this->get_nonces() : array(),
  2096 			'l10n' => $l10n,
  2112 			'l10n'              => $l10n,
  2097 			'_dirty' => array_keys( $post_values ),
  2113 			'_dirty'            => array_keys( $post_values ),
  2098 		);
  2114 		);
  2099 
  2115 
  2100 		foreach ( $this->panels as $panel_id => $panel ) {
  2116 		foreach ( $this->panels as $panel_id => $panel ) {
  2101 			if ( $panel->check_capabilities() ) {
  2117 			if ( $panel->check_capabilities() ) {
  2102 				$settings['activePanels'][ $panel_id ] = $panel->active();
  2118 				$settings['activePanels'][ $panel_id ] = $panel->active();
  2231 	 *
  2247 	 *
  2232 	 * @param $current_theme {@internal Parameter is not used}
  2248 	 * @param $current_theme {@internal Parameter is not used}
  2233 	 * @return string Theme name.
  2249 	 * @return string Theme name.
  2234 	 */
  2250 	 */
  2235 	public function current_theme( $current_theme ) {
  2251 	public function current_theme( $current_theme ) {
  2236 		return $this->theme()->display('Name');
  2252 		return $this->theme()->display( 'Name' );
  2237 	}
  2253 	}
  2238 
  2254 
  2239 	/**
  2255 	/**
  2240 	 * Validates setting values.
  2256 	 * Validates setting values.
  2241 	 *
  2257 	 *
  2257 	 *     @type bool $validate_capability Whether the setting capability will be checked.
  2273 	 *     @type bool $validate_capability Whether the setting capability will be checked.
  2258 	 * }
  2274 	 * }
  2259 	 * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`.
  2275 	 * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`.
  2260 	 */
  2276 	 */
  2261 	public function validate_setting_values( $setting_values, $options = array() ) {
  2277 	public function validate_setting_values( $setting_values, $options = array() ) {
  2262 		$options = wp_parse_args( $options, array(
  2278 		$options = wp_parse_args(
  2263 			'validate_capability' => false,
  2279 			$options,
  2264 			'validate_existence' => false,
  2280 			array(
  2265 		) );
  2281 				'validate_capability' => false,
       
  2282 				'validate_existence'  => false,
       
  2283 			)
       
  2284 		);
  2266 
  2285 
  2267 		$validities = array();
  2286 		$validities = array();
  2268 		foreach ( $setting_values as $setting_id => $unsanitized_value ) {
  2287 		foreach ( $setting_values as $setting_id => $unsanitized_value ) {
  2269 			$setting = $this->get_setting( $setting_id );
  2288 			$setting = $this->get_setting( $setting_id );
  2270 			if ( ! $setting ) {
  2289 			if ( ! $setting ) {
  2282 				$validity = $setting->validate( $unsanitized_value );
  2301 				$validity = $setting->validate( $unsanitized_value );
  2283 			}
  2302 			}
  2284 			if ( ! is_wp_error( $validity ) ) {
  2303 			if ( ! is_wp_error( $validity ) ) {
  2285 				/** This filter is documented in wp-includes/class-wp-customize-setting.php */
  2304 				/** This filter is documented in wp-includes/class-wp-customize-setting.php */
  2286 				$late_validity = apply_filters( "customize_validate_{$setting->id}", new WP_Error(), $unsanitized_value, $setting );
  2305 				$late_validity = apply_filters( "customize_validate_{$setting->id}", new WP_Error(), $unsanitized_value, $setting );
  2287 				if ( ! empty( $late_validity->errors ) ) {
  2306 				if ( is_wp_error( $late_validity ) && $late_validity->has_errors() ) {
  2288 					$validity = $late_validity;
  2307 					$validity = $late_validity;
  2289 				}
  2308 				}
  2290 			}
  2309 			}
  2291 			if ( ! is_wp_error( $validity ) ) {
  2310 			if ( ! is_wp_error( $validity ) ) {
  2292 				$value = $setting->sanitize( $unsanitized_value );
  2311 				$value = $setting->sanitize( $unsanitized_value );
  2321 		if ( is_wp_error( $validity ) ) {
  2340 		if ( is_wp_error( $validity ) ) {
  2322 			$notification = array();
  2341 			$notification = array();
  2323 			foreach ( $validity->errors as $error_code => $error_messages ) {
  2342 			foreach ( $validity->errors as $error_code => $error_messages ) {
  2324 				$notification[ $error_code ] = array(
  2343 				$notification[ $error_code ] = array(
  2325 					'message' => join( ' ', $error_messages ),
  2344 					'message' => join( ' ', $error_messages ),
  2326 					'data' => $validity->get_error_data( $error_code ),
  2345 					'data'    => $validity->get_error_data( $error_code ),
  2327 				);
  2346 				);
  2328 			}
  2347 			}
  2329 			return $notification;
  2348 			return $notification;
  2330 		} else {
  2349 		} else {
  2331 			return true;
  2350 			return true;
  2351 		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
  2370 		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
  2352 			wp_send_json_error( 'invalid_nonce' );
  2371 			wp_send_json_error( 'invalid_nonce' );
  2353 		}
  2372 		}
  2354 
  2373 
  2355 		$changeset_post_id = $this->changeset_post_id();
  2374 		$changeset_post_id = $this->changeset_post_id();
  2356 		$is_new_changeset = empty( $changeset_post_id );
  2375 		$is_new_changeset  = empty( $changeset_post_id );
  2357 		if ( $is_new_changeset ) {
  2376 		if ( $is_new_changeset ) {
  2358 			if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) {
  2377 			if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) {
  2359 				wp_send_json_error( 'cannot_create_changeset_post' );
  2378 				wp_send_json_error( 'cannot_create_changeset_post' );
  2360 			}
  2379 			}
  2361 		} else {
  2380 		} else {
  2378 		if ( isset( $_POST['customize_changeset_title'] ) ) {
  2397 		if ( isset( $_POST['customize_changeset_title'] ) ) {
  2379 			$changeset_title = sanitize_text_field( wp_unslash( $_POST['customize_changeset_title'] ) );
  2398 			$changeset_title = sanitize_text_field( wp_unslash( $_POST['customize_changeset_title'] ) );
  2380 		}
  2399 		}
  2381 
  2400 
  2382 		// Validate changeset status param.
  2401 		// Validate changeset status param.
  2383 		$is_publish = null;
  2402 		$is_publish       = null;
  2384 		$changeset_status = null;
  2403 		$changeset_status = null;
  2385 		if ( isset( $_POST['customize_changeset_status'] ) ) {
  2404 		if ( isset( $_POST['customize_changeset_status'] ) ) {
  2386 			$changeset_status = wp_unslash( $_POST['customize_changeset_status'] );
  2405 			$changeset_status = wp_unslash( $_POST['customize_changeset_status'] );
  2387 			if ( ! get_post_status_object( $changeset_status ) || ! in_array( $changeset_status, array( 'draft', 'pending', 'publish', 'future' ), true ) ) {
  2406 			if ( ! get_post_status_object( $changeset_status ) || ! in_array( $changeset_status, array( 'draft', 'pending', 'publish', 'future' ), true ) ) {
  2388 				wp_send_json_error( 'bad_customize_changeset_status', 400 );
  2407 				wp_send_json_error( 'bad_customize_changeset_status', 400 );
  2401 		 */
  2420 		 */
  2402 		$changeset_date_gmt = null;
  2421 		$changeset_date_gmt = null;
  2403 		if ( isset( $_POST['customize_changeset_date'] ) ) {
  2422 		if ( isset( $_POST['customize_changeset_date'] ) ) {
  2404 			$changeset_date = wp_unslash( $_POST['customize_changeset_date'] );
  2423 			$changeset_date = wp_unslash( $_POST['customize_changeset_date'] );
  2405 			if ( preg_match( '/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/', $changeset_date ) ) {
  2424 			if ( preg_match( '/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/', $changeset_date ) ) {
  2406 				$mm = substr( $changeset_date, 5, 2 );
  2425 				$mm         = substr( $changeset_date, 5, 2 );
  2407 				$jj = substr( $changeset_date, 8, 2 );
  2426 				$jj         = substr( $changeset_date, 8, 2 );
  2408 				$aa = substr( $changeset_date, 0, 4 );
  2427 				$aa         = substr( $changeset_date, 0, 4 );
  2409 				$valid_date = wp_checkdate( $mm, $jj, $aa, $changeset_date );
  2428 				$valid_date = wp_checkdate( $mm, $jj, $aa, $changeset_date );
  2410 				if ( ! $valid_date ) {
  2429 				if ( ! $valid_date ) {
  2411 					wp_send_json_error( 'bad_customize_changeset_date', 400 );
  2430 					wp_send_json_error( 'bad_customize_changeset_date', 400 );
  2412 				}
  2431 				}
  2413 				$changeset_date_gmt = get_gmt_from_date( $changeset_date );
  2432 				$changeset_date_gmt = get_gmt_from_date( $changeset_date );
  2419 				$changeset_date_gmt = gmdate( 'Y-m-d H:i:s', $timestamp );
  2438 				$changeset_date_gmt = gmdate( 'Y-m-d H:i:s', $timestamp );
  2420 			}
  2439 			}
  2421 		}
  2440 		}
  2422 
  2441 
  2423 		$lock_user_id = null;
  2442 		$lock_user_id = null;
  2424 		$autosave = ! empty( $_POST['customize_changeset_autosave'] );
  2443 		$autosave     = ! empty( $_POST['customize_changeset_autosave'] );
  2425 		if ( ! $is_new_changeset ) {
  2444 		if ( ! $is_new_changeset ) {
  2426 			$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
  2445 			$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
  2427 		}
  2446 		}
  2428 
  2447 
  2429 		// Force request to autosave when changeset is locked.
  2448 		// Force request to autosave when changeset is locked.
  2430 		if ( $lock_user_id && ! $autosave ) {
  2449 		if ( $lock_user_id && ! $autosave ) {
  2431 			$autosave = true;
  2450 			$autosave           = true;
  2432 			$changeset_status = null;
  2451 			$changeset_status   = null;
  2433 			$changeset_date_gmt = null;
  2452 			$changeset_date_gmt = null;
  2434 		}
  2453 		}
  2435 
  2454 
  2436 		if ( $autosave && ! defined( 'DOING_AUTOSAVE' ) ) { // Back-compat.
  2455 		if ( $autosave && ! defined( 'DOING_AUTOSAVE' ) ) { // Back-compat.
  2437 			define( 'DOING_AUTOSAVE', true );
  2456 			define( 'DOING_AUTOSAVE', true );
  2438 		}
  2457 		}
  2439 
  2458 
  2440 		$autosaved = false;
  2459 		$autosaved = false;
  2441 		$r = $this->save_changeset_post( array(
  2460 		$r         = $this->save_changeset_post(
  2442 			'status' => $changeset_status,
  2461 			array(
  2443 			'title' => $changeset_title,
  2462 				'status'   => $changeset_status,
  2444 			'date_gmt' => $changeset_date_gmt,
  2463 				'title'    => $changeset_title,
  2445 			'data' => $input_changeset_data,
  2464 				'date_gmt' => $changeset_date_gmt,
  2446 			'autosave' => $autosave,
  2465 				'data'     => $input_changeset_data,
  2447 		) );
  2466 				'autosave' => $autosave,
       
  2467 			)
       
  2468 		);
  2448 		if ( $autosave && ! is_wp_error( $r ) ) {
  2469 		if ( $autosave && ! is_wp_error( $r ) ) {
  2449 			$autosaved = true;
  2470 			$autosaved = true;
  2450 		}
  2471 		}
  2451 
  2472 
  2452 		// If the changeset was locked and an autosave request wasn't itself an error, then now explicitly return with a failure.
  2473 		// If the changeset was locked and an autosave request wasn't itself an error, then now explicitly return with a failure.
  2461 		}
  2482 		}
  2462 
  2483 
  2463 		if ( is_wp_error( $r ) ) {
  2484 		if ( is_wp_error( $r ) ) {
  2464 			$response = array(
  2485 			$response = array(
  2465 				'message' => $r->get_error_message(),
  2486 				'message' => $r->get_error_message(),
  2466 				'code' => $r->get_error_code(),
  2487 				'code'    => $r->get_error_code(),
  2467 			);
  2488 			);
  2468 			if ( is_array( $r->get_error_data() ) ) {
  2489 			if ( is_array( $r->get_error_data() ) ) {
  2469 				$response = array_merge( $response, $r->get_error_data() );
  2490 				$response = array_merge( $response, $r->get_error_data() );
  2470 			} else {
  2491 			} else {
  2471 				$response['data'] = $r->get_error_data();
  2492 				$response['data'] = $r->get_error_data();
  2472 			}
  2493 			}
  2473 		} else {
  2494 		} else {
  2474 			$response = $r;
  2495 			$response       = $r;
  2475 			$changeset_post = get_post( $this->changeset_post_id() );
  2496 			$changeset_post = get_post( $this->changeset_post_id() );
  2476 
  2497 
  2477 			// Dismiss all other auto-draft changeset posts for this user (they serve like autosave revisions), as there should only be one.
  2498 			// Dismiss all other auto-draft changeset posts for this user (they serve like autosave revisions), as there should only be one.
  2478 			if ( $is_new_changeset ) {
  2499 			if ( $is_new_changeset ) {
  2479 				$this->dismiss_user_auto_draft_changesets();
  2500 				$this->dismiss_user_auto_draft_changesets();
  2547 	 */
  2568 	 */
  2548 	function save_changeset_post( $args = array() ) {
  2569 	function save_changeset_post( $args = array() ) {
  2549 
  2570 
  2550 		$args = array_merge(
  2571 		$args = array_merge(
  2551 			array(
  2572 			array(
  2552 				'status' => null,
  2573 				'status'          => null,
  2553 				'title' => null,
  2574 				'title'           => null,
  2554 				'data' => array(),
  2575 				'data'            => array(),
  2555 				'date_gmt' => null,
  2576 				'date_gmt'        => null,
  2556 				'user_id' => get_current_user_id(),
  2577 				'user_id'         => get_current_user_id(),
  2557 				'starter_content' => false,
  2578 				'starter_content' => false,
  2558 				'autosave' => false,
  2579 				'autosave'        => false,
  2559 			),
  2580 			),
  2560 			$args
  2581 			$args
  2561 		);
  2582 		);
  2562 
  2583 
  2563 		$changeset_post_id = $this->changeset_post_id();
  2584 		$changeset_post_id       = $this->changeset_post_id();
  2564 		$existing_changeset_data = array();
  2585 		$existing_changeset_data = array();
  2565 		if ( $changeset_post_id ) {
  2586 		if ( $changeset_post_id ) {
  2566 			$existing_status = get_post_status( $changeset_post_id );
  2587 			$existing_status = get_post_status( $changeset_post_id );
  2567 			if ( 'publish' === $existing_status || 'trash' === $existing_status ) {
  2588 			if ( 'publish' === $existing_status || 'trash' === $existing_status ) {
  2568 				return new WP_Error(
  2589 				return new WP_Error(
  2624 			}
  2645 			}
  2625 		}
  2646 		}
  2626 
  2647 
  2627 		// The request was made via wp.customize.previewer.save().
  2648 		// The request was made via wp.customize.previewer.save().
  2628 		$update_transactionally = (bool) $args['status'];
  2649 		$update_transactionally = (bool) $args['status'];
  2629 		$allow_revision = (bool) $args['status'];
  2650 		$allow_revision         = (bool) $args['status'];
  2630 
  2651 
  2631 		// Amend post values with any supplied data.
  2652 		// Amend post values with any supplied data.
  2632 		foreach ( $args['data'] as $setting_id => $setting_params ) {
  2653 		foreach ( $args['data'] as $setting_id => $setting_params ) {
  2633 			if ( is_array( $setting_params ) && array_key_exists( 'value', $setting_params ) ) {
  2654 			if ( is_array( $setting_params ) && array_key_exists( 'value', $setting_params ) ) {
  2634 				$this->set_post_value( $setting_id, $setting_params['value'] ); // Add to post values so that they can be validated and sanitized.
  2655 				$this->set_post_value( $setting_id, $setting_params['value'] ); // Add to post values so that they can be validated and sanitized.
  2635 			}
  2656 			}
  2636 		}
  2657 		}
  2637 
  2658 
  2638 		// Note that in addition to post data, this will include any stashed theme mods.
  2659 		// Note that in addition to post data, this will include any stashed theme mods.
  2639 		$post_values = $this->unsanitized_post_values( array(
  2660 		$post_values = $this->unsanitized_post_values(
  2640 			'exclude_changeset' => true,
  2661 			array(
  2641 			'exclude_post_data' => false,
  2662 				'exclude_changeset' => true,
  2642 		) );
  2663 				'exclude_post_data' => false,
       
  2664 			)
       
  2665 		);
  2643 		$this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value.
  2666 		$this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value.
  2644 
  2667 
  2645 		/*
  2668 		/*
  2646 		 * Get list of IDs for settings that have values different from what is currently
  2669 		 * Get list of IDs for settings that have values different from what is currently
  2647 		 * saved in the changeset. By skipping any values that are already the same, the
  2670 		 * saved in the changeset. By skipping any values that are already the same, the
  2684 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
  2707 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
  2685 		 */
  2708 		 */
  2686 		do_action( 'customize_save_validation_before', $this );
  2709 		do_action( 'customize_save_validation_before', $this );
  2687 
  2710 
  2688 		// Validate settings.
  2711 		// Validate settings.
  2689 		$validated_values = array_merge(
  2712 		$validated_values      = array_merge(
  2690 			array_fill_keys( array_keys( $args['data'] ), null ), // Make sure existence/capability checks are done on value-less setting updates.
  2713 			array_fill_keys( array_keys( $args['data'] ), null ), // Make sure existence/capability checks are done on value-less setting updates.
  2691 			$post_values
  2714 			$post_values
  2692 		);
  2715 		);
  2693 		$setting_validities = $this->validate_setting_values( $validated_values, array(
  2716 		$setting_validities    = $this->validate_setting_values(
  2694 			'validate_capability' => true,
  2717 			$validated_values,
  2695 			'validate_existence' => true,
  2718 			array(
  2696 		) );
  2719 				'validate_capability' => true,
       
  2720 				'validate_existence'  => true,
       
  2721 			)
       
  2722 		);
  2697 		$invalid_setting_count = count( array_filter( $setting_validities, 'is_wp_error' ) );
  2723 		$invalid_setting_count = count( array_filter( $setting_validities, 'is_wp_error' ) );
  2698 
  2724 
  2699 		/*
  2725 		/*
  2700 		 * Short-circuit if there are invalid settings the update is transactional.
  2726 		 * Short-circuit if there are invalid settings the update is transactional.
  2701 		 * A changeset update is transactional when a status is supplied in the request.
  2727 		 * A changeset update is transactional when a status is supplied in the request.
  2702 		 */
  2728 		 */
  2703 		if ( $update_transactionally && $invalid_setting_count > 0 ) {
  2729 		if ( $update_transactionally && $invalid_setting_count > 0 ) {
  2704 			$response = array(
  2730 			$response = array(
  2705 				'setting_validities' => $setting_validities,
  2731 				'setting_validities' => $setting_validities,
  2706 				/* translators: %s: number of invalid settings */
  2732 				/* translators: %s: number of invalid settings */
  2707 				'message' => sprintf( _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', $invalid_setting_count ), number_format_i18n( $invalid_setting_count ) ),
  2733 				'message'            => sprintf( _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', $invalid_setting_count ), number_format_i18n( $invalid_setting_count ) ),
  2708 			);
  2734 			);
  2709 			return new WP_Error( 'transaction_fail', '', $response );
  2735 			return new WP_Error( 'transaction_fail', '', $response );
  2710 		}
  2736 		}
  2711 
  2737 
  2712 		// Obtain/merge data for changeset.
  2738 		// Obtain/merge data for changeset.
  2713 		$original_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
  2739 		$original_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
  2714 		$data = $original_changeset_data;
  2740 		$data                    = $original_changeset_data;
  2715 		if ( is_wp_error( $data ) ) {
  2741 		if ( is_wp_error( $data ) ) {
  2716 			$data = array();
  2742 			$data = array();
  2717 		}
  2743 		}
  2718 
  2744 
  2719 		// Ensure that all post values are included in the changeset data.
  2745 		// Ensure that all post values are included in the changeset data.
  2760 				}
  2786 				}
  2761 
  2787 
  2762 				$data[ $changeset_setting_id ] = array_merge(
  2788 				$data[ $changeset_setting_id ] = array_merge(
  2763 					$merged_setting_params,
  2789 					$merged_setting_params,
  2764 					array(
  2790 					array(
  2765 						'type' => $setting->type,
  2791 						'type'              => $setting->type,
  2766 						'user_id' => $args['user_id'],
  2792 						'user_id'           => $args['user_id'],
  2767 						'date_modified_gmt' => current_time( 'mysql', true ),
  2793 						'date_modified_gmt' => current_time( 'mysql', true ),
  2768 					)
  2794 					)
  2769 				);
  2795 				);
  2770 
  2796 
  2771 				// Clear starter_content flag in data if changeset is not explicitly being updated for starter content.
  2797 				// Clear starter_content flag in data if changeset is not explicitly being updated for starter content.
  2774 				}
  2800 				}
  2775 			}
  2801 			}
  2776 		}
  2802 		}
  2777 
  2803 
  2778 		$filter_context = array(
  2804 		$filter_context = array(
  2779 			'uuid' => $this->changeset_uuid(),
  2805 			'uuid'          => $this->changeset_uuid(),
  2780 			'title' => $args['title'],
  2806 			'title'         => $args['title'],
  2781 			'status' => $args['status'],
  2807 			'status'        => $args['status'],
  2782 			'date_gmt' => $args['date_gmt'],
  2808 			'date_gmt'      => $args['date_gmt'],
  2783 			'post_id' => $changeset_post_id,
  2809 			'post_id'       => $changeset_post_id,
  2784 			'previous_data' => is_wp_error( $original_changeset_data ) ? array() : $original_changeset_data,
  2810 			'previous_data' => is_wp_error( $original_changeset_data ) ? array() : $original_changeset_data,
  2785 			'manager' => $this,
  2811 			'manager'       => $this,
  2786 		);
  2812 		);
  2787 
  2813 
  2788 		/**
  2814 		/**
  2789 		 * Filters the settings' data that will be persisted into the changeset.
  2815 		 * Filters the settings' data that will be persisted into the changeset.
  2790 		 *
  2816 		 *
  2820 		$json_options = 0;
  2846 		$json_options = 0;
  2821 		if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
  2847 		if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
  2822 			$json_options |= JSON_UNESCAPED_SLASHES; // Introduced in PHP 5.4. This is only to improve readability as slashes needn't be escaped in storage.
  2848 			$json_options |= JSON_UNESCAPED_SLASHES; // Introduced in PHP 5.4. This is only to improve readability as slashes needn't be escaped in storage.
  2823 		}
  2849 		}
  2824 		$json_options |= JSON_PRETTY_PRINT; // Also introduced in PHP 5.4, but WP defines constant for back compat. See WP Trac #30139.
  2850 		$json_options |= JSON_PRETTY_PRINT; // Also introduced in PHP 5.4, but WP defines constant for back compat. See WP Trac #30139.
  2825 		$post_array = array(
  2851 		$post_array    = array(
  2826 			'post_content' => wp_json_encode( $data, $json_options ),
  2852 			'post_content' => wp_json_encode( $data, $json_options ),
  2827 		);
  2853 		);
  2828 		if ( $args['title'] ) {
  2854 		if ( $args['title'] ) {
  2829 			$post_array['post_title'] = $args['title'];
  2855 			$post_array['post_title'] = $args['title'];
  2830 		}
  2856 		}
  2831 		if ( $changeset_post_id ) {
  2857 		if ( $changeset_post_id ) {
  2832 			$post_array['ID'] = $changeset_post_id;
  2858 			$post_array['ID'] = $changeset_post_id;
  2833 		} else {
  2859 		} else {
  2834 			$post_array['post_type'] = 'customize_changeset';
  2860 			$post_array['post_type']   = 'customize_changeset';
  2835 			$post_array['post_name'] = $this->changeset_uuid();
  2861 			$post_array['post_name']   = $this->changeset_uuid();
  2836 			$post_array['post_status'] = 'auto-draft';
  2862 			$post_array['post_status'] = 'auto-draft';
  2837 		}
  2863 		}
  2838 		if ( $args['status'] ) {
  2864 		if ( $args['status'] ) {
  2839 			$post_array['post_status'] = $args['status'];
  2865 			$post_array['post_status'] = $args['status'];
  2840 		}
  2866 		}
  2841 
  2867 
  2842 		// Reset post date to now if we are publishing, otherwise pass post_date_gmt and translate for post_date.
  2868 		// Reset post date to now if we are publishing, otherwise pass post_date_gmt and translate for post_date.
  2843 		if ( 'publish' === $args['status'] ) {
  2869 		if ( 'publish' === $args['status'] ) {
  2844 			$post_array['post_date_gmt'] = '0000-00-00 00:00:00';
  2870 			$post_array['post_date_gmt'] = '0000-00-00 00:00:00';
  2845 			$post_array['post_date'] = '0000-00-00 00:00:00';
  2871 			$post_array['post_date']     = '0000-00-00 00:00:00';
  2846 		} elseif ( $args['date_gmt'] ) {
  2872 		} elseif ( $args['date_gmt'] ) {
  2847 			$post_array['post_date_gmt'] = $args['date_gmt'];
  2873 			$post_array['post_date_gmt'] = $args['date_gmt'];
  2848 			$post_array['post_date'] = get_date_from_gmt( $args['date_gmt'] );
  2874 			$post_array['post_date']     = get_date_from_gmt( $args['date_gmt'] );
  2849 		} elseif ( $changeset_post_id && 'auto-draft' === get_post_status( $changeset_post_id ) ) {
  2875 		} elseif ( $changeset_post_id && 'auto-draft' === get_post_status( $changeset_post_id ) ) {
  2850 			/*
  2876 			/*
  2851 			 * Keep bumping the date for the auto-draft whenever it is modified;
  2877 			 * Keep bumping the date for the auto-draft whenever it is modified;
  2852 			 * this extends its life, preserving it from garbage-collection via
  2878 			 * this extends its life, preserving it from garbage-collection via
  2853 			 * wp_delete_auto_drafts().
  2879 			 * wp_delete_auto_drafts().
  2854 			 */
  2880 			 */
  2855 			$post_array['post_date'] = current_time( 'mysql' );
  2881 			$post_array['post_date']     = current_time( 'mysql' );
  2856 			$post_array['post_date_gmt'] = '';
  2882 			$post_array['post_date_gmt'] = '';
  2857 		}
  2883 		}
  2858 
  2884 
  2859 		$this->store_changeset_revision = $allow_revision;
  2885 		$this->store_changeset_revision = $allow_revision;
  2860 		add_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ), 5, 3 );
  2886 		add_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ), 5, 3 );
  2861 
  2887 
  2862 		// Update the changeset post. The publish_customize_changeset action will cause the settings in the changeset to be saved via WP_Customize_Setting::save().
  2888 		/*
       
  2889 		 * Update the changeset post. The publish_customize_changeset action
       
  2890 		 * will cause the settings in the changeset to be saved via
       
  2891 		 * WP_Customize_Setting::save().
       
  2892 		 */
       
  2893 
       
  2894 		// Prevent content filters from corrupting JSON in post_content.
  2863 		$has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
  2895 		$has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
  2864 		if ( $has_kses ) {
  2896 		if ( $has_kses ) {
  2865 			kses_remove_filters(); // Prevent KSES from corrupting JSON in post_content.
  2897 			kses_remove_filters();
       
  2898 		}
       
  2899 		$has_targeted_link_rel_filters = ( false !== has_filter( 'content_save_pre', 'wp_targeted_link_rel' ) );
       
  2900 		if ( $has_targeted_link_rel_filters ) {
       
  2901 			wp_remove_targeted_link_rel_filters();
  2866 		}
  2902 		}
  2867 
  2903 
  2868 		// Note that updating a post with publish status will trigger WP_Customize_Manager::publish_changeset_values().
  2904 		// Note that updating a post with publish status will trigger WP_Customize_Manager::publish_changeset_values().
  2869 		if ( $changeset_post_id ) {
  2905 		if ( $changeset_post_id ) {
  2870 			if ( $args['autosave'] && 'auto-draft' !== get_post_status( $changeset_post_id ) ) {
  2906 			if ( $args['autosave'] && 'auto-draft' !== get_post_status( $changeset_post_id ) ) {
  2871 				// See _wp_translate_postdata() for why this is required as it will use the edit_post meta capability.
  2907 				// See _wp_translate_postdata() for why this is required as it will use the edit_post meta capability.
  2872 				add_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10, 4 );
  2908 				add_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10, 4 );
  2873 				$post_array['post_ID'] = $post_array['ID'];
  2909 				$post_array['post_ID']   = $post_array['ID'];
  2874 				$post_array['post_type'] = 'customize_changeset';
  2910 				$post_array['post_type'] = 'customize_changeset';
  2875 				$r = wp_create_post_autosave( wp_slash( $post_array ) );
  2911 				$r                       = wp_create_post_autosave( wp_slash( $post_array ) );
  2876 				remove_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10 );
  2912 				remove_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10 );
  2877 			} else {
  2913 			} else {
  2878 				$post_array['edit_date'] = true; // Prevent date clearing.
  2914 				$post_array['edit_date'] = true; // Prevent date clearing.
  2879 				$r = wp_update_post( wp_slash( $post_array ), true );
  2915 				$r                       = wp_update_post( wp_slash( $post_array ), true );
  2880 
  2916 
  2881 				// Delete autosave revision for user when the changeset is updated.
  2917 				// Delete autosave revision for user when the changeset is updated.
  2882 				if ( ! empty( $args['user_id'] ) ) {
  2918 				if ( ! empty( $args['user_id'] ) ) {
  2883 					$autosave_draft = wp_get_post_autosave( $changeset_post_id, $args['user_id'] );
  2919 					$autosave_draft = wp_get_post_autosave( $changeset_post_id, $args['user_id'] );
  2884 					if ( $autosave_draft ) {
  2920 					if ( $autosave_draft ) {
  2890 			$r = wp_insert_post( wp_slash( $post_array ), true );
  2926 			$r = wp_insert_post( wp_slash( $post_array ), true );
  2891 			if ( ! is_wp_error( $r ) ) {
  2927 			if ( ! is_wp_error( $r ) ) {
  2892 				$this->_changeset_post_id = $r; // Update cached post ID for the loaded changeset.
  2928 				$this->_changeset_post_id = $r; // Update cached post ID for the loaded changeset.
  2893 			}
  2929 			}
  2894 		}
  2930 		}
       
  2931 
       
  2932 		// Restore removed content filters.
  2895 		if ( $has_kses ) {
  2933 		if ( $has_kses ) {
  2896 			kses_init_filters();
  2934 			kses_init_filters();
  2897 		}
  2935 		}
       
  2936 		if ( $has_targeted_link_rel_filters ) {
       
  2937 			wp_init_targeted_link_rel_filters();
       
  2938 		}
       
  2939 
  2898 		$this->_changeset_data = null; // Reset so WP_Customize_Manager::changeset_data() will re-populate with updated contents.
  2940 		$this->_changeset_data = null; // Reset so WP_Customize_Manager::changeset_data() will re-populate with updated contents.
  2899 
  2941 
  2900 		remove_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ) );
  2942 		remove_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ) );
  2901 
  2943 
  2902 		$response = array(
  2944 		$response = array(
  2963 
  3005 
  2964 		$post->post_status = $new_status;
  3006 		$post->post_status = $new_status;
  2965 		wp_transition_post_status( $new_status, $old_status, $post );
  3007 		wp_transition_post_status( $new_status, $old_status, $post );
  2966 
  3008 
  2967 		/** This action is documented in wp-includes/post.php */
  3009 		/** This action is documented in wp-includes/post.php */
       
  3010 		do_action( "edit_post_{$post->post_type}", $post->ID, $post );
       
  3011 
       
  3012 		/** This action is documented in wp-includes/post.php */
  2968 		do_action( 'edit_post', $post->ID, $post );
  3013 		do_action( 'edit_post', $post->ID, $post );
  2969 
  3014 
  2970 		/** This action is documented in wp-includes/post.php */
  3015 		/** This action is documented in wp-includes/post.php */
  2971 		do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
  3016 		do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
  2972 
  3017 
  2997 		if ( ! $this->is_preview() ) {
  3042 		if ( ! $this->is_preview() ) {
  2998 			wp_send_json_error( 'not_preview' );
  3043 			wp_send_json_error( 'not_preview' );
  2999 		}
  3044 		}
  3000 
  3045 
  3001 		if ( ! check_ajax_referer( 'trash_customize_changeset', 'nonce', false ) ) {
  3046 		if ( ! check_ajax_referer( 'trash_customize_changeset', 'nonce', false ) ) {
  3002 			wp_send_json_error( array(
  3047 			wp_send_json_error(
  3003 				'code' => 'invalid_nonce',
  3048 				array(
  3004 				'message' => __( 'There was an authentication problem. Please reload and try again.' ),
  3049 					'code'    => 'invalid_nonce',
  3005 			) );
  3050 					'message' => __( 'There was an authentication problem. Please reload and try again.' ),
       
  3051 				)
       
  3052 			);
  3006 		}
  3053 		}
  3007 
  3054 
  3008 		$changeset_post_id = $this->changeset_post_id();
  3055 		$changeset_post_id = $this->changeset_post_id();
  3009 
  3056 
  3010 		if ( ! $changeset_post_id ) {
  3057 		if ( ! $changeset_post_id ) {
  3011 			wp_send_json_error( array(
  3058 			wp_send_json_error(
  3012 				'message' => __( 'No changes saved yet, so there is nothing to trash.' ),
  3059 				array(
  3013 				'code' => 'non_existent_changeset',
  3060 					'message' => __( 'No changes saved yet, so there is nothing to trash.' ),
  3014 			) );
  3061 					'code'    => 'non_existent_changeset',
       
  3062 				)
       
  3063 			);
  3015 			return;
  3064 			return;
  3016 		}
  3065 		}
  3017 
  3066 
  3018 		if ( $changeset_post_id && ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) {
  3067 		if ( $changeset_post_id && ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) {
  3019 			wp_send_json_error( array(
  3068 			wp_send_json_error(
  3020 				'code' => 'changeset_trash_unauthorized',
  3069 				array(
  3021 				'message' => __( 'Unable to trash changes.' ),
  3070 					'code'    => 'changeset_trash_unauthorized',
  3022 			) );
  3071 					'message' => __( 'Unable to trash changes.' ),
       
  3072 				)
       
  3073 			);
  3023 		}
  3074 		}
  3024 
  3075 
  3025 		if ( 'trash' === get_post_status( $changeset_post_id ) ) {
  3076 		if ( 'trash' === get_post_status( $changeset_post_id ) ) {
  3026 			wp_send_json_error( array(
  3077 			wp_send_json_error(
  3027 				'message' => __( 'Changes have already been trashed.' ),
  3078 				array(
  3028 				'code' => 'changeset_already_trashed',
  3079 					'message' => __( 'Changes have already been trashed.' ),
  3029 			) );
  3080 					'code'    => 'changeset_already_trashed',
       
  3081 				)
       
  3082 			);
  3030 			return;
  3083 			return;
  3031 		}
  3084 		}
  3032 
  3085 
  3033 		$r = $this->trash_changeset_post( $changeset_post_id );
  3086 		$r = $this->trash_changeset_post( $changeset_post_id );
  3034 		if ( ! ( $r instanceof WP_Post ) ) {
  3087 		if ( ! ( $r instanceof WP_Post ) ) {
  3035 			wp_send_json_error( array(
  3088 			wp_send_json_error(
  3036 				'code' => 'changeset_trash_failure',
  3089 				array(
  3037 				'message' => __( 'Unable to trash changes.' ),
  3090 					'code'    => 'changeset_trash_failure',
  3038 			) );
  3091 					'message' => __( 'Unable to trash changes.' ),
  3039 		}
  3092 				)
  3040 
  3093 			);
  3041 		wp_send_json_success( array(
  3094 		}
  3042 			'message' => __( 'Changes trashed successfully.' ),
  3095 
  3043 		) );
  3096 		wp_send_json_success(
       
  3097 			array(
       
  3098 				'message' => __( 'Changes trashed successfully.' ),
       
  3099 			)
       
  3100 		);
  3044 	}
  3101 	}
  3045 
  3102 
  3046 	/**
  3103 	/**
  3047 	 * Re-map 'edit_post' meta cap for a customize_changeset post to be the same as 'customize' maps.
  3104 	 * Re-map 'edit_post' meta cap for a customize_changeset post to be the same as 'customize' maps.
  3048 	 *
  3105 	 *
  3056 	 * @since 4.9.0
  3113 	 * @since 4.9.0
  3057 	 * @link https://core.trac.wordpress.org/ticket/40922
  3114 	 * @link https://core.trac.wordpress.org/ticket/40922
  3058 	 * @see WP_Customize_Manager::save_changeset_post()
  3115 	 * @see WP_Customize_Manager::save_changeset_post()
  3059 	 * @see _wp_translate_postdata()
  3116 	 * @see _wp_translate_postdata()
  3060 	 *
  3117 	 *
  3061 	 * @param array  $caps    Returns the user's actual capabilities.
  3118 	 * @param string[] $caps    Array of the user's capabilities.
  3062 	 * @param string $cap     Capability name.
  3119 	 * @param string   $cap     Capability name.
  3063 	 * @param int    $user_id The user ID.
  3120 	 * @param int      $user_id The user ID.
  3064 	 * @param array  $args    Adds the context to the cap. Typically the object ID.
  3121 	 * @param array    $args    Adds the context to the cap. Typically the object ID.
  3065 	 * @return array Capabilities.
  3122 	 * @return array   Capabilities.
  3066 	 */
  3123 	 */
  3067 	public function grant_edit_post_capability_for_changeset( $caps, $cap, $user_id, $args ) {
  3124 	public function grant_edit_post_capability_for_changeset( $caps, $cap, $user_id, $args ) {
  3068 		if ( 'edit_post' === $cap && ! empty( $args[0] ) && 'customize_changeset' === get_post_type( $args[0] ) ) {
  3125 		if ( 'edit_post' === $cap && ! empty( $args[0] ) && 'customize_changeset' === get_post_type( $args[0] ) ) {
  3069 			$post_type_obj = get_post_type_object( 'customize_changeset' );
  3126 			$post_type_obj = get_post_type_object( 'customize_changeset' );
  3070 			$caps = map_meta_cap( $post_type_obj->cap->$cap, $user_id );
  3127 			$caps          = map_meta_cap( $post_type_obj->cap->$cap, $user_id );
  3071 		}
  3128 		}
  3072 		return $caps;
  3129 		return $caps;
  3073 	}
  3130 	}
  3074 
  3131 
  3075 	/**
  3132 	/**
  3110 		}
  3167 		}
  3111 		$lock = get_post_meta( $changeset_post_id, '_edit_lock', true );
  3168 		$lock = get_post_meta( $changeset_post_id, '_edit_lock', true );
  3112 		$lock = explode( ':', $lock );
  3169 		$lock = explode( ':', $lock );
  3113 
  3170 
  3114 		if ( $lock && ! empty( $lock[1] ) ) {
  3171 		if ( $lock && ! empty( $lock[1] ) ) {
  3115 			$user_id = intval( $lock[1] );
  3172 			$user_id         = intval( $lock[1] );
  3116 			$current_user_id = get_current_user_id();
  3173 			$current_user_id = get_current_user_id();
  3117 			if ( $user_id === $current_user_id ) {
  3174 			if ( $user_id === $current_user_id ) {
  3118 				$lock = sprintf( '%s:%s', time(), $user_id );
  3175 				$lock = sprintf( '%s:%s', time(), $user_id );
  3119 				update_post_meta( $changeset_post_id, '_edit_lock', $lock );
  3176 				update_post_meta( $changeset_post_id, '_edit_lock', $lock );
  3120 			}
  3177 			}
  3151 		$lock_user = get_userdata( $user_id );
  3208 		$lock_user = get_userdata( $user_id );
  3152 		if ( ! $lock_user ) {
  3209 		if ( ! $lock_user ) {
  3153 			return null;
  3210 			return null;
  3154 		}
  3211 		}
  3155 		return array(
  3212 		return array(
  3156 			'id' => $lock_user->ID,
  3213 			'id'     => $lock_user->ID,
  3157 			'name' => $lock_user->display_name,
  3214 			'name'   => $lock_user->display_name,
  3158 			'avatar' => get_avatar_url( $lock_user->ID, array( 'size' => 128 ) ),
  3215 			'avatar' => get_avatar_url( $lock_user->ID, array( 'size' => 128 ) ),
  3159 		);
  3216 		);
  3160 	}
  3217 	}
  3161 
  3218 
  3162 	/**
  3219 	/**
  3205 		if ( ! $this->is_preview() ) {
  3262 		if ( ! $this->is_preview() ) {
  3206 			wp_send_json_error( 'not_preview', 400 );
  3263 			wp_send_json_error( 'not_preview', 400 );
  3207 		}
  3264 		}
  3208 
  3265 
  3209 		if ( ! check_ajax_referer( 'customize_override_changeset_lock', 'nonce', false ) ) {
  3266 		if ( ! check_ajax_referer( 'customize_override_changeset_lock', 'nonce', false ) ) {
  3210 			wp_send_json_error( array(
  3267 			wp_send_json_error(
  3211 				'code' => 'invalid_nonce',
  3268 				array(
  3212 				'message' => __( 'Security check failed.' ),
  3269 					'code'    => 'invalid_nonce',
  3213 			) );
  3270 					'message' => __( 'Security check failed.' ),
       
  3271 				)
       
  3272 			);
  3214 		}
  3273 		}
  3215 
  3274 
  3216 		$changeset_post_id = $this->changeset_post_id();
  3275 		$changeset_post_id = $this->changeset_post_id();
  3217 
  3276 
  3218 		if ( empty( $changeset_post_id ) ) {
  3277 		if ( empty( $changeset_post_id ) ) {
  3219 			wp_send_json_error( array(
  3278 			wp_send_json_error(
  3220 				'code' => 'no_changeset_found_to_take_over',
  3279 				array(
  3221 				'message' => __( 'No changeset found to take over' ),
  3280 					'code'    => 'no_changeset_found_to_take_over',
  3222 			) );
  3281 					'message' => __( 'No changeset found to take over' ),
       
  3282 				)
       
  3283 			);
  3223 		}
  3284 		}
  3224 
  3285 
  3225 		if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) {
  3286 		if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) {
  3226 			wp_send_json_error( array(
  3287 			wp_send_json_error(
  3227 				'code' => 'cannot_remove_changeset_lock',
  3288 				array(
  3228 				'message' => __( 'Sorry, you are not allowed to take over.' ),
  3289 					'code'    => 'cannot_remove_changeset_lock',
  3229 			) );
  3290 					'message' => __( 'Sorry, you are not allowed to take over.' ),
       
  3291 				)
       
  3292 			);
  3230 		}
  3293 		}
  3231 
  3294 
  3232 		$this->set_changeset_lock( $changeset_post_id, true );
  3295 		$this->set_changeset_lock( $changeset_post_id, true );
  3233 
  3296 
  3234 		wp_send_json_success( 'changeset_taken_over' );
  3297 		wp_send_json_success( 'changeset_taken_over' );
  3304 		$this->_changeset_uuid      = $changeset_post->post_name;
  3367 		$this->_changeset_uuid      = $changeset_post->post_name;
  3305 		$previous_changeset_data    = $this->_changeset_data;
  3368 		$previous_changeset_data    = $this->_changeset_data;
  3306 		$this->_changeset_data      = $publishing_changeset_data;
  3369 		$this->_changeset_data      = $publishing_changeset_data;
  3307 
  3370 
  3308 		// Parse changeset data to identify theme mod settings and user IDs associated with settings to be saved.
  3371 		// Parse changeset data to identify theme mod settings and user IDs associated with settings to be saved.
  3309 		$setting_user_ids = array();
  3372 		$setting_user_ids   = array();
  3310 		$theme_mod_settings = array();
  3373 		$theme_mod_settings = array();
  3311 		$namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/';
  3374 		$namespace_pattern  = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/';
  3312 		$matches = array();
  3375 		$matches            = array();
  3313 		foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) {
  3376 		foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) {
  3314 			$actual_setting_id = null;
  3377 			$actual_setting_id    = null;
  3315 			$is_theme_mod_setting = (
  3378 			$is_theme_mod_setting = (
  3316 				isset( $setting_params['value'] )
  3379 				isset( $setting_params['value'] )
  3317 				&&
  3380 				&&
  3318 				isset( $setting_params['type'] )
  3381 				isset( $setting_params['type'] )
  3319 				&&
  3382 				&&
  3338 			if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) {
  3401 			if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) {
  3339 				$setting_user_ids[ $actual_setting_id ] = $setting_params['user_id'];
  3402 				$setting_user_ids[ $actual_setting_id ] = $setting_params['user_id'];
  3340 			}
  3403 			}
  3341 		}
  3404 		}
  3342 
  3405 
  3343 		$changeset_setting_values = $this->unsanitized_post_values( array(
  3406 		$changeset_setting_values = $this->unsanitized_post_values(
  3344 			'exclude_post_data' => true,
  3407 			array(
  3345 			'exclude_changeset' => false,
  3408 				'exclude_post_data' => true,
  3346 		) );
  3409 				'exclude_changeset' => false,
  3347 		$changeset_setting_ids = array_keys( $changeset_setting_values );
  3410 			)
       
  3411 		);
       
  3412 		$changeset_setting_ids    = array_keys( $changeset_setting_values );
  3348 		$this->add_dynamic_settings( $changeset_setting_ids );
  3413 		$this->add_dynamic_settings( $changeset_setting_ids );
  3349 
  3414 
  3350 		/**
  3415 		/**
  3351 		 * Fires once the theme has switched in the Customizer, but before settings
  3416 		 * Fires once the theme has switched in the Customizer, but before settings
  3352 		 * have been saved.
  3417 		 * have been saved.
  3366 		$original_setting_capabilities = array();
  3431 		$original_setting_capabilities = array();
  3367 		foreach ( $changeset_setting_ids as $setting_id ) {
  3432 		foreach ( $changeset_setting_ids as $setting_id ) {
  3368 			$setting = $this->get_setting( $setting_id );
  3433 			$setting = $this->get_setting( $setting_id );
  3369 			if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) {
  3434 			if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) {
  3370 				$original_setting_capabilities[ $setting->id ] = $setting->capability;
  3435 				$original_setting_capabilities[ $setting->id ] = $setting->capability;
  3371 				$setting->capability = 'exist';
  3436 				$setting->capability                           = 'exist';
  3372 			}
  3437 			}
  3373 		}
  3438 		}
  3374 
  3439 
  3375 		$original_user_id = get_current_user_id();
  3440 		$original_user_id = get_current_user_id();
  3376 		foreach ( $changeset_setting_ids as $setting_id ) {
  3441 		foreach ( $changeset_setting_ids as $setting_id ) {
  3433 			if ( false !== strpos( $revision->post_name, "{$changeset_post_id}-autosave" ) ) {
  3498 			if ( false !== strpos( $revision->post_name, "{$changeset_post_id}-autosave" ) ) {
  3434 				$wpdb->update(
  3499 				$wpdb->update(
  3435 					$wpdb->posts,
  3500 					$wpdb->posts,
  3436 					array(
  3501 					array(
  3437 						'post_status' => 'auto-draft',
  3502 						'post_status' => 'auto-draft',
  3438 						'post_type' => 'customize_changeset',
  3503 						'post_type'   => 'customize_changeset',
  3439 						'post_name' => wp_generate_uuid4(),
  3504 						'post_name'   => wp_generate_uuid4(),
  3440 						'post_parent' => 0,
  3505 						'post_parent' => 0,
  3441 					),
  3506 					),
  3442 					array(
  3507 					array(
  3443 						'ID' => $revision->ID,
  3508 						'ID' => $revision->ID,
  3444 					)
  3509 					)
  3478 				$theme_mod_settings
  3543 				$theme_mod_settings
  3479 			);
  3544 			);
  3480 		}
  3545 		}
  3481 
  3546 
  3482 		$autoload = false;
  3547 		$autoload = false;
  3483 		$result = update_option( 'customize_stashed_theme_mods', $stashed_theme_mod_settings, $autoload );
  3548 		$result   = update_option( 'customize_stashed_theme_mods', $stashed_theme_mod_settings, $autoload );
  3484 		if ( ! $result ) {
  3549 		if ( ! $result ) {
  3485 			return false;
  3550 			return false;
  3486 		}
  3551 		}
  3487 		return $stashed_theme_mod_settings;
  3552 		return $stashed_theme_mod_settings;
  3488 	}
  3553 	}
  3518 		if ( ! check_ajax_referer( 'customize_dismiss_autosave_or_lock', 'nonce', false ) ) {
  3583 		if ( ! check_ajax_referer( 'customize_dismiss_autosave_or_lock', 'nonce', false ) ) {
  3519 			wp_send_json_error( 'invalid_nonce', 403 );
  3584 			wp_send_json_error( 'invalid_nonce', 403 );
  3520 		}
  3585 		}
  3521 
  3586 
  3522 		$changeset_post_id = $this->changeset_post_id();
  3587 		$changeset_post_id = $this->changeset_post_id();
  3523 		$dismiss_lock = ! empty( $_POST['dismiss_lock'] );
  3588 		$dismiss_lock      = ! empty( $_POST['dismiss_lock'] );
  3524 		$dismiss_autosave = ! empty( $_POST['dismiss_autosave'] );
  3589 		$dismiss_autosave  = ! empty( $_POST['dismiss_autosave'] );
  3525 
  3590 
  3526 		if ( $dismiss_lock ) {
  3591 		if ( $dismiss_lock ) {
  3527 			if ( empty( $changeset_post_id ) && ! $dismiss_autosave ) {
  3592 			if ( empty( $changeset_post_id ) && ! $dismiss_autosave ) {
  3528 				wp_send_json_error( 'no_changeset_to_dismiss_lock', 404 );
  3593 				wp_send_json_error( 'no_changeset_to_dismiss_lock', 404 );
  3529 			}
  3594 			}
  3577 	 * @param WP_Customize_Setting|string $id   Customize Setting object, or ID.
  3642 	 * @param WP_Customize_Setting|string $id   Customize Setting object, or ID.
  3578 	 * @param array                       $args {
  3643 	 * @param array                       $args {
  3579 	 *  Optional. Array of properties for the new WP_Customize_Setting. Default empty array.
  3644 	 *  Optional. Array of properties for the new WP_Customize_Setting. Default empty array.
  3580 	 *
  3645 	 *
  3581 	 *  @type string       $type                  Type of the setting. Default 'theme_mod'.
  3646 	 *  @type string       $type                  Type of the setting. Default 'theme_mod'.
  3582 	 *                                            Default 160.
       
  3583 	 *  @type string       $capability            Capability required for the setting. Default 'edit_theme_options'
  3647 	 *  @type string       $capability            Capability required for the setting. Default 'edit_theme_options'
  3584 	 *  @type string|array $theme_supports        Theme features required to support the panel. Default is none.
  3648 	 *  @type string|array $theme_supports        Theme features required to support the panel. Default is none.
  3585 	 *  @type string       $default               Default value for the setting. Default is empty string.
  3649 	 *  @type string       $default               Default value for the setting. Default is empty string.
  3586 	 *  @type string       $transport             Options for rendering the live preview of changes in Theme Customizer.
  3650 	 *  @type string       $transport             Options for rendering the live preview of changes in Customizer.
  3587 	 *                                            Using 'refresh' makes the change visible by reloading the whole preview.
  3651 	 *                                            Using 'refresh' makes the change visible by reloading the whole preview.
  3588 	 *                                            Using 'postMessage' allows a custom JavaScript to handle live changes.
  3652 	 *                                            Using 'postMessage' allows a custom JavaScript to handle live changes.
  3589 	 *                                            @link https://developer.wordpress.org/themes/customize-api
  3653 	 * @link https://developer.wordpress.org/themes/customize-api
  3590 	 *                                            Default is 'refresh'
  3654 	 *                                            Default is 'refresh'
  3591 	 *  @type callable     $validate_callback     Server-side validation callback for the setting's value.
  3655 	 *  @type callable     $validate_callback     Server-side validation callback for the setting's value.
  3592 	 *  @type callable     $sanitize_callback     Callback to filter a Customize setting value in un-slashed form.
  3656 	 *  @type callable     $sanitize_callback     Callback to filter a Customize setting value in un-slashed form.
  3593 	 *  @type callable     $sanitize_js_callback  Callback to convert a Customize PHP setting value to a value that is
  3657 	 *  @type callable     $sanitize_js_callback  Callback to convert a Customize PHP setting value to a value that is
  3594 	 *                                            JSON serializable.
  3658 	 *                                            JSON serializable.
  3635 			// Skip settings already created
  3699 			// Skip settings already created
  3636 			if ( $this->get_setting( $setting_id ) ) {
  3700 			if ( $this->get_setting( $setting_id ) ) {
  3637 				continue;
  3701 				continue;
  3638 			}
  3702 			}
  3639 
  3703 
  3640 			$setting_args = false;
  3704 			$setting_args  = false;
  3641 			$setting_class = 'WP_Customize_Setting';
  3705 			$setting_class = 'WP_Customize_Setting';
  3642 
  3706 
  3643 			/**
  3707 			/**
  3644 			 * Filters a dynamic setting's constructor args.
  3708 			 * Filters a dynamic setting's constructor args.
  3645 			 *
  3709 			 *
  3755 	 */
  3819 	 */
  3756 	public function remove_panel( $id ) {
  3820 	public function remove_panel( $id ) {
  3757 		// Removing core components this way is _doing_it_wrong().
  3821 		// Removing core components this way is _doing_it_wrong().
  3758 		if ( in_array( $id, $this->components, true ) ) {
  3822 		if ( in_array( $id, $this->components, true ) ) {
  3759 			/* translators: 1: panel id, 2: link to 'customize_loaded_components' filter reference */
  3823 			/* translators: 1: panel id, 2: link to 'customize_loaded_components' filter reference */
  3760 			$message = sprintf( __( 'Removing %1$s manually will cause PHP warnings. Use the %2$s filter instead.' ),
  3824 			$message = sprintf(
       
  3825 				__( 'Removing %1$s manually will cause PHP warnings. Use the %2$s filter instead.' ),
  3761 				$id,
  3826 				$id,
  3762 				'<a href="' . esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ) . '"><code>customize_loaded_components</code></a>'
  3827 				'<a href="' . esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ) . '"><code>customize_loaded_components</code></a>'
  3763 			);
  3828 			);
  3764 
  3829 
  3765 			_doing_it_wrong( __METHOD__, $message, '4.5.0' );
  3830 			_doing_it_wrong( __METHOD__, $message, '4.5.0' );
  3834 	 *
  3899 	 *
  3835 	 * @param string $id Section ID.
  3900 	 * @param string $id Section ID.
  3836 	 * @return WP_Customize_Section|void The section, if set.
  3901 	 * @return WP_Customize_Section|void The section, if set.
  3837 	 */
  3902 	 */
  3838 	public function get_section( $id ) {
  3903 	public function get_section( $id ) {
  3839 		if ( isset( $this->sections[ $id ] ) )
  3904 		if ( isset( $this->sections[ $id ] ) ) {
  3840 			return $this->sections[ $id ];
  3905 			return $this->sections[ $id ];
       
  3906 		}
  3841 	}
  3907 	}
  3842 
  3908 
  3843 	/**
  3909 	/**
  3844 	 * Remove a customize section.
  3910 	 * Remove a customize section.
  3845 	 *
  3911 	 *
  3925 	 *
  3991 	 *
  3926 	 * @param string $id ID of the control.
  3992 	 * @param string $id ID of the control.
  3927 	 * @return WP_Customize_Control|void The control object, if set.
  3993 	 * @return WP_Customize_Control|void The control object, if set.
  3928 	 */
  3994 	 */
  3929 	public function get_control( $id ) {
  3995 	public function get_control( $id ) {
  3930 		if ( isset( $this->controls[ $id ] ) )
  3996 		if ( isset( $this->controls[ $id ] ) ) {
  3931 			return $this->controls[ $id ];
  3997 			return $this->controls[ $id ];
       
  3998 		}
  3932 	}
  3999 	}
  3933 
  4000 
  3934 	/**
  4001 	/**
  3935 	 * Remove a customize control.
  4002 	 * Remove a customize control.
  3936 	 *
  4003 	 *
  3963 	 */
  4030 	 */
  3964 	public function render_control_templates() {
  4031 	public function render_control_templates() {
  3965 		if ( $this->branching() ) {
  4032 		if ( $this->branching() ) {
  3966 			$l10n = array(
  4033 			$l10n = array(
  3967 				/* translators: %s: User who is customizing the changeset in customizer. */
  4034 				/* translators: %s: User who is customizing the changeset in customizer. */
  3968 				'locked' => __( '%s is already customizing this changeset. Please wait until they are done to try customizing. Your latest changes have been autosaved.' ),
  4035 				'locked'                => __( '%s is already customizing this changeset. Please wait until they are done to try customizing. Your latest changes have been autosaved.' ),
  3969 				/* translators: %s: User who is customizing the changeset in customizer. */
  4036 				/* translators: %s: User who is customizing the changeset in customizer. */
  3970 				'locked_allow_override' => __( '%s is already customizing this changeset. Do you want to take over?' ),
  4037 				'locked_allow_override' => __( '%s is already customizing this changeset. Do you want to take over?' ),
  3971 			);
  4038 			);
  3972 		} else {
  4039 		} else {
  3973 			$l10n = array(
  4040 			$l10n = array(
  3974 				/* translators: %s: User who is customizing the changeset in customizer. */
  4041 				/* translators: %s: User who is customizing the changeset in customizer. */
  3975 				'locked' => __( '%s is already customizing this site. Please wait until they are done to try customizing. Your latest changes have been autosaved.' ),
  4042 				'locked'                => __( '%s is already customizing this site. Please wait until they are done to try customizing. Your latest changes have been autosaved.' ),
  3976 				/* translators: %s: User who is customizing the changeset in customizer. */
  4043 				/* translators: %s: User who is customizing the changeset in customizer. */
  3977 				'locked_allow_override' => __( '%s is already customizing this site. Do you want to take over?' ),
  4044 				'locked_allow_override' => __( '%s is already customizing this site. Do you want to take over?' ),
  3978 			);
  4045 			);
  3979 		}
  4046 		}
  3980 
  4047 
  3981 		foreach ( $this->registered_control_types as $control_type ) {
  4048 		foreach ( $this->registered_control_types as $control_type ) {
  3982 			$control = new $control_type( $this, 'temp', array(
  4049 			$control = new $control_type(
  3983 				'settings' => array(),
  4050 				$this,
  3984 			) );
  4051 				'temp',
       
  4052 				array(
       
  4053 					'settings' => array(),
       
  4054 				)
       
  4055 			);
  3985 			$control->print_template();
  4056 			$control->print_template();
  3986 		}
  4057 		}
  3987 		?>
  4058 		?>
  3988 
  4059 
  3989 		<script type="text/html" id="tmpl-customize-control-default-content">
  4060 		<script type="text/html" id="tmpl-customize-control-default-content">
  4194 			<div class="customize-control-notifications-container"></div>
  4265 			<div class="customize-control-notifications-container"></div>
  4195 			<div class="preview-link-wrapper">
  4266 			<div class="preview-link-wrapper">
  4196 				<label for="{{ elementPrefix }}customize-preview-link-input" class="screen-reader-text"><?php esc_html_e( 'Preview Link' ); ?></label>
  4267 				<label for="{{ elementPrefix }}customize-preview-link-input" class="screen-reader-text"><?php esc_html_e( 'Preview Link' ); ?></label>
  4197 				<a href="" target="">
  4268 				<a href="" target="">
  4198 					<span class="preview-control-element" data-component="url"></span>
  4269 					<span class="preview-control-element" data-component="url"></span>
  4199 					<span class="screen-reader-text"><?php _e( '(opens in a new window)' ); ?></span>
  4270 					<span class="screen-reader-text"><?php _e( '(opens in a new tab)' ); ?></span>
  4200 				</a>
  4271 				</a>
  4201 				<input id="{{ elementPrefix }}customize-preview-link-input" readonly tabindex="-1" class="preview-control-element" data-component="input">
  4272 				<input id="{{ elementPrefix }}customize-preview-link-input" readonly tabindex="-1" class="preview-control-element" data-component="input">
  4202 				<button class="customize-copy-preview-link preview-control-element button button-secondary" data-component="button" data-copy-text="<?php esc_attr_e( 'Copy' ); ?>" data-copied-text="<?php esc_attr_e( 'Copied' ); ?>" ><?php esc_html_e( 'Copy' ); ?></button>
  4273 				<button class="customize-copy-preview-link preview-control-element button button-secondary" data-component="button" data-copy-text="<?php esc_attr_e( 'Copy' ); ?>" data-copied-text="<?php esc_attr_e( 'Copied' ); ?>" ><?php esc_html_e( 'Copy' ); ?></button>
  4203 			</div>
  4274 			</div>
  4204 		</script>
  4275 		</script>
  4251 	 *
  4322 	 *
  4252 	 * @since 3.4.0
  4323 	 * @since 3.4.0
  4253 	 */
  4324 	 */
  4254 	public function prepare_controls() {
  4325 	public function prepare_controls() {
  4255 
  4326 
  4256 		$controls = array();
  4327 		$controls       = array();
  4257 		$this->controls = wp_list_sort( $this->controls, array(
  4328 		$this->controls = wp_list_sort(
  4258 			'priority'        => 'ASC',
  4329 			$this->controls,
  4259 			'instance_number' => 'ASC',
  4330 			array(
  4260 		), 'ASC', true );
  4331 				'priority'        => 'ASC',
       
  4332 				'instance_number' => 'ASC',
       
  4333 			),
       
  4334 			'ASC',
       
  4335 			true
       
  4336 		);
  4261 
  4337 
  4262 		foreach ( $this->controls as $id => $control ) {
  4338 		foreach ( $this->controls as $id => $control ) {
  4263 			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
  4339 			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
  4264 				continue;
  4340 				continue;
  4265 			}
  4341 			}
  4266 
  4342 
  4267 			$this->sections[ $control->section ]->controls[] = $control;
  4343 			$this->sections[ $control->section ]->controls[] = $control;
  4268 			$controls[ $id ] = $control;
  4344 			$controls[ $id ]                                 = $control;
  4269 		}
  4345 		}
  4270 		$this->controls = $controls;
  4346 		$this->controls = $controls;
  4271 
  4347 
  4272 		// Prepare sections.
  4348 		// Prepare sections.
  4273 		$this->sections = wp_list_sort( $this->sections, array(
  4349 		$this->sections = wp_list_sort(
  4274 			'priority'        => 'ASC',
  4350 			$this->sections,
  4275 			'instance_number' => 'ASC',
  4351 			array(
  4276 		), 'ASC', true );
  4352 				'priority'        => 'ASC',
  4277 		$sections = array();
  4353 				'instance_number' => 'ASC',
       
  4354 			),
       
  4355 			'ASC',
       
  4356 			true
       
  4357 		);
       
  4358 		$sections       = array();
  4278 
  4359 
  4279 		foreach ( $this->sections as $section ) {
  4360 		foreach ( $this->sections as $section ) {
  4280 			if ( ! $section->check_capabilities() ) {
  4361 			if ( ! $section->check_capabilities() ) {
  4281 				continue;
  4362 				continue;
  4282 			}
  4363 			}
  4283 
  4364 
  4284 
  4365 			$section->controls = wp_list_sort(
  4285 			$section->controls = wp_list_sort( $section->controls, array(
  4366 				$section->controls,
  4286 				'priority'        => 'ASC',
  4367 				array(
  4287 				'instance_number' => 'ASC',
  4368 					'priority'        => 'ASC',
  4288 			) );
  4369 					'instance_number' => 'ASC',
       
  4370 				)
       
  4371 			);
  4289 
  4372 
  4290 			if ( ! $section->panel ) {
  4373 			if ( ! $section->panel ) {
  4291 				// Top-level section.
  4374 				// Top-level section.
  4292 				$sections[ $section->id ] = $section;
  4375 				$sections[ $section->id ] = $section;
  4293 			} else {
  4376 			} else {
  4298 			}
  4381 			}
  4299 		}
  4382 		}
  4300 		$this->sections = $sections;
  4383 		$this->sections = $sections;
  4301 
  4384 
  4302 		// Prepare panels.
  4385 		// Prepare panels.
  4303 		$this->panels = wp_list_sort( $this->panels, array(
  4386 		$this->panels = wp_list_sort(
  4304 			'priority'        => 'ASC',
  4387 			$this->panels,
  4305 			'instance_number' => 'ASC',
  4388 			array(
  4306 		), 'ASC', true );
  4389 				'priority'        => 'ASC',
  4307 		$panels = array();
  4390 				'instance_number' => 'ASC',
       
  4391 			),
       
  4392 			'ASC',
       
  4393 			true
       
  4394 		);
       
  4395 		$panels       = array();
  4308 
  4396 
  4309 		foreach ( $this->panels as $panel ) {
  4397 		foreach ( $this->panels as $panel ) {
  4310 			if ( ! $panel->check_capabilities() ) {
  4398 			if ( ! $panel->check_capabilities() ) {
  4311 				continue;
  4399 				continue;
  4312 			}
  4400 			}
  4313 
  4401 
  4314 			$panel->sections = wp_list_sort( $panel->sections, array(
  4402 			$panel->sections      = wp_list_sort(
       
  4403 				$panel->sections,
       
  4404 				array(
       
  4405 					'priority'        => 'ASC',
       
  4406 					'instance_number' => 'ASC',
       
  4407 				),
       
  4408 				'ASC',
       
  4409 				true
       
  4410 			);
       
  4411 			$panels[ $panel->id ] = $panel;
       
  4412 		}
       
  4413 		$this->panels = $panels;
       
  4414 
       
  4415 		// Sort panels and top-level sections together.
       
  4416 		$this->containers = array_merge( $this->panels, $this->sections );
       
  4417 		$this->containers = wp_list_sort(
       
  4418 			$this->containers,
       
  4419 			array(
  4315 				'priority'        => 'ASC',
  4420 				'priority'        => 'ASC',
  4316 				'instance_number' => 'ASC',
  4421 				'instance_number' => 'ASC',
  4317 			), 'ASC', true );
  4422 			),
  4318 			$panels[ $panel->id ] = $panel;
  4423 			'ASC',
  4319 		}
  4424 			true
  4320 		$this->panels = $panels;
  4425 		);
  4321 
       
  4322 		// Sort panels and top-level sections together.
       
  4323 		$this->containers = array_merge( $this->panels, $this->sections );
       
  4324 		$this->containers = wp_list_sort( $this->containers, array(
       
  4325 			'priority'        => 'ASC',
       
  4326 			'instance_number' => 'ASC',
       
  4327 		), 'ASC', true );
       
  4328 	}
  4426 	}
  4329 
  4427 
  4330 	/**
  4428 	/**
  4331 	 * Enqueue scripts for customize controls.
  4429 	 * Enqueue scripts for customize controls.
  4332 	 *
  4430 	 *
  4337 			$control->enqueue();
  4435 			$control->enqueue();
  4338 		}
  4436 		}
  4339 
  4437 
  4340 		if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) {
  4438 		if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) {
  4341 			wp_enqueue_script( 'updates' );
  4439 			wp_enqueue_script( 'updates' );
  4342 			wp_localize_script( 'updates', '_wpUpdatesItemCounts', array(
  4440 			wp_localize_script(
  4343 				'totals' => wp_get_update_data(),
  4441 				'updates',
  4344 			) );
  4442 				'_wpUpdatesItemCounts',
       
  4443 				array(
       
  4444 					'totals' => wp_get_update_data(),
       
  4445 				)
       
  4446 			);
  4345 		}
  4447 		}
  4346 	}
  4448 	}
  4347 
  4449 
  4348 	/**
  4450 	/**
  4349 	 * Determine whether the user agent is iOS.
  4451 	 * Determine whether the user agent is iOS.
  4383 	 * @since 4.4.0
  4485 	 * @since 4.4.0
  4384 	 *
  4486 	 *
  4385 	 * @param string $preview_url URL to be previewed.
  4487 	 * @param string $preview_url URL to be previewed.
  4386 	 */
  4488 	 */
  4387 	public function set_preview_url( $preview_url ) {
  4489 	public function set_preview_url( $preview_url ) {
  4388 		$preview_url = esc_url_raw( $preview_url );
  4490 		$preview_url       = esc_url_raw( $preview_url );
  4389 		$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
  4491 		$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
  4390 	}
  4492 	}
  4391 
  4493 
  4392 	/**
  4494 	/**
  4393 	 * Get the initial URL to be previewed.
  4495 	 * Get the initial URL to be previewed.
  4412 	 *
  4514 	 *
  4413 	 * @return bool Whether cross-domain.
  4515 	 * @return bool Whether cross-domain.
  4414 	 */
  4516 	 */
  4415 	public function is_cross_domain() {
  4517 	public function is_cross_domain() {
  4416 		$admin_origin = wp_parse_url( admin_url() );
  4518 		$admin_origin = wp_parse_url( admin_url() );
  4417 		$home_origin = wp_parse_url( home_url() );
  4519 		$home_origin  = wp_parse_url( home_url() );
  4418 		$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
  4520 		$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
  4419 		return $cross_domain;
  4521 		return $cross_domain;
  4420 	}
  4522 	}
  4421 
  4523 
  4422 	/**
  4524 	/**
  4443 		/**
  4545 		/**
  4444 		 * Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
  4546 		 * Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
  4445 		 *
  4547 		 *
  4446 		 * @since 3.4.0
  4548 		 * @since 3.4.0
  4447 		 *
  4549 		 *
  4448 		 * @param array $allowed_urls An array of allowed URLs.
  4550 		 * @param string[] $allowed_urls An array of allowed URLs.
  4449 		 */
  4551 		 */
  4450 		$allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
  4552 		$allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
  4451 
  4553 
  4452 		return $allowed_urls;
  4554 		return $allowed_urls;
  4453 	}
  4555 	}
  4471 	 * @since 4.4.0
  4573 	 * @since 4.4.0
  4472 	 *
  4574 	 *
  4473 	 * @param string $return_url URL for return link.
  4575 	 * @param string $return_url URL for return link.
  4474 	 */
  4576 	 */
  4475 	public function set_return_url( $return_url ) {
  4577 	public function set_return_url( $return_url ) {
  4476 		$return_url = esc_url_raw( $return_url );
  4578 		$return_url       = esc_url_raw( $return_url );
  4477 		$return_url = remove_query_arg( wp_removable_query_args(), $return_url );
  4579 		$return_url       = remove_query_arg( wp_removable_query_args(), $return_url );
  4478 		$return_url = wp_validate_redirect( $return_url );
  4580 		$return_url       = wp_validate_redirect( $return_url );
  4479 		$this->return_url = $return_url;
  4581 		$this->return_url = $return_url;
  4480 	}
  4582 	}
  4481 
  4583 
  4482 	/**
  4584 	/**
  4483 	 * Get URL to link the user to when closing the Customizer.
  4585 	 * Get URL to link the user to when closing the Customizer.
  4485 	 * @since 4.4.0
  4587 	 * @since 4.4.0
  4486 	 *
  4588 	 *
  4487 	 * @return string URL for link to close Customizer.
  4589 	 * @return string URL for link to close Customizer.
  4488 	 */
  4590 	 */
  4489 	public function get_return_url() {
  4591 	public function get_return_url() {
  4490 		$referer = wp_get_referer();
  4592 		$referer                    = wp_get_referer();
  4491 		$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
  4593 		$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
  4492 
  4594 
  4493 		if ( $this->return_url ) {
  4595 		if ( $this->return_url ) {
  4494 			$return_url = $this->return_url;
  4596 			$return_url = $this->return_url;
  4495 		} else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
  4597 		} elseif ( $referer && ! in_array( wp_basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
  4496 			$return_url = $referer;
  4598 			$return_url = $referer;
  4497 		} else if ( $this->preview_url ) {
  4599 		} elseif ( $this->preview_url ) {
  4498 			$return_url = $this->preview_url;
  4600 			$return_url = $this->preview_url;
  4499 		} else {
  4601 		} else {
  4500 			$return_url = home_url( '/' );
  4602 			$return_url = home_url( '/' );
  4501 		}
  4603 		}
  4502 		return $return_url;
  4604 		return $return_url;
  4543 	 *
  4645 	 *
  4544 	 * @return array Nonces.
  4646 	 * @return array Nonces.
  4545 	 */
  4647 	 */
  4546 	public function get_nonces() {
  4648 	public function get_nonces() {
  4547 		$nonces = array(
  4649 		$nonces = array(
  4548 			'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
  4650 			'save'                     => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
  4549 			'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
  4651 			'preview'                  => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
  4550 			'switch_themes' => wp_create_nonce( 'switch_themes' ),
  4652 			'switch_themes'            => wp_create_nonce( 'switch_themes' ),
  4551 			'dismiss_autosave_or_lock' => wp_create_nonce( 'customize_dismiss_autosave_or_lock' ),
  4653 			'dismiss_autosave_or_lock' => wp_create_nonce( 'customize_dismiss_autosave_or_lock' ),
  4552 			'override_lock' => wp_create_nonce( 'customize_override_changeset_lock' ),
  4654 			'override_lock'            => wp_create_nonce( 'customize_override_changeset_lock' ),
  4553 			'trash' => wp_create_nonce( 'trash_customize_changeset' ),
  4655 			'trash'                    => wp_create_nonce( 'trash_customize_changeset' ),
  4554 		);
  4656 		);
  4555 
  4657 
  4556 		/**
  4658 		/**
  4557 		 * Filters nonces for Customizer.
  4659 		 * Filters nonces for Customizer.
  4558 		 *
  4660 		 *
  4559 		 * @since 4.2.0
  4661 		 * @since 4.2.0
  4560 		 *
  4662 		 *
  4561 		 * @param array                $nonces Array of refreshed nonces for save and
  4663 		 * @param string[]             $nonces Array of refreshed nonces for save and
  4562 		 *                                     preview actions.
  4664 		 *                                     preview actions.
  4563 		 * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
  4665 		 * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
  4564 		 */
  4666 		 */
  4565 		$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
  4667 		$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
  4566 
  4668 
  4572 	 *
  4674 	 *
  4573 	 * @since 4.4.0
  4675 	 * @since 4.4.0
  4574 	 */
  4676 	 */
  4575 	public function customize_pane_settings() {
  4677 	public function customize_pane_settings() {
  4576 
  4678 
  4577 		$login_url = add_query_arg( array(
  4679 		$login_url = add_query_arg(
  4578 			'interim-login' => 1,
  4680 			array(
  4579 			'customize-login' => 1,
  4681 				'interim-login'   => 1,
  4580 		), wp_login_url() );
  4682 				'customize-login' => 1,
       
  4683 			),
       
  4684 			wp_login_url()
       
  4685 		);
  4581 
  4686 
  4582 		// Ensure dirty flags are set for modified settings.
  4687 		// Ensure dirty flags are set for modified settings.
  4583 		foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) {
  4688 		foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) {
  4584 			$setting = $this->get_setting( $setting_id );
  4689 			$setting = $this->get_setting( $setting_id );
  4585 			if ( $setting ) {
  4690 			if ( $setting ) {
  4586 				$setting->dirty = true;
  4691 				$setting->dirty = true;
  4587 			}
  4692 			}
  4588 		}
  4693 		}
  4589 
  4694 
  4590 		$autosave_revision_post = null;
  4695 		$autosave_revision_post  = null;
  4591 		$autosave_autodraft_post = null;
  4696 		$autosave_autodraft_post = null;
  4592 		$changeset_post_id = $this->changeset_post_id();
  4697 		$changeset_post_id       = $this->changeset_post_id();
  4593 		if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) {
  4698 		if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) {
  4594 			if ( $changeset_post_id ) {
  4699 			if ( $changeset_post_id ) {
  4595 				if ( is_user_logged_in() ) {
  4700 				if ( is_user_logged_in() ) {
  4596 					$autosave_revision_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
  4701 					$autosave_revision_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
  4597 				}
  4702 				}
  4598 			} else {
  4703 			} else {
  4599 				$autosave_autodraft_posts = $this->get_changeset_posts( array(
  4704 				$autosave_autodraft_posts = $this->get_changeset_posts(
  4600 					'posts_per_page' => 1,
  4705 					array(
  4601 					'post_status' => 'auto-draft',
  4706 						'posts_per_page'            => 1,
  4602 					'exclude_restore_dismissed' => true,
  4707 						'post_status'               => 'auto-draft',
  4603 				) );
  4708 						'exclude_restore_dismissed' => true,
       
  4709 					)
       
  4710 				);
  4604 				if ( ! empty( $autosave_autodraft_posts ) ) {
  4711 				if ( ! empty( $autosave_autodraft_posts ) ) {
  4605 					$autosave_autodraft_post = array_shift( $autosave_autodraft_posts );
  4712 					$autosave_autodraft_post = array_shift( $autosave_autodraft_posts );
  4606 				}
  4713 				}
  4607 			}
  4714 			}
  4608 		}
  4715 		}
  4612 		// @todo Include all of the status labels here from script-loader.php, and then allow it to be filtered.
  4719 		// @todo Include all of the status labels here from script-loader.php, and then allow it to be filtered.
  4613 		$status_choices = array();
  4720 		$status_choices = array();
  4614 		if ( $current_user_can_publish ) {
  4721 		if ( $current_user_can_publish ) {
  4615 			$status_choices[] = array(
  4722 			$status_choices[] = array(
  4616 				'status' => 'publish',
  4723 				'status' => 'publish',
  4617 				'label' => __( 'Publish' ),
  4724 				'label'  => __( 'Publish' ),
  4618 			);
  4725 			);
  4619 		}
  4726 		}
  4620 		$status_choices[] = array(
  4727 		$status_choices[] = array(
  4621 			'status' => 'draft',
  4728 			'status' => 'draft',
  4622 			'label' => __( 'Save Draft' ),
  4729 			'label'  => __( 'Save Draft' ),
  4623 		);
  4730 		);
  4624 		if ( $current_user_can_publish ) {
  4731 		if ( $current_user_can_publish ) {
  4625 			$status_choices[] = array(
  4732 			$status_choices[] = array(
  4626 				'status' => 'future',
  4733 				'status' => 'future',
  4627 				'label' => _x( 'Schedule', 'customizer changeset action/button label' ),
  4734 				'label'  => _x( 'Schedule', 'customizer changeset action/button label' ),
  4628 			);
  4735 			);
  4629 		}
  4736 		}
  4630 
  4737 
  4631 		// Prepare Customizer settings to pass to JavaScript.
  4738 		// Prepare Customizer settings to pass to JavaScript.
  4632 		$changeset_post = null;
  4739 		$changeset_post = null;
  4648 		if ( $this->changeset_post_id() ) {
  4755 		if ( $this->changeset_post_id() ) {
  4649 			$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
  4756 			$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
  4650 		}
  4757 		}
  4651 
  4758 
  4652 		$settings = array(
  4759 		$settings = array(
  4653 			'changeset' => array(
  4760 			'changeset'              => array(
  4654 				'uuid' => $this->changeset_uuid(),
  4761 				'uuid'                  => $this->changeset_uuid(),
  4655 				'branching' => $this->branching(),
  4762 				'branching'             => $this->branching(),
  4656 				'autosaved' => $this->autosaved(),
  4763 				'autosaved'             => $this->autosaved(),
  4657 				'hasAutosaveRevision' => ! empty( $autosave_revision_post ),
  4764 				'hasAutosaveRevision'   => ! empty( $autosave_revision_post ),
  4658 				'latestAutoDraftUuid' => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null,
  4765 				'latestAutoDraftUuid'   => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null,
  4659 				'status' => $changeset_post ? $changeset_post->post_status : '',
  4766 				'status'                => $changeset_post ? $changeset_post->post_status : '',
  4660 				'currentUserCanPublish' => $current_user_can_publish,
  4767 				'currentUserCanPublish' => $current_user_can_publish,
  4661 				'publishDate' => $initial_date,
  4768 				'publishDate'           => $initial_date,
  4662 				'statusChoices' => $status_choices,
  4769 				'statusChoices'         => $status_choices,
  4663 				'lockUser' => $lock_user_id ? $this->get_lock_user_data( $lock_user_id ) : null,
  4770 				'lockUser'              => $lock_user_id ? $this->get_lock_user_data( $lock_user_id ) : null,
  4664 			),
  4771 			),
  4665 			'initialServerDate' => $current_time,
  4772 			'initialServerDate'      => $current_time,
  4666 			'dateFormat' => get_option( 'date_format' ),
  4773 			'dateFormat'             => get_option( 'date_format' ),
  4667 			'timeFormat' => get_option( 'time_format' ),
  4774 			'timeFormat'             => get_option( 'time_format' ),
  4668 			'initialServerTimestamp' => floor( microtime( true ) * 1000 ),
  4775 			'initialServerTimestamp' => floor( microtime( true ) * 1000 ),
  4669 			'initialClientTimestamp' => -1, // To be set with JS below.
  4776 			'initialClientTimestamp' => -1, // To be set with JS below.
  4670 			'timeouts' => array(
  4777 			'timeouts'               => array(
  4671 				'windowRefresh' => 250,
  4778 				'windowRefresh'           => 250,
  4672 				'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000,
  4779 				'changesetAutoSave'       => AUTOSAVE_INTERVAL * 1000,
  4673 				'keepAliveCheck' => 2500,
  4780 				'keepAliveCheck'          => 2500,
  4674 				'reflowPaneContents' => 100,
  4781 				'reflowPaneContents'      => 100,
  4675 				'previewFrameSensitivity' => 2000,
  4782 				'previewFrameSensitivity' => 2000,
  4676 			),
  4783 			),
  4677 			'theme'    => array(
  4784 			'theme'                  => array(
  4678 				'stylesheet'  => $this->get_stylesheet(),
  4785 				'stylesheet'  => $this->get_stylesheet(),
  4679 				'active'      => $this->is_theme_active(),
  4786 				'active'      => $this->is_theme_active(),
  4680 				'_canInstall' => current_user_can( 'install_themes' ),
  4787 				'_canInstall' => current_user_can( 'install_themes' ),
  4681 			),
  4788 			),
  4682 			'url'      => array(
  4789 			'url'                    => array(
  4683 				'preview'       => esc_url_raw( $this->get_preview_url() ),
  4790 				'preview'       => esc_url_raw( $this->get_preview_url() ),
  4684 				'return'        => esc_url_raw( $this->get_return_url() ),
  4791 				'return'        => esc_url_raw( $this->get_return_url() ),
  4685 				'parent'        => esc_url_raw( admin_url() ),
  4792 				'parent'        => esc_url_raw( admin_url() ),
  4686 				'activated'     => esc_url_raw( home_url( '/' ) ),
  4793 				'activated'     => esc_url_raw( home_url( '/' ) ),
  4687 				'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
  4794 				'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
  4688 				'allowed'       => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
  4795 				'allowed'       => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
  4689 				'isCrossDomain' => $this->is_cross_domain(),
  4796 				'isCrossDomain' => $this->is_cross_domain(),
  4690 				'home'          => esc_url_raw( home_url( '/' ) ),
  4797 				'home'          => esc_url_raw( home_url( '/' ) ),
  4691 				'login'         => esc_url_raw( $login_url ),
  4798 				'login'         => esc_url_raw( $login_url ),
  4692 			),
  4799 			),
  4693 			'browser'  => array(
  4800 			'browser'                => array(
  4694 				'mobile' => wp_is_mobile(),
  4801 				'mobile' => wp_is_mobile(),
  4695 				'ios'    => $this->is_ios(),
  4802 				'ios'    => $this->is_ios(),
  4696 			),
  4803 			),
  4697 			'panels'   => array(),
  4804 			'panels'                 => array(),
  4698 			'sections' => array(),
  4805 			'sections'               => array(),
  4699 			'nonce'    => $this->get_nonces(),
  4806 			'nonce'                  => $this->get_nonces(),
  4700 			'autofocus' => $this->get_autofocus(),
  4807 			'autofocus'              => $this->get_autofocus(),
  4701 			'documentTitleTmpl' => $this->get_document_title_template(),
  4808 			'documentTitleTmpl'      => $this->get_document_title_template(),
  4702 			'previewableDevices' => $this->get_previewable_devices(),
  4809 			'previewableDevices'     => $this->get_previewable_devices(),
  4703 			'l10n' => array(
  4810 			'l10n'                   => array(
  4704 				'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ),
  4811 				'confirmDeleteTheme'   => __( 'Are you sure you want to delete this theme?' ),
  4705 				/* translators: %d: number of theme search results, which cannot currently consider singular vs. plural forms */
  4812 				/* translators: %d: number of theme search results, which cannot currently consider singular vs. plural forms */
  4706 				'themeSearchResults' => __( '%d themes found' ),
  4813 				'themeSearchResults'   => __( '%d themes found' ),
  4707 				/* translators: %d: number of themes being displayed, which cannot currently consider singular vs. plural forms */
  4814 				/* translators: %d: number of themes being displayed, which cannot currently consider singular vs. plural forms */
  4708 				'announceThemeCount' => __( 'Displaying %d themes' ),
  4815 				'announceThemeCount'   => __( 'Displaying %d themes' ),
  4709 				/* translators: %s: theme name */
  4816 				/* translators: %s: theme name */
  4710 				'announceThemeDetails' => __( 'Showing details for theme: %s' ),
  4817 				'announceThemeDetails' => __( 'Showing details for theme: %s' ),
  4711 			),
  4818 			),
  4712 		);
  4819 		);
  4713 
  4820 
  4770 						wp_json_encode( $control->json() )
  4877 						wp_json_encode( $control->json() )
  4771 					);
  4878 					);
  4772 				}
  4879 				}
  4773 			}
  4880 			}
  4774 			echo "})( _wpCustomizeSettings.controls );\n";
  4881 			echo "})( _wpCustomizeSettings.controls );\n";
  4775 		?>
  4882 			?>
  4776 		</script>
  4883 		</script>
  4777 		<?php
  4884 		<?php
  4778 	}
  4885 	}
  4779 
  4886 
  4780 	/**
  4887 	/**
  4785 	 * @return array List of devices with labels and default setting.
  4892 	 * @return array List of devices with labels and default setting.
  4786 	 */
  4893 	 */
  4787 	public function get_previewable_devices() {
  4894 	public function get_previewable_devices() {
  4788 		$devices = array(
  4895 		$devices = array(
  4789 			'desktop' => array(
  4896 			'desktop' => array(
  4790 				'label' => __( 'Enter desktop preview mode' ),
  4897 				'label'   => __( 'Enter desktop preview mode' ),
  4791 				'default' => true,
  4898 				'default' => true,
  4792 			),
  4899 			),
  4793 			'tablet' => array(
  4900 			'tablet'  => array(
  4794 				'label' => __( 'Enter tablet preview mode' ),
  4901 				'label' => __( 'Enter tablet preview mode' ),
  4795 			),
  4902 			),
  4796 			'mobile' => array(
  4903 			'mobile'  => array(
  4797 				'label' => __( 'Enter mobile preview mode' ),
  4904 				'label' => __( 'Enter mobile preview mode' ),
  4798 			),
  4905 			),
  4799 		);
  4906 		);
  4800 
  4907 
  4801 		/**
  4908 		/**
  4819 	 */
  4926 	 */
  4820 	public function register_controls() {
  4927 	public function register_controls() {
  4821 
  4928 
  4822 		/* Themes (controls are loaded via ajax) */
  4929 		/* Themes (controls are loaded via ajax) */
  4823 
  4930 
  4824 		$this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array(
  4931 		$this->add_panel(
  4825 			'title'       => $this->theme()->display( 'Name' ),
  4932 			new WP_Customize_Themes_Panel(
  4826 			'description' => (
  4933 				$this,
  4827 				'<p>' . __( 'Looking for a theme? You can search or browse the WordPress.org theme directory, install and preview themes, then activate them right here.' ) . '</p>' .
  4934 				'themes',
  4828 				'<p>' . __( 'While previewing a new theme, you can continue to tailor things like widgets and menus, and explore theme-specific options.' ) . '</p>'
  4935 				array(
  4829 			),
  4936 					'title'       => $this->theme()->display( 'Name' ),
  4830 			'capability'  => 'switch_themes',
  4937 					'description' => (
  4831 			'priority'    => 0,
  4938 					'<p>' . __( 'Looking for a theme? You can search or browse the WordPress.org theme directory, install and preview themes, then activate them right here.' ) . '</p>' .
  4832 		) ) );
  4939 					'<p>' . __( 'While previewing a new theme, you can continue to tailor things like widgets and menus, and explore theme-specific options.' ) . '</p>'
  4833 
  4940 					),
  4834 		$this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array(
  4941 					'capability'  => 'switch_themes',
  4835 			'title'       => __( 'Installed themes' ),
  4942 					'priority'    => 0,
  4836 			'action'      => 'installed',
  4943 				)
  4837 			'capability'  => 'switch_themes',
  4944 			)
  4838 			'panel'       => 'themes',
  4945 		);
  4839 			'priority'    => 0,
  4946 
  4840 		) ) );
  4947 		$this->add_section(
       
  4948 			new WP_Customize_Themes_Section(
       
  4949 				$this,
       
  4950 				'installed_themes',
       
  4951 				array(
       
  4952 					'title'      => __( 'Installed themes' ),
       
  4953 					'action'     => 'installed',
       
  4954 					'capability' => 'switch_themes',
       
  4955 					'panel'      => 'themes',
       
  4956 					'priority'   => 0,
       
  4957 				)
       
  4958 			)
       
  4959 		);
  4841 
  4960 
  4842 		if ( ! is_multisite() ) {
  4961 		if ( ! is_multisite() ) {
  4843 			$this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array(
  4962 			$this->add_section(
  4844 				'title'       => __( 'WordPress.org themes' ),
  4963 				new WP_Customize_Themes_Section(
  4845 				'action'      => 'wporg',
  4964 					$this,
  4846 				'filter_type' => 'remote',
  4965 					'wporg_themes',
  4847 				'capability'  => 'install_themes',
  4966 					array(
  4848 				'panel'       => 'themes',
  4967 						'title'       => __( 'WordPress.org themes' ),
  4849 				'priority'    => 5,
  4968 						'action'      => 'wporg',
  4850 			) ) );
  4969 						'filter_type' => 'remote',
       
  4970 						'capability'  => 'install_themes',
       
  4971 						'panel'       => 'themes',
       
  4972 						'priority'    => 5,
       
  4973 					)
       
  4974 				)
       
  4975 			);
  4851 		}
  4976 		}
  4852 
  4977 
  4853 		// Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
  4978 		// Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
  4854 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
  4979 		$this->add_setting(
  4855 			'capability' => 'switch_themes',
  4980 			new WP_Customize_Filter_Setting(
  4856 		) ) );
  4981 				$this,
       
  4982 				'active_theme',
       
  4983 				array(
       
  4984 					'capability' => 'switch_themes',
       
  4985 				)
       
  4986 			)
       
  4987 		);
  4857 
  4988 
  4858 		/* Site Identity */
  4989 		/* Site Identity */
  4859 
  4990 
  4860 		$this->add_section( 'title_tagline', array(
  4991 		$this->add_section(
  4861 			'title'    => __( 'Site Identity' ),
  4992 			'title_tagline',
  4862 			'priority' => 20,
  4993 			array(
  4863 		) );
  4994 				'title'    => __( 'Site Identity' ),
  4864 
  4995 				'priority' => 20,
  4865 		$this->add_setting( 'blogname', array(
  4996 			)
  4866 			'default'    => get_option( 'blogname' ),
  4997 		);
  4867 			'type'       => 'option',
  4998 
  4868 			'capability' => 'manage_options',
  4999 		$this->add_setting(
  4869 		) );
  5000 			'blogname',
  4870 
  5001 			array(
  4871 		$this->add_control( 'blogname', array(
  5002 				'default'    => get_option( 'blogname' ),
  4872 			'label'      => __( 'Site Title' ),
  5003 				'type'       => 'option',
  4873 			'section'    => 'title_tagline',
  5004 				'capability' => 'manage_options',
  4874 		) );
  5005 			)
  4875 
  5006 		);
  4876 		$this->add_setting( 'blogdescription', array(
  5007 
  4877 			'default'    => get_option( 'blogdescription' ),
  5008 		$this->add_control(
  4878 			'type'       => 'option',
  5009 			'blogname',
  4879 			'capability' => 'manage_options',
  5010 			array(
  4880 		) );
  5011 				'label'   => __( 'Site Title' ),
  4881 
  5012 				'section' => 'title_tagline',
  4882 		$this->add_control( 'blogdescription', array(
  5013 			)
  4883 			'label'      => __( 'Tagline' ),
  5014 		);
  4884 			'section'    => 'title_tagline',
  5015 
  4885 		) );
  5016 		$this->add_setting(
       
  5017 			'blogdescription',
       
  5018 			array(
       
  5019 				'default'    => get_option( 'blogdescription' ),
       
  5020 				'type'       => 'option',
       
  5021 				'capability' => 'manage_options',
       
  5022 			)
       
  5023 		);
       
  5024 
       
  5025 		$this->add_control(
       
  5026 			'blogdescription',
       
  5027 			array(
       
  5028 				'label'   => __( 'Tagline' ),
       
  5029 				'section' => 'title_tagline',
       
  5030 			)
       
  5031 		);
  4886 
  5032 
  4887 		// Add a setting to hide header text if the theme doesn't support custom headers.
  5033 		// Add a setting to hide header text if the theme doesn't support custom headers.
  4888 		if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
  5034 		if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
  4889 			$this->add_setting( 'header_text', array(
  5035 			$this->add_setting(
  4890 				'theme_supports'    => array( 'custom-logo', 'header-text' ),
  5036 				'header_text',
  4891 				'default'           => 1,
  5037 				array(
  4892 				'sanitize_callback' => 'absint',
  5038 					'theme_supports'    => array( 'custom-logo', 'header-text' ),
  4893 			) );
  5039 					'default'           => 1,
  4894 
  5040 					'sanitize_callback' => 'absint',
  4895 			$this->add_control( 'header_text', array(
  5041 				)
       
  5042 			);
       
  5043 
       
  5044 			$this->add_control(
       
  5045 				'header_text',
       
  5046 				array(
       
  5047 					'label'    => __( 'Display Site Title and Tagline' ),
       
  5048 					'section'  => 'title_tagline',
       
  5049 					'settings' => 'header_text',
       
  5050 					'type'     => 'checkbox',
       
  5051 				)
       
  5052 			);
       
  5053 		}
       
  5054 
       
  5055 		$this->add_setting(
       
  5056 			'site_icon',
       
  5057 			array(
       
  5058 				'type'       => 'option',
       
  5059 				'capability' => 'manage_options',
       
  5060 				'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
       
  5061 			)
       
  5062 		);
       
  5063 
       
  5064 		$this->add_control(
       
  5065 			new WP_Customize_Site_Icon_Control(
       
  5066 				$this,
       
  5067 				'site_icon',
       
  5068 				array(
       
  5069 					'label'       => __( 'Site Icon' ),
       
  5070 					'description' => sprintf(
       
  5071 						'<p>' . __( 'Site Icons are what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. Upload one here!' ) . '</p>' .
       
  5072 						/* translators: %s: site icon size in pixels */
       
  5073 						'<p>' . __( 'Site Icons should be square and at least %s pixels.' ) . '</p>',
       
  5074 						'<strong>512 &times; 512</strong>'
       
  5075 					),
       
  5076 					'section'     => 'title_tagline',
       
  5077 					'priority'    => 60,
       
  5078 					'height'      => 512,
       
  5079 					'width'       => 512,
       
  5080 				)
       
  5081 			)
       
  5082 		);
       
  5083 
       
  5084 		$this->add_setting(
       
  5085 			'custom_logo',
       
  5086 			array(
       
  5087 				'theme_supports' => array( 'custom-logo' ),
       
  5088 				'transport'      => 'postMessage',
       
  5089 			)
       
  5090 		);
       
  5091 
       
  5092 		$custom_logo_args = get_theme_support( 'custom-logo' );
       
  5093 		$this->add_control(
       
  5094 			new WP_Customize_Cropped_Image_Control(
       
  5095 				$this,
       
  5096 				'custom_logo',
       
  5097 				array(
       
  5098 					'label'         => __( 'Logo' ),
       
  5099 					'section'       => 'title_tagline',
       
  5100 					'priority'      => 8,
       
  5101 					'height'        => $custom_logo_args[0]['height'],
       
  5102 					'width'         => $custom_logo_args[0]['width'],
       
  5103 					'flex_height'   => $custom_logo_args[0]['flex-height'],
       
  5104 					'flex_width'    => $custom_logo_args[0]['flex-width'],
       
  5105 					'button_labels' => array(
       
  5106 						'select'       => __( 'Select logo' ),
       
  5107 						'change'       => __( 'Change logo' ),
       
  5108 						'remove'       => __( 'Remove' ),
       
  5109 						'default'      => __( 'Default' ),
       
  5110 						'placeholder'  => __( 'No logo selected' ),
       
  5111 						'frame_title'  => __( 'Select logo' ),
       
  5112 						'frame_button' => __( 'Choose logo' ),
       
  5113 					),
       
  5114 				)
       
  5115 			)
       
  5116 		);
       
  5117 
       
  5118 		$this->selective_refresh->add_partial(
       
  5119 			'custom_logo',
       
  5120 			array(
       
  5121 				'settings'            => array( 'custom_logo' ),
       
  5122 				'selector'            => '.custom-logo-link',
       
  5123 				'render_callback'     => array( $this, '_render_custom_logo_partial' ),
       
  5124 				'container_inclusive' => true,
       
  5125 			)
       
  5126 		);
       
  5127 
       
  5128 		/* Colors */
       
  5129 
       
  5130 		$this->add_section(
       
  5131 			'colors',
       
  5132 			array(
       
  5133 				'title'    => __( 'Colors' ),
       
  5134 				'priority' => 40,
       
  5135 			)
       
  5136 		);
       
  5137 
       
  5138 		$this->add_setting(
       
  5139 			'header_textcolor',
       
  5140 			array(
       
  5141 				'theme_supports'       => array( 'custom-header', 'header-text' ),
       
  5142 				'default'              => get_theme_support( 'custom-header', 'default-text-color' ),
       
  5143 
       
  5144 				'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
       
  5145 				'sanitize_js_callback' => 'maybe_hash_hex_color',
       
  5146 			)
       
  5147 		);
       
  5148 
       
  5149 		// Input type: checkbox
       
  5150 		// With custom value
       
  5151 		$this->add_control(
       
  5152 			'display_header_text',
       
  5153 			array(
       
  5154 				'settings' => 'header_textcolor',
  4896 				'label'    => __( 'Display Site Title and Tagline' ),
  5155 				'label'    => __( 'Display Site Title and Tagline' ),
  4897 				'section'  => 'title_tagline',
  5156 				'section'  => 'title_tagline',
  4898 				'settings' => 'header_text',
       
  4899 				'type'     => 'checkbox',
  5157 				'type'     => 'checkbox',
  4900 			) );
  5158 				'priority' => 40,
  4901 		}
  5159 			)
  4902 
  5160 		);
  4903 		$this->add_setting( 'site_icon', array(
  5161 
  4904 			'type'       => 'option',
  5162 		$this->add_control(
  4905 			'capability' => 'manage_options',
  5163 			new WP_Customize_Color_Control(
  4906 			'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
  5164 				$this,
  4907 		) );
  5165 				'header_textcolor',
  4908 
  5166 				array(
  4909 		$this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
  5167 					'label'   => __( 'Header Text Color' ),
  4910 			'label'       => __( 'Site Icon' ),
  5168 					'section' => 'colors',
  4911 			'description' => sprintf(
  5169 				)
  4912 				'<p>' . __( 'Site Icons are what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. Upload one here!' ) . '</p>' .
  5170 			)
  4913 				/* translators: %s: site icon size in pixels */
  5171 		);
  4914 				'<p>' . __( 'Site Icons should be square and at least %s pixels.' ) . '</p>',
       
  4915 				'<strong>512 &times; 512</strong>'
       
  4916 			),
       
  4917 			'section'     => 'title_tagline',
       
  4918 			'priority'    => 60,
       
  4919 			'height'      => 512,
       
  4920 			'width'       => 512,
       
  4921 		) ) );
       
  4922 
       
  4923 		$this->add_setting( 'custom_logo', array(
       
  4924 			'theme_supports' => array( 'custom-logo' ),
       
  4925 			'transport'      => 'postMessage',
       
  4926 		) );
       
  4927 
       
  4928 		$custom_logo_args = get_theme_support( 'custom-logo' );
       
  4929 		$this->add_control( new WP_Customize_Cropped_Image_Control( $this, 'custom_logo', array(
       
  4930 			'label'         => __( 'Logo' ),
       
  4931 			'section'       => 'title_tagline',
       
  4932 			'priority'      => 8,
       
  4933 			'height'        => $custom_logo_args[0]['height'],
       
  4934 			'width'         => $custom_logo_args[0]['width'],
       
  4935 			'flex_height'   => $custom_logo_args[0]['flex-height'],
       
  4936 			'flex_width'    => $custom_logo_args[0]['flex-width'],
       
  4937 			'button_labels' => array(
       
  4938 				'select'       => __( 'Select logo' ),
       
  4939 				'change'       => __( 'Change logo' ),
       
  4940 				'remove'       => __( 'Remove' ),
       
  4941 				'default'      => __( 'Default' ),
       
  4942 				'placeholder'  => __( 'No logo selected' ),
       
  4943 				'frame_title'  => __( 'Select logo' ),
       
  4944 				'frame_button' => __( 'Choose logo' ),
       
  4945 			),
       
  4946 		) ) );
       
  4947 
       
  4948 		$this->selective_refresh->add_partial( 'custom_logo', array(
       
  4949 			'settings'            => array( 'custom_logo' ),
       
  4950 			'selector'            => '.custom-logo-link',
       
  4951 			'render_callback'     => array( $this, '_render_custom_logo_partial' ),
       
  4952 			'container_inclusive' => true,
       
  4953 		) );
       
  4954 
       
  4955 		/* Colors */
       
  4956 
       
  4957 		$this->add_section( 'colors', array(
       
  4958 			'title'          => __( 'Colors' ),
       
  4959 			'priority'       => 40,
       
  4960 		) );
       
  4961 
       
  4962 		$this->add_setting( 'header_textcolor', array(
       
  4963 			'theme_supports' => array( 'custom-header', 'header-text' ),
       
  4964 			'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
       
  4965 
       
  4966 			'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
       
  4967 			'sanitize_js_callback' => 'maybe_hash_hex_color',
       
  4968 		) );
       
  4969 
       
  4970 		// Input type: checkbox
       
  4971 		// With custom value
       
  4972 		$this->add_control( 'display_header_text', array(
       
  4973 			'settings' => 'header_textcolor',
       
  4974 			'label'    => __( 'Display Site Title and Tagline' ),
       
  4975 			'section'  => 'title_tagline',
       
  4976 			'type'     => 'checkbox',
       
  4977 			'priority' => 40,
       
  4978 		) );
       
  4979 
       
  4980 		$this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
       
  4981 			'label'   => __( 'Header Text Color' ),
       
  4982 			'section' => 'colors',
       
  4983 		) ) );
       
  4984 
  5172 
  4985 		// Input type: Color
  5173 		// Input type: Color
  4986 		// With sanitize_callback
  5174 		// With sanitize_callback
  4987 		$this->add_setting( 'background_color', array(
  5175 		$this->add_setting(
  4988 			'default'        => get_theme_support( 'custom-background', 'default-color' ),
  5176 			'background_color',
  4989 			'theme_supports' => 'custom-background',
  5177 			array(
  4990 
  5178 				'default'              => get_theme_support( 'custom-background', 'default-color' ),
  4991 			'sanitize_callback'    => 'sanitize_hex_color_no_hash',
  5179 				'theme_supports'       => 'custom-background',
  4992 			'sanitize_js_callback' => 'maybe_hash_hex_color',
  5180 
  4993 		) );
  5181 				'sanitize_callback'    => 'sanitize_hex_color_no_hash',
  4994 
  5182 				'sanitize_js_callback' => 'maybe_hash_hex_color',
  4995 		$this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
  5183 			)
  4996 			'label'   => __( 'Background Color' ),
  5184 		);
  4997 			'section' => 'colors',
  5185 
  4998 		) ) );
  5186 		$this->add_control(
       
  5187 			new WP_Customize_Color_Control(
       
  5188 				$this,
       
  5189 				'background_color',
       
  5190 				array(
       
  5191 					'label'   => __( 'Background Color' ),
       
  5192 					'section' => 'colors',
       
  5193 				)
       
  5194 			)
       
  5195 		);
  4999 
  5196 
  5000 		/* Custom Header */
  5197 		/* Custom Header */
  5001 
  5198 
  5002 		if ( current_theme_supports( 'custom-header', 'video' ) ) {
  5199 		if ( current_theme_supports( 'custom-header', 'video' ) ) {
  5003 			$title = __( 'Header Media' );
  5200 			$title       = __( 'Header Media' );
  5004 			$description = '<p>' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '</p>';
  5201 			$description = '<p>' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '</p>';
  5005 
  5202 
  5006 			$width = absint( get_theme_support( 'custom-header', 'width' ) );
  5203 			$width  = absint( get_theme_support( 'custom-header', 'width' ) );
  5007 			$height = absint( get_theme_support( 'custom-header', 'height' ) );
  5204 			$height = absint( get_theme_support( 'custom-header', 'height' ) );
  5008 			if ( $width && $height ) {
  5205 			if ( $width && $height ) {
  5009 				$control_description = sprintf(
  5206 				$control_description = sprintf(
  5010 					/* translators: 1: .mp4, 2: header size in pixels */
  5207 					/* translators: 1: .mp4, 2: header size in pixels */
  5011 					__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends dimensions of %2$s pixels.' ),
  5208 					__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends dimensions of %2$s pixels.' ),
  5026 					'<code>.mp4</code>',
  5223 					'<code>.mp4</code>',
  5027 					sprintf( '<strong>%s</strong>', $height )
  5224 					sprintf( '<strong>%s</strong>', $height )
  5028 				);
  5225 				);
  5029 			}
  5226 			}
  5030 		} else {
  5227 		} else {
  5031 			$title = __( 'Header Image' );
  5228 			$title               = __( 'Header Image' );
  5032 			$description = '';
  5229 			$description         = '';
  5033 			$control_description = '';
  5230 			$control_description = '';
  5034 		}
  5231 		}
  5035 
  5232 
  5036 		$this->add_section( 'header_image', array(
  5233 		$this->add_section(
  5037 			'title'          => $title,
  5234 			'header_image',
  5038 			'description'    => $description,
  5235 			array(
  5039 			'theme_supports' => 'custom-header',
  5236 				'title'          => $title,
  5040 			'priority'       => 60,
  5237 				'description'    => $description,
  5041 		) );
  5238 				'theme_supports' => 'custom-header',
  5042 
  5239 				'priority'       => 60,
  5043 		$this->add_setting( 'header_video', array(
  5240 			)
  5044 			'theme_supports'    => array( 'custom-header', 'video' ),
  5241 		);
  5045 			'transport'         => 'postMessage',
  5242 
  5046 			'sanitize_callback' => 'absint',
  5243 		$this->add_setting(
  5047 			'validate_callback' => array( $this, '_validate_header_video' ),
  5244 			'header_video',
  5048 		) );
  5245 			array(
  5049 
  5246 				'theme_supports'    => array( 'custom-header', 'video' ),
  5050 		$this->add_setting( 'external_header_video', array(
  5247 				'transport'         => 'postMessage',
  5051 			'theme_supports'    => array( 'custom-header', 'video' ),
  5248 				'sanitize_callback' => 'absint',
  5052 			'transport'         => 'postMessage',
  5249 				'validate_callback' => array( $this, '_validate_header_video' ),
  5053 			'sanitize_callback' => array( $this, '_sanitize_external_header_video' ),
  5250 			)
  5054 			'validate_callback' => array( $this, '_validate_external_header_video' ),
  5251 		);
  5055 		) );
  5252 
  5056 
  5253 		$this->add_setting(
  5057 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
  5254 			'external_header_video',
  5058 			'default'        => sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() ),
  5255 			array(
  5059 			'theme_supports' => 'custom-header',
  5256 				'theme_supports'    => array( 'custom-header', 'video' ),
  5060 		) ) );
  5257 				'transport'         => 'postMessage',
  5061 
  5258 				'sanitize_callback' => array( $this, '_sanitize_external_header_video' ),
  5062 		$this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
  5259 				'validate_callback' => array( $this, '_validate_external_header_video' ),
  5063 			'theme_supports' => 'custom-header',
  5260 			)
  5064 		) ) );
  5261 		);
       
  5262 
       
  5263 		$this->add_setting(
       
  5264 			new WP_Customize_Filter_Setting(
       
  5265 				$this,
       
  5266 				'header_image',
       
  5267 				array(
       
  5268 					'default'        => sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() ),
       
  5269 					'theme_supports' => 'custom-header',
       
  5270 				)
       
  5271 			)
       
  5272 		);
       
  5273 
       
  5274 		$this->add_setting(
       
  5275 			new WP_Customize_Header_Image_Setting(
       
  5276 				$this,
       
  5277 				'header_image_data',
       
  5278 				array(
       
  5279 					'theme_supports' => 'custom-header',
       
  5280 				)
       
  5281 			)
       
  5282 		);
  5065 
  5283 
  5066 		/*
  5284 		/*
  5067 		 * Switch image settings to postMessage when video support is enabled since
  5285 		 * Switch image settings to postMessage when video support is enabled since
  5068 		 * it entails that the_custom_header_markup() will be used, and thus selective
  5286 		 * it entails that the_custom_header_markup() will be used, and thus selective
  5069 		 * refresh can be utilized.
  5287 		 * refresh can be utilized.
  5070 		 */
  5288 		 */
  5071 		if ( current_theme_supports( 'custom-header', 'video' ) ) {
  5289 		if ( current_theme_supports( 'custom-header', 'video' ) ) {
  5072 			$this->get_setting( 'header_image' )->transport = 'postMessage';
  5290 			$this->get_setting( 'header_image' )->transport      = 'postMessage';
  5073 			$this->get_setting( 'header_image_data' )->transport = 'postMessage';
  5291 			$this->get_setting( 'header_image_data' )->transport = 'postMessage';
  5074 		}
  5292 		}
  5075 
  5293 
  5076 		$this->add_control( new WP_Customize_Media_Control( $this, 'header_video', array(
  5294 		$this->add_control(
  5077 			'theme_supports' => array( 'custom-header', 'video' ),
  5295 			new WP_Customize_Media_Control(
  5078 			'label'          => __( 'Header Video' ),
  5296 				$this,
  5079 			'description'    => $control_description,
  5297 				'header_video',
  5080 			'section'        => 'header_image',
  5298 				array(
  5081 			'mime_type'      => 'video',
  5299 					'theme_supports'  => array( 'custom-header', 'video' ),
  5082 			'active_callback' => 'is_header_video_active',
  5300 					'label'           => __( 'Header Video' ),
  5083 		) ) );
  5301 					'description'     => $control_description,
  5084 
  5302 					'section'         => 'header_image',
  5085 		$this->add_control( 'external_header_video', array(
  5303 					'mime_type'       => 'video',
  5086 			'theme_supports' => array( 'custom-header', 'video' ),
  5304 					'active_callback' => 'is_header_video_active',
  5087 			'type'           => 'url',
  5305 				)
  5088 			'description'    => __( 'Or, enter a YouTube URL:' ),
  5306 			)
  5089 			'section'        => 'header_image',
  5307 		);
  5090 			'active_callback' => 'is_header_video_active',
  5308 
  5091 		) );
  5309 		$this->add_control(
       
  5310 			'external_header_video',
       
  5311 			array(
       
  5312 				'theme_supports'  => array( 'custom-header', 'video' ),
       
  5313 				'type'            => 'url',
       
  5314 				'description'     => __( 'Or, enter a YouTube URL:' ),
       
  5315 				'section'         => 'header_image',
       
  5316 				'active_callback' => 'is_header_video_active',
       
  5317 			)
       
  5318 		);
  5092 
  5319 
  5093 		$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
  5320 		$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
  5094 
  5321 
  5095 		$this->selective_refresh->add_partial( 'custom_header', array(
  5322 		$this->selective_refresh->add_partial(
  5096 			'selector'            => '#wp-custom-header',
  5323 			'custom_header',
  5097 			'render_callback'     => 'the_custom_header_markup',
  5324 			array(
  5098 			'settings'            => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
  5325 				'selector'            => '#wp-custom-header',
  5099 			'container_inclusive' => true,
  5326 				'render_callback'     => 'the_custom_header_markup',
  5100 		) );
  5327 				'settings'            => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
       
  5328 				'container_inclusive' => true,
       
  5329 			)
       
  5330 		);
  5101 
  5331 
  5102 		/* Custom Background */
  5332 		/* Custom Background */
  5103 
  5333 
  5104 		$this->add_section( 'background_image', array(
  5334 		$this->add_section(
  5105 			'title'          => __( 'Background Image' ),
  5335 			'background_image',
  5106 			'theme_supports' => 'custom-background',
  5336 			array(
  5107 			'priority'       => 80,
  5337 				'title'          => __( 'Background Image' ),
  5108 		) );
  5338 				'theme_supports' => 'custom-background',
  5109 
  5339 				'priority'       => 80,
  5110 		$this->add_setting( 'background_image', array(
  5340 			)
  5111 			'default'        => get_theme_support( 'custom-background', 'default-image' ),
  5341 		);
  5112 			'theme_supports' => 'custom-background',
  5342 
  5113 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5343 		$this->add_setting(
  5114 		) );
  5344 			'background_image',
  5115 
  5345 			array(
  5116 		$this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
  5346 				'default'           => get_theme_support( 'custom-background', 'default-image' ),
  5117 			'theme_supports' => 'custom-background',
  5347 				'theme_supports'    => 'custom-background',
  5118 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5348 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5119 		) ) );
  5349 			)
       
  5350 		);
       
  5351 
       
  5352 		$this->add_setting(
       
  5353 			new WP_Customize_Background_Image_Setting(
       
  5354 				$this,
       
  5355 				'background_image_thumb',
       
  5356 				array(
       
  5357 					'theme_supports'    => 'custom-background',
       
  5358 					'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5359 				)
       
  5360 			)
       
  5361 		);
  5120 
  5362 
  5121 		$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
  5363 		$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
  5122 
  5364 
  5123 		$this->add_setting( 'background_preset', array(
  5365 		$this->add_setting(
  5124 			'default'        => get_theme_support( 'custom-background', 'default-preset' ),
  5366 			'background_preset',
  5125 			'theme_supports' => 'custom-background',
  5367 			array(
  5126 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5368 				'default'           => get_theme_support( 'custom-background', 'default-preset' ),
  5127 		) );
  5369 				'theme_supports'    => 'custom-background',
  5128 
  5370 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5129 		$this->add_control( 'background_preset', array(
  5371 			)
  5130 			'label'      => _x( 'Preset', 'Background Preset' ),
  5372 		);
  5131 			'section'    => 'background_image',
  5373 
  5132 			'type'       => 'select',
  5374 		$this->add_control(
  5133 			'choices'    => array(
  5375 			'background_preset',
  5134 				'default' => _x( 'Default', 'Default Preset' ),
  5376 			array(
  5135 				'fill'    => __( 'Fill Screen' ),
  5377 				'label'   => _x( 'Preset', 'Background Preset' ),
  5136 				'fit'     => __( 'Fit to Screen' ),
  5378 				'section' => 'background_image',
  5137 				'repeat'  => _x( 'Repeat', 'Repeat Image' ),
  5379 				'type'    => 'select',
  5138 				'custom'  => _x( 'Custom', 'Custom Preset' ),
  5380 				'choices' => array(
  5139 			),
  5381 					'default' => _x( 'Default', 'Default Preset' ),
  5140 		) );
  5382 					'fill'    => __( 'Fill Screen' ),
  5141 
  5383 					'fit'     => __( 'Fit to Screen' ),
  5142 		$this->add_setting( 'background_position_x', array(
  5384 					'repeat'  => _x( 'Repeat', 'Repeat Image' ),
  5143 			'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
  5385 					'custom'  => _x( 'Custom', 'Custom Preset' ),
  5144 			'theme_supports' => 'custom-background',
  5386 				),
  5145 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5387 			)
  5146 		) );
  5388 		);
  5147 
  5389 
  5148 		$this->add_setting( 'background_position_y', array(
  5390 		$this->add_setting(
  5149 			'default'        => get_theme_support( 'custom-background', 'default-position-y' ),
  5391 			'background_position_x',
  5150 			'theme_supports' => 'custom-background',
  5392 			array(
  5151 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5393 				'default'           => get_theme_support( 'custom-background', 'default-position-x' ),
  5152 		) );
  5394 				'theme_supports'    => 'custom-background',
  5153 
  5395 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5154 		$this->add_control( new WP_Customize_Background_Position_Control( $this, 'background_position', array(
  5396 			)
  5155 			'label'    => __( 'Image Position' ),
  5397 		);
  5156 			'section'  => 'background_image',
  5398 
  5157 			'settings' => array(
  5399 		$this->add_setting(
  5158 				'x' => 'background_position_x',
  5400 			'background_position_y',
  5159 				'y' => 'background_position_y',
  5401 			array(
  5160 			),
  5402 				'default'           => get_theme_support( 'custom-background', 'default-position-y' ),
  5161 		) ) );
  5403 				'theme_supports'    => 'custom-background',
  5162 
  5404 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5163 		$this->add_setting( 'background_size', array(
  5405 			)
  5164 			'default'        => get_theme_support( 'custom-background', 'default-size' ),
  5406 		);
  5165 			'theme_supports' => 'custom-background',
  5407 
  5166 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5408 		$this->add_control(
  5167 		) );
  5409 			new WP_Customize_Background_Position_Control(
  5168 
  5410 				$this,
  5169 		$this->add_control( 'background_size', array(
  5411 				'background_position',
  5170 			'label'      => __( 'Image Size' ),
  5412 				array(
  5171 			'section'    => 'background_image',
  5413 					'label'    => __( 'Image Position' ),
  5172 			'type'       => 'select',
  5414 					'section'  => 'background_image',
  5173 			'choices'    => array(
  5415 					'settings' => array(
  5174 				'auto'    => __( 'Original' ),
  5416 						'x' => 'background_position_x',
  5175 				'contain' => __( 'Fit to Screen' ),
  5417 						'y' => 'background_position_y',
  5176 				'cover'   => __( 'Fill Screen' ),
  5418 					),
  5177 			),
  5419 				)
  5178 		) );
  5420 			)
  5179 
  5421 		);
  5180 		$this->add_setting( 'background_repeat', array(
  5422 
  5181 			'default'           => get_theme_support( 'custom-background', 'default-repeat' ),
  5423 		$this->add_setting(
  5182 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5424 			'background_size',
  5183 			'theme_supports'    => 'custom-background',
  5425 			array(
  5184 		) );
  5426 				'default'           => get_theme_support( 'custom-background', 'default-size' ),
  5185 
  5427 				'theme_supports'    => 'custom-background',
  5186 		$this->add_control( 'background_repeat', array(
  5428 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5187 			'label'    => __( 'Repeat Background Image' ),
  5429 			)
  5188 			'section'  => 'background_image',
  5430 		);
  5189 			'type'     => 'checkbox',
  5431 
  5190 		) );
  5432 		$this->add_control(
  5191 
  5433 			'background_size',
  5192 		$this->add_setting( 'background_attachment', array(
  5434 			array(
  5193 			'default'           => get_theme_support( 'custom-background', 'default-attachment' ),
  5435 				'label'   => __( 'Image Size' ),
  5194 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  5436 				'section' => 'background_image',
  5195 			'theme_supports'    => 'custom-background',
  5437 				'type'    => 'select',
  5196 		) );
  5438 				'choices' => array(
  5197 
  5439 					'auto'    => __( 'Original' ),
  5198 		$this->add_control( 'background_attachment', array(
  5440 					'contain' => __( 'Fit to Screen' ),
  5199 			'label'    => __( 'Scroll with Page' ),
  5441 					'cover'   => __( 'Fill Screen' ),
  5200 			'section'  => 'background_image',
  5442 				),
  5201 			'type'     => 'checkbox',
  5443 			)
  5202 		) );
  5444 		);
  5203 
  5445 
       
  5446 		$this->add_setting(
       
  5447 			'background_repeat',
       
  5448 			array(
       
  5449 				'default'           => get_theme_support( 'custom-background', 'default-repeat' ),
       
  5450 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5451 				'theme_supports'    => 'custom-background',
       
  5452 			)
       
  5453 		);
       
  5454 
       
  5455 		$this->add_control(
       
  5456 			'background_repeat',
       
  5457 			array(
       
  5458 				'label'   => __( 'Repeat Background Image' ),
       
  5459 				'section' => 'background_image',
       
  5460 				'type'    => 'checkbox',
       
  5461 			)
       
  5462 		);
       
  5463 
       
  5464 		$this->add_setting(
       
  5465 			'background_attachment',
       
  5466 			array(
       
  5467 				'default'           => get_theme_support( 'custom-background', 'default-attachment' ),
       
  5468 				'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5469 				'theme_supports'    => 'custom-background',
       
  5470 			)
       
  5471 		);
       
  5472 
       
  5473 		$this->add_control(
       
  5474 			'background_attachment',
       
  5475 			array(
       
  5476 				'label'   => __( 'Scroll with Page' ),
       
  5477 				'section' => 'background_image',
       
  5478 				'type'    => 'checkbox',
       
  5479 			)
       
  5480 		);
  5204 
  5481 
  5205 		// If the theme is using the default background callback, we can update
  5482 		// If the theme is using the default background callback, we can update
  5206 		// the background CSS using postMessage.
  5483 		// the background CSS using postMessage.
  5207 		if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
  5484 		if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
  5208 			foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) {
  5485 			foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) {
  5214 		 * Static Front Page
  5491 		 * Static Front Page
  5215 		 * See also https://core.trac.wordpress.org/ticket/19627 which introduces the static-front-page theme_support.
  5492 		 * See also https://core.trac.wordpress.org/ticket/19627 which introduces the static-front-page theme_support.
  5216 		 * The following replicates behavior from options-reading.php.
  5493 		 * The following replicates behavior from options-reading.php.
  5217 		 */
  5494 		 */
  5218 
  5495 
  5219 		$this->add_section( 'static_front_page', array(
  5496 		$this->add_section(
  5220 			'title' => __( 'Homepage Settings' ),
  5497 			'static_front_page',
  5221 			'priority' => 120,
  5498 			array(
  5222 			'description' => __( 'You can choose what&#8217;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.' ),
  5499 				'title'           => __( 'Homepage Settings' ),
  5223 			'active_callback' => array( $this, 'has_published_pages' ),
  5500 				'priority'        => 120,
  5224 		) );
  5501 				'description'     => __( 'You can choose what&#8217;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.' ),
  5225 
  5502 				'active_callback' => array( $this, 'has_published_pages' ),
  5226 		$this->add_setting( 'show_on_front', array(
  5503 			)
  5227 			'default' => get_option( 'show_on_front' ),
  5504 		);
  5228 			'capability' => 'manage_options',
  5505 
  5229 			'type' => 'option',
  5506 		$this->add_setting(
  5230 		) );
  5507 			'show_on_front',
  5231 
  5508 			array(
  5232 		$this->add_control( 'show_on_front', array(
  5509 				'default'    => get_option( 'show_on_front' ),
  5233 			'label' => __( 'Your homepage displays' ),
  5510 				'capability' => 'manage_options',
  5234 			'section' => 'static_front_page',
  5511 				'type'       => 'option',
  5235 			'type' => 'radio',
  5512 			)
  5236 			'choices' => array(
  5513 		);
  5237 				'posts' => __( 'Your latest posts' ),
  5514 
  5238 				'page'  => __( 'A static page' ),
  5515 		$this->add_control(
  5239 			),
  5516 			'show_on_front',
  5240 		) );
  5517 			array(
  5241 
  5518 				'label'   => __( 'Your homepage displays' ),
  5242 		$this->add_setting( 'page_on_front', array(
  5519 				'section' => 'static_front_page',
  5243 			'type'       => 'option',
  5520 				'type'    => 'radio',
  5244 			'capability' => 'manage_options',
  5521 				'choices' => array(
  5245 		) );
  5522 					'posts' => __( 'Your latest posts' ),
  5246 
  5523 					'page'  => __( 'A static page' ),
  5247 		$this->add_control( 'page_on_front', array(
  5524 				),
  5248 			'label' => __( 'Homepage' ),
  5525 			)
  5249 			'section' => 'static_front_page',
  5526 		);
  5250 			'type' => 'dropdown-pages',
  5527 
  5251 			'allow_addition' => true,
  5528 		$this->add_setting(
  5252 		) );
  5529 			'page_on_front',
  5253 
  5530 			array(
  5254 		$this->add_setting( 'page_for_posts', array(
  5531 				'type'       => 'option',
  5255 			'type' => 'option',
  5532 				'capability' => 'manage_options',
  5256 			'capability' => 'manage_options',
  5533 			)
  5257 		) );
  5534 		);
  5258 
  5535 
  5259 		$this->add_control( 'page_for_posts', array(
  5536 		$this->add_control(
  5260 			'label' => __( 'Posts page' ),
  5537 			'page_on_front',
  5261 			'section' => 'static_front_page',
  5538 			array(
  5262 			'type' => 'dropdown-pages',
  5539 				'label'          => __( 'Homepage' ),
  5263 			'allow_addition' => true,
  5540 				'section'        => 'static_front_page',
  5264 		) );
  5541 				'type'           => 'dropdown-pages',
       
  5542 				'allow_addition' => true,
       
  5543 			)
       
  5544 		);
       
  5545 
       
  5546 		$this->add_setting(
       
  5547 			'page_for_posts',
       
  5548 			array(
       
  5549 				'type'       => 'option',
       
  5550 				'capability' => 'manage_options',
       
  5551 			)
       
  5552 		);
       
  5553 
       
  5554 		$this->add_control(
       
  5555 			'page_for_posts',
       
  5556 			array(
       
  5557 				'label'          => __( 'Posts page' ),
       
  5558 				'section'        => 'static_front_page',
       
  5559 				'type'           => 'dropdown-pages',
       
  5560 				'allow_addition' => true,
       
  5561 			)
       
  5562 		);
  5265 
  5563 
  5266 		/* Custom CSS */
  5564 		/* Custom CSS */
  5267 		$section_description = '<p>';
  5565 		$section_description  = '<p>';
  5268 		$section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.' );
  5566 		$section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.' );
  5269 		$section_description .= sprintf(
  5567 		$section_description .= sprintf(
  5270 			' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text"> %3$s</span></a>',
  5568 			' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text"> %3$s</span></a>',
  5271 			esc_url( __( 'https://codex.wordpress.org/CSS' ) ),
  5569 			esc_url( __( 'https://codex.wordpress.org/CSS' ) ),
  5272 			__( 'Learn more about CSS' ),
  5570 			__( 'Learn more about CSS' ),
  5273 			/* translators: accessibility text */
  5571 			/* translators: accessibility text */
  5274 			__( '(opens in a new window)' )
  5572 			__( '(opens in a new tab)' )
  5275 		);
  5573 		);
  5276 		$section_description .= '</p>';
  5574 		$section_description .= '</p>';
  5277 
  5575 
  5278 		$section_description .= '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>';
  5576 		$section_description .= '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>';
  5279 		$section_description .= '<ul>';
  5577 		$section_description .= '<ul>';
  5287 			$section_description .= sprintf(
  5585 			$section_description .= sprintf(
  5288 				/* translators: 1: link to user profile, 2: additional link attributes, 3: accessibility text */
  5586 				/* translators: 1: link to user profile, 2: additional link attributes, 3: accessibility text */
  5289 				__( '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.' ),
  5587 				__( '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.' ),
  5290 				esc_url( get_edit_profile_url() ),
  5588 				esc_url( get_edit_profile_url() ),
  5291 				'class="external-link" target="_blank"',
  5589 				'class="external-link" target="_blank"',
  5292 				sprintf( '<span class="screen-reader-text"> %s</span>',
  5590 				sprintf(
       
  5591 					'<span class="screen-reader-text"> %s</span>',
  5293 					/* translators: accessibility text */
  5592 					/* translators: accessibility text */
  5294 					__( '(opens in a new window)' )
  5593 					__( '(opens in a new tab)' )
  5295 				)
  5594 				)
  5296 			);
  5595 			);
  5297 			$section_description .= '</p>';
  5596 			$section_description .= '</p>';
  5298 		}
  5597 		}
  5299 
  5598 
  5300 		$section_description .= '<p class="section-description-buttons">';
  5599 		$section_description .= '<p class="section-description-buttons">';
  5301 		$section_description .= '<button type="button" class="button-link section-description-close">' . __( 'Close' ) . '</button>';
  5600 		$section_description .= '<button type="button" class="button-link section-description-close">' . __( 'Close' ) . '</button>';
  5302 		$section_description .= '</p>';
  5601 		$section_description .= '</p>';
  5303 
  5602 
  5304 		$this->add_section( 'custom_css', array(
  5603 		$this->add_section(
  5305 			'title'              => __( 'Additional CSS' ),
  5604 			'custom_css',
  5306 			'priority'           => 200,
  5605 			array(
  5307 			'description_hidden' => true,
  5606 				'title'              => __( 'Additional CSS' ),
  5308 			'description'        => $section_description,
  5607 				'priority'           => 200,
  5309 		) );
  5608 				'description_hidden' => true,
  5310 
  5609 				'description'        => $section_description,
  5311 		$custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array(
  5610 			)
  5312 			'capability' => 'edit_css',
  5611 		);
  5313 			'default' => '',
  5612 
  5314 		) );
  5613 		$custom_css_setting = new WP_Customize_Custom_CSS_Setting(
       
  5614 			$this,
       
  5615 			sprintf( 'custom_css[%s]', get_stylesheet() ),
       
  5616 			array(
       
  5617 				'capability' => 'edit_css',
       
  5618 				'default'    => '',
       
  5619 			)
       
  5620 		);
  5315 		$this->add_setting( $custom_css_setting );
  5621 		$this->add_setting( $custom_css_setting );
  5316 
  5622 
  5317 		$this->add_control( new WP_Customize_Code_Editor_Control( $this, 'custom_css', array(
  5623 		$this->add_control(
  5318 			'label'       => __( 'CSS code' ),
  5624 			new WP_Customize_Code_Editor_Control(
  5319 			'section'     => 'custom_css',
  5625 				$this,
  5320 			'settings'    => array( 'default' => $custom_css_setting->id ),
  5626 				'custom_css',
  5321 			'code_type'   => 'text/css',
  5627 				array(
  5322 			'input_attrs' => array(
  5628 					'label'       => __( 'CSS code' ),
  5323 				'aria-describedby' => 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4',
  5629 					'section'     => 'custom_css',
  5324 			),
  5630 					'settings'    => array( 'default' => $custom_css_setting->id ),
  5325 		) ) );
  5631 					'code_type'   => 'text/css',
       
  5632 					'input_attrs' => array(
       
  5633 						'aria-describedby' => 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4',
       
  5634 					),
       
  5635 				)
       
  5636 			)
       
  5637 		);
  5326 	}
  5638 	}
  5327 
  5639 
  5328 	/**
  5640 	/**
  5329 	 * Return whether there are published pages.
  5641 	 * Return whether there are published pages.
  5330 	 *
  5642 	 *
  5373 
  5685 
  5374 		if ( empty( $_POST['theme_action'] ) ) {
  5686 		if ( empty( $_POST['theme_action'] ) ) {
  5375 			wp_send_json_error( 'missing_theme_action' );
  5687 			wp_send_json_error( 'missing_theme_action' );
  5376 		}
  5688 		}
  5377 		$theme_action = sanitize_key( $_POST['theme_action'] );
  5689 		$theme_action = sanitize_key( $_POST['theme_action'] );
  5378 		$themes = array();
  5690 		$themes       = array();
  5379 		$args = array();
  5691 		$args         = array();
  5380 
  5692 
  5381 		// Define query filters based on user input.
  5693 		// Define query filters based on user input.
  5382 		if ( ! array_key_exists( 'search', $_POST ) ) {
  5694 		if ( ! array_key_exists( 'search', $_POST ) ) {
  5383 			$args['search'] = '';
  5695 			$args['search'] = '';
  5384 		} else {
  5696 		} else {
  5402 		if ( 'installed' === $theme_action ) {
  5714 		if ( 'installed' === $theme_action ) {
  5403 
  5715 
  5404 			// Load all installed themes from wp_prepare_themes_for_js().
  5716 			// Load all installed themes from wp_prepare_themes_for_js().
  5405 			$themes = array( 'themes' => wp_prepare_themes_for_js() );
  5717 			$themes = array( 'themes' => wp_prepare_themes_for_js() );
  5406 			foreach ( $themes['themes'] as &$theme ) {
  5718 			foreach ( $themes['themes'] as &$theme ) {
  5407 				$theme['type'] = 'installed';
  5719 				$theme['type']   = 'installed';
  5408 				$theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
  5720 				$theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
  5409 			}
  5721 			}
  5410 
       
  5411 		} elseif ( 'wporg' === $theme_action ) {
  5722 		} elseif ( 'wporg' === $theme_action ) {
  5412 
  5723 
  5413 			// Load WordPress.org themes from the .org API and normalize data to match installed theme objects.
  5724 			// Load WordPress.org themes from the .org API and normalize data to match installed theme objects.
  5414 			if ( ! current_user_can( 'install_themes' ) ) {
  5725 			if ( ! current_user_can( 'install_themes' ) ) {
  5415 				wp_die( -1 );
  5726 				wp_die( -1 );
  5416 			}
  5727 			}
  5417 
  5728 
  5418 			// Arguments for all queries.
  5729 			// Arguments for all queries.
  5419 			$wporg_args = array(
  5730 			$wporg_args = array(
  5420 				'per_page' => 100,
  5731 				'per_page' => 100,
  5421 				'fields' => array(
  5732 				'fields'   => array(
  5422 					'screenshot_url' => true,
  5733 					'reviews_url' => true, // Explicitly request the reviews URL to be linked from the customizer.
  5423 					'description' => true,
       
  5424 					'rating' => true,
       
  5425 					'downloaded' => true,
       
  5426 					'downloadlink' => true,
       
  5427 					'last_updated' => true,
       
  5428 					'homepage' => true,
       
  5429 					'num_ratings' => true,
       
  5430 					'tags' => true,
       
  5431 					'parent' => true,
       
  5432 					// 'extended_author' => true, @todo: WordPress.org throws a 500 server error when this is here.
       
  5433 				),
  5734 				),
  5434 			);
  5735 			);
  5435 
  5736 
  5436 			$args = array_merge( $wporg_args, $args );
  5737 			$args = array_merge( $wporg_args, $args );
  5437 
  5738 
  5444 			if ( is_wp_error( $themes ) ) {
  5745 			if ( is_wp_error( $themes ) ) {
  5445 				wp_send_json_error();
  5746 				wp_send_json_error();
  5446 			}
  5747 			}
  5447 
  5748 
  5448 			// This list matches the allowed tags in wp-admin/includes/theme-install.php.
  5749 			// This list matches the allowed tags in wp-admin/includes/theme-install.php.
  5449 			$themes_allowedtags = array_fill_keys(
  5750 			$themes_allowedtags                     = array_fill_keys(
  5450 				array( 'a', 'abbr', 'acronym', 'code', 'pre', 'em', 'strong', 'div', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img' ),
  5751 				array( 'a', 'abbr', 'acronym', 'code', 'pre', 'em', 'strong', 'div', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img' ),
  5451 				array()
  5752 				array()
  5452 			);
  5753 			);
  5453 			$themes_allowedtags['a'] = array_fill_keys( array( 'href', 'title', 'target' ), true );
  5754 			$themes_allowedtags['a']                = array_fill_keys( array( 'href', 'title', 'target' ), true );
  5454 			$themes_allowedtags['acronym']['title'] = true;
  5755 			$themes_allowedtags['acronym']['title'] = true;
  5455 			$themes_allowedtags['abbr']['title'] = true;
  5756 			$themes_allowedtags['abbr']['title']    = true;
  5456 			$themes_allowedtags['img'] = array_fill_keys( array( 'src', 'class', 'alt' ), true );
  5757 			$themes_allowedtags['img']              = array_fill_keys( array( 'src', 'class', 'alt' ), true );
  5457 
  5758 
  5458 			// Prepare a list of installed themes to check against before the loop.
  5759 			// Prepare a list of installed themes to check against before the loop.
  5459 			$installed_themes = array();
  5760 			$installed_themes = array();
  5460 			$wp_themes = wp_get_themes();
  5761 			$wp_themes        = wp_get_themes();
  5461 			foreach ( $wp_themes as $theme ) {
  5762 			foreach ( $wp_themes as $theme ) {
  5462 				$installed_themes[] = $theme->get_stylesheet();
  5763 				$installed_themes[] = $theme->get_stylesheet();
  5463 			}
  5764 			}
  5464 			$update_php = network_admin_url( 'update.php?action=install-theme' );
  5765 			$update_php = network_admin_url( 'update.php?action=install-theme' );
  5465 
  5766 
  5466 			// Set up properties for themes available on WordPress.org.
  5767 			// Set up properties for themes available on WordPress.org.
  5467 			foreach ( $themes->themes as &$theme ) {
  5768 			foreach ( $themes->themes as &$theme ) {
  5468 				$theme->install_url = add_query_arg( array(
  5769 				$theme->install_url = add_query_arg(
  5469 					'theme'    => $theme->slug,
  5770 					array(
  5470 					'_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
  5771 						'theme'    => $theme->slug,
  5471 				), $update_php );
  5772 						'_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
       
  5773 					),
       
  5774 					$update_php
       
  5775 				);
  5472 
  5776 
  5473 				$theme->name        = wp_kses( $theme->name, $themes_allowedtags );
  5777 				$theme->name        = wp_kses( $theme->name, $themes_allowedtags );
  5474 				$theme->author      = wp_kses( $theme->author, $themes_allowedtags );
       
  5475 				$theme->version     = wp_kses( $theme->version, $themes_allowedtags );
  5778 				$theme->version     = wp_kses( $theme->version, $themes_allowedtags );
  5476 				$theme->description = wp_kses( $theme->description, $themes_allowedtags );
  5779 				$theme->description = wp_kses( $theme->description, $themes_allowedtags );
  5477 				$theme->tags        = implode( ', ', $theme->tags );
  5780 				$theme->stars       = wp_star_rating(
  5478 				$theme->stars       = wp_star_rating( array(
  5781 					array(
  5479 					'rating' => $theme->rating,
  5782 						'rating' => $theme->rating,
  5480 					'type' => 'percent',
  5783 						'type'   => 'percent',
  5481 					'number' => $theme->num_ratings,
  5784 						'number' => $theme->num_ratings,
  5482 					'echo' => false,
  5785 						'echo'   => false,
  5483 				) );
  5786 					)
       
  5787 				);
  5484 				$theme->num_ratings = number_format_i18n( $theme->num_ratings );
  5788 				$theme->num_ratings = number_format_i18n( $theme->num_ratings );
  5485 				$theme->preview_url = set_url_scheme( $theme->preview_url );
  5789 				$theme->preview_url = set_url_scheme( $theme->preview_url );
  5486 
  5790 
  5487 				// Handle themes that are already installed as installed themes.
  5791 				// Handle themes that are already installed as installed themes.
  5488 				if ( in_array( $theme->slug, $installed_themes, true ) ) {
  5792 				if ( in_array( $theme->slug, $installed_themes, true ) ) {
  5495 				$theme->active = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme->slug );
  5799 				$theme->active = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme->slug );
  5496 
  5800 
  5497 				// Map available theme properties to installed theme properties.
  5801 				// Map available theme properties to installed theme properties.
  5498 				$theme->id           = $theme->slug;
  5802 				$theme->id           = $theme->slug;
  5499 				$theme->screenshot   = array( $theme->screenshot_url );
  5803 				$theme->screenshot   = array( $theme->screenshot_url );
  5500 				$theme->authorAndUri = $theme->author;
  5804 				$theme->authorAndUri = wp_kses( $theme->author['display_name'], $themes_allowedtags );
  5501 				// The .org API can return the full parent theme details if passed the 'parent' arg, or if passed the 'template' option it'll return that in the event it's a child theme.
  5805 
  5502 				if ( isset( $theme->parent ) ) {
  5806 				if ( isset( $theme->parent ) ) {
  5503 					$theme->parent = $theme->parent['slug'];
  5807 					$theme->parent = $theme->parent['slug'];
  5504 				} else {
  5808 				} else {
  5505 					$theme->parent = false;
  5809 					$theme->parent = false;
  5506 				}
  5810 				}
  5543 	 *
  5847 	 *
  5544 	 * @param string $color
  5848 	 * @param string $color
  5545 	 * @return mixed
  5849 	 * @return mixed
  5546 	 */
  5850 	 */
  5547 	public function _sanitize_header_textcolor( $color ) {
  5851 	public function _sanitize_header_textcolor( $color ) {
  5548 		if ( 'blank' === $color )
  5852 		if ( 'blank' === $color ) {
  5549 			return 'blank';
  5853 			return 'blank';
       
  5854 		}
  5550 
  5855 
  5551 		$color = sanitize_hex_color_no_hash( $color );
  5856 		$color = sanitize_hex_color_no_hash( $color );
  5552 		if ( empty( $color ) )
  5857 		if ( empty( $color ) ) {
  5553 			$color = get_theme_support( 'custom-header', 'default-text-color' );
  5858 			$color = get_theme_support( 'custom-header', 'default-text-color' );
       
  5859 		}
  5554 
  5860 
  5555 		return $color;
  5861 		return $color;
  5556 	}
  5862 	}
  5557 
  5863 
  5558 	/**
  5864 	/**
  5629 	public function _validate_header_video( $validity, $value ) {
  5935 	public function _validate_header_video( $validity, $value ) {
  5630 		$video = get_attached_file( absint( $value ) );
  5936 		$video = get_attached_file( absint( $value ) );
  5631 		if ( $video ) {
  5937 		if ( $video ) {
  5632 			$size = filesize( $video );
  5938 			$size = filesize( $video );
  5633 			if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB.
  5939 			if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB.
  5634 				$validity->add( 'size_too_large',
  5940 				$validity->add(
       
  5941 					'size_too_large',
  5635 					__( 'This video file is too large to use as a header video. Try a shorter video or optimize the compression settings and re-upload a file that is less than 8MB. Or, upload your video to YouTube and link it with the option below.' )
  5942 					__( 'This video file is too large to use as a header video. Try a shorter video or optimize the compression settings and re-upload a file that is less than 8MB. Or, upload your video to YouTube and link it with the option below.' )
  5636 				);
  5943 				);
  5637 			}
  5944 			}
  5638 			if ( '.mp4' !== substr( $video, -4 ) && '.mov' !== substr( $video, -4 ) ) { // Check for .mp4 or .mov format, which (assuming h.264 encoding) are the only cross-browser-supported formats.
  5945 			if ( '.mp4' !== substr( $video, -4 ) && '.mov' !== substr( $video, -4 ) ) { // Check for .mp4 or .mov format, which (assuming h.264 encoding) are the only cross-browser-supported formats.
  5639 				$validity->add( 'invalid_file_type', sprintf(
  5946 				$validity->add(
  5640 					/* translators: 1: .mp4, 2: .mov */
  5947 					'invalid_file_type',
  5641 					__( 'Only %1$s or %2$s files may be used for header video. Please convert your video file and try again, or, upload your video to YouTube and link it with the option below.' ),
  5948 					sprintf(
  5642 					'<code>.mp4</code>',
  5949 						/* translators: 1: .mp4, 2: .mov */
  5643 					'<code>.mov</code>'
  5950 						__( 'Only %1$s or %2$s files may be used for header video. Please convert your video file and try again, or, upload your video to YouTube and link it with the option below.' ),
  5644 				) );
  5951 						'<code>.mp4</code>',
       
  5952 						'<code>.mov</code>'
       
  5953 					)
       
  5954 				);
  5645 			}
  5955 			}
  5646 		}
  5956 		}
  5647 		return $validity;
  5957 		return $validity;
  5648 	}
  5958 	}
  5649 
  5959