wp/wp-includes/class-wp-customize-manager.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    22  */
    22  */
    23 final class WP_Customize_Manager {
    23 final class WP_Customize_Manager {
    24 	/**
    24 	/**
    25 	 * An instance of the theme being previewed.
    25 	 * An instance of the theme being previewed.
    26 	 *
    26 	 *
       
    27 	 * @since 3.4.0
    27 	 * @var WP_Theme
    28 	 * @var WP_Theme
    28 	 */
    29 	 */
    29 	protected $theme;
    30 	protected $theme;
    30 
    31 
    31 	/**
    32 	/**
    32 	 * The directory name of the previously active theme (within the theme_root).
    33 	 * The directory name of the previously active theme (within the theme_root).
    33 	 *
    34 	 *
       
    35 	 * @since 3.4.0
    34 	 * @var string
    36 	 * @var string
    35 	 */
    37 	 */
    36 	protected $original_stylesheet;
    38 	protected $original_stylesheet;
    37 
    39 
    38 	/**
    40 	/**
    39 	 * Whether this is a Customizer pageload.
    41 	 * Whether this is a Customizer pageload.
    40 	 *
    42 	 *
    41 	 * @var boolean
    43 	 * @since 3.4.0
       
    44 	 * @var bool
    42 	 */
    45 	 */
    43 	protected $previewing = false;
    46 	protected $previewing = false;
    44 
    47 
    45 	/**
    48 	/**
    46 	 * Methods and properties deailing with managing widgets in the Customizer.
    49 	 * Methods and properties dealing with managing widgets in the Customizer.
    47 	 *
    50 	 *
       
    51 	 * @since 3.9.0
    48 	 * @var WP_Customize_Widgets
    52 	 * @var WP_Customize_Widgets
    49 	 */
    53 	 */
    50 	public $widgets;
    54 	public $widgets;
    51 
    55 
    52 	protected $settings   = array();
    56 	/**
       
    57 	 * Methods and properties dealing with managing nav menus in the Customizer.
       
    58 	 *
       
    59 	 * @since 4.3.0
       
    60 	 * @var WP_Customize_Nav_Menus
       
    61 	 */
       
    62 	public $nav_menus;
       
    63 
       
    64 	/**
       
    65 	 * Methods and properties dealing with selective refresh in the Customizer preview.
       
    66 	 *
       
    67 	 * @since 4.5.0
       
    68 	 * @var WP_Customize_Selective_Refresh
       
    69 	 */
       
    70 	public $selective_refresh;
       
    71 
       
    72 	/**
       
    73 	 * Registered instances of WP_Customize_Setting.
       
    74 	 *
       
    75 	 * @since 3.4.0
       
    76 	 * @var array
       
    77 	 */
       
    78 	protected $settings = array();
       
    79 
       
    80 	/**
       
    81 	 * Sorted top-level instances of WP_Customize_Panel and WP_Customize_Section.
       
    82 	 *
       
    83 	 * @since 4.0.0
       
    84 	 * @var array
       
    85 	 */
    53 	protected $containers = array();
    86 	protected $containers = array();
    54 	protected $panels     = array();
    87 
    55 	protected $sections   = array();
    88 	/**
    56 	protected $controls   = array();
    89 	 * Registered instances of WP_Customize_Panel.
    57 
    90 	 *
    58 	protected $nonce_tick;
    91 	 * @since 4.0.0
    59 
    92 	 * @var array
    60 	protected $customized;
    93 	 */
    61 
    94 	protected $panels = array();
    62 	/**
    95 
    63 	 * Controls that may be rendered from JS templates.
    96 	/**
       
    97 	 * List of core components.
       
    98 	 *
       
    99 	 * @since 4.5.0
       
   100 	 * @var array
       
   101 	 */
       
   102 	protected $components = array( 'widgets', 'nav_menus' );
       
   103 
       
   104 	/**
       
   105 	 * Registered instances of WP_Customize_Section.
       
   106 	 *
       
   107 	 * @since 3.4.0
       
   108 	 * @var array
       
   109 	 */
       
   110 	protected $sections = array();
       
   111 
       
   112 	/**
       
   113 	 * Registered instances of WP_Customize_Control.
       
   114 	 *
       
   115 	 * @since 3.4.0
       
   116 	 * @var array
       
   117 	 */
       
   118 	protected $controls = array();
       
   119 
       
   120 	/**
       
   121 	 * Panel types that may be rendered from JS templates.
       
   122 	 *
       
   123 	 * @since 4.3.0
       
   124 	 * @var array
       
   125 	 */
       
   126 	protected $registered_panel_types = array();
       
   127 
       
   128 	/**
       
   129 	 * Section types that may be rendered from JS templates.
       
   130 	 *
       
   131 	 * @since 4.3.0
       
   132 	 * @var array
       
   133 	 */
       
   134 	protected $registered_section_types = array();
       
   135 
       
   136 	/**
       
   137 	 * Control types that may be rendered from JS templates.
    64 	 *
   138 	 *
    65 	 * @since 4.1.0
   139 	 * @since 4.1.0
    66 	 * @access protected
       
    67 	 * @var array
   140 	 * @var array
    68 	 */
   141 	 */
    69 	protected $registered_control_types = array();
   142 	protected $registered_control_types = array();
    70 
   143 
    71 	/**
   144 	/**
       
   145 	 * Initial URL being previewed.
       
   146 	 *
       
   147 	 * @since 4.4.0
       
   148 	 * @var string
       
   149 	 */
       
   150 	protected $preview_url;
       
   151 
       
   152 	/**
       
   153 	 * URL to link the user to when closing the Customizer.
       
   154 	 *
       
   155 	 * @since 4.4.0
       
   156 	 * @var string
       
   157 	 */
       
   158 	protected $return_url;
       
   159 
       
   160 	/**
       
   161 	 * Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
       
   162 	 *
       
   163 	 * @since 4.4.0
       
   164 	 * @var array
       
   165 	 */
       
   166 	protected $autofocus = array();
       
   167 
       
   168 	/**
       
   169 	 * Messenger channel.
       
   170 	 *
       
   171 	 * @since 4.7.0
       
   172 	 * @var string
       
   173 	 */
       
   174 	protected $messenger_channel;
       
   175 
       
   176 	/**
       
   177 	 * Whether the autosave revision of the changeset should be loaded.
       
   178 	 *
       
   179 	 * @since 4.9.0
       
   180 	 * @var bool
       
   181 	 */
       
   182 	protected $autosaved = false;
       
   183 
       
   184 	/**
       
   185 	 * Whether the changeset branching is allowed.
       
   186 	 *
       
   187 	 * @since 4.9.0
       
   188 	 * @var bool
       
   189 	 */
       
   190 	protected $branching = true;
       
   191 
       
   192 	/**
       
   193 	 * Whether settings should be previewed.
       
   194 	 *
       
   195 	 * @since 4.9.0
       
   196 	 * @var bool
       
   197 	 */
       
   198 	protected $settings_previewed = true;
       
   199 
       
   200 	/**
       
   201 	 * Whether a starter content changeset was saved.
       
   202 	 *
       
   203 	 * @since 4.9.0
       
   204 	 * @var bool
       
   205 	 */
       
   206 	protected $saved_starter_content_changeset = false;
       
   207 
       
   208 	/**
    72 	 * Unsanitized values for Customize Settings parsed from $_POST['customized'].
   209 	 * Unsanitized values for Customize Settings parsed from $_POST['customized'].
    73 	 *
   210 	 *
    74 	 * @var array
   211 	 * @var array
    75 	 */
   212 	 */
    76 	private $_post_values;
   213 	private $_post_values;
    77 
   214 
    78 	/**
   215 	/**
       
   216 	 * Changeset UUID.
       
   217 	 *
       
   218 	 * @since 4.7.0
       
   219 	 * @var string
       
   220 	 */
       
   221 	private $_changeset_uuid;
       
   222 
       
   223 	/**
       
   224 	 * Changeset post ID.
       
   225 	 *
       
   226 	 * @since 4.7.0
       
   227 	 * @var int|false
       
   228 	 */
       
   229 	private $_changeset_post_id;
       
   230 
       
   231 	/**
       
   232 	 * Changeset data loaded from a customize_changeset post.
       
   233 	 *
       
   234 	 * @since 4.7.0
       
   235 	 * @var array
       
   236 	 */
       
   237 	private $_changeset_data;
       
   238 
       
   239 	/**
    79 	 * Constructor.
   240 	 * Constructor.
    80 	 *
   241 	 *
    81 	 * @since 3.4.0
   242 	 * @since 3.4.0
    82 	 */
   243 	 * @since 4.7.0 Added $args param.
    83 	public function __construct() {
   244 	 *
       
   245 	 * @param array $args {
       
   246 	 *     Args.
       
   247 	 *
       
   248 	 *     @type null|string|false $changeset_uuid     Changeset UUID, the `post_name` for the customize_changeset post containing the customized state.
       
   249 	 *                                                 Defaults to `null` resulting in a UUID to be immediately generated. If `false` is provided, then
       
   250 	 *                                                 then the changeset UUID will be determined during `after_setup_theme`: when the
       
   251 	 *                                                 `customize_changeset_branching` filter returns false, then the default UUID will be that
       
   252 	 *                                                 of the most recent `customize_changeset` post that has a status other than 'auto-draft',
       
   253 	 *                                                 'publish', or 'trash'. Otherwise, if changeset branching is enabled, then a random UUID will be used.
       
   254 	 *     @type string            $theme              Theme to be previewed (for theme switch). Defaults to customize_theme or theme query params.
       
   255 	 *     @type string            $messenger_channel  Messenger channel. Defaults to customize_messenger_channel query param.
       
   256 	 *     @type bool              $settings_previewed If settings should be previewed. Defaults to true.
       
   257 	 *     @type bool              $branching          If changeset branching is allowed; otherwise, changesets are linear. Defaults to true.
       
   258 	 *     @type bool              $autosaved          If data from a changeset's autosaved revision should be loaded if it exists. Defaults to false.
       
   259 	 * }
       
   260 	 */
       
   261 	public function __construct( $args = array() ) {
       
   262 
       
   263 		$args = array_merge(
       
   264 			array_fill_keys( array( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed', 'autosaved', 'branching' ), null ),
       
   265 			$args
       
   266 		);
       
   267 
       
   268 		// Note that the UUID format will be validated in the setup_theme() method.
       
   269 		if ( ! isset( $args['changeset_uuid'] ) ) {
       
   270 			$args['changeset_uuid'] = wp_generate_uuid4();
       
   271 		}
       
   272 
       
   273 		// The theme and messenger_channel should be supplied via $args, but they are also looked at in the $_REQUEST global here for back-compat.
       
   274 		if ( ! isset( $args['theme'] ) ) {
       
   275 			if ( isset( $_REQUEST['customize_theme'] ) ) {
       
   276 				$args['theme'] = wp_unslash( $_REQUEST['customize_theme'] );
       
   277 			} elseif ( isset( $_REQUEST['theme'] ) ) { // Deprecated.
       
   278 				$args['theme'] = wp_unslash( $_REQUEST['theme'] );
       
   279 			}
       
   280 		}
       
   281 		if ( ! isset( $args['messenger_channel'] ) && isset( $_REQUEST['customize_messenger_channel'] ) ) {
       
   282 			$args['messenger_channel'] = sanitize_key( wp_unslash( $_REQUEST['customize_messenger_channel'] ) );
       
   283 		}
       
   284 
       
   285 		$this->original_stylesheet = get_stylesheet();
       
   286 		$this->theme = wp_get_theme( 0 === validate_file( $args['theme'] ) ? $args['theme'] : null );
       
   287 		$this->messenger_channel = $args['messenger_channel'];
       
   288 		$this->_changeset_uuid = $args['changeset_uuid'];
       
   289 
       
   290 		foreach ( array( 'settings_previewed', 'autosaved', 'branching' ) as $key ) {
       
   291 			if ( isset( $args[ $key ] ) ) {
       
   292 				$this->$key = (bool) $args[ $key ];
       
   293 			}
       
   294 		}
       
   295 
    84 		require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
   296 		require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
    85 		require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
   297 		require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
    86 		require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
   298 		require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
    87 		require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
   299 		require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
    88 		require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
   300 
    89 
   301 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-color-control.php' );
    90 		$this->widgets = new WP_Customize_Widgets( $this );
   302 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-media-control.php' );
    91 
   303 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php' );
    92 		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
   304 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php' );
       
   305 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' );
       
   306 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-position-control.php' );
       
   307 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' );
       
   308 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
       
   309 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
       
   310 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
       
   311 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-code-editor-control.php' );
       
   312 		require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
       
   313 		require_once( ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php' );
       
   314 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-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' );
       
   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' );
       
   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.
       
   321 
       
   322 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
       
   323 
       
   324 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.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' );
       
   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.
       
   329 
       
   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' );
       
   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' );
       
   334 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
       
   335 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
       
   336 
       
   337 		/**
       
   338 		 * Filters the core Customizer components to load.
       
   339 		 *
       
   340 		 * This allows Core components to be excluded from being instantiated by
       
   341 		 * filtering them out of the array. Note that this filter generally runs
       
   342 		 * during the {@see 'plugins_loaded'} action, so it cannot be added
       
   343 		 * in a theme.
       
   344 		 *
       
   345 		 * @since 4.4.0
       
   346 		 *
       
   347 		 * @see WP_Customize_Manager::__construct()
       
   348 		 *
       
   349 		 * @param array                $components List of core components to load.
       
   350 		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
       
   351 		 */
       
   352 		$components = apply_filters( 'customize_loaded_components', $this->components, $this );
       
   353 
       
   354 		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
       
   355 		$this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
       
   356 
       
   357 		if ( in_array( 'widgets', $components, true ) ) {
       
   358 			require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
       
   359 			$this->widgets = new WP_Customize_Widgets( $this );
       
   360 		}
       
   361 
       
   362 		if ( in_array( 'nav_menus', $components, true ) ) {
       
   363 			require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
       
   364 			$this->nav_menus = new WP_Customize_Nav_Menus( $this );
       
   365 		}
    93 
   366 
    94 		add_action( 'setup_theme', array( $this, 'setup_theme' ) );
   367 		add_action( 'setup_theme', array( $this, 'setup_theme' ) );
    95 		add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
   368 		add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
    96 
       
    97 		// Run wp_redirect_status late to make sure we override the status last.
       
    98 		add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
       
    99 
   369 
   100 		// 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.
   101 		remove_action( 'init', 'wp_cron' );
   371 		remove_action( 'init', 'wp_cron' );
   102 
   372 
   103 		// Do not run update checks when rendering the controls.
   373 		// Do not run update checks when rendering the controls.
   104 		remove_action( 'admin_init', '_maybe_update_core' );
   374 		remove_action( 'admin_init', '_maybe_update_core' );
   105 		remove_action( 'admin_init', '_maybe_update_plugins' );
   375 		remove_action( 'admin_init', '_maybe_update_plugins' );
   106 		remove_action( 'admin_init', '_maybe_update_themes' );
   376 		remove_action( 'admin_init', '_maybe_update_themes' );
   107 
   377 
   108 		add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
   378 		add_action( 'wp_ajax_customize_save',                     array( $this, 'save' ) );
   109 		add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
   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' ) );
       
   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' ) );
       
   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' ) );
       
   385 		add_action( 'wp_ajax_customize_dismiss_autosave_or_lock', array( $this, 'handle_dismiss_autosave_or_lock_request' ) );
   110 
   386 
   111 		add_action( 'customize_register',                 array( $this, 'register_controls' ) );
   387 		add_action( 'customize_register',                 array( $this, 'register_controls' ) );
   112 		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
   113 		add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
   389 		add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
   114 		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
   390 		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
   115 	}
   391 
   116 
   392 		// Render Common, Panel, Section, and Control templates.
   117 	/**
   393 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
   118 	 * Return true if it's an AJAX request.
   394 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
       
   395 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
       
   396 
       
   397 		// Export header video settings with the partial response.
       
   398 		add_filter( 'customize_render_partials_response', array( $this, 'export_header_video_settings' ), 10, 3 );
       
   399 
       
   400 		// Export the settings to JS via the _wpCustomizeSettings variable.
       
   401 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
       
   402 
       
   403 		// Add theme update notices.
       
   404 		if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) {
       
   405 			require_once ABSPATH . '/wp-admin/includes/update.php';
       
   406 			add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' );
       
   407 		}
       
   408 	}
       
   409 
       
   410 	/**
       
   411 	 * Return true if it's an Ajax request.
   119 	 *
   412 	 *
   120 	 * @since 3.4.0
   413 	 * @since 3.4.0
   121 	 * @since 4.2.0 Added `$action` param.
   414 	 * @since 4.2.0 Added `$action` param.
   122 	 * @access public
   415 	 *
   123 	 *
   416 	 * @param string|null $action Whether the supplied Ajax action is being run.
   124 	 * @param string|null $action Whether the supplied AJAX action is being run.
   417 	 * @return bool True if it's an Ajax request, false otherwise.
   125 	 * @return bool True if it's an AJAX request, false otherwise.
       
   126 	 */
   418 	 */
   127 	public function doing_ajax( $action = null ) {
   419 	public function doing_ajax( $action = null ) {
   128 		$doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
   420 		if ( ! wp_doing_ajax() ) {
   129 		if ( ! $doing_ajax ) {
       
   130 			return false;
   421 			return false;
   131 		}
   422 		}
   132 
   423 
   133 		if ( ! $action ) {
   424 		if ( ! $action ) {
   134 			return true;
   425 			return true;
   141 		}
   432 		}
   142 	}
   433 	}
   143 
   434 
   144 	/**
   435 	/**
   145 	 * Custom wp_die wrapper. Returns either the standard message for UI
   436 	 * Custom wp_die wrapper. Returns either the standard message for UI
   146 	 * or the AJAX message.
   437 	 * or the Ajax message.
   147 	 *
   438 	 *
   148 	 * @since 3.4.0
   439 	 * @since 3.4.0
   149 	 *
   440 	 *
   150 	 * @param mixed $ajax_message AJAX return
   441 	 * @param mixed $ajax_message Ajax return
   151 	 * @param mixed $message UI message
   442 	 * @param mixed $message UI message
   152 	 */
   443 	 */
   153 	protected function wp_die( $ajax_message, $message = null ) {
   444 	protected function wp_die( $ajax_message, $message = null ) {
   154 		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
   445 		if ( $this->doing_ajax() ) {
   155 			wp_die( $ajax_message );
   446 			wp_die( $ajax_message );
   156 		}
   447 		}
   157 
   448 
   158 		if ( ! $message ) {
   449 		if ( ! $message ) {
   159 			$message = __( 'Cheatin’ uh?' );
   450 			$message = __( 'Something went wrong.' );
       
   451 		}
       
   452 
       
   453 		if ( $this->messenger_channel ) {
       
   454 			ob_start();
       
   455 			wp_enqueue_scripts();
       
   456 			wp_print_scripts( array( 'customize-base' ) );
       
   457 
       
   458 			$settings = array(
       
   459 				'messengerArgs' => array(
       
   460 					'channel' => $this->messenger_channel,
       
   461 					'url' => wp_customize_url(),
       
   462 				),
       
   463 				'error' => $ajax_message,
       
   464 			);
       
   465 			?>
       
   466 			<script>
       
   467 			( function( api, settings ) {
       
   468 				var preview = new api.Messenger( settings.messengerArgs );
       
   469 				preview.send( 'iframe-loading-error', settings.error );
       
   470 			} )( wp.customize, <?php echo wp_json_encode( $settings ) ?> );
       
   471 			</script>
       
   472 			<?php
       
   473 			$message .= ob_get_clean();
   160 		}
   474 		}
   161 
   475 
   162 		wp_die( $message );
   476 		wp_die( $message );
   163 	}
   477 	}
   164 
   478 
   165 	/**
   479 	/**
   166 	 * Return the AJAX wp_die() handler if it's a customized request.
   480 	 * Return the Ajax wp_die() handler if it's a customized request.
   167 	 *
   481 	 *
   168 	 * @since 3.4.0
   482 	 * @since 3.4.0
   169 	 *
   483 	 * @deprecated 4.7.0
   170 	 * @return string
   484 	 *
       
   485 	 * @return callable Die handler.
   171 	 */
   486 	 */
   172 	public function wp_die_handler() {
   487 	public function wp_die_handler() {
       
   488 		_deprecated_function( __METHOD__, '4.7.0' );
       
   489 
   173 		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
   490 		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
   174 			return '_ajax_wp_die_handler';
   491 			return '_ajax_wp_die_handler';
   175 		}
   492 		}
   176 
   493 
   177 		return '_default_wp_die_handler';
   494 		return '_default_wp_die_handler';
   181 	 * Start preview and customize theme.
   498 	 * Start preview and customize theme.
   182 	 *
   499 	 *
   183 	 * Check if customize query variable exist. Init filters to filter the current theme.
   500 	 * Check if customize query variable exist. Init filters to filter the current theme.
   184 	 *
   501 	 *
   185 	 * @since 3.4.0
   502 	 * @since 3.4.0
       
   503 	 *
       
   504 	 * @global string $pagenow
   186 	 */
   505 	 */
   187 	public function setup_theme() {
   506 	public function setup_theme() {
   188 		send_origin_headers();
   507 		global $pagenow;
   189 
   508 
   190 		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
   509 		// Check permissions for customize.php access since this method is called before customize.php can run any code,
   191 		if ( is_admin() && ! $doing_ajax_or_is_customized ) {
   510 		if ( 'customize.php' === $pagenow && ! current_user_can( 'customize' ) ) {
   192 			auth_redirect();
   511 			if ( ! is_user_logged_in() ) {
   193 		} elseif ( $doing_ajax_or_is_customized && ! is_user_logged_in() ) {
   512 				auth_redirect();
   194 			$this->wp_die( 0 );
   513 			} else {
   195 		}
   514 				wp_die(
   196 
   515 					'<h1>' . __( 'You need a higher level of permission.' ) . '</h1>' .
   197 		show_admin_bar( false );
   516 					'<p>' . __( 'Sorry, you are not allowed to customize this site.' ) . '</p>',
   198 
   517 					403
   199 		if ( ! current_user_can( 'customize' ) ) {
   518 				);
   200 			$this->wp_die( -1 );
   519 			}
   201 		}
   520 			return;
   202 
   521 		}
   203 		$this->original_stylesheet = get_stylesheet();
   522 
   204 
   523 		// If a changeset was provided is invalid.
   205 		$this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
   524 		if ( isset( $this->_changeset_uuid ) && false !== $this->_changeset_uuid && ! wp_is_uuid( $this->_changeset_uuid ) ) {
       
   525 			$this->wp_die( -1, __( 'Invalid changeset UUID' ) );
       
   526 		}
       
   527 
       
   528 		/*
       
   529 		 * Clear incoming post data if the user lacks a CSRF token (nonce). Note that the customizer
       
   530 		 * application will inject the customize_preview_nonce query parameter into all Ajax requests.
       
   531 		 * For similar behavior elsewhere in WordPress, see rest_cookie_check_errors() which logs out
       
   532 		 * a user when a valid nonce isn't present.
       
   533 		 */
       
   534 		$has_post_data_nonce = (
       
   535 			check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce', false )
       
   536 			||
       
   537 			check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce', false )
       
   538 			||
       
   539 			check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'customize_preview_nonce', false )
       
   540 		);
       
   541 		if ( ! current_user_can( 'customize' ) || ! $has_post_data_nonce ) {
       
   542 			unset( $_POST['customized'] );
       
   543 			unset( $_REQUEST['customized'] );
       
   544 		}
       
   545 
       
   546 		/*
       
   547 		 * If unauthenticated then require a valid changeset UUID to load the preview.
       
   548 		 * In this way, the UUID serves as a secret key. If the messenger channel is present,
       
   549 		 * then send unauthenticated code to prompt re-auth.
       
   550 		 */
       
   551 		if ( ! current_user_can( 'customize' ) && ! $this->changeset_post_id() ) {
       
   552 			$this->wp_die( $this->messenger_channel ? 0 : -1, __( 'Non-existent changeset UUID.' ) );
       
   553 		}
       
   554 
       
   555 		if ( ! headers_sent() ) {
       
   556 			send_origin_headers();
       
   557 		}
       
   558 
       
   559 		// Hide the admin bar if we're embedded in the customizer iframe.
       
   560 		if ( $this->messenger_channel ) {
       
   561 			show_admin_bar( false );
       
   562 		}
   206 
   563 
   207 		if ( $this->is_theme_active() ) {
   564 		if ( $this->is_theme_active() ) {
   208 			// Once the theme is loaded, we'll validate it.
   565 			// Once the theme is loaded, we'll validate it.
   209 			add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
   566 			add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
   210 		} else {
   567 		} else {
   211 			// If the requested theme is not the active theme and the user doesn't have the
   568 			// If the requested theme is not the active theme and the user doesn't have the
   212 			// switch_themes cap, bail.
   569 			// switch_themes cap, bail.
   213 			if ( ! current_user_can( 'switch_themes' ) ) {
   570 			if ( ! current_user_can( 'switch_themes' ) ) {
   214 				$this->wp_die( -1 );
   571 				$this->wp_die( -1, __( 'Sorry, you are not allowed to edit theme options on this site.' ) );
   215 			}
   572 			}
   216 
   573 
   217 			// If the theme has errors while loading, bail.
   574 			// If the theme has errors while loading, bail.
   218 			if ( $this->theme()->errors() ) {
   575 			if ( $this->theme()->errors() ) {
   219 				$this->wp_die( -1 );
   576 				$this->wp_die( -1, $this->theme()->errors()->get_error_message() );
   220 			}
   577 			}
   221 
   578 
   222 			// If the theme isn't allowed per multisite settings, bail.
   579 			// If the theme isn't allowed per multisite settings, bail.
   223 			if ( ! $this->theme()->is_allowed() ) {
   580 			if ( ! $this->theme()->is_allowed() ) {
   224 				$this->wp_die( -1 );
   581 				$this->wp_die( -1, __( 'The requested theme does not exist.' ) );
   225 			}
   582 			}
       
   583 		}
       
   584 
       
   585 		// Make sure changeset UUID is established immediately after the theme is loaded.
       
   586 		add_action( 'after_setup_theme', array( $this, 'establish_loaded_changeset' ), 5 );
       
   587 
       
   588 		/*
       
   589 		 * Import theme starter content for fresh installations when landing in the customizer.
       
   590 		 * Import starter content at after_setup_theme:100 so that any
       
   591 		 * add_theme_support( 'starter-content' ) calls will have been made.
       
   592 		 */
       
   593 		if ( get_option( 'fresh_site' ) && 'customize.php' === $pagenow ) {
       
   594 			add_action( 'after_setup_theme', array( $this, 'import_theme_starter_content' ), 100 );
   226 		}
   595 		}
   227 
   596 
   228 		$this->start_previewing_theme();
   597 		$this->start_previewing_theme();
   229 	}
   598 	}
   230 
   599 
   231 	/**
   600 	/**
       
   601 	 * Establish the loaded changeset.
       
   602 	 *
       
   603 	 * This method runs right at after_setup_theme and applies the 'customize_changeset_branching' filter to determine
       
   604 	 * whether concurrent changesets are allowed. Then if the Customizer is not initialized with a `changeset_uuid` param,
       
   605 	 * this method will determine which UUID should be used. If changeset branching is disabled, then the most saved
       
   606 	 * changeset will be loaded by default. Otherwise, if there are no existing saved changesets or if changeset branching is
       
   607 	 * enabled, then a new UUID will be generated.
       
   608 	 *
       
   609 	 * @since 4.9.0
       
   610 	 * @global string $pagenow
       
   611 	 */
       
   612 	public function establish_loaded_changeset() {
       
   613 		global $pagenow;
       
   614 
       
   615 		if ( empty( $this->_changeset_uuid ) ) {
       
   616 			$changeset_uuid = null;
       
   617 
       
   618 			if ( ! $this->branching() && $this->is_theme_active() ) {
       
   619 				$unpublished_changeset_posts = $this->get_changeset_posts( array(
       
   620 					'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ),
       
   621 					'exclude_restore_dismissed' => false,
       
   622 					'author' => 'any',
       
   623 					'posts_per_page' => 1,
       
   624 					'order' => 'DESC',
       
   625 					'orderby' => 'date',
       
   626 				) );
       
   627 				$unpublished_changeset_post = array_shift( $unpublished_changeset_posts );
       
   628 				if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) {
       
   629 					$changeset_uuid = $unpublished_changeset_post->post_name;
       
   630 				}
       
   631 			}
       
   632 
       
   633 			// If no changeset UUID has been set yet, then generate a new one.
       
   634 			if ( empty( $changeset_uuid ) ) {
       
   635 				$changeset_uuid = wp_generate_uuid4();
       
   636 			}
       
   637 
       
   638 			$this->_changeset_uuid = $changeset_uuid;
       
   639 		}
       
   640 
       
   641 		if ( is_admin() && 'customize.php' === $pagenow ) {
       
   642 			$this->set_changeset_lock( $this->changeset_post_id() );
       
   643 		}
       
   644 	}
       
   645 
       
   646 	/**
   232 	 * Callback to validate a theme once it is loaded
   647 	 * Callback to validate a theme once it is loaded
   233 	 *
   648 	 *
   234 	 * @since 3.4.0
   649 	 * @since 3.4.0
   235 	 */
   650 	 */
   236 	public function after_setup_theme() {
   651 	public function after_setup_theme() {
   237 		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_SERVER['customized'] ) );
   652 		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
   238 		if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
   653 		if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
   239 			wp_redirect( 'themes.php?broken=true' );
   654 			wp_redirect( 'themes.php?broken=true' );
   240 			exit;
   655 			exit;
   241 		}
   656 		}
   242 	}
   657 	}
   316 		 */
   731 		 */
   317 		do_action( 'stop_previewing_theme', $this );
   732 		do_action( 'stop_previewing_theme', $this );
   318 	}
   733 	}
   319 
   734 
   320 	/**
   735 	/**
       
   736 	 * Gets whether settings are or will be previewed.
       
   737 	 *
       
   738 	 * @since 4.9.0
       
   739 	 * @see WP_Customize_Setting::preview()
       
   740 	 *
       
   741 	 * @return bool
       
   742 	 */
       
   743 	public function settings_previewed() {
       
   744 		return $this->settings_previewed;
       
   745 	}
       
   746 
       
   747 	/**
       
   748 	 * Gets whether data from a changeset's autosaved revision should be loaded if it exists.
       
   749 	 *
       
   750 	 * @since 4.9.0
       
   751 	 * @see WP_Customize_Manager::changeset_data()
       
   752 	 *
       
   753 	 * @return bool Is using autosaved changeset revision.
       
   754 	 */
       
   755 	public function autosaved() {
       
   756 		return $this->autosaved;
       
   757 	}
       
   758 
       
   759 	/**
       
   760 	 * Whether the changeset branching is allowed.
       
   761 	 *
       
   762 	 * @since 4.9.0
       
   763 	 * @see WP_Customize_Manager::establish_loaded_changeset()
       
   764 	 *
       
   765 	 * @return bool Is changeset branching.
       
   766 	 */
       
   767 	public function branching() {
       
   768 
       
   769 		/**
       
   770 		 * Filters whether or not changeset branching is allowed.
       
   771 		 *
       
   772 		 * By default in core, when changeset branching is not allowed, changesets will operate
       
   773 		 * linearly in that only one saved changeset will exist at a time (with a 'draft' or
       
   774 		 * 'future' status). This makes the Customizer operate in a way that is similar to going to
       
   775 		 * "edit" to one existing post: all users will be making changes to the same post, and autosave
       
   776 		 * revisions will be made for that post.
       
   777 		 *
       
   778 		 * By contrast, when changeset branching is allowed, then the model is like users going
       
   779 		 * to "add new" for a page and each user makes changes independently of each other since
       
   780 		 * they are all operating on their own separate pages, each getting their own separate
       
   781 		 * initial auto-drafts and then once initially saved, autosave revisions on top of that
       
   782 		 * user's specific post.
       
   783 		 *
       
   784 		 * Since linear changesets are deemed to be more suitable for the majority of WordPress users,
       
   785 		 * they are the default. For WordPress sites that have heavy site management in the Customizer
       
   786 		 * by multiple users then branching changesets should be enabled by means of this filter.
       
   787 		 *
       
   788 		 * @since 4.9.0
       
   789 		 *
       
   790 		 * @param bool                 $allow_branching Whether branching is allowed. If `false`, the default,
       
   791 		 *                                              then only one saved changeset exists at a time.
       
   792 		 * @param WP_Customize_Manager $wp_customize    Manager instance.
       
   793 		 */
       
   794 		$this->branching = apply_filters( 'customize_changeset_branching', $this->branching, $this );
       
   795 
       
   796 		return $this->branching;
       
   797 	}
       
   798 
       
   799 	/**
       
   800 	 * Get the changeset UUID.
       
   801 	 *
       
   802 	 * @since 4.7.0
       
   803 	 * @see WP_Customize_Manager::establish_loaded_changeset()
       
   804 	 *
       
   805 	 * @return string UUID.
       
   806 	 */
       
   807 	public function changeset_uuid() {
       
   808 		if ( empty( $this->_changeset_uuid ) ) {
       
   809 			$this->establish_loaded_changeset();
       
   810 		}
       
   811 		return $this->_changeset_uuid;
       
   812 	}
       
   813 
       
   814 	/**
   321 	 * Get the theme being customized.
   815 	 * Get the theme being customized.
   322 	 *
   816 	 *
   323 	 * @since 3.4.0
   817 	 * @since 3.4.0
   324 	 *
   818 	 *
   325 	 * @return WP_Theme
   819 	 * @return WP_Theme
   377 
   871 
   378 	/**
   872 	/**
   379 	 * Get the registered panels.
   873 	 * Get the registered panels.
   380 	 *
   874 	 *
   381 	 * @since 4.0.0
   875 	 * @since 4.0.0
   382 	 * @access public
       
   383 	 *
   876 	 *
   384 	 * @return array Panels.
   877 	 * @return array Panels.
   385 	 */
   878 	 */
   386 	public function panels() {
   879 	public function panels() {
   387 		return $this->panels;
   880 		return $this->panels;
   402 	 * Register styles/scripts and initialize the preview of each setting
   895 	 * Register styles/scripts and initialize the preview of each setting
   403 	 *
   896 	 *
   404 	 * @since 3.4.0
   897 	 * @since 3.4.0
   405 	 */
   898 	 */
   406 	public function wp_loaded() {
   899 	public function wp_loaded() {
       
   900 
       
   901 		// Unconditionally register core types for panels, sections, and controls in case plugin unhooks all customize_register actions.
       
   902 		$this->register_panel_type( 'WP_Customize_Panel' );
       
   903 		$this->register_panel_type( 'WP_Customize_Themes_Panel' );
       
   904 		$this->register_section_type( 'WP_Customize_Section' );
       
   905 		$this->register_section_type( 'WP_Customize_Sidebar_Section' );
       
   906 		$this->register_section_type( 'WP_Customize_Themes_Section' );
       
   907 		$this->register_control_type( 'WP_Customize_Color_Control' );
       
   908 		$this->register_control_type( 'WP_Customize_Media_Control' );
       
   909 		$this->register_control_type( 'WP_Customize_Upload_Control' );
       
   910 		$this->register_control_type( 'WP_Customize_Image_Control' );
       
   911 		$this->register_control_type( 'WP_Customize_Background_Image_Control' );
       
   912 		$this->register_control_type( 'WP_Customize_Background_Position_Control' );
       
   913 		$this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
       
   914 		$this->register_control_type( 'WP_Customize_Site_Icon_Control' );
       
   915 		$this->register_control_type( 'WP_Customize_Theme_Control' );
       
   916 		$this->register_control_type( 'WP_Customize_Code_Editor_Control' );
       
   917 		$this->register_control_type( 'WP_Customize_Date_Time_Control' );
   407 
   918 
   408 		/**
   919 		/**
   409 		 * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
   920 		 * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
   410 		 *
   921 		 *
   411 		 * @since 3.4.0
   922 		 * @since 3.4.0
   412 		 *
   923 		 *
   413 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
   924 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
   414 		 */
   925 		 */
   415 		do_action( 'customize_register', $this );
   926 		do_action( 'customize_register', $this );
   416 
   927 
   417 		if ( $this->is_preview() && ! is_admin() )
   928 		if ( $this->settings_previewed() ) {
       
   929 			foreach ( $this->settings as $setting ) {
       
   930 				$setting->preview();
       
   931 			}
       
   932 		}
       
   933 
       
   934 		if ( $this->is_preview() && ! is_admin() ) {
   418 			$this->customize_preview_init();
   935 			$this->customize_preview_init();
   419 	}
   936 		}
   420 
   937 	}
   421 	/**
   938 
   422 	 * Prevents AJAX requests from following redirects when previewing a theme
   939 	/**
       
   940 	 * Prevents Ajax requests from following redirects when previewing a theme
   423 	 * by issuing a 200 response instead of a 30x.
   941 	 * by issuing a 200 response instead of a 30x.
   424 	 *
   942 	 *
   425 	 * Instead, the JS will sniff out the location header.
   943 	 * Instead, the JS will sniff out the location header.
   426 	 *
   944 	 *
   427 	 * @since 3.4.0
   945 	 * @since 3.4.0
   428 	 *
   946 	 * @deprecated 4.7.0
   429 	 * @param $status
   947 	 *
       
   948 	 * @param int $status Status.
   430 	 * @return int
   949 	 * @return int
   431 	 */
   950 	 */
   432 	public function wp_redirect_status( $status ) {
   951 	public function wp_redirect_status( $status ) {
   433 		if ( $this->is_preview() && ! is_admin() )
   952 		_deprecated_function( __FUNCTION__, '4.7.0' );
       
   953 
       
   954 		if ( $this->is_preview() && ! is_admin() ) {
   434 			return 200;
   955 			return 200;
       
   956 		}
   435 
   957 
   436 		return $status;
   958 		return $status;
   437 	}
   959 	}
   438 
   960 
   439 	/**
   961 	/**
   440 	 * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
   962 	 * Find the changeset post ID for a given changeset UUID.
   441 	 * settings for subsequent post_value() lookups.
   963 	 *
       
   964 	 * @since 4.7.0
       
   965 	 *
       
   966 	 * @param string $uuid Changeset UUID.
       
   967 	 * @return int|null Returns post ID on success and null on failure.
       
   968 	 */
       
   969 	public function find_changeset_post_id( $uuid ) {
       
   970 		$cache_group = 'customize_changeset_post';
       
   971 		$changeset_post_id = wp_cache_get( $uuid, $cache_group );
       
   972 		if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) {
       
   973 			return $changeset_post_id;
       
   974 		}
       
   975 
       
   976 		$changeset_post_query = new WP_Query( array(
       
   977 			'post_type' => 'customize_changeset',
       
   978 			'post_status' => get_post_stati(),
       
   979 			'name' => $uuid,
       
   980 			'posts_per_page' => 1,
       
   981 			'no_found_rows' => true,
       
   982 			'cache_results' => true,
       
   983 			'update_post_meta_cache' => false,
       
   984 			'update_post_term_cache' => false,
       
   985 			'lazy_load_term_meta' => false,
       
   986 		) );
       
   987 		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.
       
   989 			$changeset_post_id = $changeset_post_query->posts[0]->ID;
       
   990 			wp_cache_set( $uuid, $changeset_post_id, $cache_group );
       
   991 			return $changeset_post_id;
       
   992 		}
       
   993 
       
   994 		return null;
       
   995 	}
       
   996 
       
   997 	/**
       
   998 	 * Get changeset posts.
       
   999 	 *
       
  1000 	 * @since 4.9.0
       
  1001 	 *
       
  1002 	 * @param array $args {
       
  1003 	 *     Args to pass into `get_posts()` to query changesets.
       
  1004 	 *
       
  1005 	 *     @type int    $posts_per_page             Number of posts to return. Defaults to -1 (all posts).
       
  1006 	 *     @type int    $author                     Post author. Defaults to current user.
       
  1007 	 *     @type string $post_status                Status of changeset. Defaults to 'auto-draft'.
       
  1008 	 *     @type bool   $exclude_restore_dismissed  Whether to exclude changeset auto-drafts that have been dismissed. Defaults to true.
       
  1009 	 * }
       
  1010 	 * @return WP_Post[] Auto-draft changesets.
       
  1011 	 */
       
  1012 	protected function get_changeset_posts( $args = array() ) {
       
  1013 		$default_args = array(
       
  1014 			'exclude_restore_dismissed' => true,
       
  1015 			'posts_per_page' => -1,
       
  1016 			'post_type' => 'customize_changeset',
       
  1017 			'post_status' => 'auto-draft',
       
  1018 			'order' => 'DESC',
       
  1019 			'orderby' => 'date',
       
  1020 			'no_found_rows' => true,
       
  1021 			'cache_results' => true,
       
  1022 			'update_post_meta_cache' => false,
       
  1023 			'update_post_term_cache' => false,
       
  1024 			'lazy_load_term_meta' => false,
       
  1025 		);
       
  1026 		if ( get_current_user_id() ) {
       
  1027 			$default_args['author'] = get_current_user_id();
       
  1028 		}
       
  1029 		$args = array_merge( $default_args, $args );
       
  1030 
       
  1031 		if ( ! empty( $args['exclude_restore_dismissed'] ) ) {
       
  1032 			unset( $args['exclude_restore_dismissed'] );
       
  1033 			$args['meta_query'] = array(
       
  1034 				array(
       
  1035 					'key' => '_customize_restore_dismissed',
       
  1036 					'compare' => 'NOT EXISTS',
       
  1037 				),
       
  1038 			);
       
  1039 		}
       
  1040 
       
  1041 		return get_posts( $args );
       
  1042 	}
       
  1043 
       
  1044 	/**
       
  1045 	 * Dismiss all of the current user's auto-drafts (other than the present one).
       
  1046 	 *
       
  1047 	 * @since 4.9.0
       
  1048 	 * @return int The number of auto-drafts that were dismissed.
       
  1049 	 */
       
  1050 	protected function dismiss_user_auto_draft_changesets() {
       
  1051 		$changeset_autodraft_posts = $this->get_changeset_posts( array(
       
  1052 			'post_status' => 'auto-draft',
       
  1053 			'exclude_restore_dismissed' => true,
       
  1054 			'posts_per_page' => -1,
       
  1055 		) );
       
  1056 		$dismissed = 0;
       
  1057 		foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) {
       
  1058 			if ( $autosave_autodraft_post->ID === $this->changeset_post_id() ) {
       
  1059 				continue;
       
  1060 			}
       
  1061 			if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) {
       
  1062 				$dismissed++;
       
  1063 			}
       
  1064 		}
       
  1065 		return $dismissed;
       
  1066 	}
       
  1067 
       
  1068 	/**
       
  1069 	 * Get the changeset post id for the loaded changeset.
       
  1070 	 *
       
  1071 	 * @since 4.7.0
       
  1072 	 *
       
  1073 	 * @return int|null Post ID on success or null if there is no post yet saved.
       
  1074 	 */
       
  1075 	public function changeset_post_id() {
       
  1076 		if ( ! isset( $this->_changeset_post_id ) ) {
       
  1077 			$post_id = $this->find_changeset_post_id( $this->changeset_uuid() );
       
  1078 			if ( ! $post_id ) {
       
  1079 				$post_id = false;
       
  1080 			}
       
  1081 			$this->_changeset_post_id = $post_id;
       
  1082 		}
       
  1083 		if ( false === $this->_changeset_post_id ) {
       
  1084 			return null;
       
  1085 		}
       
  1086 		return $this->_changeset_post_id;
       
  1087 	}
       
  1088 
       
  1089 	/**
       
  1090 	 * Get the data stored in a changeset post.
       
  1091 	 *
       
  1092 	 * @since 4.7.0
       
  1093 	 *
       
  1094 	 * @param int $post_id Changeset post ID.
       
  1095 	 * @return array|WP_Error Changeset data or WP_Error on error.
       
  1096 	 */
       
  1097 	protected function get_changeset_post_data( $post_id ) {
       
  1098 		if ( ! $post_id ) {
       
  1099 			return new WP_Error( 'empty_post_id' );
       
  1100 		}
       
  1101 		$changeset_post = get_post( $post_id );
       
  1102 		if ( ! $changeset_post ) {
       
  1103 			return new WP_Error( 'missing_post' );
       
  1104 		}
       
  1105 		if ( 'revision' === $changeset_post->post_type ) {
       
  1106 			if ( 'customize_changeset' !== get_post_type( $changeset_post->post_parent ) ) {
       
  1107 				return new WP_Error( 'wrong_post_type' );
       
  1108 			}
       
  1109 		} elseif ( 'customize_changeset' !== $changeset_post->post_type ) {
       
  1110 			return new WP_Error( 'wrong_post_type' );
       
  1111 		}
       
  1112 		$changeset_data = json_decode( $changeset_post->post_content, true );
       
  1113 		if ( function_exists( 'json_last_error' ) && json_last_error() ) {
       
  1114 			return new WP_Error( 'json_parse_error', '', json_last_error() );
       
  1115 		}
       
  1116 		if ( ! is_array( $changeset_data ) ) {
       
  1117 			return new WP_Error( 'expected_array' );
       
  1118 		}
       
  1119 		return $changeset_data;
       
  1120 	}
       
  1121 
       
  1122 	/**
       
  1123 	 * Get changeset data.
       
  1124 	 *
       
  1125 	 * @since 4.7.0
       
  1126 	 * @since 4.9.0 This will return the changeset's data with a user's autosave revision merged on top, if one exists and $autosaved is true.
       
  1127 	 *
       
  1128 	 * @return array Changeset data.
       
  1129 	 */
       
  1130 	public function changeset_data() {
       
  1131 		if ( isset( $this->_changeset_data ) ) {
       
  1132 			return $this->_changeset_data;
       
  1133 		}
       
  1134 		$changeset_post_id = $this->changeset_post_id();
       
  1135 		if ( ! $changeset_post_id ) {
       
  1136 			$this->_changeset_data = array();
       
  1137 		} else {
       
  1138 			if ( $this->autosaved() && is_user_logged_in() ) {
       
  1139 				$autosave_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
       
  1140 				if ( $autosave_post ) {
       
  1141 					$data = $this->get_changeset_post_data( $autosave_post->ID );
       
  1142 					if ( ! is_wp_error( $data ) ) {
       
  1143 						$this->_changeset_data = $data;
       
  1144 					}
       
  1145 				}
       
  1146 			}
       
  1147 
       
  1148 			// Load data from the changeset if it was not loaded from an autosave.
       
  1149 			if ( ! isset( $this->_changeset_data ) ) {
       
  1150 				$data = $this->get_changeset_post_data( $changeset_post_id );
       
  1151 				if ( ! is_wp_error( $data ) ) {
       
  1152 					$this->_changeset_data = $data;
       
  1153 				} else {
       
  1154 					$this->_changeset_data = array();
       
  1155 				}
       
  1156 			}
       
  1157 		}
       
  1158 		return $this->_changeset_data;
       
  1159 	}
       
  1160 
       
  1161 	/**
       
  1162 	 * Starter content setting IDs.
       
  1163 	 *
       
  1164 	 * @since 4.7.0
       
  1165 	 * @var array
       
  1166 	 */
       
  1167 	protected $pending_starter_content_settings_ids = array();
       
  1168 
       
  1169 	/**
       
  1170 	 * Import theme starter content into the customized state.
       
  1171 	 *
       
  1172 	 * @since 4.7.0
       
  1173 	 *
       
  1174 	 * @param array $starter_content Starter content. Defaults to `get_theme_starter_content()`.
       
  1175 	 */
       
  1176 	function import_theme_starter_content( $starter_content = array() ) {
       
  1177 		if ( empty( $starter_content ) ) {
       
  1178 			$starter_content = get_theme_starter_content();
       
  1179 		}
       
  1180 
       
  1181 		$changeset_data = array();
       
  1182 		if ( $this->changeset_post_id() ) {
       
  1183 			/*
       
  1184 			 * Don't re-import starter content into a changeset saved persistently.
       
  1185 			 * This will need to be revisited in the future once theme switching
       
  1186 			 * is allowed with drafted/scheduled changesets, since switching to
       
  1187 			 * another theme could result in more starter content being applied.
       
  1188 			 * However, when doing an explicit save it is currently possible for
       
  1189 			 * nav menus and nav menu items specifically to lose their starter_content
       
  1190 			 * flags, thus resulting in duplicates being created since they fail
       
  1191 			 * to get re-used. See #40146.
       
  1192 			 */
       
  1193 			if ( 'auto-draft' !== get_post_status( $this->changeset_post_id() ) ) {
       
  1194 				return;
       
  1195 			}
       
  1196 
       
  1197 			$changeset_data = $this->get_changeset_post_data( $this->changeset_post_id() );
       
  1198 		}
       
  1199 
       
  1200 		$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();
       
  1202 		$posts = isset( $starter_content['posts'] ) && ! empty( $this->nav_menus ) ? $starter_content['posts'] : array();
       
  1203 		$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();
       
  1205 		$theme_mods = isset( $starter_content['theme_mods'] ) ? $starter_content['theme_mods'] : array();
       
  1206 
       
  1207 		// Widgets.
       
  1208 		$max_widget_numbers = array();
       
  1209 		foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
       
  1210 			$sidebar_widget_ids = array();
       
  1211 			foreach ( $widgets as $widget ) {
       
  1212 				list( $id_base, $instance ) = $widget;
       
  1213 
       
  1214 				if ( ! isset( $max_widget_numbers[ $id_base ] ) ) {
       
  1215 
       
  1216 					// When $settings is an array-like object, get an intrinsic array for use with array_keys().
       
  1217 					$settings = get_option( "widget_{$id_base}", array() );
       
  1218 					if ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) {
       
  1219 						$settings = $settings->getArrayCopy();
       
  1220 					}
       
  1221 
       
  1222 					// Find the max widget number for this type.
       
  1223 					$widget_numbers = array_keys( $settings );
       
  1224 					if ( count( $widget_numbers ) > 0 ) {
       
  1225 						$widget_numbers[] = 1;
       
  1226 						$max_widget_numbers[ $id_base ] = call_user_func_array( 'max', $widget_numbers );
       
  1227 					} else {
       
  1228 						$max_widget_numbers[ $id_base ] = 1;
       
  1229 					}
       
  1230 				}
       
  1231 				$max_widget_numbers[ $id_base ] += 1;
       
  1232 
       
  1233 				$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 ] );
       
  1235 
       
  1236 				$setting_value = $this->widgets->sanitize_widget_js_instance( $instance );
       
  1237 				if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
       
  1238 					$this->set_post_value( $setting_id, $setting_value );
       
  1239 					$this->pending_starter_content_settings_ids[] = $setting_id;
       
  1240 				}
       
  1241 				$sidebar_widget_ids[] = $widget_id;
       
  1242 			}
       
  1243 
       
  1244 			$setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
       
  1245 			if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
       
  1246 				$this->set_post_value( $setting_id, $sidebar_widget_ids );
       
  1247 				$this->pending_starter_content_settings_ids[] = $setting_id;
       
  1248 			}
       
  1249 		}
       
  1250 
       
  1251 		$starter_content_auto_draft_post_ids = array();
       
  1252 		if ( ! empty( $changeset_data['nav_menus_created_posts']['value'] ) ) {
       
  1253 			$starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, $changeset_data['nav_menus_created_posts']['value'] );
       
  1254 		}
       
  1255 
       
  1256 		// Make an index of all the posts needed and what their slugs are.
       
  1257 		$needed_posts = array();
       
  1258 		$attachments = $this->prepare_starter_content_attachments( $attachments );
       
  1259 		foreach ( $attachments as $attachment ) {
       
  1260 			$key = 'attachment:' . $attachment['post_name'];
       
  1261 			$needed_posts[ $key ] = true;
       
  1262 		}
       
  1263 		foreach ( array_keys( $posts ) as $post_symbol ) {
       
  1264 			if ( empty( $posts[ $post_symbol ]['post_name'] ) && empty( $posts[ $post_symbol ]['post_title'] ) ) {
       
  1265 				unset( $posts[ $post_symbol ] );
       
  1266 				continue;
       
  1267 			}
       
  1268 			if ( empty( $posts[ $post_symbol ]['post_name'] ) ) {
       
  1269 				$posts[ $post_symbol ]['post_name'] = sanitize_title( $posts[ $post_symbol ]['post_title'] );
       
  1270 			}
       
  1271 			if ( empty( $posts[ $post_symbol ]['post_type'] ) ) {
       
  1272 				$posts[ $post_symbol ]['post_type'] = 'post';
       
  1273 			}
       
  1274 			$needed_posts[ $posts[ $post_symbol ]['post_type'] . ':' . $posts[ $post_symbol ]['post_name'] ] = true;
       
  1275 		}
       
  1276 		$all_post_slugs = array_merge(
       
  1277 			wp_list_pluck( $attachments, 'post_name' ),
       
  1278 			wp_list_pluck( $posts, 'post_name' )
       
  1279 		);
       
  1280 
       
  1281 		/*
       
  1282 		 * Obtain all post types referenced in starter content to use in query.
       
  1283 		 * This is needed because 'any' will not account for post types not yet registered.
       
  1284 		 */
       
  1285 		$post_types = array_filter( array_merge( array( 'attachment' ), wp_list_pluck( $posts, 'post_type' ) ) );
       
  1286 
       
  1287 		// Re-use auto-draft starter content posts referenced in the current customized state.
       
  1288 		$existing_starter_content_posts = array();
       
  1289 		if ( ! empty( $starter_content_auto_draft_post_ids ) ) {
       
  1290 			$existing_posts_query = new WP_Query( array(
       
  1291 				'post__in' => $starter_content_auto_draft_post_ids,
       
  1292 				'post_status' => 'auto-draft',
       
  1293 				'post_type' => $post_types,
       
  1294 				'posts_per_page' => -1,
       
  1295 			) );
       
  1296 			foreach ( $existing_posts_query->posts as $existing_post ) {
       
  1297 				$post_name = $existing_post->post_name;
       
  1298 				if ( empty( $post_name ) ) {
       
  1299 					$post_name = get_post_meta( $existing_post->ID, '_customize_draft_post_name', true );
       
  1300 				}
       
  1301 				$existing_starter_content_posts[ $existing_post->post_type . ':' . $post_name ] = $existing_post;
       
  1302 			}
       
  1303 		}
       
  1304 
       
  1305 		// Re-use non-auto-draft posts.
       
  1306 		if ( ! empty( $all_post_slugs ) ) {
       
  1307 			$existing_posts_query = new WP_Query( array(
       
  1308 				'post_name__in' => $all_post_slugs,
       
  1309 				'post_status' => array_diff( get_post_stati(), array( 'auto-draft' ) ),
       
  1310 				'post_type' => 'any',
       
  1311 				'posts_per_page' => -1,
       
  1312 			) );
       
  1313 			foreach ( $existing_posts_query->posts as $existing_post ) {
       
  1314 				$key = $existing_post->post_type . ':' . $existing_post->post_name;
       
  1315 				if ( isset( $needed_posts[ $key ] ) && ! isset( $existing_starter_content_posts[ $key ] ) ) {
       
  1316 					$existing_starter_content_posts[ $key ] = $existing_post;
       
  1317 				}
       
  1318 			}
       
  1319 		}
       
  1320 
       
  1321 		// Attachments are technically posts but handled differently.
       
  1322 		if ( ! empty( $attachments ) ) {
       
  1323 
       
  1324 			$attachment_ids = array();
       
  1325 
       
  1326 			foreach ( $attachments as $symbol => $attachment ) {
       
  1327 				$file_array = array(
       
  1328 					'name' => $attachment['file_name'],
       
  1329 				);
       
  1330 				$file_path = $attachment['file_path'];
       
  1331 				$attachment_id = null;
       
  1332 				$attached_file = null;
       
  1333 				if ( isset( $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ] ) ) {
       
  1334 					$attachment_post = $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ];
       
  1335 					$attachment_id = $attachment_post->ID;
       
  1336 					$attached_file = get_attached_file( $attachment_id );
       
  1337 					if ( empty( $attached_file ) || ! file_exists( $attached_file ) ) {
       
  1338 						$attachment_id = null;
       
  1339 						$attached_file = null;
       
  1340 					} elseif ( $this->get_stylesheet() !== get_post_meta( $attachment_post->ID, '_starter_content_theme', true ) ) {
       
  1341 
       
  1342 						// Re-generate attachment metadata since it was previously generated for a different theme.
       
  1343 						$metadata = wp_generate_attachment_metadata( $attachment_post->ID, $attached_file );
       
  1344 						wp_update_attachment_metadata( $attachment_id, $metadata );
       
  1345 						update_post_meta( $attachment_id, '_starter_content_theme', $this->get_stylesheet() );
       
  1346 					}
       
  1347 				}
       
  1348 
       
  1349 				// Insert the attachment auto-draft because it doesn't yet exist or the attached file is gone.
       
  1350 				if ( ! $attachment_id ) {
       
  1351 
       
  1352 					// 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 ) );
       
  1354 					if ( $temp_file_name && copy( $file_path, $temp_file_name ) ) {
       
  1355 						$file_array['tmp_name'] = $temp_file_name;
       
  1356 					}
       
  1357 					if ( empty( $file_array['tmp_name'] ) ) {
       
  1358 						continue;
       
  1359 					}
       
  1360 
       
  1361 					$attachment_post_data = array_merge(
       
  1362 						wp_array_slice_assoc( $attachment, array( 'post_title', 'post_content', 'post_excerpt' ) ),
       
  1363 						array(
       
  1364 							'post_status' => 'auto-draft', // So attachment will be garbage collected in a week if changeset is never published.
       
  1365 						)
       
  1366 					);
       
  1367 
       
  1368 					// In PHP < 5.6 filesize() returns 0 for the temp files unless we clear the file status cache.
       
  1369 					// Technically, PHP < 5.6.0 || < 5.5.13 || < 5.4.29 but no need to be so targeted.
       
  1370 					// See https://bugs.php.net/bug.php?id=65701
       
  1371 					if ( version_compare( PHP_VERSION, '5.6', '<' ) ) {
       
  1372 						clearstatcache();
       
  1373 					}
       
  1374 
       
  1375 					$attachment_id = media_handle_sideload( $file_array, 0, null, $attachment_post_data );
       
  1376 					if ( is_wp_error( $attachment_id ) ) {
       
  1377 						continue;
       
  1378 					}
       
  1379 					update_post_meta( $attachment_id, '_starter_content_theme', $this->get_stylesheet() );
       
  1380 					update_post_meta( $attachment_id, '_customize_draft_post_name', $attachment['post_name'] );
       
  1381 				}
       
  1382 
       
  1383 				$attachment_ids[ $symbol ] = $attachment_id;
       
  1384 			}
       
  1385 			$starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, array_values( $attachment_ids ) );
       
  1386 		}
       
  1387 
       
  1388 		// Posts & pages.
       
  1389 		if ( ! empty( $posts ) ) {
       
  1390 			foreach ( array_keys( $posts ) as $post_symbol ) {
       
  1391 				if ( empty( $posts[ $post_symbol ]['post_type'] ) || empty( $posts[ $post_symbol ]['post_name'] ) ) {
       
  1392 					continue;
       
  1393 				}
       
  1394 				$post_type = $posts[ $post_symbol ]['post_type'];
       
  1395 				if ( ! empty( $posts[ $post_symbol ]['post_name'] ) ) {
       
  1396 					$post_name = $posts[ $post_symbol ]['post_name'];
       
  1397 				} elseif ( ! empty( $posts[ $post_symbol ]['post_title'] ) ) {
       
  1398 					$post_name = sanitize_title( $posts[ $post_symbol ]['post_title'] );
       
  1399 				} else {
       
  1400 					continue;
       
  1401 				}
       
  1402 
       
  1403 				// Use existing auto-draft post if one already exists with the same type and name.
       
  1404 				if ( isset( $existing_starter_content_posts[ $post_type . ':' . $post_name ] ) ) {
       
  1405 					$posts[ $post_symbol ]['ID'] = $existing_starter_content_posts[ $post_type . ':' . $post_name ]->ID;
       
  1406 					continue;
       
  1407 				}
       
  1408 
       
  1409 				// Translate the featured image symbol.
       
  1410 				if ( ! empty( $posts[ $post_symbol ]['thumbnail'] )
       
  1411 					&& preg_match( '/^{{(?P<symbol>.+)}}$/', $posts[ $post_symbol ]['thumbnail'], $matches )
       
  1412 					&& isset( $attachment_ids[ $matches['symbol'] ] ) ) {
       
  1413 					$posts[ $post_symbol ]['meta_input']['_thumbnail_id'] = $attachment_ids[ $matches['symbol'] ];
       
  1414 				}
       
  1415 
       
  1416 				if ( ! empty( $posts[ $post_symbol ]['template'] ) ) {
       
  1417 					$posts[ $post_symbol ]['meta_input']['_wp_page_template'] = $posts[ $post_symbol ]['template'];
       
  1418 				}
       
  1419 
       
  1420 				$r = $this->nav_menus->insert_auto_draft_post( $posts[ $post_symbol ] );
       
  1421 				if ( $r instanceof WP_Post ) {
       
  1422 					$posts[ $post_symbol ]['ID'] = $r->ID;
       
  1423 				}
       
  1424 			}
       
  1425 
       
  1426 			$starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, wp_list_pluck( $posts, 'ID' ) );
       
  1427 		}
       
  1428 
       
  1429 		// The nav_menus_created_posts setting is why nav_menus component is dependency for adding posts.
       
  1430 		if ( ! empty( $this->nav_menus ) && ! empty( $starter_content_auto_draft_post_ids ) ) {
       
  1431 			$setting_id = 'nav_menus_created_posts';
       
  1432 			$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;
       
  1434 		}
       
  1435 
       
  1436 		// Nav menus.
       
  1437 		$placeholder_id = -1;
       
  1438 		$reused_nav_menu_setting_ids = array();
       
  1439 		foreach ( $nav_menus as $nav_menu_location => $nav_menu ) {
       
  1440 
       
  1441 			$nav_menu_term_id = null;
       
  1442 			$nav_menu_setting_id = null;
       
  1443 			$matches = array();
       
  1444 
       
  1445 			// Look for an existing placeholder menu with starter content to re-use.
       
  1446 			foreach ( $changeset_data as $setting_id => $setting_params ) {
       
  1447 				$can_reuse = (
       
  1448 					! empty( $setting_params['starter_content'] )
       
  1449 					&&
       
  1450 					! in_array( $setting_id, $reused_nav_menu_setting_ids, true )
       
  1451 					&&
       
  1452 					preg_match( '#^nav_menu\[(?P<nav_menu_id>-?\d+)\]$#', $setting_id, $matches )
       
  1453 				);
       
  1454 				if ( $can_reuse ) {
       
  1455 					$nav_menu_term_id = intval( $matches['nav_menu_id'] );
       
  1456 					$nav_menu_setting_id = $setting_id;
       
  1457 					$reused_nav_menu_setting_ids[] = $setting_id;
       
  1458 					break;
       
  1459 				}
       
  1460 			}
       
  1461 
       
  1462 			if ( ! $nav_menu_term_id ) {
       
  1463 				while ( isset( $changeset_data[ sprintf( 'nav_menu[%d]', $placeholder_id ) ] ) ) {
       
  1464 					$placeholder_id--;
       
  1465 				}
       
  1466 				$nav_menu_term_id = $placeholder_id;
       
  1467 				$nav_menu_setting_id = sprintf( 'nav_menu[%d]', $placeholder_id );
       
  1468 			}
       
  1469 
       
  1470 			$this->set_post_value( $nav_menu_setting_id, array(
       
  1471 				'name' => isset( $nav_menu['name'] ) ? $nav_menu['name'] : $nav_menu_location,
       
  1472 			) );
       
  1473 			$this->pending_starter_content_settings_ids[] = $nav_menu_setting_id;
       
  1474 
       
  1475 			// @todo Add support for menu_item_parent.
       
  1476 			$position = 0;
       
  1477 			foreach ( $nav_menu['items'] as $nav_menu_item ) {
       
  1478 				$nav_menu_item_setting_id = sprintf( 'nav_menu_item[%d]', $placeholder_id-- );
       
  1479 				if ( ! isset( $nav_menu_item['position'] ) ) {
       
  1480 					$nav_menu_item['position'] = $position++;
       
  1481 				}
       
  1482 				$nav_menu_item['nav_menu_term_id'] = $nav_menu_term_id;
       
  1483 
       
  1484 				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'] ] ) ) {
       
  1486 						$nav_menu_item['object_id'] = $posts[ $matches['symbol'] ]['ID'];
       
  1487 						if ( empty( $nav_menu_item['title'] ) ) {
       
  1488 							$original_object = get_post( $nav_menu_item['object_id'] );
       
  1489 							$nav_menu_item['title'] = $original_object->post_title;
       
  1490 						}
       
  1491 					} else {
       
  1492 						continue;
       
  1493 					}
       
  1494 				} else {
       
  1495 					$nav_menu_item['object_id'] = 0;
       
  1496 				}
       
  1497 
       
  1498 				if ( empty( $changeset_data[ $nav_menu_item_setting_id ] ) || ! empty( $changeset_data[ $nav_menu_item_setting_id ]['starter_content'] ) ) {
       
  1499 					$this->set_post_value( $nav_menu_item_setting_id, $nav_menu_item );
       
  1500 					$this->pending_starter_content_settings_ids[] = $nav_menu_item_setting_id;
       
  1501 				}
       
  1502 			}
       
  1503 
       
  1504 			$setting_id = sprintf( 'nav_menu_locations[%s]', $nav_menu_location );
       
  1505 			if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
       
  1506 				$this->set_post_value( $setting_id, $nav_menu_term_id );
       
  1507 				$this->pending_starter_content_settings_ids[] = $setting_id;
       
  1508 			}
       
  1509 		}
       
  1510 
       
  1511 		// Options.
       
  1512 		foreach ( $options as $name => $value ) {
       
  1513 			if ( preg_match( '/^{{(?P<symbol>.+)}}$/', $value, $matches ) ) {
       
  1514 				if ( isset( $posts[ $matches['symbol'] ] ) ) {
       
  1515 					$value = $posts[ $matches['symbol'] ]['ID'];
       
  1516 				} elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) {
       
  1517 					$value = $attachment_ids[ $matches['symbol'] ];
       
  1518 				} else {
       
  1519 					continue;
       
  1520 				}
       
  1521 			}
       
  1522 
       
  1523 			if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) {
       
  1524 				$this->set_post_value( $name, $value );
       
  1525 				$this->pending_starter_content_settings_ids[] = $name;
       
  1526 			}
       
  1527 		}
       
  1528 
       
  1529 		// Theme mods.
       
  1530 		foreach ( $theme_mods as $name => $value ) {
       
  1531 			if ( preg_match( '/^{{(?P<symbol>.+)}}$/', $value, $matches ) ) {
       
  1532 				if ( isset( $posts[ $matches['symbol'] ] ) ) {
       
  1533 					$value = $posts[ $matches['symbol'] ]['ID'];
       
  1534 				} elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) {
       
  1535 					$value = $attachment_ids[ $matches['symbol'] ];
       
  1536 				} else {
       
  1537 					continue;
       
  1538 				}
       
  1539 			}
       
  1540 
       
  1541 			// Handle header image as special case since setting has a legacy format.
       
  1542 			if ( 'header_image' === $name ) {
       
  1543 				$name = 'header_image_data';
       
  1544 				$metadata = wp_get_attachment_metadata( $value );
       
  1545 				if ( empty( $metadata ) ) {
       
  1546 					continue;
       
  1547 				}
       
  1548 				$value = array(
       
  1549 					'attachment_id' => $value,
       
  1550 					'url' => wp_get_attachment_url( $value ),
       
  1551 					'height' => $metadata['height'],
       
  1552 					'width' => $metadata['width'],
       
  1553 				);
       
  1554 			} elseif ( 'background_image' === $name ) {
       
  1555 				$value = wp_get_attachment_url( $value );
       
  1556 			}
       
  1557 
       
  1558 			if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) {
       
  1559 				$this->set_post_value( $name, $value );
       
  1560 				$this->pending_starter_content_settings_ids[] = $name;
       
  1561 			}
       
  1562 		}
       
  1563 
       
  1564 		if ( ! empty( $this->pending_starter_content_settings_ids ) ) {
       
  1565 			if ( did_action( 'customize_register' ) ) {
       
  1566 				$this->_save_starter_content_changeset();
       
  1567 			} else {
       
  1568 				add_action( 'customize_register', array( $this, '_save_starter_content_changeset' ), 1000 );
       
  1569 			}
       
  1570 		}
       
  1571 	}
       
  1572 
       
  1573 	/**
       
  1574 	 * Prepare starter content attachments.
       
  1575 	 *
       
  1576 	 * Ensure that the attachments are valid and that they have slugs and file name/path.
       
  1577 	 *
       
  1578 	 * @since 4.7.0
       
  1579 	 *
       
  1580 	 * @param array $attachments Attachments.
       
  1581 	 * @return array Prepared attachments.
       
  1582 	 */
       
  1583 	protected function prepare_starter_content_attachments( $attachments ) {
       
  1584 		$prepared_attachments = array();
       
  1585 		if ( empty( $attachments ) ) {
       
  1586 			return $prepared_attachments;
       
  1587 		}
       
  1588 
       
  1589 		// Such is The WordPress Way.
       
  1590 		require_once( ABSPATH . 'wp-admin/includes/file.php' );
       
  1591 		require_once( ABSPATH . 'wp-admin/includes/media.php' );
       
  1592 		require_once( ABSPATH . 'wp-admin/includes/image.php' );
       
  1593 
       
  1594 		foreach ( $attachments as $symbol => $attachment ) {
       
  1595 
       
  1596 			// A file is required and URLs to files are not currently allowed.
       
  1597 			if ( empty( $attachment['file'] ) || preg_match( '#^https?://$#', $attachment['file'] ) ) {
       
  1598 				continue;
       
  1599 			}
       
  1600 
       
  1601 			$file_path = null;
       
  1602 			if ( file_exists( $attachment['file'] ) ) {
       
  1603 				$file_path = $attachment['file']; // Could be absolute path to file in plugin.
       
  1604 			} elseif ( is_child_theme() && file_exists( get_stylesheet_directory() . '/' . $attachment['file'] ) ) {
       
  1605 				$file_path = get_stylesheet_directory() . '/' . $attachment['file'];
       
  1606 			} elseif ( file_exists( get_template_directory() . '/' . $attachment['file'] ) ) {
       
  1607 				$file_path = get_template_directory() . '/' . $attachment['file'];
       
  1608 			} else {
       
  1609 				continue;
       
  1610 			}
       
  1611 			$file_name = basename( $attachment['file'] );
       
  1612 
       
  1613 			// Skip file types that are not recognized.
       
  1614 			$checked_filetype = wp_check_filetype( $file_name );
       
  1615 			if ( empty( $checked_filetype['type'] ) ) {
       
  1616 				continue;
       
  1617 			}
       
  1618 
       
  1619 			// Ensure post_name is set since not automatically derived from post_title for new auto-draft posts.
       
  1620 			if ( empty( $attachment['post_name'] ) ) {
       
  1621 				if ( ! empty( $attachment['post_title'] ) ) {
       
  1622 					$attachment['post_name'] = sanitize_title( $attachment['post_title'] );
       
  1623 				} else {
       
  1624 					$attachment['post_name'] = sanitize_title( preg_replace( '/\.\w+$/', '', $file_name ) );
       
  1625 				}
       
  1626 			}
       
  1627 
       
  1628 			$attachment['file_name'] = $file_name;
       
  1629 			$attachment['file_path'] = $file_path;
       
  1630 			$prepared_attachments[ $symbol ] = $attachment;
       
  1631 		}
       
  1632 		return $prepared_attachments;
       
  1633 	}
       
  1634 
       
  1635 	/**
       
  1636 	 * Save starter content changeset.
       
  1637 	 *
       
  1638 	 * @since 4.7.0
       
  1639 	 */
       
  1640 	public function _save_starter_content_changeset() {
       
  1641 
       
  1642 		if ( empty( $this->pending_starter_content_settings_ids ) ) {
       
  1643 			return;
       
  1644 		}
       
  1645 
       
  1646 		$this->save_changeset_post( array(
       
  1647 			'data' => array_fill_keys( $this->pending_starter_content_settings_ids, array( 'starter_content' => true ) ),
       
  1648 			'starter_content' => true,
       
  1649 		) );
       
  1650 		$this->saved_starter_content_changeset = true;
       
  1651 
       
  1652 		$this->pending_starter_content_settings_ids = array();
       
  1653 	}
       
  1654 
       
  1655 	/**
       
  1656 	 * Get dirty pre-sanitized setting values in the current customized state.
       
  1657 	 *
       
  1658 	 * The returned array consists of a merge of three sources:
       
  1659 	 * 1. If the theme is not currently active, then the base array is any stashed
       
  1660 	 *    theme mods that were modified previously but never published.
       
  1661 	 * 2. The values from the current changeset, if it exists.
       
  1662 	 * 3. If the user can customize, the values parsed from the incoming
       
  1663 	 *    `$_POST['customized']` JSON data.
       
  1664 	 * 4. Any programmatically-set post values via `WP_Customize_Manager::set_post_value()`.
       
  1665 	 *
       
  1666 	 * The name "unsanitized_post_values" is a carry-over from when the customized
       
  1667 	 * state was exclusively sourced from `$_POST['customized']`. Nevertheless,
       
  1668 	 * the value returned will come from the current changeset post and from the
       
  1669 	 * incoming post data.
   442 	 *
  1670 	 *
   443 	 * @since 4.1.1
  1671 	 * @since 4.1.1
   444 	 *
  1672 	 * @since 4.7.0 Added $args param and merging with changeset values and stashed theme mods.
       
  1673 	 *
       
  1674 	 * @param array $args {
       
  1675 	 *     Args.
       
  1676 	 *
       
  1677 	 *     @type bool $exclude_changeset Whether the changeset values should also be excluded. Defaults to false.
       
  1678 	 *     @type bool $exclude_post_data Whether the post input values should also be excluded. Defaults to false when lacking the customize capability.
       
  1679 	 * }
   445 	 * @return array
  1680 	 * @return array
   446 	 */
  1681 	 */
   447 	public function unsanitized_post_values() {
  1682 	public function unsanitized_post_values( $args = array() ) {
   448 		if ( ! isset( $this->_post_values ) ) {
  1683 		$args = array_merge(
   449 			if ( isset( $_POST['customized'] ) ) {
  1684 			array(
   450 				$this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
  1685 				'exclude_changeset' => false,
   451 			}
  1686 				'exclude_post_data' => ! current_user_can( 'customize' ),
   452 			if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
  1687 			),
   453 				$this->_post_values = array();
  1688 			$args
   454 			}
  1689 		);
   455 		}
  1690 
   456 		if ( empty( $this->_post_values ) ) {
  1691 		$values = array();
   457 			return array();
  1692 
   458 		} else {
  1693 		// Let default values be from the stashed theme mods if doing a theme switch and if no changeset is present.
   459 			return $this->_post_values;
  1694 		if ( ! $this->is_theme_active() ) {
   460 		}
  1695 			$stashed_theme_mods = get_option( 'customize_stashed_theme_mods' );
   461 	}
  1696 			$stylesheet = $this->get_stylesheet();
   462 
  1697 			if ( isset( $stashed_theme_mods[ $stylesheet ] ) ) {
   463 	/**
  1698 				$values = array_merge( $values, wp_list_pluck( $stashed_theme_mods[ $stylesheet ], 'value' ) );
   464 	 * Return the sanitized value for a given setting from the request's POST data.
  1699 			}
       
  1700 		}
       
  1701 
       
  1702 		if ( ! $args['exclude_changeset'] ) {
       
  1703 			foreach ( $this->changeset_data() as $setting_id => $setting_params ) {
       
  1704 				if ( ! array_key_exists( 'value', $setting_params ) ) {
       
  1705 					continue;
       
  1706 				}
       
  1707 				if ( isset( $setting_params['type'] ) && 'theme_mod' === $setting_params['type'] ) {
       
  1708 
       
  1709 					// Ensure that theme mods values are only used if they were saved under the current theme.
       
  1710 					$namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/';
       
  1711 					if ( preg_match( $namespace_pattern, $setting_id, $matches ) && $this->get_stylesheet() === $matches['stylesheet'] ) {
       
  1712 						$values[ $matches['setting_id'] ] = $setting_params['value'];
       
  1713 					}
       
  1714 				} else {
       
  1715 					$values[ $setting_id ] = $setting_params['value'];
       
  1716 				}
       
  1717 			}
       
  1718 		}
       
  1719 
       
  1720 		if ( ! $args['exclude_post_data'] ) {
       
  1721 			if ( ! isset( $this->_post_values ) ) {
       
  1722 				if ( isset( $_POST['customized'] ) ) {
       
  1723 					$post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
       
  1724 				} else {
       
  1725 					$post_values = array();
       
  1726 				}
       
  1727 				if ( is_array( $post_values ) ) {
       
  1728 					$this->_post_values = $post_values;
       
  1729 				} else {
       
  1730 					$this->_post_values = array();
       
  1731 				}
       
  1732 			}
       
  1733 			$values = array_merge( $values, $this->_post_values );
       
  1734 		}
       
  1735 		return $values;
       
  1736 	}
       
  1737 
       
  1738 	/**
       
  1739 	 * Returns the sanitized value for a given setting from the current customized state.
       
  1740 	 *
       
  1741 	 * The name "post_value" is a carry-over from when the customized state was exclusively
       
  1742 	 * sourced from `$_POST['customized']`. Nevertheless, the value returned will come
       
  1743 	 * from the current changeset post and from the incoming post data.
   465 	 *
  1744 	 *
   466 	 * @since 3.4.0
  1745 	 * @since 3.4.0
   467 	 * @since 4.1.1 Introduced 'default' parameter.
  1746 	 * @since 4.1.1 Introduced the `$default` parameter.
   468 	 *
  1747 	 * @since 4.6.0 `$default` is now returned early when the setting post value is invalid.
   469 	 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
  1748 	 *
   470 	 * @param mixed $default value returned $setting has no post value (added in 4.2.0).
  1749 	 * @see WP_REST_Server::dispatch()
   471 	 * @return string|mixed $post_value Sanitized value or the $default provided
  1750 	 * @see WP_REST_Request::sanitize_params()
       
  1751 	 * @see WP_REST_Request::has_valid_params()
       
  1752 	 *
       
  1753 	 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object.
       
  1754 	 * @param mixed                $default Value returned $setting has no post value (added in 4.2.0)
       
  1755 	 *                                      or the post value is invalid (added in 4.6.0).
       
  1756 	 * @return string|mixed $post_value Sanitized value or the $default provided.
   472 	 */
  1757 	 */
   473 	public function post_value( $setting, $default = null ) {
  1758 	public function post_value( $setting, $default = null ) {
   474 		$post_values = $this->unsanitized_post_values();
  1759 		$post_values = $this->unsanitized_post_values();
   475 		if ( array_key_exists( $setting->id, $post_values ) ) {
  1760 		if ( ! array_key_exists( $setting->id, $post_values ) ) {
   476 			return $setting->sanitize( $post_values[ $setting->id ] );
       
   477 		} else {
       
   478 			return $default;
  1761 			return $default;
   479 		}
  1762 		}
   480 	}
  1763 		$value = $post_values[ $setting->id ];
   481 
  1764 		$valid = $setting->validate( $value );
   482 	/**
  1765 		if ( is_wp_error( $valid ) ) {
   483 	 * Override a setting's (unsanitized) value as found in any incoming $_POST['customized'].
  1766 			return $default;
       
  1767 		}
       
  1768 		$value = $setting->sanitize( $value );
       
  1769 		if ( is_null( $value ) || is_wp_error( $value ) ) {
       
  1770 			return $default;
       
  1771 		}
       
  1772 		return $value;
       
  1773 	}
       
  1774 
       
  1775 	/**
       
  1776 	 * Override a setting's value in the current customized state.
       
  1777 	 *
       
  1778 	 * The name "post_value" is a carry-over from when the customized state was
       
  1779 	 * exclusively sourced from `$_POST['customized']`.
   484 	 *
  1780 	 *
   485 	 * @since 4.2.0
  1781 	 * @since 4.2.0
   486 	 * @access public
       
   487 	 *
  1782 	 *
   488 	 * @param string $setting_id ID for the WP_Customize_Setting instance.
  1783 	 * @param string $setting_id ID for the WP_Customize_Setting instance.
   489 	 * @param mixed  $value      Post value.
  1784 	 * @param mixed  $value      Post value.
   490 	 */
  1785 	 */
   491 	public function set_post_value( $setting_id, $value ) {
  1786 	public function set_post_value( $setting_id, $value ) {
   492 		$this->unsanitized_post_values();
  1787 		$this->unsanitized_post_values(); // Populate _post_values from $_POST['customized'].
   493 		$this->_post_values[ $setting_id ] = $value;
  1788 		$this->_post_values[ $setting_id ] = $value;
       
  1789 
       
  1790 		/**
       
  1791 		 * Announce when a specific setting's unsanitized post value has been set.
       
  1792 		 *
       
  1793 		 * Fires when the WP_Customize_Manager::set_post_value() method is called.
       
  1794 		 *
       
  1795 		 * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID.
       
  1796 		 *
       
  1797 		 * @since 4.4.0
       
  1798 		 *
       
  1799 		 * @param mixed                $value Unsanitized setting post value.
       
  1800 		 * @param WP_Customize_Manager $this  WP_Customize_Manager instance.
       
  1801 		 */
       
  1802 		do_action( "customize_post_value_set_{$setting_id}", $value, $this );
       
  1803 
       
  1804 		/**
       
  1805 		 * Announce when any setting's unsanitized post value has been set.
       
  1806 		 *
       
  1807 		 * Fires when the WP_Customize_Manager::set_post_value() method is called.
       
  1808 		 *
       
  1809 		 * This is useful for `WP_Customize_Setting` instances to watch
       
  1810 		 * in order to update a cached previewed value.
       
  1811 		 *
       
  1812 		 * @since 4.4.0
       
  1813 		 *
       
  1814 		 * @param string               $setting_id Setting ID.
       
  1815 		 * @param mixed                $value      Unsanitized setting post value.
       
  1816 		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
       
  1817 		 */
       
  1818 		do_action( 'customize_post_value_set', $setting_id, $value, $this );
   494 	}
  1819 	}
   495 
  1820 
   496 	/**
  1821 	/**
   497 	 * Print JavaScript settings.
  1822 	 * Print JavaScript settings.
   498 	 *
  1823 	 *
   499 	 * @since 3.4.0
  1824 	 * @since 3.4.0
   500 	 */
  1825 	 */
   501 	public function customize_preview_init() {
  1826 	public function customize_preview_init() {
   502 		$this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
  1827 
       
  1828 		/*
       
  1829 		 * Now that Customizer previews are loaded into iframes via GET requests
       
  1830 		 * and natural URLs with transaction UUIDs added, we need to ensure that
       
  1831 		 * the responses are never cached by proxies. In practice, this will not
       
  1832 		 * be needed if the user is logged-in anyway. But if anonymous access is
       
  1833 		 * allowed then the auth cookies would not be sent and WordPress would
       
  1834 		 * not send no-cache headers by default.
       
  1835 		 */
       
  1836 		if ( ! headers_sent() ) {
       
  1837 			nocache_headers();
       
  1838 			header( 'X-Robots: noindex, nofollow, noarchive' );
       
  1839 		}
       
  1840 		add_action( 'wp_head', 'wp_no_robots' );
       
  1841 		add_filter( 'wp_headers', array( $this, 'filter_iframe_security_headers' ) );
       
  1842 
       
  1843 		/*
       
  1844 		 * If preview is being served inside the customizer preview iframe, and
       
  1845 		 * if the user doesn't have customize capability, then it is assumed
       
  1846 		 * that the user's session has expired and they need to re-authenticate.
       
  1847 		 */
       
  1848 		if ( $this->messenger_channel && ! current_user_can( 'customize' ) ) {
       
  1849 			$this->wp_die( -1, __( 'Unauthorized. You may remove the customize_messenger_channel param to preview as frontend.' ) );
       
  1850 			return;
       
  1851 		}
   503 
  1852 
   504 		$this->prepare_controls();
  1853 		$this->prepare_controls();
   505 
  1854 
       
  1855 		add_filter( 'wp_redirect', array( $this, 'add_state_query_params' ) );
       
  1856 
   506 		wp_enqueue_script( 'customize-preview' );
  1857 		wp_enqueue_script( 'customize-preview' );
   507 		add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
  1858 		wp_enqueue_style( 'customize-preview' );
   508 		add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
       
   509 		add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
       
   510 		add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
  1859 		add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
       
  1860 		add_action( 'wp_head', array( $this, 'remove_frameless_preview_messenger_channel' ) );
   511 		add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
  1861 		add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
   512 		add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
  1862 		add_filter( 'get_edit_post_link', '__return_empty_string' );
   513 		add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
       
   514 
       
   515 		foreach ( $this->settings as $setting ) {
       
   516 			$setting->preview();
       
   517 		}
       
   518 
  1863 
   519 		/**
  1864 		/**
   520 		 * Fires once the Customizer preview has initialized and JavaScript
  1865 		 * Fires once the Customizer preview has initialized and JavaScript
   521 		 * settings have been printed.
  1866 		 * settings have been printed.
   522 		 *
  1867 		 *
   526 		 */
  1871 		 */
   527 		do_action( 'customize_preview_init', $this );
  1872 		do_action( 'customize_preview_init', $this );
   528 	}
  1873 	}
   529 
  1874 
   530 	/**
  1875 	/**
       
  1876 	 * Filter the X-Frame-Options and Content-Security-Policy headers to ensure frontend can load in customizer.
       
  1877 	 *
       
  1878 	 * @since 4.7.0
       
  1879 	 *
       
  1880 	 * @param array $headers Headers.
       
  1881 	 * @return array Headers.
       
  1882 	 */
       
  1883 	public function filter_iframe_security_headers( $headers ) {
       
  1884 		$customize_url = admin_url( 'customize.php' );
       
  1885 		$headers['X-Frame-Options'] = 'ALLOW-FROM ' . $customize_url;
       
  1886 		$headers['Content-Security-Policy'] = 'frame-ancestors ' . preg_replace( '#^(\w+://[^/]+).+?$#', '$1', $customize_url );
       
  1887 		return $headers;
       
  1888 	}
       
  1889 
       
  1890 	/**
       
  1891 	 * Add customize state query params to a given URL if preview is allowed.
       
  1892 	 *
       
  1893 	 * @since 4.7.0
       
  1894 	 * @see wp_redirect()
       
  1895 	 * @see WP_Customize_Manager::get_allowed_url()
       
  1896 	 *
       
  1897 	 * @param string $url URL.
       
  1898 	 * @return string URL.
       
  1899 	 */
       
  1900 	public function add_state_query_params( $url ) {
       
  1901 		$parsed_original_url = wp_parse_url( $url );
       
  1902 		$is_allowed = false;
       
  1903 		foreach ( $this->get_allowed_urls() as $allowed_url ) {
       
  1904 			$parsed_allowed_url = wp_parse_url( $allowed_url );
       
  1905 			$is_allowed = (
       
  1906 				$parsed_allowed_url['scheme'] === $parsed_original_url['scheme']
       
  1907 				&&
       
  1908 				$parsed_allowed_url['host'] === $parsed_original_url['host']
       
  1909 				&&
       
  1910 				0 === strpos( $parsed_original_url['path'], $parsed_allowed_url['path'] )
       
  1911 			);
       
  1912 			if ( $is_allowed ) {
       
  1913 				break;
       
  1914 			}
       
  1915 		}
       
  1916 
       
  1917 		if ( $is_allowed ) {
       
  1918 			$query_params = array(
       
  1919 				'customize_changeset_uuid' => $this->changeset_uuid(),
       
  1920 			);
       
  1921 			if ( ! $this->is_theme_active() ) {
       
  1922 				$query_params['customize_theme'] = $this->get_stylesheet();
       
  1923 			}
       
  1924 			if ( $this->messenger_channel ) {
       
  1925 				$query_params['customize_messenger_channel'] = $this->messenger_channel;
       
  1926 			}
       
  1927 			$url = add_query_arg( $query_params, $url );
       
  1928 		}
       
  1929 
       
  1930 		return $url;
       
  1931 	}
       
  1932 
       
  1933 	/**
   531 	 * Prevent sending a 404 status when returning the response for the customize
  1934 	 * Prevent sending a 404 status when returning the response for the customize
   532 	 * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
  1935 	 * preview, since it causes the jQuery Ajax to fail. Send 200 instead.
   533 	 *
  1936 	 *
   534 	 * @since 4.0.0
  1937 	 * @since 4.0.0
   535 	 * @access public
  1938 	 * @deprecated 4.7.0
   536 	 */
  1939 	 */
   537 	public function customize_preview_override_404_status() {
  1940 	public function customize_preview_override_404_status() {
   538 		if ( is_404() ) {
  1941 		_deprecated_function( __METHOD__, '4.7.0' );
   539 			status_header( 200 );
       
   540 		}
       
   541 	}
  1942 	}
   542 
  1943 
   543 	/**
  1944 	/**
   544 	 * Print base element for preview frame.
  1945 	 * Print base element for preview frame.
   545 	 *
  1946 	 *
   546 	 * @since 3.4.0
  1947 	 * @since 3.4.0
       
  1948 	 * @deprecated 4.7.0
   547 	 */
  1949 	 */
   548 	public function customize_preview_base() {
  1950 	public function customize_preview_base() {
   549 		?><base href="<?php echo home_url( '/' ); ?>" /><?php
  1951 		_deprecated_function( __METHOD__, '4.7.0' );
   550 	}
  1952 	}
   551 
  1953 
   552 	/**
  1954 	/**
   553 	 * Print a workaround to handle HTML5 tags in IE < 9.
  1955 	 * Print a workaround to handle HTML5 tags in IE < 9.
   554 	 *
  1956 	 *
   555 	 * @since 3.4.0
  1957 	 * @since 3.4.0
   556 	 */
  1958 	 * @deprecated 4.7.0 Customizer no longer supports IE8, so all supported browsers recognize HTML5.
   557 	public function customize_preview_html5() { ?>
  1959 	 */
   558 		<!--[if lt IE 9]>
  1960 	public function customize_preview_html5() {
   559 		<script type="text/javascript">
  1961 		_deprecated_function( __FUNCTION__, '4.7.0' );
   560 			var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
       
   561 				'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
       
   562 				'output', 'progress', 'section', 'time', 'video' ];
       
   563 			for ( var i = 0; i < e.length; i++ ) {
       
   564 				document.createElement( e[i] );
       
   565 			}
       
   566 		</script>
       
   567 		<![endif]--><?php
       
   568 	}
  1962 	}
   569 
  1963 
   570 	/**
  1964 	/**
   571 	 * Print CSS for loading indicators for the Customizer preview.
  1965 	 * Print CSS for loading indicators for the Customizer preview.
   572 	 *
  1966 	 *
   573 	 * @since 4.2.0
  1967 	 * @since 4.2.0
   574 	 * @access public
       
   575 	 */
  1968 	 */
   576 	public function customize_preview_loading_style() {
  1969 	public function customize_preview_loading_style() {
   577 		?><style>
  1970 		?><style>
   578 			body.wp-customizer-unloading {
  1971 			body.wp-customizer-unloading {
   579 				opacity: 0.25;
  1972 				opacity: 0.25;
   582 				transition: opacity 0.5s;
  1975 				transition: opacity 0.5s;
   583 			}
  1976 			}
   584 			body.wp-customizer-unloading * {
  1977 			body.wp-customizer-unloading * {
   585 				pointer-events: none !important;
  1978 				pointer-events: none !important;
   586 			}
  1979 			}
       
  1980 			form.customize-unpreviewable,
       
  1981 			form.customize-unpreviewable input,
       
  1982 			form.customize-unpreviewable select,
       
  1983 			form.customize-unpreviewable button,
       
  1984 			a.customize-unpreviewable,
       
  1985 			area.customize-unpreviewable {
       
  1986 				cursor: not-allowed !important;
       
  1987 			}
   587 		</style><?php
  1988 		</style><?php
   588 	}
  1989 	}
   589 
  1990 
   590 	/**
  1991 	/**
       
  1992 	 * Remove customize_messenger_channel query parameter from the preview window when it is not in an iframe.
       
  1993 	 *
       
  1994 	 * This ensures that the admin bar will be shown. It also ensures that link navigation will
       
  1995 	 * work as expected since the parent frame is not being sent the URL to navigate to.
       
  1996 	 *
       
  1997 	 * @since 4.7.0
       
  1998 	 */
       
  1999 	public function remove_frameless_preview_messenger_channel() {
       
  2000 		if ( ! $this->messenger_channel ) {
       
  2001 			return;
       
  2002 		}
       
  2003 		?>
       
  2004 		<script>
       
  2005 		( function() {
       
  2006 			var urlParser, oldQueryParams, newQueryParams, i;
       
  2007 			if ( parent !== window ) {
       
  2008 				return;
       
  2009 			}
       
  2010 			urlParser = document.createElement( 'a' );
       
  2011 			urlParser.href = location.href;
       
  2012 			oldQueryParams = urlParser.search.substr( 1 ).split( /&/ );
       
  2013 			newQueryParams = [];
       
  2014 			for ( i = 0; i < oldQueryParams.length; i += 1 ) {
       
  2015 				if ( ! /^customize_messenger_channel=/.test( oldQueryParams[ i ] ) ) {
       
  2016 					newQueryParams.push( oldQueryParams[ i ] );
       
  2017 				}
       
  2018 			}
       
  2019 			urlParser.search = newQueryParams.join( '&' );
       
  2020 			if ( urlParser.search !== location.search ) {
       
  2021 				location.replace( urlParser.href );
       
  2022 			}
       
  2023 		} )();
       
  2024 		</script>
       
  2025 		<?php
       
  2026 	}
       
  2027 
       
  2028 	/**
   591 	 * Print JavaScript settings for preview frame.
  2029 	 * Print JavaScript settings for preview frame.
   592 	 *
  2030 	 *
   593 	 * @since 3.4.0
  2031 	 * @since 3.4.0
   594 	 */
  2032 	 */
   595 	public function customize_preview_settings() {
  2033 	public function customize_preview_settings() {
       
  2034 		$post_values = $this->unsanitized_post_values( array( 'exclude_changeset' => true ) );
       
  2035 		$setting_validities = $this->validate_setting_values( $post_values );
       
  2036 		$exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities );
       
  2037 
       
  2038 		// 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'] ) );
       
  2040 		$state_query_params = array(
       
  2041 			'customize_theme',
       
  2042 			'customize_changeset_uuid',
       
  2043 			'customize_messenger_channel',
       
  2044 		);
       
  2045 		$self_url = remove_query_arg( $state_query_params, $self_url );
       
  2046 
       
  2047 		$allowed_urls = $this->get_allowed_urls();
       
  2048 		$allowed_hosts = array();
       
  2049 		foreach ( $allowed_urls as $allowed_url ) {
       
  2050 			$parsed = wp_parse_url( $allowed_url );
       
  2051 			if ( empty( $parsed['host'] ) ) {
       
  2052 				continue;
       
  2053 			}
       
  2054 			$host = $parsed['host'];
       
  2055 			if ( ! empty( $parsed['port'] ) ) {
       
  2056 				$host .= ':' . $parsed['port'];
       
  2057 			}
       
  2058 			$allowed_hosts[] = $host;
       
  2059 		}
       
  2060 
       
  2061 		$switched_locale = switch_to_locale( get_user_locale() );
       
  2062 		$l10n = array(
       
  2063 			'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
       
  2064 			'linkUnpreviewable' => __( 'This link is not live-previewable.' ),
       
  2065 			'formUnpreviewable' => __( 'This form is not live-previewable.' ),
       
  2066 		);
       
  2067 		if ( $switched_locale ) {
       
  2068 			restore_previous_locale();
       
  2069 		}
       
  2070 
   596 		$settings = array(
  2071 		$settings = array(
   597 			'values'  => array(),
  2072 			'changeset' => array(
   598 			'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
  2073 				'uuid' => $this->changeset_uuid(),
       
  2074 				'autosaved' => $this->autosaved(),
       
  2075 			),
       
  2076 			'timeouts' => array(
       
  2077 				'selectiveRefresh' => 250,
       
  2078 				'keepAliveSend' => 1000,
       
  2079 			),
       
  2080 			'theme' => array(
       
  2081 				'stylesheet' => $this->get_stylesheet(),
       
  2082 				'active'     => $this->is_theme_active(),
       
  2083 			),
       
  2084 			'url' => array(
       
  2085 				'self' => $self_url,
       
  2086 				'allowed' => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
       
  2087 				'allowedHosts' => array_unique( $allowed_hosts ),
       
  2088 				'isCrossDomain' => $this->is_cross_domain(),
       
  2089 			),
       
  2090 			'channel' => $this->messenger_channel,
   599 			'activePanels' => array(),
  2091 			'activePanels' => array(),
   600 			'activeSections' => array(),
  2092 			'activeSections' => array(),
   601 			'activeControls' => array(),
  2093 			'activeControls' => array(),
   602 			'l10n' => array(
  2094 			'settingValidities' => $exported_setting_validities,
   603 				'loading'  => __( 'Loading ...' ),
  2095 			'nonce' => current_user_can( 'customize' ) ? $this->get_nonces() : array(),
   604 			),
  2096 			'l10n' => $l10n,
       
  2097 			'_dirty' => array_keys( $post_values ),
   605 		);
  2098 		);
   606 
  2099 
   607 		if ( 2 == $this->nonce_tick ) {
  2100 		foreach ( $this->panels as $panel_id => $panel ) {
   608 			$settings['nonce'] = array(
  2101 			if ( $panel->check_capabilities() ) {
   609 				'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
  2102 				$settings['activePanels'][ $panel_id ] = $panel->active();
   610 				'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
  2103 				foreach ( $panel->sections as $section_id => $section ) {
   611 			);
  2104 					if ( $section->check_capabilities() ) {
   612 		}
  2105 						$settings['activeSections'][ $section_id ] = $section->active();
   613 
  2106 					}
   614 		foreach ( $this->settings as $id => $setting ) {
  2107 				}
   615 			$settings['values'][ $id ] = $setting->js_value();
  2108 			}
   616 		}
  2109 		}
   617 		foreach ( $this->panels as $id => $panel ) {
  2110 		foreach ( $this->sections as $id => $section ) {
   618 			$settings['activePanels'][ $id ] = $panel->active();
  2111 			if ( $section->check_capabilities() ) {
   619 			foreach ( $panel->sections as $id => $section ) {
       
   620 				$settings['activeSections'][ $id ] = $section->active();
  2112 				$settings['activeSections'][ $id ] = $section->active();
   621 			}
  2113 			}
   622 		}
  2114 		}
   623 		foreach ( $this->sections as $id => $section ) {
       
   624 			$settings['activeSections'][ $id ] = $section->active();
       
   625 		}
       
   626 		foreach ( $this->controls as $id => $control ) {
  2115 		foreach ( $this->controls as $id => $control ) {
   627 			$settings['activeControls'][ $id ] = $control->active();
  2116 			if ( $control->check_capabilities() ) {
       
  2117 				$settings['activeControls'][ $id ] = $control->active();
       
  2118 			}
   628 		}
  2119 		}
   629 
  2120 
   630 		?>
  2121 		?>
   631 		<script type="text/javascript">
  2122 		<script type="text/javascript">
   632 			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
  2123 			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
       
  2124 			_wpCustomizeSettings.values = {};
       
  2125 			(function( v ) {
       
  2126 				<?php
       
  2127 				/*
       
  2128 				 * Serialize settings separately from the initial _wpCustomizeSettings
       
  2129 				 * serialization in order to avoid a peak memory usage spike.
       
  2130 				 * @todo We may not even need to export the values at all since the pane syncs them anyway.
       
  2131 				 */
       
  2132 				foreach ( $this->settings as $id => $setting ) {
       
  2133 					if ( $setting->check_capabilities() ) {
       
  2134 						printf(
       
  2135 							"v[%s] = %s;\n",
       
  2136 							wp_json_encode( $id ),
       
  2137 							wp_json_encode( $setting->js_value() )
       
  2138 						);
       
  2139 					}
       
  2140 				}
       
  2141 				?>
       
  2142 			})( _wpCustomizeSettings.values );
   633 		</script>
  2143 		</script>
   634 		<?php
  2144 		<?php
   635 	}
  2145 	}
   636 
  2146 
   637 	/**
  2147 	/**
   638 	 * Prints a signature so we can ensure the Customizer was properly executed.
  2148 	 * Prints a signature so we can ensure the Customizer was properly executed.
   639 	 *
  2149 	 *
   640 	 * @since 3.4.0
  2150 	 * @since 3.4.0
       
  2151 	 * @deprecated 4.7.0
   641 	 */
  2152 	 */
   642 	public function customize_preview_signature() {
  2153 	public function customize_preview_signature() {
   643 		echo 'WP_CUSTOMIZER_SIGNATURE';
  2154 		_deprecated_function( __METHOD__, '4.7.0' );
   644 	}
  2155 	}
   645 
  2156 
   646 	/**
  2157 	/**
   647 	 * Removes the signature in case we experience a case where the Customizer was not properly executed.
  2158 	 * Removes the signature in case we experience a case where the Customizer was not properly executed.
   648 	 *
  2159 	 *
   649 	 * @since 3.4.0
  2160 	 * @since 3.4.0
       
  2161 	 * @deprecated 4.7.0
       
  2162 	 *
       
  2163 	 * @param mixed $return Value passed through for {@see 'wp_die_handler'} filter.
       
  2164 	 * @return mixed Value passed through for {@see 'wp_die_handler'} filter.
   650 	 */
  2165 	 */
   651 	public function remove_preview_signature( $return = null ) {
  2166 	public function remove_preview_signature( $return = null ) {
   652 		remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
  2167 		_deprecated_function( __METHOD__, '4.7.0' );
   653 
  2168 
   654 		return $return;
  2169 		return $return;
   655 	}
  2170 	}
   656 
  2171 
   657 	/**
  2172 	/**
   708 	public function get_stylesheet_root() {
  2223 	public function get_stylesheet_root() {
   709 		return get_raw_theme_root( $this->get_stylesheet(), true );
  2224 		return get_raw_theme_root( $this->get_stylesheet(), true );
   710 	}
  2225 	}
   711 
  2226 
   712 	/**
  2227 	/**
   713 	 * Filter the current theme and return the name of the previewed theme.
  2228 	 * Filters the current theme and return the name of the previewed theme.
   714 	 *
  2229 	 *
   715 	 * @since 3.4.0
  2230 	 * @since 3.4.0
   716 	 *
  2231 	 *
   717 	 * @param $current_theme {@internal Parameter is not used}
  2232 	 * @param $current_theme {@internal Parameter is not used}
   718 	 * @return string Theme name.
  2233 	 * @return string Theme name.
   720 	public function current_theme( $current_theme ) {
  2235 	public function current_theme( $current_theme ) {
   721 		return $this->theme()->display('Name');
  2236 		return $this->theme()->display('Name');
   722 	}
  2237 	}
   723 
  2238 
   724 	/**
  2239 	/**
   725 	 * Switch the theme and trigger the save() method on each setting.
  2240 	 * Validates setting values.
       
  2241 	 *
       
  2242 	 * Validation is skipped for unregistered settings or for values that are
       
  2243 	 * already null since they will be skipped anyway. Sanitization is applied
       
  2244 	 * to values that pass validation, and values that become null or `WP_Error`
       
  2245 	 * after sanitizing are marked invalid.
       
  2246 	 *
       
  2247 	 * @since 4.6.0
       
  2248 	 *
       
  2249 	 * @see WP_REST_Request::has_valid_params()
       
  2250 	 * @see WP_Customize_Setting::validate()
       
  2251 	 *
       
  2252 	 * @param array $setting_values Mapping of setting IDs to values to validate and sanitize.
       
  2253 	 * @param array $options {
       
  2254 	 *     Options.
       
  2255 	 *
       
  2256 	 *     @type bool $validate_existence  Whether a setting's existence will be checked.
       
  2257 	 *     @type bool $validate_capability Whether the setting capability will be checked.
       
  2258 	 * }
       
  2259 	 * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`.
       
  2260 	 */
       
  2261 	public function validate_setting_values( $setting_values, $options = array() ) {
       
  2262 		$options = wp_parse_args( $options, array(
       
  2263 			'validate_capability' => false,
       
  2264 			'validate_existence' => false,
       
  2265 		) );
       
  2266 
       
  2267 		$validities = array();
       
  2268 		foreach ( $setting_values as $setting_id => $unsanitized_value ) {
       
  2269 			$setting = $this->get_setting( $setting_id );
       
  2270 			if ( ! $setting ) {
       
  2271 				if ( $options['validate_existence'] ) {
       
  2272 					$validities[ $setting_id ] = new WP_Error( 'unrecognized', __( 'Setting does not exist or is unrecognized.' ) );
       
  2273 				}
       
  2274 				continue;
       
  2275 			}
       
  2276 			if ( $options['validate_capability'] && ! current_user_can( $setting->capability ) ) {
       
  2277 				$validity = new WP_Error( 'unauthorized', __( 'Unauthorized to modify setting due to capability.' ) );
       
  2278 			} else {
       
  2279 				if ( is_null( $unsanitized_value ) ) {
       
  2280 					continue;
       
  2281 				}
       
  2282 				$validity = $setting->validate( $unsanitized_value );
       
  2283 			}
       
  2284 			if ( ! is_wp_error( $validity ) ) {
       
  2285 				/** 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 );
       
  2287 				if ( ! empty( $late_validity->errors ) ) {
       
  2288 					$validity = $late_validity;
       
  2289 				}
       
  2290 			}
       
  2291 			if ( ! is_wp_error( $validity ) ) {
       
  2292 				$value = $setting->sanitize( $unsanitized_value );
       
  2293 				if ( is_null( $value ) ) {
       
  2294 					$validity = false;
       
  2295 				} elseif ( is_wp_error( $value ) ) {
       
  2296 					$validity = $value;
       
  2297 				}
       
  2298 			}
       
  2299 			if ( false === $validity ) {
       
  2300 				$validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
       
  2301 			}
       
  2302 			$validities[ $setting_id ] = $validity;
       
  2303 		}
       
  2304 		return $validities;
       
  2305 	}
       
  2306 
       
  2307 	/**
       
  2308 	 * Prepares setting validity for exporting to the client (JS).
       
  2309 	 *
       
  2310 	 * Converts `WP_Error` instance into array suitable for passing into the
       
  2311 	 * `wp.customize.Notification` JS model.
       
  2312 	 *
       
  2313 	 * @since 4.6.0
       
  2314 	 *
       
  2315 	 * @param true|WP_Error $validity Setting validity.
       
  2316 	 * @return true|array If `$validity` was a WP_Error, the error codes will be array-mapped
       
  2317 	 *                    to their respective `message` and `data` to pass into the
       
  2318 	 *                    `wp.customize.Notification` JS model.
       
  2319 	 */
       
  2320 	public function prepare_setting_validity_for_js( $validity ) {
       
  2321 		if ( is_wp_error( $validity ) ) {
       
  2322 			$notification = array();
       
  2323 			foreach ( $validity->errors as $error_code => $error_messages ) {
       
  2324 				$notification[ $error_code ] = array(
       
  2325 					'message' => join( ' ', $error_messages ),
       
  2326 					'data' => $validity->get_error_data( $error_code ),
       
  2327 				);
       
  2328 			}
       
  2329 			return $notification;
       
  2330 		} else {
       
  2331 			return true;
       
  2332 		}
       
  2333 	}
       
  2334 
       
  2335 	/**
       
  2336 	 * Handle customize_save WP Ajax request to save/update a changeset.
   726 	 *
  2337 	 *
   727 	 * @since 3.4.0
  2338 	 * @since 3.4.0
       
  2339 	 * @since 4.7.0 The semantics of this method have changed to update a changeset, optionally to also change the status and other attributes.
   728 	 */
  2340 	 */
   729 	public function save() {
  2341 	public function save() {
       
  2342 		if ( ! is_user_logged_in() ) {
       
  2343 			wp_send_json_error( 'unauthenticated' );
       
  2344 		}
       
  2345 
   730 		if ( ! $this->is_preview() ) {
  2346 		if ( ! $this->is_preview() ) {
   731 			wp_send_json_error( 'not_preview' );
  2347 			wp_send_json_error( 'not_preview' );
   732 		}
  2348 		}
   733 
  2349 
   734 		$action = 'save-customize_' . $this->get_stylesheet();
  2350 		$action = 'save-customize_' . $this->get_stylesheet();
   735 		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
  2351 		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
   736 			wp_send_json_error( 'invalid_nonce' );
  2352 			wp_send_json_error( 'invalid_nonce' );
   737 		}
  2353 		}
   738 
  2354 
   739 		// Do we have to switch themes?
  2355 		$changeset_post_id = $this->changeset_post_id();
   740 		if ( ! $this->is_theme_active() ) {
  2356 		$is_new_changeset = empty( $changeset_post_id );
   741 			// Temporarily stop previewing the theme to allow switch_themes()
  2357 		if ( $is_new_changeset ) {
   742 			// to operate properly.
  2358 			if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) {
       
  2359 				wp_send_json_error( 'cannot_create_changeset_post' );
       
  2360 			}
       
  2361 		} else {
       
  2362 			if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) {
       
  2363 				wp_send_json_error( 'cannot_edit_changeset_post' );
       
  2364 			}
       
  2365 		}
       
  2366 
       
  2367 		if ( ! empty( $_POST['customize_changeset_data'] ) ) {
       
  2368 			$input_changeset_data = json_decode( wp_unslash( $_POST['customize_changeset_data'] ), true );
       
  2369 			if ( ! is_array( $input_changeset_data ) ) {
       
  2370 				wp_send_json_error( 'invalid_customize_changeset_data' );
       
  2371 			}
       
  2372 		} else {
       
  2373 			$input_changeset_data = array();
       
  2374 		}
       
  2375 
       
  2376 		// Validate title.
       
  2377 		$changeset_title = null;
       
  2378 		if ( isset( $_POST['customize_changeset_title'] ) ) {
       
  2379 			$changeset_title = sanitize_text_field( wp_unslash( $_POST['customize_changeset_title'] ) );
       
  2380 		}
       
  2381 
       
  2382 		// Validate changeset status param.
       
  2383 		$is_publish = null;
       
  2384 		$changeset_status = null;
       
  2385 		if ( isset( $_POST['customize_changeset_status'] ) ) {
       
  2386 			$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 ) ) {
       
  2388 				wp_send_json_error( 'bad_customize_changeset_status', 400 );
       
  2389 			}
       
  2390 			$is_publish = ( 'publish' === $changeset_status || 'future' === $changeset_status );
       
  2391 			if ( $is_publish && ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) {
       
  2392 				wp_send_json_error( 'changeset_publish_unauthorized', 403 );
       
  2393 			}
       
  2394 		}
       
  2395 
       
  2396 		/*
       
  2397 		 * Validate changeset date param. Date is assumed to be in local time for
       
  2398 		 * the WP if in MySQL format (YYYY-MM-DD HH:MM:SS). Otherwise, the date
       
  2399 		 * is parsed with strtotime() so that ISO date format may be supplied
       
  2400 		 * or a string like "+10 minutes".
       
  2401 		 */
       
  2402 		$changeset_date_gmt = null;
       
  2403 		if ( isset( $_POST['customize_changeset_date'] ) ) {
       
  2404 			$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 ) ) {
       
  2406 				$mm = substr( $changeset_date, 5, 2 );
       
  2407 				$jj = substr( $changeset_date, 8, 2 );
       
  2408 				$aa = substr( $changeset_date, 0, 4 );
       
  2409 				$valid_date = wp_checkdate( $mm, $jj, $aa, $changeset_date );
       
  2410 				if ( ! $valid_date ) {
       
  2411 					wp_send_json_error( 'bad_customize_changeset_date', 400 );
       
  2412 				}
       
  2413 				$changeset_date_gmt = get_gmt_from_date( $changeset_date );
       
  2414 			} else {
       
  2415 				$timestamp = strtotime( $changeset_date );
       
  2416 				if ( ! $timestamp ) {
       
  2417 					wp_send_json_error( 'bad_customize_changeset_date', 400 );
       
  2418 				}
       
  2419 				$changeset_date_gmt = gmdate( 'Y-m-d H:i:s', $timestamp );
       
  2420 			}
       
  2421 		}
       
  2422 
       
  2423 		$lock_user_id = null;
       
  2424 		$autosave = ! empty( $_POST['customize_changeset_autosave'] );
       
  2425 		if ( ! $is_new_changeset ) {
       
  2426 			$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
       
  2427 		}
       
  2428 
       
  2429 		// Force request to autosave when changeset is locked.
       
  2430 		if ( $lock_user_id && ! $autosave ) {
       
  2431 			$autosave = true;
       
  2432 			$changeset_status = null;
       
  2433 			$changeset_date_gmt = null;
       
  2434 		}
       
  2435 
       
  2436 		if ( $autosave && ! defined( 'DOING_AUTOSAVE' ) ) { // Back-compat.
       
  2437 			define( 'DOING_AUTOSAVE', true );
       
  2438 		}
       
  2439 
       
  2440 		$autosaved = false;
       
  2441 		$r = $this->save_changeset_post( array(
       
  2442 			'status' => $changeset_status,
       
  2443 			'title' => $changeset_title,
       
  2444 			'date_gmt' => $changeset_date_gmt,
       
  2445 			'data' => $input_changeset_data,
       
  2446 			'autosave' => $autosave,
       
  2447 		) );
       
  2448 		if ( $autosave && ! is_wp_error( $r ) ) {
       
  2449 			$autosaved = true;
       
  2450 		}
       
  2451 
       
  2452 		// If the changeset was locked and an autosave request wasn't itself an error, then now explicitly return with a failure.
       
  2453 		if ( $lock_user_id && ! is_wp_error( $r ) ) {
       
  2454 			$r = new WP_Error(
       
  2455 				'changeset_locked',
       
  2456 				__( 'Changeset is being edited by other user.' ),
       
  2457 				array(
       
  2458 					'lock_user' => $this->get_lock_user_data( $lock_user_id ),
       
  2459 				)
       
  2460 			);
       
  2461 		}
       
  2462 
       
  2463 		if ( is_wp_error( $r ) ) {
       
  2464 			$response = array(
       
  2465 				'message' => $r->get_error_message(),
       
  2466 				'code' => $r->get_error_code(),
       
  2467 			);
       
  2468 			if ( is_array( $r->get_error_data() ) ) {
       
  2469 				$response = array_merge( $response, $r->get_error_data() );
       
  2470 			} else {
       
  2471 				$response['data'] = $r->get_error_data();
       
  2472 			}
       
  2473 		} else {
       
  2474 			$response = $r;
       
  2475 			$changeset_post = get_post( $this->changeset_post_id() );
       
  2476 
       
  2477 			// 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 ) {
       
  2479 				$this->dismiss_user_auto_draft_changesets();
       
  2480 			}
       
  2481 
       
  2482 			// Note that if the changeset status was publish, then it will get set to trash if revisions are not supported.
       
  2483 			$response['changeset_status'] = $changeset_post->post_status;
       
  2484 			if ( $is_publish && 'trash' === $response['changeset_status'] ) {
       
  2485 				$response['changeset_status'] = 'publish';
       
  2486 			}
       
  2487 
       
  2488 			if ( 'publish' !== $response['changeset_status'] ) {
       
  2489 				$this->set_changeset_lock( $changeset_post->ID );
       
  2490 			}
       
  2491 
       
  2492 			if ( 'future' === $response['changeset_status'] ) {
       
  2493 				$response['changeset_date'] = $changeset_post->post_date;
       
  2494 			}
       
  2495 
       
  2496 			if ( 'publish' === $response['changeset_status'] || 'trash' === $response['changeset_status'] ) {
       
  2497 				$response['next_changeset_uuid'] = wp_generate_uuid4();
       
  2498 			}
       
  2499 		}
       
  2500 
       
  2501 		if ( $autosave ) {
       
  2502 			$response['autosaved'] = $autosaved;
       
  2503 		}
       
  2504 
       
  2505 		if ( isset( $response['setting_validities'] ) ) {
       
  2506 			$response['setting_validities'] = array_map( array( $this, 'prepare_setting_validity_for_js' ), $response['setting_validities'] );
       
  2507 		}
       
  2508 
       
  2509 		/**
       
  2510 		 * Filters response data for a successful customize_save Ajax request.
       
  2511 		 *
       
  2512 		 * This filter does not apply if there was a nonce or authentication failure.
       
  2513 		 *
       
  2514 		 * @since 4.2.0
       
  2515 		 *
       
  2516 		 * @param array                $response Additional information passed back to the 'saved'
       
  2517 		 *                                       event on `wp.customize`.
       
  2518 		 * @param WP_Customize_Manager $this     WP_Customize_Manager instance.
       
  2519 		 */
       
  2520 		$response = apply_filters( 'customize_save_response', $response, $this );
       
  2521 
       
  2522 		if ( is_wp_error( $r ) ) {
       
  2523 			wp_send_json_error( $response );
       
  2524 		} else {
       
  2525 			wp_send_json_success( $response );
       
  2526 		}
       
  2527 	}
       
  2528 
       
  2529 	/**
       
  2530 	 * Save the post for the loaded changeset.
       
  2531 	 *
       
  2532 	 * @since 4.7.0
       
  2533 	 *
       
  2534 	 * @param array $args {
       
  2535 	 *     Args for changeset post.
       
  2536 	 *
       
  2537 	 *     @type array  $data            Optional additional changeset data. Values will be merged on top of any existing post values.
       
  2538 	 *     @type string $status          Post status. Optional. If supplied, the save will be transactional and a post revision will be allowed.
       
  2539 	 *     @type string $title           Post title. Optional.
       
  2540 	 *     @type string $date_gmt        Date in GMT. Optional.
       
  2541 	 *     @type int    $user_id         ID for user who is saving the changeset. Optional, defaults to the current user ID.
       
  2542 	 *     @type bool   $starter_content Whether the data is starter content. If false (default), then $starter_content will be cleared for any $data being saved.
       
  2543 	 *     @type bool   $autosave        Whether this is a request to create an autosave revision.
       
  2544 	 * }
       
  2545 	 *
       
  2546 	 * @return array|WP_Error Returns array on success and WP_Error with array data on error.
       
  2547 	 */
       
  2548 	function save_changeset_post( $args = array() ) {
       
  2549 
       
  2550 		$args = array_merge(
       
  2551 			array(
       
  2552 				'status' => null,
       
  2553 				'title' => null,
       
  2554 				'data' => array(),
       
  2555 				'date_gmt' => null,
       
  2556 				'user_id' => get_current_user_id(),
       
  2557 				'starter_content' => false,
       
  2558 				'autosave' => false,
       
  2559 			),
       
  2560 			$args
       
  2561 		);
       
  2562 
       
  2563 		$changeset_post_id = $this->changeset_post_id();
       
  2564 		$existing_changeset_data = array();
       
  2565 		if ( $changeset_post_id ) {
       
  2566 			$existing_status = get_post_status( $changeset_post_id );
       
  2567 			if ( 'publish' === $existing_status || 'trash' === $existing_status ) {
       
  2568 				return new WP_Error(
       
  2569 					'changeset_already_published',
       
  2570 					__( 'The previous set of changes has already been published. Please try saving your current set of changes again.' ),
       
  2571 					array(
       
  2572 						'next_changeset_uuid' => wp_generate_uuid4(),
       
  2573 					)
       
  2574 				);
       
  2575 			}
       
  2576 
       
  2577 			$existing_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
       
  2578 			if ( is_wp_error( $existing_changeset_data ) ) {
       
  2579 				return $existing_changeset_data;
       
  2580 			}
       
  2581 		}
       
  2582 
       
  2583 		// Fail if attempting to publish but publish hook is missing.
       
  2584 		if ( 'publish' === $args['status'] && false === has_action( 'transition_post_status', '_wp_customize_publish_changeset' ) ) {
       
  2585 			return new WP_Error( 'missing_publish_callback' );
       
  2586 		}
       
  2587 
       
  2588 		// Validate date.
       
  2589 		$now = gmdate( 'Y-m-d H:i:59' );
       
  2590 		if ( $args['date_gmt'] ) {
       
  2591 			$is_future_dated = ( mysql2date( 'U', $args['date_gmt'], false ) > mysql2date( 'U', $now, false ) );
       
  2592 			if ( ! $is_future_dated ) {
       
  2593 				return new WP_Error( 'not_future_date', __( 'You must supply a future date to schedule.' ) ); // Only future dates are allowed.
       
  2594 			}
       
  2595 
       
  2596 			if ( ! $this->is_theme_active() && ( 'future' === $args['status'] || $is_future_dated ) ) {
       
  2597 				return new WP_Error( 'cannot_schedule_theme_switches' ); // This should be allowed in the future, when theme is a regular setting.
       
  2598 			}
       
  2599 			$will_remain_auto_draft = ( ! $args['status'] && ( ! $changeset_post_id || 'auto-draft' === get_post_status( $changeset_post_id ) ) );
       
  2600 			if ( $will_remain_auto_draft ) {
       
  2601 				return new WP_Error( 'cannot_supply_date_for_auto_draft_changeset' );
       
  2602 			}
       
  2603 		} elseif ( $changeset_post_id && 'future' === $args['status'] ) {
       
  2604 
       
  2605 			// Fail if the new status is future but the existing post's date is not in the future.
       
  2606 			$changeset_post = get_post( $changeset_post_id );
       
  2607 			if ( mysql2date( 'U', $changeset_post->post_date_gmt, false ) <= mysql2date( 'U', $now, false ) ) {
       
  2608 				return new WP_Error( 'not_future_date', __( 'You must supply a future date to schedule.' ) );
       
  2609 			}
       
  2610 		}
       
  2611 
       
  2612 		if ( ! empty( $is_future_dated ) && 'publish' === $args['status'] ) {
       
  2613 			$args['status'] = 'future';
       
  2614 		}
       
  2615 
       
  2616 		// Validate autosave param. See _wp_post_revision_fields() for why these fields are disallowed.
       
  2617 		if ( $args['autosave'] ) {
       
  2618 			if ( $args['date_gmt'] ) {
       
  2619 				return new WP_Error( 'illegal_autosave_with_date_gmt' );
       
  2620 			} elseif ( $args['status'] ) {
       
  2621 				return new WP_Error( 'illegal_autosave_with_status' );
       
  2622 			} elseif ( $args['user_id'] && get_current_user_id() !== $args['user_id'] ) {
       
  2623 				return new WP_Error( 'illegal_autosave_with_non_current_user' );
       
  2624 			}
       
  2625 		}
       
  2626 
       
  2627 		// The request was made via wp.customize.previewer.save().
       
  2628 		$update_transactionally = (bool) $args['status'];
       
  2629 		$allow_revision = (bool) $args['status'];
       
  2630 
       
  2631 		// Amend post values with any supplied data.
       
  2632 		foreach ( $args['data'] as $setting_id => $setting_params ) {
       
  2633 			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.
       
  2635 			}
       
  2636 		}
       
  2637 
       
  2638 		// Note that in addition to post data, this will include any stashed theme mods.
       
  2639 		$post_values = $this->unsanitized_post_values( array(
       
  2640 			'exclude_changeset' => true,
       
  2641 			'exclude_post_data' => false,
       
  2642 		) );
       
  2643 		$this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value.
       
  2644 
       
  2645 		/*
       
  2646 		 * 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
       
  2648 		 * subset of changed settings can be passed into validate_setting_values to prevent
       
  2649 		 * an underprivileged modifying a single setting for which they have the capability
       
  2650 		 * from being blocked from saving. This also prevents a user from touching of the
       
  2651 		 * previous saved settings and overriding the associated user_id if they made no change.
       
  2652 		 */
       
  2653 		$changed_setting_ids = array();
       
  2654 		foreach ( $post_values as $setting_id => $setting_value ) {
       
  2655 			$setting = $this->get_setting( $setting_id );
       
  2656 
       
  2657 			if ( $setting && 'theme_mod' === $setting->type ) {
       
  2658 				$prefixed_setting_id = $this->get_stylesheet() . '::' . $setting->id;
       
  2659 			} else {
       
  2660 				$prefixed_setting_id = $setting_id;
       
  2661 			}
       
  2662 
       
  2663 			$is_value_changed = (
       
  2664 				! isset( $existing_changeset_data[ $prefixed_setting_id ] )
       
  2665 				||
       
  2666 				! array_key_exists( 'value', $existing_changeset_data[ $prefixed_setting_id ] )
       
  2667 				||
       
  2668 				$existing_changeset_data[ $prefixed_setting_id ]['value'] !== $setting_value
       
  2669 			);
       
  2670 			if ( $is_value_changed ) {
       
  2671 				$changed_setting_ids[] = $setting_id;
       
  2672 			}
       
  2673 		}
       
  2674 
       
  2675 		/**
       
  2676 		 * Fires before save validation happens.
       
  2677 		 *
       
  2678 		 * Plugins can add just-in-time {@see 'customize_validate_{$this->ID}'} filters
       
  2679 		 * at this point to catch any settings registered after `customize_register`.
       
  2680 		 * The dynamic portion of the hook name, `$this->ID` refers to the setting ID.
       
  2681 		 *
       
  2682 		 * @since 4.6.0
       
  2683 		 *
       
  2684 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
       
  2685 		 */
       
  2686 		do_action( 'customize_save_validation_before', $this );
       
  2687 
       
  2688 		// Validate settings.
       
  2689 		$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.
       
  2691 			$post_values
       
  2692 		);
       
  2693 		$setting_validities = $this->validate_setting_values( $validated_values, array(
       
  2694 			'validate_capability' => true,
       
  2695 			'validate_existence' => true,
       
  2696 		) );
       
  2697 		$invalid_setting_count = count( array_filter( $setting_validities, 'is_wp_error' ) );
       
  2698 
       
  2699 		/*
       
  2700 		 * 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.
       
  2702 		 */
       
  2703 		if ( $update_transactionally && $invalid_setting_count > 0 ) {
       
  2704 			$response = array(
       
  2705 				'setting_validities' => $setting_validities,
       
  2706 				/* 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 ) ),
       
  2708 			);
       
  2709 			return new WP_Error( 'transaction_fail', '', $response );
       
  2710 		}
       
  2711 
       
  2712 		// Obtain/merge data for changeset.
       
  2713 		$original_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
       
  2714 		$data = $original_changeset_data;
       
  2715 		if ( is_wp_error( $data ) ) {
       
  2716 			$data = array();
       
  2717 		}
       
  2718 
       
  2719 		// Ensure that all post values are included in the changeset data.
       
  2720 		foreach ( $post_values as $setting_id => $post_value ) {
       
  2721 			if ( ! isset( $args['data'][ $setting_id ] ) ) {
       
  2722 				$args['data'][ $setting_id ] = array();
       
  2723 			}
       
  2724 			if ( ! isset( $args['data'][ $setting_id ]['value'] ) ) {
       
  2725 				$args['data'][ $setting_id ]['value'] = $post_value;
       
  2726 			}
       
  2727 		}
       
  2728 
       
  2729 		foreach ( $args['data'] as $setting_id => $setting_params ) {
       
  2730 			$setting = $this->get_setting( $setting_id );
       
  2731 			if ( ! $setting || ! $setting->check_capabilities() ) {
       
  2732 				continue;
       
  2733 			}
       
  2734 
       
  2735 			// Skip updating changeset for invalid setting values.
       
  2736 			if ( isset( $setting_validities[ $setting_id ] ) && is_wp_error( $setting_validities[ $setting_id ] ) ) {
       
  2737 				continue;
       
  2738 			}
       
  2739 
       
  2740 			$changeset_setting_id = $setting_id;
       
  2741 			if ( 'theme_mod' === $setting->type ) {
       
  2742 				$changeset_setting_id = sprintf( '%s::%s', $this->get_stylesheet(), $setting_id );
       
  2743 			}
       
  2744 
       
  2745 			if ( null === $setting_params ) {
       
  2746 				// Remove setting from changeset entirely.
       
  2747 				unset( $data[ $changeset_setting_id ] );
       
  2748 			} else {
       
  2749 
       
  2750 				if ( ! isset( $data[ $changeset_setting_id ] ) ) {
       
  2751 					$data[ $changeset_setting_id ] = array();
       
  2752 				}
       
  2753 
       
  2754 				// Merge any additional setting params that have been supplied with the existing params.
       
  2755 				$merged_setting_params = array_merge( $data[ $changeset_setting_id ], $setting_params );
       
  2756 
       
  2757 				// Skip updating setting params if unchanged (ensuring the user_id is not overwritten).
       
  2758 				if ( $data[ $changeset_setting_id ] === $merged_setting_params ) {
       
  2759 					continue;
       
  2760 				}
       
  2761 
       
  2762 				$data[ $changeset_setting_id ] = array_merge(
       
  2763 					$merged_setting_params,
       
  2764 					array(
       
  2765 						'type' => $setting->type,
       
  2766 						'user_id' => $args['user_id'],
       
  2767 						'date_modified_gmt' => current_time( 'mysql', true ),
       
  2768 					)
       
  2769 				);
       
  2770 
       
  2771 				// Clear starter_content flag in data if changeset is not explicitly being updated for starter content.
       
  2772 				if ( empty( $args['starter_content'] ) ) {
       
  2773 					unset( $data[ $changeset_setting_id ]['starter_content'] );
       
  2774 				}
       
  2775 			}
       
  2776 		}
       
  2777 
       
  2778 		$filter_context = array(
       
  2779 			'uuid' => $this->changeset_uuid(),
       
  2780 			'title' => $args['title'],
       
  2781 			'status' => $args['status'],
       
  2782 			'date_gmt' => $args['date_gmt'],
       
  2783 			'post_id' => $changeset_post_id,
       
  2784 			'previous_data' => is_wp_error( $original_changeset_data ) ? array() : $original_changeset_data,
       
  2785 			'manager' => $this,
       
  2786 		);
       
  2787 
       
  2788 		/**
       
  2789 		 * Filters the settings' data that will be persisted into the changeset.
       
  2790 		 *
       
  2791 		 * Plugins may amend additional data (such as additional meta for settings) into the changeset with this filter.
       
  2792 		 *
       
  2793 		 * @since 4.7.0
       
  2794 		 *
       
  2795 		 * @param array $data Updated changeset data, mapping setting IDs to arrays containing a $value item and optionally other metadata.
       
  2796 		 * @param array $context {
       
  2797 		 *     Filter context.
       
  2798 		 *
       
  2799 		 *     @type string               $uuid          Changeset UUID.
       
  2800 		 *     @type string               $title         Requested title for the changeset post.
       
  2801 		 *     @type string               $status        Requested status for the changeset post.
       
  2802 		 *     @type string               $date_gmt      Requested date for the changeset post in MySQL format and GMT timezone.
       
  2803 		 *     @type int|false            $post_id       Post ID for the changeset, or false if it doesn't exist yet.
       
  2804 		 *     @type array                $previous_data Previous data contained in the changeset.
       
  2805 		 *     @type WP_Customize_Manager $manager       Manager instance.
       
  2806 		 * }
       
  2807 		 */
       
  2808 		$data = apply_filters( 'customize_changeset_save_data', $data, $filter_context );
       
  2809 
       
  2810 		// Switch theme if publishing changes now.
       
  2811 		if ( 'publish' === $args['status'] && ! $this->is_theme_active() ) {
       
  2812 			// Temporarily stop previewing the theme to allow switch_themes() to operate properly.
   743 			$this->stop_previewing_theme();
  2813 			$this->stop_previewing_theme();
   744 			switch_theme( $this->get_stylesheet() );
  2814 			switch_theme( $this->get_stylesheet() );
   745 			update_option( 'theme_switched_via_customizer', true );
  2815 			update_option( 'theme_switched_via_customizer', true );
   746 			$this->start_previewing_theme();
  2816 			$this->start_previewing_theme();
   747 		}
  2817 		}
       
  2818 
       
  2819 		// Gather the data for wp_insert_post()/wp_update_post().
       
  2820 		$json_options = 0;
       
  2821 		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.
       
  2823 		}
       
  2824 		$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(
       
  2826 			'post_content' => wp_json_encode( $data, $json_options ),
       
  2827 		);
       
  2828 		if ( $args['title'] ) {
       
  2829 			$post_array['post_title'] = $args['title'];
       
  2830 		}
       
  2831 		if ( $changeset_post_id ) {
       
  2832 			$post_array['ID'] = $changeset_post_id;
       
  2833 		} else {
       
  2834 			$post_array['post_type'] = 'customize_changeset';
       
  2835 			$post_array['post_name'] = $this->changeset_uuid();
       
  2836 			$post_array['post_status'] = 'auto-draft';
       
  2837 		}
       
  2838 		if ( $args['status'] ) {
       
  2839 			$post_array['post_status'] = $args['status'];
       
  2840 		}
       
  2841 
       
  2842 		// Reset post date to now if we are publishing, otherwise pass post_date_gmt and translate for post_date.
       
  2843 		if ( 'publish' === $args['status'] ) {
       
  2844 			$post_array['post_date_gmt'] = '0000-00-00 00:00:00';
       
  2845 			$post_array['post_date'] = '0000-00-00 00:00:00';
       
  2846 		} elseif ( $args['date_gmt'] ) {
       
  2847 			$post_array['post_date_gmt'] = $args['date_gmt'];
       
  2848 			$post_array['post_date'] = get_date_from_gmt( $args['date_gmt'] );
       
  2849 		} elseif ( $changeset_post_id && 'auto-draft' === get_post_status( $changeset_post_id ) ) {
       
  2850 			/*
       
  2851 			 * Keep bumping the date for the auto-draft whenever it is modified;
       
  2852 			 * this extends its life, preserving it from garbage-collection via
       
  2853 			 * wp_delete_auto_drafts().
       
  2854 			 */
       
  2855 			$post_array['post_date'] = current_time( 'mysql' );
       
  2856 			$post_array['post_date_gmt'] = '';
       
  2857 		}
       
  2858 
       
  2859 		$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 );
       
  2861 
       
  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().
       
  2863 		$has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
       
  2864 		if ( $has_kses ) {
       
  2865 			kses_remove_filters(); // Prevent KSES from corrupting JSON in post_content.
       
  2866 		}
       
  2867 
       
  2868 		// Note that updating a post with publish status will trigger WP_Customize_Manager::publish_changeset_values().
       
  2869 		if ( $changeset_post_id ) {
       
  2870 			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.
       
  2872 				add_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10, 4 );
       
  2873 				$post_array['post_ID'] = $post_array['ID'];
       
  2874 				$post_array['post_type'] = 'customize_changeset';
       
  2875 				$r = wp_create_post_autosave( wp_slash( $post_array ) );
       
  2876 				remove_filter( 'map_meta_cap', array( $this, 'grant_edit_post_capability_for_changeset' ), 10 );
       
  2877 			} else {
       
  2878 				$post_array['edit_date'] = true; // Prevent date clearing.
       
  2879 				$r = wp_update_post( wp_slash( $post_array ), true );
       
  2880 
       
  2881 				// Delete autosave revision for user when the changeset is updated.
       
  2882 				if ( ! empty( $args['user_id'] ) ) {
       
  2883 					$autosave_draft = wp_get_post_autosave( $changeset_post_id, $args['user_id'] );
       
  2884 					if ( $autosave_draft ) {
       
  2885 						wp_delete_post( $autosave_draft->ID, true );
       
  2886 					}
       
  2887 				}
       
  2888 			}
       
  2889 		} else {
       
  2890 			$r = wp_insert_post( wp_slash( $post_array ), true );
       
  2891 			if ( ! is_wp_error( $r ) ) {
       
  2892 				$this->_changeset_post_id = $r; // Update cached post ID for the loaded changeset.
       
  2893 			}
       
  2894 		}
       
  2895 		if ( $has_kses ) {
       
  2896 			kses_init_filters();
       
  2897 		}
       
  2898 		$this->_changeset_data = null; // Reset so WP_Customize_Manager::changeset_data() will re-populate with updated contents.
       
  2899 
       
  2900 		remove_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ) );
       
  2901 
       
  2902 		$response = array(
       
  2903 			'setting_validities' => $setting_validities,
       
  2904 		);
       
  2905 
       
  2906 		if ( is_wp_error( $r ) ) {
       
  2907 			$response['changeset_post_save_failure'] = $r->get_error_code();
       
  2908 			return new WP_Error( 'changeset_post_save_failure', '', $response );
       
  2909 		}
       
  2910 
       
  2911 		return $response;
       
  2912 	}
       
  2913 
       
  2914 	/**
       
  2915 	 * Trash or delete a changeset post.
       
  2916 	 *
       
  2917 	 * The following re-formulates the logic from `wp_trash_post()` as done in
       
  2918 	 * `wp_publish_post()`. The reason for bypassing `wp_trash_post()` is that it
       
  2919 	 * will mutate the the `post_content` and the `post_name` when they should be
       
  2920 	 * untouched.
       
  2921 	 *
       
  2922 	 * @since 4.9.0
       
  2923 	 * @global wpdb $wpdb WordPress database abstraction object.
       
  2924 	 * @see wp_trash_post()
       
  2925 	 *
       
  2926 	 * @param int|WP_Post $post The changeset post.
       
  2927 	 * @return mixed A WP_Post object for the trashed post or an empty value on failure.
       
  2928 	 */
       
  2929 	public function trash_changeset_post( $post ) {
       
  2930 		global $wpdb;
       
  2931 
       
  2932 		$post = get_post( $post );
       
  2933 
       
  2934 		if ( ! ( $post instanceof WP_Post ) ) {
       
  2935 			return $post;
       
  2936 		}
       
  2937 		$post_id = $post->ID;
       
  2938 
       
  2939 		if ( ! EMPTY_TRASH_DAYS ) {
       
  2940 			return wp_delete_post( $post_id, true );
       
  2941 		}
       
  2942 
       
  2943 		if ( 'trash' === get_post_status( $post ) ) {
       
  2944 			return false;
       
  2945 		}
       
  2946 
       
  2947 		/** This filter is documented in wp-includes/post.php */
       
  2948 		$check = apply_filters( 'pre_trash_post', null, $post );
       
  2949 		if ( null !== $check ) {
       
  2950 			return $check;
       
  2951 		}
       
  2952 
       
  2953 		/** This action is documented in wp-includes/post.php */
       
  2954 		do_action( 'wp_trash_post', $post_id );
       
  2955 
       
  2956 		add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
       
  2957 		add_post_meta( $post_id, '_wp_trash_meta_time', time() );
       
  2958 
       
  2959 		$old_status = $post->post_status;
       
  2960 		$new_status = 'trash';
       
  2961 		$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $post->ID ) );
       
  2962 		clean_post_cache( $post->ID );
       
  2963 
       
  2964 		$post->post_status = $new_status;
       
  2965 		wp_transition_post_status( $new_status, $old_status, $post );
       
  2966 
       
  2967 		/** This action is documented in wp-includes/post.php */
       
  2968 		do_action( 'edit_post', $post->ID, $post );
       
  2969 
       
  2970 		/** This action is documented in wp-includes/post.php */
       
  2971 		do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
       
  2972 
       
  2973 		/** This action is documented in wp-includes/post.php */
       
  2974 		do_action( 'save_post', $post->ID, $post, true );
       
  2975 
       
  2976 		/** This action is documented in wp-includes/post.php */
       
  2977 		do_action( 'wp_insert_post', $post->ID, $post, true );
       
  2978 
       
  2979 		wp_trash_post_comments( $post_id );
       
  2980 
       
  2981 		/** This action is documented in wp-includes/post.php */
       
  2982 		do_action( 'trashed_post', $post_id );
       
  2983 
       
  2984 		return $post;
       
  2985 	}
       
  2986 
       
  2987 	/**
       
  2988 	 * Handle request to trash a changeset.
       
  2989 	 *
       
  2990 	 * @since 4.9.0
       
  2991 	 */
       
  2992 	public function handle_changeset_trash_request() {
       
  2993 		if ( ! is_user_logged_in() ) {
       
  2994 			wp_send_json_error( 'unauthenticated' );
       
  2995 		}
       
  2996 
       
  2997 		if ( ! $this->is_preview() ) {
       
  2998 			wp_send_json_error( 'not_preview' );
       
  2999 		}
       
  3000 
       
  3001 		if ( ! check_ajax_referer( 'trash_customize_changeset', 'nonce', false ) ) {
       
  3002 			wp_send_json_error( array(
       
  3003 				'code' => 'invalid_nonce',
       
  3004 				'message' => __( 'There was an authentication problem. Please reload and try again.' ),
       
  3005 			) );
       
  3006 		}
       
  3007 
       
  3008 		$changeset_post_id = $this->changeset_post_id();
       
  3009 
       
  3010 		if ( ! $changeset_post_id ) {
       
  3011 			wp_send_json_error( array(
       
  3012 				'message' => __( 'No changes saved yet, so there is nothing to trash.' ),
       
  3013 				'code' => 'non_existent_changeset',
       
  3014 			) );
       
  3015 			return;
       
  3016 		}
       
  3017 
       
  3018 		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(
       
  3020 				'code' => 'changeset_trash_unauthorized',
       
  3021 				'message' => __( 'Unable to trash changes.' ),
       
  3022 			) );
       
  3023 		}
       
  3024 
       
  3025 		if ( 'trash' === get_post_status( $changeset_post_id ) ) {
       
  3026 			wp_send_json_error( array(
       
  3027 				'message' => __( 'Changes have already been trashed.' ),
       
  3028 				'code' => 'changeset_already_trashed',
       
  3029 			) );
       
  3030 			return;
       
  3031 		}
       
  3032 
       
  3033 		$r = $this->trash_changeset_post( $changeset_post_id );
       
  3034 		if ( ! ( $r instanceof WP_Post ) ) {
       
  3035 			wp_send_json_error( array(
       
  3036 				'code' => 'changeset_trash_failure',
       
  3037 				'message' => __( 'Unable to trash changes.' ),
       
  3038 			) );
       
  3039 		}
       
  3040 
       
  3041 		wp_send_json_success( array(
       
  3042 			'message' => __( 'Changes trashed successfully.' ),
       
  3043 		) );
       
  3044 	}
       
  3045 
       
  3046 	/**
       
  3047 	 * Re-map 'edit_post' meta cap for a customize_changeset post to be the same as 'customize' maps.
       
  3048 	 *
       
  3049 	 * There is essentially a "meta meta" cap in play here, where 'edit_post' meta cap maps to
       
  3050 	 * the 'customize' meta cap which then maps to 'edit_theme_options'. This is currently
       
  3051 	 * required in core for `wp_create_post_autosave()` because it will call
       
  3052 	 * `_wp_translate_postdata()` which in turn will check if a user can 'edit_post', but the
       
  3053 	 * the caps for the customize_changeset post type are all mapping to the meta capability.
       
  3054 	 * This should be able to be removed once #40922 is addressed in core.
       
  3055 	 *
       
  3056 	 * @since 4.9.0
       
  3057 	 * @link https://core.trac.wordpress.org/ticket/40922
       
  3058 	 * @see WP_Customize_Manager::save_changeset_post()
       
  3059 	 * @see _wp_translate_postdata()
       
  3060 	 *
       
  3061 	 * @param array  $caps    Returns the user's actual capabilities.
       
  3062 	 * @param string $cap     Capability name.
       
  3063 	 * @param int    $user_id The user ID.
       
  3064 	 * @param array  $args    Adds the context to the cap. Typically the object ID.
       
  3065 	 * @return array Capabilities.
       
  3066 	 */
       
  3067 	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] ) ) {
       
  3069 			$post_type_obj = get_post_type_object( 'customize_changeset' );
       
  3070 			$caps = map_meta_cap( $post_type_obj->cap->$cap, $user_id );
       
  3071 		}
       
  3072 		return $caps;
       
  3073 	}
       
  3074 
       
  3075 	/**
       
  3076 	 * Marks the changeset post as being currently edited by the current user.
       
  3077 	 *
       
  3078 	 * @since 4.9.0
       
  3079 	 *
       
  3080 	 * @param int  $changeset_post_id Changeset post id.
       
  3081 	 * @param bool $take_over Take over the changeset, default is false.
       
  3082 	 */
       
  3083 	public function set_changeset_lock( $changeset_post_id, $take_over = false ) {
       
  3084 		if ( $changeset_post_id ) {
       
  3085 			$can_override = ! (bool) get_post_meta( $changeset_post_id, '_edit_lock', true );
       
  3086 
       
  3087 			if ( $take_over ) {
       
  3088 				$can_override = true;
       
  3089 			}
       
  3090 
       
  3091 			if ( $can_override ) {
       
  3092 				$lock = sprintf( '%s:%s', time(), get_current_user_id() );
       
  3093 				update_post_meta( $changeset_post_id, '_edit_lock', $lock );
       
  3094 			} else {
       
  3095 				$this->refresh_changeset_lock( $changeset_post_id );
       
  3096 			}
       
  3097 		}
       
  3098 	}
       
  3099 
       
  3100 	/**
       
  3101 	 * Refreshes changeset lock with the current time if current user edited the changeset before.
       
  3102 	 *
       
  3103 	 * @since 4.9.0
       
  3104 	 *
       
  3105 	 * @param int $changeset_post_id Changeset post id.
       
  3106 	 */
       
  3107 	public function refresh_changeset_lock( $changeset_post_id ) {
       
  3108 		if ( ! $changeset_post_id ) {
       
  3109 			return;
       
  3110 		}
       
  3111 		$lock = get_post_meta( $changeset_post_id, '_edit_lock', true );
       
  3112 		$lock = explode( ':', $lock );
       
  3113 
       
  3114 		if ( $lock && ! empty( $lock[1] ) ) {
       
  3115 			$user_id = intval( $lock[1] );
       
  3116 			$current_user_id = get_current_user_id();
       
  3117 			if ( $user_id === $current_user_id ) {
       
  3118 				$lock = sprintf( '%s:%s', time(), $user_id );
       
  3119 				update_post_meta( $changeset_post_id, '_edit_lock', $lock );
       
  3120 			}
       
  3121 		}
       
  3122 	}
       
  3123 
       
  3124 	/**
       
  3125 	 * Filter heartbeat settings for the Customizer.
       
  3126 	 *
       
  3127 	 * @since 4.9.0
       
  3128 	 * @param array $settings Current settings to filter.
       
  3129 	 * @return array Heartbeat settings.
       
  3130 	 */
       
  3131 	public function add_customize_screen_to_heartbeat_settings( $settings ) {
       
  3132 		global $pagenow;
       
  3133 		if ( 'customize.php' === $pagenow ) {
       
  3134 			$settings['screenId'] = 'customize';
       
  3135 		}
       
  3136 		return $settings;
       
  3137 	}
       
  3138 
       
  3139 	/**
       
  3140 	 * Get lock user data.
       
  3141 	 *
       
  3142 	 * @since 4.9.0
       
  3143 	 *
       
  3144 	 * @param int $user_id User ID.
       
  3145 	 * @return array|null User data formatted for client.
       
  3146 	 */
       
  3147 	protected function get_lock_user_data( $user_id ) {
       
  3148 		if ( ! $user_id ) {
       
  3149 			return null;
       
  3150 		}
       
  3151 		$lock_user = get_userdata( $user_id );
       
  3152 		if ( ! $lock_user ) {
       
  3153 			return null;
       
  3154 		}
       
  3155 		return array(
       
  3156 			'id' => $lock_user->ID,
       
  3157 			'name' => $lock_user->display_name,
       
  3158 			'avatar' => get_avatar_url( $lock_user->ID, array( 'size' => 128 ) ),
       
  3159 		);
       
  3160 	}
       
  3161 
       
  3162 	/**
       
  3163 	 * Check locked changeset with heartbeat API.
       
  3164 	 *
       
  3165 	 * @since 4.9.0
       
  3166 	 *
       
  3167 	 * @param array  $response  The Heartbeat response.
       
  3168 	 * @param array  $data      The $_POST data sent.
       
  3169 	 * @param string $screen_id The screen id.
       
  3170 	 * @return array The Heartbeat response.
       
  3171 	 */
       
  3172 	public function check_changeset_lock_with_heartbeat( $response, $data, $screen_id ) {
       
  3173 		if ( isset( $data['changeset_uuid'] ) ) {
       
  3174 			$changeset_post_id = $this->find_changeset_post_id( $data['changeset_uuid'] );
       
  3175 		} else {
       
  3176 			$changeset_post_id = $this->changeset_post_id();
       
  3177 		}
       
  3178 
       
  3179 		if (
       
  3180 			array_key_exists( 'check_changeset_lock', $data )
       
  3181 			&& 'customize' === $screen_id
       
  3182 			&& $changeset_post_id
       
  3183 			&& current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id )
       
  3184 		) {
       
  3185 			$lock_user_id = wp_check_post_lock( $changeset_post_id );
       
  3186 
       
  3187 			if ( $lock_user_id ) {
       
  3188 				$response['customize_changeset_lock_user'] = $this->get_lock_user_data( $lock_user_id );
       
  3189 			} else {
       
  3190 
       
  3191 				// Refreshing time will ensure that the user is sitting on customizer and has not closed the customizer tab.
       
  3192 				$this->refresh_changeset_lock( $changeset_post_id );
       
  3193 			}
       
  3194 		}
       
  3195 
       
  3196 		return $response;
       
  3197 	}
       
  3198 
       
  3199 	/**
       
  3200 	 * Removes changeset lock when take over request is sent via Ajax.
       
  3201 	 *
       
  3202 	 * @since 4.9.0
       
  3203 	 */
       
  3204 	public function handle_override_changeset_lock_request() {
       
  3205 		if ( ! $this->is_preview() ) {
       
  3206 			wp_send_json_error( 'not_preview', 400 );
       
  3207 		}
       
  3208 
       
  3209 		if ( ! check_ajax_referer( 'customize_override_changeset_lock', 'nonce', false ) ) {
       
  3210 			wp_send_json_error( array(
       
  3211 				'code' => 'invalid_nonce',
       
  3212 				'message' => __( 'Security check failed.' ),
       
  3213 			) );
       
  3214 		}
       
  3215 
       
  3216 		$changeset_post_id = $this->changeset_post_id();
       
  3217 
       
  3218 		if ( empty( $changeset_post_id ) ) {
       
  3219 			wp_send_json_error( array(
       
  3220 				'code' => 'no_changeset_found_to_take_over',
       
  3221 				'message' => __( 'No changeset found to take over' ),
       
  3222 			) );
       
  3223 		}
       
  3224 
       
  3225 		if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) {
       
  3226 			wp_send_json_error( array(
       
  3227 				'code' => 'cannot_remove_changeset_lock',
       
  3228 				'message' => __( 'Sorry, you are not allowed to take over.' ),
       
  3229 			) );
       
  3230 		}
       
  3231 
       
  3232 		$this->set_changeset_lock( $changeset_post_id, true );
       
  3233 
       
  3234 		wp_send_json_success( 'changeset_taken_over' );
       
  3235 	}
       
  3236 
       
  3237 	/**
       
  3238 	 * Whether a changeset revision should be made.
       
  3239 	 *
       
  3240 	 * @since 4.7.0
       
  3241 	 * @var bool
       
  3242 	 */
       
  3243 	protected $store_changeset_revision;
       
  3244 
       
  3245 	/**
       
  3246 	 * Filters whether a changeset has changed to create a new revision.
       
  3247 	 *
       
  3248 	 * Note that this will not be called while a changeset post remains in auto-draft status.
       
  3249 	 *
       
  3250 	 * @since 4.7.0
       
  3251 	 *
       
  3252 	 * @param bool    $post_has_changed Whether the post has changed.
       
  3253 	 * @param WP_Post $last_revision    The last revision post object.
       
  3254 	 * @param WP_Post $post             The post object.
       
  3255 	 *
       
  3256 	 * @return bool Whether a revision should be made.
       
  3257 	 */
       
  3258 	public function _filter_revision_post_has_changed( $post_has_changed, $last_revision, $post ) {
       
  3259 		unset( $last_revision );
       
  3260 		if ( 'customize_changeset' === $post->post_type ) {
       
  3261 			$post_has_changed = $this->store_changeset_revision;
       
  3262 		}
       
  3263 		return $post_has_changed;
       
  3264 	}
       
  3265 
       
  3266 	/**
       
  3267 	 * Publish changeset values.
       
  3268 	 *
       
  3269 	 * This will the values contained in a changeset, even changesets that do not
       
  3270 	 * correspond to current manager instance. This is called by
       
  3271 	 * `_wp_customize_publish_changeset()` when a customize_changeset post is
       
  3272 	 * transitioned to the `publish` status. As such, this method should not be
       
  3273 	 * called directly and instead `wp_publish_post()` should be used.
       
  3274 	 *
       
  3275 	 * Please note that if the settings in the changeset are for a non-activated
       
  3276 	 * theme, the theme must first be switched to (via `switch_theme()`) before
       
  3277 	 * invoking this method.
       
  3278 	 *
       
  3279 	 * @since 4.7.0
       
  3280 	 * @see _wp_customize_publish_changeset()
       
  3281 	 * @global wpdb $wpdb
       
  3282 	 *
       
  3283 	 * @param int $changeset_post_id ID for customize_changeset post. Defaults to the changeset for the current manager instance.
       
  3284 	 * @return true|WP_Error True or error info.
       
  3285 	 */
       
  3286 	public function _publish_changeset_values( $changeset_post_id ) {
       
  3287 		global $wpdb;
       
  3288 
       
  3289 		$publishing_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
       
  3290 		if ( is_wp_error( $publishing_changeset_data ) ) {
       
  3291 			return $publishing_changeset_data;
       
  3292 		}
       
  3293 
       
  3294 		$changeset_post = get_post( $changeset_post_id );
       
  3295 
       
  3296 		/*
       
  3297 		 * Temporarily override the changeset context so that it will be read
       
  3298 		 * in calls to unsanitized_post_values() and so that it will be available
       
  3299 		 * on the $wp_customize object passed to hooks during the save logic.
       
  3300 		 */
       
  3301 		$previous_changeset_post_id = $this->_changeset_post_id;
       
  3302 		$this->_changeset_post_id   = $changeset_post_id;
       
  3303 		$previous_changeset_uuid    = $this->_changeset_uuid;
       
  3304 		$this->_changeset_uuid      = $changeset_post->post_name;
       
  3305 		$previous_changeset_data    = $this->_changeset_data;
       
  3306 		$this->_changeset_data      = $publishing_changeset_data;
       
  3307 
       
  3308 		// Parse changeset data to identify theme mod settings and user IDs associated with settings to be saved.
       
  3309 		$setting_user_ids = array();
       
  3310 		$theme_mod_settings = array();
       
  3311 		$namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/';
       
  3312 		$matches = array();
       
  3313 		foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) {
       
  3314 			$actual_setting_id = null;
       
  3315 			$is_theme_mod_setting = (
       
  3316 				isset( $setting_params['value'] )
       
  3317 				&&
       
  3318 				isset( $setting_params['type'] )
       
  3319 				&&
       
  3320 				'theme_mod' === $setting_params['type']
       
  3321 				&&
       
  3322 				preg_match( $namespace_pattern, $raw_setting_id, $matches )
       
  3323 			);
       
  3324 			if ( $is_theme_mod_setting ) {
       
  3325 				if ( ! isset( $theme_mod_settings[ $matches['stylesheet'] ] ) ) {
       
  3326 					$theme_mod_settings[ $matches['stylesheet'] ] = array();
       
  3327 				}
       
  3328 				$theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params;
       
  3329 
       
  3330 				if ( $this->get_stylesheet() === $matches['stylesheet'] ) {
       
  3331 					$actual_setting_id = $matches['setting_id'];
       
  3332 				}
       
  3333 			} else {
       
  3334 				$actual_setting_id = $raw_setting_id;
       
  3335 			}
       
  3336 
       
  3337 			// Keep track of the user IDs for settings actually for this theme.
       
  3338 			if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) {
       
  3339 				$setting_user_ids[ $actual_setting_id ] = $setting_params['user_id'];
       
  3340 			}
       
  3341 		}
       
  3342 
       
  3343 		$changeset_setting_values = $this->unsanitized_post_values( array(
       
  3344 			'exclude_post_data' => true,
       
  3345 			'exclude_changeset' => false,
       
  3346 		) );
       
  3347 		$changeset_setting_ids = array_keys( $changeset_setting_values );
       
  3348 		$this->add_dynamic_settings( $changeset_setting_ids );
   748 
  3349 
   749 		/**
  3350 		/**
   750 		 * Fires once the theme has switched in the Customizer, but before settings
  3351 		 * Fires once the theme has switched in the Customizer, but before settings
   751 		 * have been saved.
  3352 		 * have been saved.
   752 		 *
  3353 		 *
   753 		 * @since 3.4.0
  3354 		 * @since 3.4.0
   754 		 *
  3355 		 *
   755 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
  3356 		 * @param WP_Customize_Manager $manager WP_Customize_Manager instance.
   756 		 */
  3357 		 */
   757 		do_action( 'customize_save', $this );
  3358 		do_action( 'customize_save', $this );
   758 
  3359 
   759 		foreach ( $this->settings as $setting ) {
  3360 		/*
   760 			$setting->save();
  3361 		 * Ensure that all settings will allow themselves to be saved. Note that
       
  3362 		 * this is safe because the setting would have checked the capability
       
  3363 		 * when the setting value was written into the changeset. So this is why
       
  3364 		 * an additional capability check is not required here.
       
  3365 		 */
       
  3366 		$original_setting_capabilities = array();
       
  3367 		foreach ( $changeset_setting_ids as $setting_id ) {
       
  3368 			$setting = $this->get_setting( $setting_id );
       
  3369 			if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) {
       
  3370 				$original_setting_capabilities[ $setting->id ] = $setting->capability;
       
  3371 				$setting->capability = 'exist';
       
  3372 			}
       
  3373 		}
       
  3374 
       
  3375 		$original_user_id = get_current_user_id();
       
  3376 		foreach ( $changeset_setting_ids as $setting_id ) {
       
  3377 			$setting = $this->get_setting( $setting_id );
       
  3378 			if ( $setting ) {
       
  3379 				/*
       
  3380 				 * Set the current user to match the user who saved the value into
       
  3381 				 * the changeset so that any filters that apply during the save
       
  3382 				 * process will respect the original user's capabilities. This
       
  3383 				 * will ensure, for example, that KSES won't strip unsafe HTML
       
  3384 				 * when a scheduled changeset publishes via WP Cron.
       
  3385 				 */
       
  3386 				if ( isset( $setting_user_ids[ $setting_id ] ) ) {
       
  3387 					wp_set_current_user( $setting_user_ids[ $setting_id ] );
       
  3388 				} else {
       
  3389 					wp_set_current_user( $original_user_id );
       
  3390 				}
       
  3391 
       
  3392 				$setting->save();
       
  3393 			}
       
  3394 		}
       
  3395 		wp_set_current_user( $original_user_id );
       
  3396 
       
  3397 		// Update the stashed theme mod settings, removing the active theme's stashed settings, if activated.
       
  3398 		if ( did_action( 'switch_theme' ) ) {
       
  3399 			$other_theme_mod_settings = $theme_mod_settings;
       
  3400 			unset( $other_theme_mod_settings[ $this->get_stylesheet() ] );
       
  3401 			$this->update_stashed_theme_mod_settings( $other_theme_mod_settings );
   761 		}
  3402 		}
   762 
  3403 
   763 		/**
  3404 		/**
   764 		 * Fires after Customize settings have been saved.
  3405 		 * Fires after Customize settings have been saved.
   765 		 *
  3406 		 *
   766 		 * @since 3.6.0
  3407 		 * @since 3.6.0
   767 		 *
  3408 		 *
   768 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
  3409 		 * @param WP_Customize_Manager $manager WP_Customize_Manager instance.
   769 		 */
  3410 		 */
   770 		do_action( 'customize_save_after', $this );
  3411 		do_action( 'customize_save_after', $this );
   771 
  3412 
   772 		/**
  3413 		// Restore original capabilities.
   773 		 * Filter response data for a successful customize_save AJAX request.
  3414 		foreach ( $original_setting_capabilities as $setting_id => $capability ) {
   774 		 *
  3415 			$setting = $this->get_setting( $setting_id );
   775 		 * This filter does not apply if there was a nonce or authentication failure.
  3416 			if ( $setting ) {
   776 		 *
  3417 				$setting->capability = $capability;
   777 		 * @since 4.2.0
  3418 			}
   778 		 *
  3419 		}
   779 		 * @param array                $data Additional information passed back to the 'saved'
  3420 
   780 		 *                                   event on `wp.customize`.
  3421 		// Restore original changeset data.
   781 		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
  3422 		$this->_changeset_data    = $previous_changeset_data;
       
  3423 		$this->_changeset_post_id = $previous_changeset_post_id;
       
  3424 		$this->_changeset_uuid    = $previous_changeset_uuid;
       
  3425 
       
  3426 		/*
       
  3427 		 * Convert all autosave revisions into their own auto-drafts so that users can be prompted to
       
  3428 		 * restore them when a changeset is published, but they had been locked out from including
       
  3429 		 * their changes in the changeset.
   782 		 */
  3430 		 */
   783 		$response = apply_filters( 'customize_save_response', array(), $this );
  3431 		$revisions = wp_get_post_revisions( $changeset_post_id, array( 'check_enabled' => false ) );
   784 		wp_send_json_success( $response );
  3432 		foreach ( $revisions as $revision ) {
       
  3433 			if ( false !== strpos( $revision->post_name, "{$changeset_post_id}-autosave" ) ) {
       
  3434 				$wpdb->update(
       
  3435 					$wpdb->posts,
       
  3436 					array(
       
  3437 						'post_status' => 'auto-draft',
       
  3438 						'post_type' => 'customize_changeset',
       
  3439 						'post_name' => wp_generate_uuid4(),
       
  3440 						'post_parent' => 0,
       
  3441 					),
       
  3442 					array(
       
  3443 						'ID' => $revision->ID,
       
  3444 					)
       
  3445 				);
       
  3446 				clean_post_cache( $revision->ID );
       
  3447 			}
       
  3448 		}
       
  3449 
       
  3450 		return true;
       
  3451 	}
       
  3452 
       
  3453 	/**
       
  3454 	 * Update stashed theme mod settings.
       
  3455 	 *
       
  3456 	 * @since 4.7.0
       
  3457 	 *
       
  3458 	 * @param array $inactive_theme_mod_settings Mapping of stylesheet to arrays of theme mod settings.
       
  3459 	 * @return array|false Returns array of updated stashed theme mods or false if the update failed or there were no changes.
       
  3460 	 */
       
  3461 	protected function update_stashed_theme_mod_settings( $inactive_theme_mod_settings ) {
       
  3462 		$stashed_theme_mod_settings = get_option( 'customize_stashed_theme_mods' );
       
  3463 		if ( empty( $stashed_theme_mod_settings ) ) {
       
  3464 			$stashed_theme_mod_settings = array();
       
  3465 		}
       
  3466 
       
  3467 		// Delete any stashed theme mods for the active theme since they would have been loaded and saved upon activation.
       
  3468 		unset( $stashed_theme_mod_settings[ $this->get_stylesheet() ] );
       
  3469 
       
  3470 		// Merge inactive theme mods with the stashed theme mod settings.
       
  3471 		foreach ( $inactive_theme_mod_settings as $stylesheet => $theme_mod_settings ) {
       
  3472 			if ( ! isset( $stashed_theme_mod_settings[ $stylesheet ] ) ) {
       
  3473 				$stashed_theme_mod_settings[ $stylesheet ] = array();
       
  3474 			}
       
  3475 
       
  3476 			$stashed_theme_mod_settings[ $stylesheet ] = array_merge(
       
  3477 				$stashed_theme_mod_settings[ $stylesheet ],
       
  3478 				$theme_mod_settings
       
  3479 			);
       
  3480 		}
       
  3481 
       
  3482 		$autoload = false;
       
  3483 		$result = update_option( 'customize_stashed_theme_mods', $stashed_theme_mod_settings, $autoload );
       
  3484 		if ( ! $result ) {
       
  3485 			return false;
       
  3486 		}
       
  3487 		return $stashed_theme_mod_settings;
   785 	}
  3488 	}
   786 
  3489 
   787 	/**
  3490 	/**
   788 	 * Refresh nonces for the current preview.
  3491 	 * Refresh nonces for the current preview.
   789 	 *
  3492 	 *
   792 	public function refresh_nonces() {
  3495 	public function refresh_nonces() {
   793 		if ( ! $this->is_preview() ) {
  3496 		if ( ! $this->is_preview() ) {
   794 			wp_send_json_error( 'not_preview' );
  3497 			wp_send_json_error( 'not_preview' );
   795 		}
  3498 		}
   796 
  3499 
   797 		$nonces = array(
  3500 		wp_send_json_success( $this->get_nonces() );
   798 			'save'    => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
  3501 	}
   799 			'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
  3502 
   800 		);
  3503 	/**
   801 
  3504 	 * Delete a given auto-draft changeset or the autosave revision for a given changeset or delete changeset lock.
   802 		/**
  3505 	 *
   803 		 * Filter nonces for a customize_refresh_nonces AJAX request.
  3506 	 * @since 4.9.0
   804 		 *
  3507 	 */
   805 		 * @since 4.2.0
  3508 	public function handle_dismiss_autosave_or_lock_request() {
   806 		 *
  3509 		// Calls to dismiss_user_auto_draft_changesets() and wp_get_post_autosave() require non-zero get_current_user_id().
   807 		 * @param array                $nonces Array of refreshed nonces for save and
  3510 		if ( ! is_user_logged_in() ) {
   808 		 *                                     preview actions.
  3511 			wp_send_json_error( 'unauthenticated', 401 );
   809 		 * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
  3512 		}
   810 		 */
  3513 
   811 		$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
  3514 		if ( ! $this->is_preview() ) {
   812 		wp_send_json_success( $nonces );
  3515 			wp_send_json_error( 'not_preview', 400 );
       
  3516 		}
       
  3517 
       
  3518 		if ( ! check_ajax_referer( 'customize_dismiss_autosave_or_lock', 'nonce', false ) ) {
       
  3519 			wp_send_json_error( 'invalid_nonce', 403 );
       
  3520 		}
       
  3521 
       
  3522 		$changeset_post_id = $this->changeset_post_id();
       
  3523 		$dismiss_lock = ! empty( $_POST['dismiss_lock'] );
       
  3524 		$dismiss_autosave = ! empty( $_POST['dismiss_autosave'] );
       
  3525 
       
  3526 		if ( $dismiss_lock ) {
       
  3527 			if ( empty( $changeset_post_id ) && ! $dismiss_autosave ) {
       
  3528 				wp_send_json_error( 'no_changeset_to_dismiss_lock', 404 );
       
  3529 			}
       
  3530 			if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) && ! $dismiss_autosave ) {
       
  3531 				wp_send_json_error( 'cannot_remove_changeset_lock', 403 );
       
  3532 			}
       
  3533 
       
  3534 			delete_post_meta( $changeset_post_id, '_edit_lock' );
       
  3535 
       
  3536 			if ( ! $dismiss_autosave ) {
       
  3537 				wp_send_json_success( 'changeset_lock_dismissed' );
       
  3538 			}
       
  3539 		}
       
  3540 
       
  3541 		if ( $dismiss_autosave ) {
       
  3542 			if ( empty( $changeset_post_id ) || 'auto-draft' === get_post_status( $changeset_post_id ) ) {
       
  3543 				$dismissed = $this->dismiss_user_auto_draft_changesets();
       
  3544 				if ( $dismissed > 0 ) {
       
  3545 					wp_send_json_success( 'auto_draft_dismissed' );
       
  3546 				} else {
       
  3547 					wp_send_json_error( 'no_auto_draft_to_delete', 404 );
       
  3548 				}
       
  3549 			} else {
       
  3550 				$revision = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
       
  3551 
       
  3552 				if ( $revision ) {
       
  3553 					if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->delete_post, $changeset_post_id ) ) {
       
  3554 						wp_send_json_error( 'cannot_delete_autosave_revision', 403 );
       
  3555 					}
       
  3556 
       
  3557 					if ( ! wp_delete_post( $revision->ID, true ) ) {
       
  3558 						wp_send_json_error( 'autosave_revision_deletion_failure', 500 );
       
  3559 					} else {
       
  3560 						wp_send_json_success( 'autosave_revision_deleted' );
       
  3561 					}
       
  3562 				} else {
       
  3563 					wp_send_json_error( 'no_autosave_revision_to_delete', 404 );
       
  3564 				}
       
  3565 			}
       
  3566 		}
       
  3567 
       
  3568 		wp_send_json_error( 'unknown_error', 500 );
   813 	}
  3569 	}
   814 
  3570 
   815 	/**
  3571 	/**
   816 	 * Add a customize setting.
  3572 	 * Add a customize setting.
   817 	 *
  3573 	 *
   818 	 * @since 3.4.0
  3574 	 * @since 3.4.0
   819 	 *
  3575 	 * @since 4.5.0 Return added WP_Customize_Setting instance.
   820 	 * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
  3576 	 *
   821 	 * @param array $args                     Setting arguments; passed to WP_Customize_Setting
  3577 	 * @param WP_Customize_Setting|string $id   Customize Setting object, or ID.
   822 	 *                                        constructor.
  3578 	 * @param array                       $args {
       
  3579 	 *  Optional. Array of properties for the new WP_Customize_Setting. Default empty array.
       
  3580 	 *
       
  3581 	 *  @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'
       
  3584 	 *  @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.
       
  3586 	 *  @type string       $transport             Options for rendering the live preview of changes in Theme Customizer.
       
  3587 	 *                                            Using 'refresh' makes the change visible by reloading the whole preview.
       
  3588 	 *                                            Using 'postMessage' allows a custom JavaScript to handle live changes.
       
  3589 	 *                                            @link https://developer.wordpress.org/themes/customize-api
       
  3590 	 *                                            Default is 'refresh'
       
  3591 	 *  @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.
       
  3593 	 *  @type callable     $sanitize_js_callback  Callback to convert a Customize PHP setting value to a value that is
       
  3594 	 *                                            JSON serializable.
       
  3595 	 *  @type bool         $dirty                 Whether or not the setting is initially dirty when created.
       
  3596 	 * }
       
  3597 	 * @return WP_Customize_Setting             The instance of the setting that was added.
   823 	 */
  3598 	 */
   824 	public function add_setting( $id, $args = array() ) {
  3599 	public function add_setting( $id, $args = array() ) {
   825 		if ( $id instanceof WP_Customize_Setting ) {
  3600 		if ( $id instanceof WP_Customize_Setting ) {
   826 			$setting = $id;
  3601 			$setting = $id;
   827 		} else {
  3602 		} else {
   828 			$setting = new WP_Customize_Setting( $this, $id, $args );
  3603 			$class = 'WP_Customize_Setting';
   829 		}
  3604 
       
  3605 			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
       
  3606 			$args = apply_filters( 'customize_dynamic_setting_args', $args, $id );
       
  3607 
       
  3608 			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
       
  3609 			$class = apply_filters( 'customize_dynamic_setting_class', $class, $id, $args );
       
  3610 
       
  3611 			$setting = new $class( $this, $id, $args );
       
  3612 		}
       
  3613 
   830 		$this->settings[ $setting->id ] = $setting;
  3614 		$this->settings[ $setting->id ] = $setting;
       
  3615 		return $setting;
   831 	}
  3616 	}
   832 
  3617 
   833 	/**
  3618 	/**
   834 	 * Register any dynamically-created settings, such as those from $_POST['customized']
  3619 	 * Register any dynamically-created settings, such as those from $_POST['customized']
   835 	 * that have no corresponding setting created.
  3620 	 * that have no corresponding setting created.
   836 	 *
  3621 	 *
   837 	 * This is a mechanism to "wake up" settings that have been dynamically created
  3622 	 * This is a mechanism to "wake up" settings that have been dynamically created
   838 	 * on the frontend and have been sent to WordPress in `$_POST['customized']`. When WP
  3623 	 * on the front end and have been sent to WordPress in `$_POST['customized']`. When WP
   839 	 * loads, the dynamically-created settings then will get created and previewed
  3624 	 * loads, the dynamically-created settings then will get created and previewed
   840 	 * even though they are not directly created statically with code.
  3625 	 * even though they are not directly created statically with code.
   841 	 *
  3626 	 *
   842 	 * @since 4.2.0
  3627 	 * @since 4.2.0
   843 	 *
  3628 	 *
   844 	 * @param string $setting_ids The setting IDs to add.
  3629 	 * @param array $setting_ids The setting IDs to add.
   845 	 * @return WP_Customize_Setting The settings added.
  3630 	 * @return array The WP_Customize_Setting objects added.
   846 	 */
  3631 	 */
   847 	public function add_dynamic_settings( $setting_ids ) {
  3632 	public function add_dynamic_settings( $setting_ids ) {
   848 		$new_settings = array();
  3633 		$new_settings = array();
   849 		foreach ( $setting_ids as $setting_id ) {
  3634 		foreach ( $setting_ids as $setting_id ) {
   850 			// Skip settings already created
  3635 			// Skip settings already created
   854 
  3639 
   855 			$setting_args = false;
  3640 			$setting_args = false;
   856 			$setting_class = 'WP_Customize_Setting';
  3641 			$setting_class = 'WP_Customize_Setting';
   857 
  3642 
   858 			/**
  3643 			/**
   859 			 * Filter a dynamic setting's constructor args.
  3644 			 * Filters a dynamic setting's constructor args.
   860 			 *
  3645 			 *
   861 			 * For a dynamic setting to be registered, this filter must be employed
  3646 			 * For a dynamic setting to be registered, this filter must be employed
   862 			 * to override the default false value with an array of args to pass to
  3647 			 * to override the default false value with an array of args to pass to
   863 			 * the WP_Customize_Setting constructor.
  3648 			 * the WP_Customize_Setting constructor.
   864 			 *
  3649 			 *
   877 			 *
  3662 			 *
   878 			 * @since 4.2.0
  3663 			 * @since 4.2.0
   879 			 *
  3664 			 *
   880 			 * @param string $setting_class WP_Customize_Setting or a subclass.
  3665 			 * @param string $setting_class WP_Customize_Setting or a subclass.
   881 			 * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
  3666 			 * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
   882 			 * @param string $setting_args  WP_Customize_Setting or a subclass.
  3667 			 * @param array  $setting_args  WP_Customize_Setting or a subclass.
   883 			 */
  3668 			 */
   884 			$setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
  3669 			$setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
   885 
  3670 
   886 			$setting = new $setting_class( $this, $setting_id, $setting_args );
  3671 			$setting = new $setting_class( $this, $setting_id, $setting_args );
   887 
  3672 
   895 	 * Retrieve a customize setting.
  3680 	 * Retrieve a customize setting.
   896 	 *
  3681 	 *
   897 	 * @since 3.4.0
  3682 	 * @since 3.4.0
   898 	 *
  3683 	 *
   899 	 * @param string $id Customize Setting ID.
  3684 	 * @param string $id Customize Setting ID.
   900 	 * @return WP_Customize_Setting
  3685 	 * @return WP_Customize_Setting|void The setting, if set.
   901 	 */
  3686 	 */
   902 	public function get_setting( $id ) {
  3687 	public function get_setting( $id ) {
   903 		if ( isset( $this->settings[ $id ] ) ) {
  3688 		if ( isset( $this->settings[ $id ] ) ) {
   904 			return $this->settings[ $id ];
  3689 			return $this->settings[ $id ];
   905 		}
  3690 		}
   918 
  3703 
   919 	/**
  3704 	/**
   920 	 * Add a customize panel.
  3705 	 * Add a customize panel.
   921 	 *
  3706 	 *
   922 	 * @since 4.0.0
  3707 	 * @since 4.0.0
   923 	 * @access public
  3708 	 * @since 4.5.0 Return added WP_Customize_Panel instance.
   924 	 *
  3709 	 *
   925 	 * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
  3710 	 * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
   926 	 * @param array                     $args Optional. Panel arguments. Default empty array.
  3711 	 * @param array                     $args {
       
  3712 	 *  Optional. Array of properties for the new Panel object. Default empty array.
       
  3713 	 *  @type int          $priority              Priority of the panel, defining the display order of panels and sections.
       
  3714 	 *                                            Default 160.
       
  3715 	 *  @type string       $capability            Capability required for the panel. Default `edit_theme_options`
       
  3716 	 *  @type string|array $theme_supports        Theme features required to support the panel.
       
  3717 	 *  @type string       $title                 Title of the panel to show in UI.
       
  3718 	 *  @type string       $description           Description to show in the UI.
       
  3719 	 *  @type string       $type                  Type of the panel.
       
  3720 	 *  @type callable     $active_callback       Active callback.
       
  3721 	 * }
       
  3722 	 * @return WP_Customize_Panel             The instance of the panel that was added.
   927 	 */
  3723 	 */
   928 	public function add_panel( $id, $args = array() ) {
  3724 	public function add_panel( $id, $args = array() ) {
   929 		if ( $id instanceof WP_Customize_Panel ) {
  3725 		if ( $id instanceof WP_Customize_Panel ) {
   930 			$panel = $id;
  3726 			$panel = $id;
   931 		} else {
  3727 		} else {
   932 			$panel = new WP_Customize_Panel( $this, $id, $args );
  3728 			$panel = new WP_Customize_Panel( $this, $id, $args );
   933 		}
  3729 		}
   934 
  3730 
   935 		$this->panels[ $panel->id ] = $panel;
  3731 		$this->panels[ $panel->id ] = $panel;
       
  3732 		return $panel;
   936 	}
  3733 	}
   937 
  3734 
   938 	/**
  3735 	/**
   939 	 * Retrieve a customize panel.
  3736 	 * Retrieve a customize panel.
   940 	 *
  3737 	 *
   941 	 * @since 4.0.0
  3738 	 * @since 4.0.0
   942 	 * @access public
       
   943 	 *
  3739 	 *
   944 	 * @param string $id Panel ID to get.
  3740 	 * @param string $id Panel ID to get.
   945 	 * @return WP_Customize_Panel Requested panel instance.
  3741 	 * @return WP_Customize_Panel|void Requested panel instance, if set.
   946 	 */
  3742 	 */
   947 	public function get_panel( $id ) {
  3743 	public function get_panel( $id ) {
   948 		if ( isset( $this->panels[ $id ] ) ) {
  3744 		if ( isset( $this->panels[ $id ] ) ) {
   949 			return $this->panels[ $id ];
  3745 			return $this->panels[ $id ];
   950 		}
  3746 		}
   952 
  3748 
   953 	/**
  3749 	/**
   954 	 * Remove a customize panel.
  3750 	 * Remove a customize panel.
   955 	 *
  3751 	 *
   956 	 * @since 4.0.0
  3752 	 * @since 4.0.0
   957 	 * @access public
       
   958 	 *
  3753 	 *
   959 	 * @param string $id Panel ID to remove.
  3754 	 * @param string $id Panel ID to remove.
   960 	 */
  3755 	 */
   961 	public function remove_panel( $id ) {
  3756 	public function remove_panel( $id ) {
       
  3757 		// Removing core components this way is _doing_it_wrong().
       
  3758 		if ( in_array( $id, $this->components, true ) ) {
       
  3759 			/* 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.' ),
       
  3761 				$id,
       
  3762 				'<a href="' . esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ) . '"><code>customize_loaded_components</code></a>'
       
  3763 			);
       
  3764 
       
  3765 			_doing_it_wrong( __METHOD__, $message, '4.5.0' );
       
  3766 		}
   962 		unset( $this->panels[ $id ] );
  3767 		unset( $this->panels[ $id ] );
   963 	}
  3768 	}
   964 
  3769 
   965 	/**
  3770 	/**
       
  3771 	 * Register a customize panel type.
       
  3772 	 *
       
  3773 	 * Registered types are eligible to be rendered via JS and created dynamically.
       
  3774 	 *
       
  3775 	 * @since 4.3.0
       
  3776 	 *
       
  3777 	 * @see WP_Customize_Panel
       
  3778 	 *
       
  3779 	 * @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
       
  3780 	 */
       
  3781 	public function register_panel_type( $panel ) {
       
  3782 		$this->registered_panel_types[] = $panel;
       
  3783 	}
       
  3784 
       
  3785 	/**
       
  3786 	 * Render JS templates for all registered panel types.
       
  3787 	 *
       
  3788 	 * @since 4.3.0
       
  3789 	 */
       
  3790 	public function render_panel_templates() {
       
  3791 		foreach ( $this->registered_panel_types as $panel_type ) {
       
  3792 			$panel = new $panel_type( $this, 'temp', array() );
       
  3793 			$panel->print_template();
       
  3794 		}
       
  3795 	}
       
  3796 
       
  3797 	/**
   966 	 * Add a customize section.
  3798 	 * Add a customize section.
   967 	 *
  3799 	 *
   968 	 * @since 3.4.0
  3800 	 * @since 3.4.0
       
  3801 	 * @since 4.5.0 Return added WP_Customize_Section instance.
   969 	 *
  3802 	 *
   970 	 * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
  3803 	 * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
   971 	 * @param array                       $args Section arguments.
  3804 	 * @param array                     $args {
       
  3805 	 *  Optional. Array of properties for the new Section object. Default empty array.
       
  3806 	 *  @type int          $priority              Priority of the section, defining the display order of panels and sections.
       
  3807 	 *                                            Default 160.
       
  3808 	 *  @type string       $panel                 The panel this section belongs to (if any). Default empty.
       
  3809 	 *  @type string       $capability            Capability required for the section. Default 'edit_theme_options'
       
  3810 	 *  @type string|array $theme_supports        Theme features required to support the section.
       
  3811 	 *  @type string       $title                 Title of the section to show in UI.
       
  3812 	 *  @type string       $description           Description to show in the UI.
       
  3813 	 *  @type string       $type                  Type of the section.
       
  3814 	 *  @type callable     $active_callback       Active callback.
       
  3815 	 *  @type bool         $description_hidden    Hide the description behind a help icon, instead of inline above the first control. Default false.
       
  3816 	 * }
       
  3817 	 * @return WP_Customize_Section             The instance of the section that was added.
   972 	 */
  3818 	 */
   973 	public function add_section( $id, $args = array() ) {
  3819 	public function add_section( $id, $args = array() ) {
   974 		if ( $id instanceof WP_Customize_Section ) {
  3820 		if ( $id instanceof WP_Customize_Section ) {
   975 			$section = $id;
  3821 			$section = $id;
   976 		} else {
  3822 		} else {
   977 			$section = new WP_Customize_Section( $this, $id, $args );
  3823 			$section = new WP_Customize_Section( $this, $id, $args );
   978 		}
  3824 		}
       
  3825 
   979 		$this->sections[ $section->id ] = $section;
  3826 		$this->sections[ $section->id ] = $section;
       
  3827 		return $section;
   980 	}
  3828 	}
   981 
  3829 
   982 	/**
  3830 	/**
   983 	 * Retrieve a customize section.
  3831 	 * Retrieve a customize section.
   984 	 *
  3832 	 *
   985 	 * @since 3.4.0
  3833 	 * @since 3.4.0
   986 	 *
  3834 	 *
   987 	 * @param string $id Section ID.
  3835 	 * @param string $id Section ID.
   988 	 * @return WP_Customize_Section
  3836 	 * @return WP_Customize_Section|void The section, if set.
   989 	 */
  3837 	 */
   990 	public function get_section( $id ) {
  3838 	public function get_section( $id ) {
   991 		if ( isset( $this->sections[ $id ] ) )
  3839 		if ( isset( $this->sections[ $id ] ) )
   992 			return $this->sections[ $id ];
  3840 			return $this->sections[ $id ];
   993 	}
  3841 	}
  1002 	public function remove_section( $id ) {
  3850 	public function remove_section( $id ) {
  1003 		unset( $this->sections[ $id ] );
  3851 		unset( $this->sections[ $id ] );
  1004 	}
  3852 	}
  1005 
  3853 
  1006 	/**
  3854 	/**
       
  3855 	 * Register a customize section type.
       
  3856 	 *
       
  3857 	 * Registered types are eligible to be rendered via JS and created dynamically.
       
  3858 	 *
       
  3859 	 * @since 4.3.0
       
  3860 	 *
       
  3861 	 * @see WP_Customize_Section
       
  3862 	 *
       
  3863 	 * @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
       
  3864 	 */
       
  3865 	public function register_section_type( $section ) {
       
  3866 		$this->registered_section_types[] = $section;
       
  3867 	}
       
  3868 
       
  3869 	/**
       
  3870 	 * Render JS templates for all registered section types.
       
  3871 	 *
       
  3872 	 * @since 4.3.0
       
  3873 	 */
       
  3874 	public function render_section_templates() {
       
  3875 		foreach ( $this->registered_section_types as $section_type ) {
       
  3876 			$section = new $section_type( $this, 'temp', array() );
       
  3877 			$section->print_template();
       
  3878 		}
       
  3879 	}
       
  3880 
       
  3881 	/**
  1007 	 * Add a customize control.
  3882 	 * Add a customize control.
  1008 	 *
  3883 	 *
  1009 	 * @since 3.4.0
  3884 	 * @since 3.4.0
       
  3885 	 * @since 4.5.0 Return added WP_Customize_Control instance.
  1010 	 *
  3886 	 *
  1011 	 * @param WP_Customize_Control|string $id   Customize Control object, or ID.
  3887 	 * @param WP_Customize_Control|string $id   Customize Control object, or ID.
  1012 	 * @param array                       $args Control arguments; passed to WP_Customize_Control
  3888 	 * @param array                       $args {
  1013 	 *                                          constructor.
  3889 	 *  Optional. Array of properties for the new Control object. Default empty array.
       
  3890 	 *
       
  3891 	 *  @type array        $settings              All settings tied to the control. If undefined, defaults to `$setting`.
       
  3892 	 *                                            IDs in the array correspond to the ID of a registered `WP_Customize_Setting`.
       
  3893 	 *  @type string       $setting               The primary setting for the control (if there is one). Default is 'default'.
       
  3894 	 *  @type string       $capability            Capability required to use this control. Normally derived from `$settings`.
       
  3895 	 *  @type int          $priority              Order priority to load the control. Default 10.
       
  3896 	 *  @type string       $section               The section this control belongs to. Default empty.
       
  3897 	 *  @type string       $label                 Label for the control. Default empty.
       
  3898 	 *  @type string       $description           Description for the control. Default empty.
       
  3899 	 *  @type array        $choices               List of choices for 'radio' or 'select' type controls, where values
       
  3900 	 *                                            are the keys, and labels are the values. Default empty array.
       
  3901 	 *  @type array        $input_attrs           List of custom input attributes for control output, where attribute
       
  3902 	 *                                            names are the keys and values are the values. Default empty array.
       
  3903 	 *  @type bool         $allow_addition        Show UI for adding new content, currently only used for the
       
  3904 	 *                                            dropdown-pages control. Default false.
       
  3905 	 *  @type string       $type                  The type of the control. Default 'text'.
       
  3906 	 *  @type callback     $active_callback       Active callback.
       
  3907 	 * }
       
  3908 	 * @return WP_Customize_Control             The instance of the control that was added.
  1014 	 */
  3909 	 */
  1015 	public function add_control( $id, $args = array() ) {
  3910 	public function add_control( $id, $args = array() ) {
  1016 		if ( $id instanceof WP_Customize_Control ) {
  3911 		if ( $id instanceof WP_Customize_Control ) {
  1017 			$control = $id;
  3912 			$control = $id;
  1018 		} else {
  3913 		} else {
  1019 			$control = new WP_Customize_Control( $this, $id, $args );
  3914 			$control = new WP_Customize_Control( $this, $id, $args );
  1020 		}
  3915 		}
       
  3916 
  1021 		$this->controls[ $control->id ] = $control;
  3917 		$this->controls[ $control->id ] = $control;
       
  3918 		return $control;
  1022 	}
  3919 	}
  1023 
  3920 
  1024 	/**
  3921 	/**
  1025 	 * Retrieve a customize control.
  3922 	 * Retrieve a customize control.
  1026 	 *
  3923 	 *
  1027 	 * @since 3.4.0
  3924 	 * @since 3.4.0
  1028 	 *
  3925 	 *
  1029 	 * @param string $id ID of the control.
  3926 	 * @param string $id ID of the control.
  1030 	 * @return WP_Customize_Control $control The control object.
  3927 	 * @return WP_Customize_Control|void The control object, if set.
  1031 	 */
  3928 	 */
  1032 	public function get_control( $id ) {
  3929 	public function get_control( $id ) {
  1033 		if ( isset( $this->controls[ $id ] ) )
  3930 		if ( isset( $this->controls[ $id ] ) )
  1034 			return $this->controls[ $id ];
  3931 			return $this->controls[ $id ];
  1035 	}
  3932 	}
  1049 	 * Register a customize control type.
  3946 	 * Register a customize control type.
  1050 	 *
  3947 	 *
  1051 	 * Registered types are eligible to be rendered via JS and created dynamically.
  3948 	 * Registered types are eligible to be rendered via JS and created dynamically.
  1052 	 *
  3949 	 *
  1053 	 * @since 4.1.0
  3950 	 * @since 4.1.0
  1054 	 * @access public
       
  1055 	 *
  3951 	 *
  1056 	 * @param string $control Name of a custom control which is a subclass of
  3952 	 * @param string $control Name of a custom control which is a subclass of
  1057 	 *                        {@see WP_Customize_Control}.
  3953 	 *                        WP_Customize_Control.
  1058 	 */
  3954 	 */
  1059 	public function register_control_type( $control ) {
  3955 	public function register_control_type( $control ) {
  1060 		$this->registered_control_types[] = $control;
  3956 		$this->registered_control_types[] = $control;
  1061 	}
  3957 	}
  1062 
  3958 
  1063 	/**
  3959 	/**
  1064 	 * Render JS templates for all registered control types.
  3960 	 * Render JS templates for all registered control types.
  1065 	 *
  3961 	 *
  1066 	 * @since 4.1.0
  3962 	 * @since 4.1.0
  1067 	 * @access public
       
  1068 	 */
  3963 	 */
  1069 	public function render_control_templates() {
  3964 	public function render_control_templates() {
       
  3965 		if ( $this->branching() ) {
       
  3966 			$l10n = array(
       
  3967 				/* 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.' ),
       
  3969 				/* 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?' ),
       
  3971 			);
       
  3972 		} else {
       
  3973 			$l10n = array(
       
  3974 				/* 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.' ),
       
  3976 				/* 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?' ),
       
  3978 			);
       
  3979 		}
       
  3980 
  1070 		foreach ( $this->registered_control_types as $control_type ) {
  3981 		foreach ( $this->registered_control_types as $control_type ) {
  1071 			$control = new $control_type( $this, 'temp', array() );
  3982 			$control = new $control_type( $this, 'temp', array(
       
  3983 				'settings' => array(),
       
  3984 			) );
  1072 			$control->print_template();
  3985 			$control->print_template();
  1073 		}
  3986 		}
       
  3987 		?>
       
  3988 
       
  3989 		<script type="text/html" id="tmpl-customize-control-default-content">
       
  3990 			<#
       
  3991 			var inputId = _.uniqueId( 'customize-control-default-input-' );
       
  3992 			var descriptionId = _.uniqueId( 'customize-control-default-description-' );
       
  3993 			var describedByAttr = data.description ? ' aria-describedby="' + descriptionId + '" ' : '';
       
  3994 			#>
       
  3995 			<# switch ( data.type ) {
       
  3996 				case 'checkbox': #>
       
  3997 					<span class="customize-inside-control-row">
       
  3998 						<input
       
  3999 							id="{{ inputId }}"
       
  4000 							{{{ describedByAttr }}}
       
  4001 							type="checkbox"
       
  4002 							value="{{ data.value }}"
       
  4003 							data-customize-setting-key-link="default"
       
  4004 						>
       
  4005 						<label for="{{ inputId }}">
       
  4006 							{{ data.label }}
       
  4007 						</label>
       
  4008 						<# if ( data.description ) { #>
       
  4009 							<span id="{{ descriptionId }}" class="description customize-control-description">{{{ data.description }}}</span>
       
  4010 						<# } #>
       
  4011 					</span>
       
  4012 					<#
       
  4013 					break;
       
  4014 				case 'radio':
       
  4015 					if ( ! data.choices ) {
       
  4016 						return;
       
  4017 					}
       
  4018 					#>
       
  4019 					<# if ( data.label ) { #>
       
  4020 						<label for="{{ inputId }}" class="customize-control-title">
       
  4021 							{{ data.label }}
       
  4022 						</label>
       
  4023 					<# } #>
       
  4024 					<# if ( data.description ) { #>
       
  4025 						<span id="{{ descriptionId }}" class="description customize-control-description">{{{ data.description }}}</span>
       
  4026 					<# } #>
       
  4027 					<# _.each( data.choices, function( val, key ) { #>
       
  4028 						<span class="customize-inside-control-row">
       
  4029 							<#
       
  4030 							var value, text;
       
  4031 							if ( _.isObject( val ) ) {
       
  4032 								value = val.value;
       
  4033 								text = val.text;
       
  4034 							} else {
       
  4035 								value = key;
       
  4036 								text = val;
       
  4037 							}
       
  4038 							#>
       
  4039 							<input
       
  4040 								id="{{ inputId + '-' + value }}"
       
  4041 								type="radio"
       
  4042 								value="{{ value }}"
       
  4043 								name="{{ inputId }}"
       
  4044 								data-customize-setting-key-link="default"
       
  4045 								{{{ describedByAttr }}}
       
  4046 							>
       
  4047 							<label for="{{ inputId + '-' + value }}">{{ text }}</label>
       
  4048 						</span>
       
  4049 					<# } ); #>
       
  4050 					<#
       
  4051 					break;
       
  4052 				default:
       
  4053 					#>
       
  4054 					<# if ( data.label ) { #>
       
  4055 						<label for="{{ inputId }}" class="customize-control-title">
       
  4056 							{{ data.label }}
       
  4057 						</label>
       
  4058 					<# } #>
       
  4059 					<# if ( data.description ) { #>
       
  4060 						<span id="{{ descriptionId }}" class="description customize-control-description">{{{ data.description }}}</span>
       
  4061 					<# } #>
       
  4062 
       
  4063 					<#
       
  4064 					var inputAttrs = {
       
  4065 						id: inputId,
       
  4066 						'data-customize-setting-key-link': 'default'
       
  4067 					};
       
  4068 					if ( 'textarea' === data.type ) {
       
  4069 						inputAttrs.rows = '5';
       
  4070 					} else if ( 'button' === data.type ) {
       
  4071 						inputAttrs['class'] = 'button button-secondary';
       
  4072 						inputAttrs.type = 'button';
       
  4073 					} else {
       
  4074 						inputAttrs.type = data.type;
       
  4075 					}
       
  4076 					if ( data.description ) {
       
  4077 						inputAttrs['aria-describedby'] = descriptionId;
       
  4078 					}
       
  4079 					_.extend( inputAttrs, data.input_attrs );
       
  4080 					#>
       
  4081 
       
  4082 					<# if ( 'button' === data.type ) { #>
       
  4083 						<button
       
  4084 							<# _.each( _.extend( inputAttrs ), function( value, key ) { #>
       
  4085 								{{{ key }}}="{{ value }}"
       
  4086 							<# } ); #>
       
  4087 						>{{ inputAttrs.value }}</button>
       
  4088 					<# } else if ( 'textarea' === data.type ) { #>
       
  4089 						<textarea
       
  4090 							<# _.each( _.extend( inputAttrs ), function( value, key ) { #>
       
  4091 								{{{ key }}}="{{ value }}"
       
  4092 							<# }); #>
       
  4093 						>{{ inputAttrs.value }}</textarea>
       
  4094 					<# } else if ( 'select' === data.type ) { #>
       
  4095 						<# delete inputAttrs.type; #>
       
  4096 						<select
       
  4097 							<# _.each( _.extend( inputAttrs ), function( value, key ) { #>
       
  4098 								{{{ key }}}="{{ value }}"
       
  4099 							<# }); #>
       
  4100 							>
       
  4101 							<# _.each( data.choices, function( val, key ) { #>
       
  4102 								<#
       
  4103 								var value, text;
       
  4104 								if ( _.isObject( val ) ) {
       
  4105 									value = val.value;
       
  4106 									text = val.text;
       
  4107 								} else {
       
  4108 									value = key;
       
  4109 									text = val;
       
  4110 								}
       
  4111 								#>
       
  4112 								<option value="{{ value }}">{{ text }}</option>
       
  4113 							<# } ); #>
       
  4114 						</select>
       
  4115 					<# } else { #>
       
  4116 						<input
       
  4117 							<# _.each( _.extend( inputAttrs ), function( value, key ) { #>
       
  4118 								{{{ key }}}="{{ value }}"
       
  4119 							<# }); #>
       
  4120 							>
       
  4121 					<# } #>
       
  4122 			<# } #>
       
  4123 		</script>
       
  4124 
       
  4125 		<script type="text/html" id="tmpl-customize-notification">
       
  4126 			<li class="notice notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.containerClasses || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}">
       
  4127 				<div class="notification-message">{{{ data.message || data.code }}}</div>
       
  4128 				<# if ( data.dismissible ) { #>
       
  4129 					<button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>
       
  4130 				<# } #>
       
  4131 			</li>
       
  4132 		</script>
       
  4133 
       
  4134 		<script type="text/html" id="tmpl-customize-changeset-locked-notification">
       
  4135 			<li class="notice notice-{{ data.type || 'info' }} {{ data.containerClasses || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}">
       
  4136 				<div class="notification-message customize-changeset-locked-message">
       
  4137 					<img class="customize-changeset-locked-avatar" src="{{ data.lockUser.avatar }}" alt="{{ data.lockUser.name }}">
       
  4138 					<p class="currently-editing">
       
  4139 						<# if ( data.message ) { #>
       
  4140 							{{{ data.message }}}
       
  4141 						<# } else if ( data.allowOverride ) { #>
       
  4142 							<?php
       
  4143 							echo esc_html( sprintf( $l10n['locked_allow_override'], '{{ data.lockUser.name }}' ) );
       
  4144 							?>
       
  4145 						<# } else { #>
       
  4146 							<?php
       
  4147 							echo esc_html( sprintf( $l10n['locked'], '{{ data.lockUser.name }}' ) );
       
  4148 							?>
       
  4149 						<# } #>
       
  4150 					</p>
       
  4151 					<p class="notice notice-error notice-alt" hidden></p>
       
  4152 					<p class="action-buttons">
       
  4153 						<# if ( data.returnUrl !== data.previewUrl ) { #>
       
  4154 							<a class="button customize-notice-go-back-button" href="{{ data.returnUrl }}"><?php _e( 'Go back' ); ?></a>
       
  4155 						<# } #>
       
  4156 						<a class="button customize-notice-preview-button" href="{{ data.frontendPreviewUrl }}"><?php _e( 'Preview' ); ?></a>
       
  4157 						<# if ( data.allowOverride ) { #>
       
  4158 							<button class="button button-primary wp-tab-last customize-notice-take-over-button"><?php _e( 'Take over' ); ?></button>
       
  4159 						<# } #>
       
  4160 					</p>
       
  4161 				</div>
       
  4162 			</li>
       
  4163 		</script>
       
  4164 
       
  4165 		<script type="text/html" id="tmpl-customize-code-editor-lint-error-notification">
       
  4166 			<li class="notice notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.containerClasses || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}">
       
  4167 				<div class="notification-message">{{{ data.message || data.code }}}</div>
       
  4168 
       
  4169 				<p>
       
  4170 					<# var elementId = 'el-' + String( Math.random() ); #>
       
  4171 					<input id="{{ elementId }}" type="checkbox">
       
  4172 					<label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
       
  4173 				</p>
       
  4174 			</li>
       
  4175 		</script>
       
  4176 
       
  4177 		<?php
       
  4178 		/* The following template is obsolete in core but retained for plugins. */
       
  4179 		?>
       
  4180 		<script type="text/html" id="tmpl-customize-control-notifications">
       
  4181 			<ul>
       
  4182 				<# _.each( data.notifications, function( notification ) { #>
       
  4183 					<li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{{ notification.message || notification.code }}}</li>
       
  4184 				<# } ); #>
       
  4185 			</ul>
       
  4186 		</script>
       
  4187 
       
  4188 		<script type="text/html" id="tmpl-customize-preview-link-control" >
       
  4189 			<# var elementPrefix = _.uniqueId( 'el' ) + '-' #>
       
  4190 			<p class="customize-control-title">
       
  4191 				<?php esc_html_e( 'Share Preview Link' ); ?>
       
  4192 			</p>
       
  4193 			<p class="description customize-control-description"><?php esc_html_e( 'See how changes would look live on your website, and share the preview with people who can\'t access the Customizer.' ); ?></p>
       
  4194 			<div class="customize-control-notifications-container"></div>
       
  4195 			<div class="preview-link-wrapper">
       
  4196 				<label for="{{ elementPrefix }}customize-preview-link-input" class="screen-reader-text"><?php esc_html_e( 'Preview Link' ); ?></label>
       
  4197 				<a href="" target="">
       
  4198 					<span class="preview-control-element" data-component="url"></span>
       
  4199 					<span class="screen-reader-text"><?php _e( '(opens in a new window)' ); ?></span>
       
  4200 				</a>
       
  4201 				<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>
       
  4203 			</div>
       
  4204 		</script>
       
  4205 		<script type="text/html" id="tmpl-customize-selected-changeset-status-control">
       
  4206 			<# var inputId = _.uniqueId( 'customize-selected-changeset-status-control-input-' ); #>
       
  4207 			<# var descriptionId = _.uniqueId( 'customize-selected-changeset-status-control-description-' ); #>
       
  4208 			<# if ( data.label ) { #>
       
  4209 				<label for="{{ inputId }}" class="customize-control-title">{{ data.label }}</label>
       
  4210 			<# } #>
       
  4211 			<# if ( data.description ) { #>
       
  4212 				<span id="{{ descriptionId }}" class="description customize-control-description">{{{ data.description }}}</span>
       
  4213 			<# } #>
       
  4214 			<# _.each( data.choices, function( choice ) { #>
       
  4215 				<# var choiceId = inputId + '-' + choice.status; #>
       
  4216 				<span class="customize-inside-control-row">
       
  4217 					<input id="{{ choiceId }}" type="radio" value="{{ choice.status }}" name="{{ inputId }}" data-customize-setting-key-link="default">
       
  4218 					<label for="{{ choiceId }}">{{ choice.label }}</label>
       
  4219 				</span>
       
  4220 			<# } ); #>
       
  4221 		</script>
       
  4222 		<?php
  1074 	}
  4223 	}
  1075 
  4224 
  1076 	/**
  4225 	/**
  1077 	 * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
  4226 	 * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
  1078 	 *
  4227 	 *
  1079 	 * @since 3.4.0
  4228 	 * @since 3.4.0
       
  4229 	 * @deprecated 4.7.0 Use wp_list_sort()
  1080 	 *
  4230 	 *
  1081 	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
  4231 	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
  1082 	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
  4232 	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
  1083 	 * @return int
  4233 	 * @return int
  1084 	 */
  4234 	 */
  1085 	protected function _cmp_priority( $a, $b ) {
  4235 	protected function _cmp_priority( $a, $b ) {
       
  4236 		_deprecated_function( __METHOD__, '4.7.0', 'wp_list_sort' );
       
  4237 
  1086 		if ( $a->priority === $b->priority ) {
  4238 		if ( $a->priority === $b->priority ) {
  1087 			return $a->instance_number - $a->instance_number;
  4239 			return $a->instance_number - $b->instance_number;
  1088 		} else {
  4240 		} else {
  1089 			return $a->priority - $b->priority;
  4241 			return $a->priority - $b->priority;
  1090 		}
  4242 		}
  1091 	}
  4243 	}
  1092 
  4244 
  1100 	 * @since 3.4.0
  4252 	 * @since 3.4.0
  1101 	 */
  4253 	 */
  1102 	public function prepare_controls() {
  4254 	public function prepare_controls() {
  1103 
  4255 
  1104 		$controls = array();
  4256 		$controls = array();
  1105 		uasort( $this->controls, array( $this, '_cmp_priority' ) );
  4257 		$this->controls = wp_list_sort( $this->controls, array(
       
  4258 			'priority'        => 'ASC',
       
  4259 			'instance_number' => 'ASC',
       
  4260 		), 'ASC', true );
  1106 
  4261 
  1107 		foreach ( $this->controls as $id => $control ) {
  4262 		foreach ( $this->controls as $id => $control ) {
  1108 			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
  4263 			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
  1109 				continue;
  4264 				continue;
  1110 			}
  4265 			}
  1113 			$controls[ $id ] = $control;
  4268 			$controls[ $id ] = $control;
  1114 		}
  4269 		}
  1115 		$this->controls = $controls;
  4270 		$this->controls = $controls;
  1116 
  4271 
  1117 		// Prepare sections.
  4272 		// Prepare sections.
  1118 		uasort( $this->sections, array( $this, '_cmp_priority' ) );
  4273 		$this->sections = wp_list_sort( $this->sections, array(
       
  4274 			'priority'        => 'ASC',
       
  4275 			'instance_number' => 'ASC',
       
  4276 		), 'ASC', true );
  1119 		$sections = array();
  4277 		$sections = array();
  1120 
  4278 
  1121 		foreach ( $this->sections as $section ) {
  4279 		foreach ( $this->sections as $section ) {
  1122 			if ( ! $section->check_capabilities() || ! $section->controls ) {
  4280 			if ( ! $section->check_capabilities() ) {
  1123 				continue;
  4281 				continue;
  1124 			}
  4282 			}
  1125 
  4283 
  1126 			usort( $section->controls, array( $this, '_cmp_priority' ) );
  4284 
       
  4285 			$section->controls = wp_list_sort( $section->controls, array(
       
  4286 				'priority'        => 'ASC',
       
  4287 				'instance_number' => 'ASC',
       
  4288 			) );
  1127 
  4289 
  1128 			if ( ! $section->panel ) {
  4290 			if ( ! $section->panel ) {
  1129 				// Top-level section.
  4291 				// Top-level section.
  1130 				$sections[ $section->id ] = $section;
  4292 				$sections[ $section->id ] = $section;
  1131 			} else {
  4293 			} else {
  1136 			}
  4298 			}
  1137 		}
  4299 		}
  1138 		$this->sections = $sections;
  4300 		$this->sections = $sections;
  1139 
  4301 
  1140 		// Prepare panels.
  4302 		// Prepare panels.
  1141 		uasort( $this->panels, array( $this, '_cmp_priority' ) );
  4303 		$this->panels = wp_list_sort( $this->panels, array(
       
  4304 			'priority'        => 'ASC',
       
  4305 			'instance_number' => 'ASC',
       
  4306 		), 'ASC', true );
  1142 		$panels = array();
  4307 		$panels = array();
  1143 
  4308 
  1144 		foreach ( $this->panels as $panel ) {
  4309 		foreach ( $this->panels as $panel ) {
  1145 			if ( ! $panel->check_capabilities() || ! $panel->sections ) {
  4310 			if ( ! $panel->check_capabilities() ) {
  1146 				continue;
  4311 				continue;
  1147 			}
  4312 			}
  1148 
  4313 
  1149 			uasort( $panel->sections, array( $this, '_cmp_priority' ) );
  4314 			$panel->sections = wp_list_sort( $panel->sections, array(
       
  4315 				'priority'        => 'ASC',
       
  4316 				'instance_number' => 'ASC',
       
  4317 			), 'ASC', true );
  1150 			$panels[ $panel->id ] = $panel;
  4318 			$panels[ $panel->id ] = $panel;
  1151 		}
  4319 		}
  1152 		$this->panels = $panels;
  4320 		$this->panels = $panels;
  1153 
  4321 
  1154 		// Sort panels and top-level sections together.
  4322 		// Sort panels and top-level sections together.
  1155 		$this->containers = array_merge( $this->panels, $this->sections );
  4323 		$this->containers = array_merge( $this->panels, $this->sections );
  1156 		uasort( $this->containers, array( $this, '_cmp_priority' ) );
  4324 		$this->containers = wp_list_sort( $this->containers, array(
       
  4325 			'priority'        => 'ASC',
       
  4326 			'instance_number' => 'ASC',
       
  4327 		), 'ASC', true );
  1157 	}
  4328 	}
  1158 
  4329 
  1159 	/**
  4330 	/**
  1160 	 * Enqueue scripts for customize controls.
  4331 	 * Enqueue scripts for customize controls.
  1161 	 *
  4332 	 *
  1163 	 */
  4334 	 */
  1164 	public function enqueue_control_scripts() {
  4335 	public function enqueue_control_scripts() {
  1165 		foreach ( $this->controls as $control ) {
  4336 		foreach ( $this->controls as $control ) {
  1166 			$control->enqueue();
  4337 			$control->enqueue();
  1167 		}
  4338 		}
       
  4339 
       
  4340 		if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) {
       
  4341 			wp_enqueue_script( 'updates' );
       
  4342 			wp_localize_script( 'updates', '_wpUpdatesItemCounts', array(
       
  4343 				'totals' => wp_get_update_data(),
       
  4344 			) );
       
  4345 		}
       
  4346 	}
       
  4347 
       
  4348 	/**
       
  4349 	 * Determine whether the user agent is iOS.
       
  4350 	 *
       
  4351 	 * @since 4.4.0
       
  4352 	 *
       
  4353 	 * @return bool Whether the user agent is iOS.
       
  4354 	 */
       
  4355 	public function is_ios() {
       
  4356 		return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
       
  4357 	}
       
  4358 
       
  4359 	/**
       
  4360 	 * Get the template string for the Customizer pane document title.
       
  4361 	 *
       
  4362 	 * @since 4.4.0
       
  4363 	 *
       
  4364 	 * @return string The template string for the document title.
       
  4365 	 */
       
  4366 	public function get_document_title_template() {
       
  4367 		if ( $this->is_theme_active() ) {
       
  4368 			/* translators: %s: document title from the preview */
       
  4369 			$document_title_tmpl = __( 'Customize: %s' );
       
  4370 		} else {
       
  4371 			/* translators: %s: document title from the preview */
       
  4372 			$document_title_tmpl = __( 'Live Preview: %s' );
       
  4373 		}
       
  4374 		$document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
       
  4375 		return $document_title_tmpl;
       
  4376 	}
       
  4377 
       
  4378 	/**
       
  4379 	 * Set the initial URL to be previewed.
       
  4380 	 *
       
  4381 	 * URL is validated.
       
  4382 	 *
       
  4383 	 * @since 4.4.0
       
  4384 	 *
       
  4385 	 * @param string $preview_url URL to be previewed.
       
  4386 	 */
       
  4387 	public function set_preview_url( $preview_url ) {
       
  4388 		$preview_url = esc_url_raw( $preview_url );
       
  4389 		$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
       
  4390 	}
       
  4391 
       
  4392 	/**
       
  4393 	 * Get the initial URL to be previewed.
       
  4394 	 *
       
  4395 	 * @since 4.4.0
       
  4396 	 *
       
  4397 	 * @return string URL being previewed.
       
  4398 	 */
       
  4399 	public function get_preview_url() {
       
  4400 		if ( empty( $this->preview_url ) ) {
       
  4401 			$preview_url = home_url( '/' );
       
  4402 		} else {
       
  4403 			$preview_url = $this->preview_url;
       
  4404 		}
       
  4405 		return $preview_url;
       
  4406 	}
       
  4407 
       
  4408 	/**
       
  4409 	 * Determines whether the admin and the frontend are on different domains.
       
  4410 	 *
       
  4411 	 * @since 4.7.0
       
  4412 	 *
       
  4413 	 * @return bool Whether cross-domain.
       
  4414 	 */
       
  4415 	public function is_cross_domain() {
       
  4416 		$admin_origin = wp_parse_url( admin_url() );
       
  4417 		$home_origin = wp_parse_url( home_url() );
       
  4418 		$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
       
  4419 		return $cross_domain;
       
  4420 	}
       
  4421 
       
  4422 	/**
       
  4423 	 * Get URLs allowed to be previewed.
       
  4424 	 *
       
  4425 	 * If the front end and the admin are served from the same domain, load the
       
  4426 	 * preview over ssl if the Customizer is being loaded over ssl. This avoids
       
  4427 	 * insecure content warnings. This is not attempted if the admin and front end
       
  4428 	 * are on different domains to avoid the case where the front end doesn't have
       
  4429 	 * ssl certs. Domain mapping plugins can allow other urls in these conditions
       
  4430 	 * using the customize_allowed_urls filter.
       
  4431 	 *
       
  4432 	 * @since 4.7.0
       
  4433 	 *
       
  4434 	 * @returns array Allowed URLs.
       
  4435 	 */
       
  4436 	public function get_allowed_urls() {
       
  4437 		$allowed_urls = array( home_url( '/' ) );
       
  4438 
       
  4439 		if ( is_ssl() && ! $this->is_cross_domain() ) {
       
  4440 			$allowed_urls[] = home_url( '/', 'https' );
       
  4441 		}
       
  4442 
       
  4443 		/**
       
  4444 		 * Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
       
  4445 		 *
       
  4446 		 * @since 3.4.0
       
  4447 		 *
       
  4448 		 * @param array $allowed_urls An array of allowed URLs.
       
  4449 		 */
       
  4450 		$allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
       
  4451 
       
  4452 		return $allowed_urls;
       
  4453 	}
       
  4454 
       
  4455 	/**
       
  4456 	 * Get messenger channel.
       
  4457 	 *
       
  4458 	 * @since 4.7.0
       
  4459 	 *
       
  4460 	 * @return string Messenger channel.
       
  4461 	 */
       
  4462 	public function get_messenger_channel() {
       
  4463 		return $this->messenger_channel;
       
  4464 	}
       
  4465 
       
  4466 	/**
       
  4467 	 * Set URL to link the user to when closing the Customizer.
       
  4468 	 *
       
  4469 	 * URL is validated.
       
  4470 	 *
       
  4471 	 * @since 4.4.0
       
  4472 	 *
       
  4473 	 * @param string $return_url URL for return link.
       
  4474 	 */
       
  4475 	public function set_return_url( $return_url ) {
       
  4476 		$return_url = esc_url_raw( $return_url );
       
  4477 		$return_url = remove_query_arg( wp_removable_query_args(), $return_url );
       
  4478 		$return_url = wp_validate_redirect( $return_url );
       
  4479 		$this->return_url = $return_url;
       
  4480 	}
       
  4481 
       
  4482 	/**
       
  4483 	 * Get URL to link the user to when closing the Customizer.
       
  4484 	 *
       
  4485 	 * @since 4.4.0
       
  4486 	 *
       
  4487 	 * @return string URL for link to close Customizer.
       
  4488 	 */
       
  4489 	public function get_return_url() {
       
  4490 		$referer = wp_get_referer();
       
  4491 		$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
       
  4492 
       
  4493 		if ( $this->return_url ) {
       
  4494 			$return_url = $this->return_url;
       
  4495 		} else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
       
  4496 			$return_url = $referer;
       
  4497 		} else if ( $this->preview_url ) {
       
  4498 			$return_url = $this->preview_url;
       
  4499 		} else {
       
  4500 			$return_url = home_url( '/' );
       
  4501 		}
       
  4502 		return $return_url;
       
  4503 	}
       
  4504 
       
  4505 	/**
       
  4506 	 * Set the autofocused constructs.
       
  4507 	 *
       
  4508 	 * @since 4.4.0
       
  4509 	 *
       
  4510 	 * @param array $autofocus {
       
  4511 	 *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
       
  4512 	 *
       
  4513 	 *     @type string [$control]  ID for control to be autofocused.
       
  4514 	 *     @type string [$section]  ID for section to be autofocused.
       
  4515 	 *     @type string [$panel]    ID for panel to be autofocused.
       
  4516 	 * }
       
  4517 	 */
       
  4518 	public function set_autofocus( $autofocus ) {
       
  4519 		$this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
       
  4520 	}
       
  4521 
       
  4522 	/**
       
  4523 	 * Get the autofocused constructs.
       
  4524 	 *
       
  4525 	 * @since 4.4.0
       
  4526 	 *
       
  4527 	 * @return array {
       
  4528 	 *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
       
  4529 	 *
       
  4530 	 *     @type string [$control]  ID for control to be autofocused.
       
  4531 	 *     @type string [$section]  ID for section to be autofocused.
       
  4532 	 *     @type string [$panel]    ID for panel to be autofocused.
       
  4533 	 * }
       
  4534 	 */
       
  4535 	public function get_autofocus() {
       
  4536 		return $this->autofocus;
       
  4537 	}
       
  4538 
       
  4539 	/**
       
  4540 	 * Get nonces for the Customizer.
       
  4541 	 *
       
  4542 	 * @since 4.5.0
       
  4543 	 *
       
  4544 	 * @return array Nonces.
       
  4545 	 */
       
  4546 	public function get_nonces() {
       
  4547 		$nonces = array(
       
  4548 			'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
       
  4549 			'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
       
  4550 			'switch_themes' => wp_create_nonce( 'switch_themes' ),
       
  4551 			'dismiss_autosave_or_lock' => wp_create_nonce( 'customize_dismiss_autosave_or_lock' ),
       
  4552 			'override_lock' => wp_create_nonce( 'customize_override_changeset_lock' ),
       
  4553 			'trash' => wp_create_nonce( 'trash_customize_changeset' ),
       
  4554 		);
       
  4555 
       
  4556 		/**
       
  4557 		 * Filters nonces for Customizer.
       
  4558 		 *
       
  4559 		 * @since 4.2.0
       
  4560 		 *
       
  4561 		 * @param array                $nonces Array of refreshed nonces for save and
       
  4562 		 *                                     preview actions.
       
  4563 		 * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
       
  4564 		 */
       
  4565 		$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
       
  4566 
       
  4567 		return $nonces;
       
  4568 	}
       
  4569 
       
  4570 	/**
       
  4571 	 * Print JavaScript settings for parent window.
       
  4572 	 *
       
  4573 	 * @since 4.4.0
       
  4574 	 */
       
  4575 	public function customize_pane_settings() {
       
  4576 
       
  4577 		$login_url = add_query_arg( array(
       
  4578 			'interim-login' => 1,
       
  4579 			'customize-login' => 1,
       
  4580 		), wp_login_url() );
       
  4581 
       
  4582 		// Ensure dirty flags are set for modified settings.
       
  4583 		foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) {
       
  4584 			$setting = $this->get_setting( $setting_id );
       
  4585 			if ( $setting ) {
       
  4586 				$setting->dirty = true;
       
  4587 			}
       
  4588 		}
       
  4589 
       
  4590 		$autosave_revision_post = null;
       
  4591 		$autosave_autodraft_post = null;
       
  4592 		$changeset_post_id = $this->changeset_post_id();
       
  4593 		if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) {
       
  4594 			if ( $changeset_post_id ) {
       
  4595 				if ( is_user_logged_in() ) {
       
  4596 					$autosave_revision_post = wp_get_post_autosave( $changeset_post_id, get_current_user_id() );
       
  4597 				}
       
  4598 			} else {
       
  4599 				$autosave_autodraft_posts = $this->get_changeset_posts( array(
       
  4600 					'posts_per_page' => 1,
       
  4601 					'post_status' => 'auto-draft',
       
  4602 					'exclude_restore_dismissed' => true,
       
  4603 				) );
       
  4604 				if ( ! empty( $autosave_autodraft_posts ) ) {
       
  4605 					$autosave_autodraft_post = array_shift( $autosave_autodraft_posts );
       
  4606 				}
       
  4607 			}
       
  4608 		}
       
  4609 
       
  4610 		$current_user_can_publish = current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts );
       
  4611 
       
  4612 		// @todo Include all of the status labels here from script-loader.php, and then allow it to be filtered.
       
  4613 		$status_choices = array();
       
  4614 		if ( $current_user_can_publish ) {
       
  4615 			$status_choices[] = array(
       
  4616 				'status' => 'publish',
       
  4617 				'label' => __( 'Publish' ),
       
  4618 			);
       
  4619 		}
       
  4620 		$status_choices[] = array(
       
  4621 			'status' => 'draft',
       
  4622 			'label' => __( 'Save Draft' ),
       
  4623 		);
       
  4624 		if ( $current_user_can_publish ) {
       
  4625 			$status_choices[] = array(
       
  4626 				'status' => 'future',
       
  4627 				'label' => _x( 'Schedule', 'customizer changeset action/button label' ),
       
  4628 			);
       
  4629 		}
       
  4630 
       
  4631 		// Prepare Customizer settings to pass to JavaScript.
       
  4632 		$changeset_post = null;
       
  4633 		if ( $changeset_post_id ) {
       
  4634 			$changeset_post = get_post( $changeset_post_id );
       
  4635 		}
       
  4636 
       
  4637 		// Determine initial date to be at present or future, not past.
       
  4638 		$current_time = current_time( 'mysql', false );
       
  4639 		$initial_date = $current_time;
       
  4640 		if ( $changeset_post ) {
       
  4641 			$initial_date = get_the_time( 'Y-m-d H:i:s', $changeset_post->ID );
       
  4642 			if ( $initial_date < $current_time ) {
       
  4643 				$initial_date = $current_time;
       
  4644 			}
       
  4645 		}
       
  4646 
       
  4647 		$lock_user_id = false;
       
  4648 		if ( $this->changeset_post_id() ) {
       
  4649 			$lock_user_id = wp_check_post_lock( $this->changeset_post_id() );
       
  4650 		}
       
  4651 
       
  4652 		$settings = array(
       
  4653 			'changeset' => array(
       
  4654 				'uuid' => $this->changeset_uuid(),
       
  4655 				'branching' => $this->branching(),
       
  4656 				'autosaved' => $this->autosaved(),
       
  4657 				'hasAutosaveRevision' => ! empty( $autosave_revision_post ),
       
  4658 				'latestAutoDraftUuid' => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null,
       
  4659 				'status' => $changeset_post ? $changeset_post->post_status : '',
       
  4660 				'currentUserCanPublish' => $current_user_can_publish,
       
  4661 				'publishDate' => $initial_date,
       
  4662 				'statusChoices' => $status_choices,
       
  4663 				'lockUser' => $lock_user_id ? $this->get_lock_user_data( $lock_user_id ) : null,
       
  4664 			),
       
  4665 			'initialServerDate' => $current_time,
       
  4666 			'dateFormat' => get_option( 'date_format' ),
       
  4667 			'timeFormat' => get_option( 'time_format' ),
       
  4668 			'initialServerTimestamp' => floor( microtime( true ) * 1000 ),
       
  4669 			'initialClientTimestamp' => -1, // To be set with JS below.
       
  4670 			'timeouts' => array(
       
  4671 				'windowRefresh' => 250,
       
  4672 				'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000,
       
  4673 				'keepAliveCheck' => 2500,
       
  4674 				'reflowPaneContents' => 100,
       
  4675 				'previewFrameSensitivity' => 2000,
       
  4676 			),
       
  4677 			'theme'    => array(
       
  4678 				'stylesheet'  => $this->get_stylesheet(),
       
  4679 				'active'      => $this->is_theme_active(),
       
  4680 				'_canInstall' => current_user_can( 'install_themes' ),
       
  4681 			),
       
  4682 			'url'      => array(
       
  4683 				'preview'       => esc_url_raw( $this->get_preview_url() ),
       
  4684 				'return'        => esc_url_raw( $this->get_return_url() ),
       
  4685 				'parent'        => esc_url_raw( admin_url() ),
       
  4686 				'activated'     => esc_url_raw( home_url( '/' ) ),
       
  4687 				'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
       
  4688 				'allowed'       => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
       
  4689 				'isCrossDomain' => $this->is_cross_domain(),
       
  4690 				'home'          => esc_url_raw( home_url( '/' ) ),
       
  4691 				'login'         => esc_url_raw( $login_url ),
       
  4692 			),
       
  4693 			'browser'  => array(
       
  4694 				'mobile' => wp_is_mobile(),
       
  4695 				'ios'    => $this->is_ios(),
       
  4696 			),
       
  4697 			'panels'   => array(),
       
  4698 			'sections' => array(),
       
  4699 			'nonce'    => $this->get_nonces(),
       
  4700 			'autofocus' => $this->get_autofocus(),
       
  4701 			'documentTitleTmpl' => $this->get_document_title_template(),
       
  4702 			'previewableDevices' => $this->get_previewable_devices(),
       
  4703 			'l10n' => array(
       
  4704 				'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 */
       
  4706 				'themeSearchResults' => __( '%d themes found' ),
       
  4707 				/* translators: %d: number of themes being displayed, which cannot currently consider singular vs. plural forms */
       
  4708 				'announceThemeCount' => __( 'Displaying %d themes' ),
       
  4709 				/* translators: %s: theme name */
       
  4710 				'announceThemeDetails' => __( 'Showing details for theme: %s' ),
       
  4711 			),
       
  4712 		);
       
  4713 
       
  4714 		// Temporarily disable installation in Customizer. See #42184.
       
  4715 		$filesystem_method = get_filesystem_method();
       
  4716 		ob_start();
       
  4717 		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
       
  4718 		ob_end_clean();
       
  4719 		if ( 'direct' !== $filesystem_method && ! $filesystem_credentials_are_stored ) {
       
  4720 			$settings['theme']['_filesystemCredentialsNeeded'] = true;
       
  4721 		}
       
  4722 
       
  4723 		// Prepare Customize Section objects to pass to JavaScript.
       
  4724 		foreach ( $this->sections() as $id => $section ) {
       
  4725 			if ( $section->check_capabilities() ) {
       
  4726 				$settings['sections'][ $id ] = $section->json();
       
  4727 			}
       
  4728 		}
       
  4729 
       
  4730 		// Prepare Customize Panel objects to pass to JavaScript.
       
  4731 		foreach ( $this->panels() as $panel_id => $panel ) {
       
  4732 			if ( $panel->check_capabilities() ) {
       
  4733 				$settings['panels'][ $panel_id ] = $panel->json();
       
  4734 				foreach ( $panel->sections as $section_id => $section ) {
       
  4735 					if ( $section->check_capabilities() ) {
       
  4736 						$settings['sections'][ $section_id ] = $section->json();
       
  4737 					}
       
  4738 				}
       
  4739 			}
       
  4740 		}
       
  4741 
       
  4742 		?>
       
  4743 		<script type="text/javascript">
       
  4744 			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
       
  4745 			_wpCustomizeSettings.initialClientTimestamp = _.now();
       
  4746 			_wpCustomizeSettings.controls = {};
       
  4747 			_wpCustomizeSettings.settings = {};
       
  4748 			<?php
       
  4749 
       
  4750 			// Serialize settings one by one to improve memory usage.
       
  4751 			echo "(function ( s ){\n";
       
  4752 			foreach ( $this->settings() as $setting ) {
       
  4753 				if ( $setting->check_capabilities() ) {
       
  4754 					printf(
       
  4755 						"s[%s] = %s;\n",
       
  4756 						wp_json_encode( $setting->id ),
       
  4757 						wp_json_encode( $setting->json() )
       
  4758 					);
       
  4759 				}
       
  4760 			}
       
  4761 			echo "})( _wpCustomizeSettings.settings );\n";
       
  4762 
       
  4763 			// Serialize controls one by one to improve memory usage.
       
  4764 			echo "(function ( c ){\n";
       
  4765 			foreach ( $this->controls() as $control ) {
       
  4766 				if ( $control->check_capabilities() ) {
       
  4767 					printf(
       
  4768 						"c[%s] = %s;\n",
       
  4769 						wp_json_encode( $control->id ),
       
  4770 						wp_json_encode( $control->json() )
       
  4771 					);
       
  4772 				}
       
  4773 			}
       
  4774 			echo "})( _wpCustomizeSettings.controls );\n";
       
  4775 		?>
       
  4776 		</script>
       
  4777 		<?php
       
  4778 	}
       
  4779 
       
  4780 	/**
       
  4781 	 * Returns a list of devices to allow previewing.
       
  4782 	 *
       
  4783 	 * @since 4.5.0
       
  4784 	 *
       
  4785 	 * @return array List of devices with labels and default setting.
       
  4786 	 */
       
  4787 	public function get_previewable_devices() {
       
  4788 		$devices = array(
       
  4789 			'desktop' => array(
       
  4790 				'label' => __( 'Enter desktop preview mode' ),
       
  4791 				'default' => true,
       
  4792 			),
       
  4793 			'tablet' => array(
       
  4794 				'label' => __( 'Enter tablet preview mode' ),
       
  4795 			),
       
  4796 			'mobile' => array(
       
  4797 				'label' => __( 'Enter mobile preview mode' ),
       
  4798 			),
       
  4799 		);
       
  4800 
       
  4801 		/**
       
  4802 		 * Filters the available devices to allow previewing in the Customizer.
       
  4803 		 *
       
  4804 		 * @since 4.5.0
       
  4805 		 *
       
  4806 		 * @see WP_Customize_Manager::get_previewable_devices()
       
  4807 		 *
       
  4808 		 * @param array $devices List of devices with labels and default setting.
       
  4809 		 */
       
  4810 		$devices = apply_filters( 'customize_previewable_devices', $devices );
       
  4811 
       
  4812 		return $devices;
  1168 	}
  4813 	}
  1169 
  4814 
  1170 	/**
  4815 	/**
  1171 	 * Register some default controls.
  4816 	 * Register some default controls.
  1172 	 *
  4817 	 *
  1173 	 * @since 3.4.0
  4818 	 * @since 3.4.0
  1174 	 */
  4819 	 */
  1175 	public function register_controls() {
  4820 	public function register_controls() {
  1176 
  4821 
  1177 		/* Control Types (custom control classes) */
  4822 		/* Themes (controls are loaded via ajax) */
  1178 		$this->register_control_type( 'WP_Customize_Color_Control' );
  4823 
  1179 		$this->register_control_type( 'WP_Customize_Media_Control' );
  4824 		$this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array(
  1180 		$this->register_control_type( 'WP_Customize_Upload_Control' );
  4825 			'title'       => $this->theme()->display( 'Name' ),
  1181 		$this->register_control_type( 'WP_Customize_Image_Control' );
  4826 			'description' => (
  1182 		$this->register_control_type( 'WP_Customize_Background_Image_Control' );
  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>' .
  1183 		$this->register_control_type( 'WP_Customize_Theme_Control' );
  4828 				'<p>' . __( 'While previewing a new theme, you can continue to tailor things like widgets and menus, and explore theme-specific options.' ) . '</p>'
  1184 
  4829 			),
  1185 		/* Themes */
  4830 			'capability'  => 'switch_themes',
  1186 
  4831 			'priority'    => 0,
  1187 		$this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
       
  1188 			'title'      => $this->theme()->display( 'Name' ),
       
  1189 			'capability' => 'switch_themes',
       
  1190 			'priority'   => 0,
       
  1191 		) ) );
  4832 		) ) );
       
  4833 
       
  4834 		$this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array(
       
  4835 			'title'       => __( 'Installed themes' ),
       
  4836 			'action'      => 'installed',
       
  4837 			'capability'  => 'switch_themes',
       
  4838 			'panel'       => 'themes',
       
  4839 			'priority'    => 0,
       
  4840 		) ) );
       
  4841 
       
  4842 		if ( ! is_multisite() ) {
       
  4843 			$this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array(
       
  4844 				'title'       => __( 'WordPress.org themes' ),
       
  4845 				'action'      => 'wporg',
       
  4846 				'filter_type' => 'remote',
       
  4847 				'capability'  => 'install_themes',
       
  4848 				'panel'       => 'themes',
       
  4849 				'priority'    => 5,
       
  4850 			) ) );
       
  4851 		}
  1192 
  4852 
  1193 		// Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
  4853 		// Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
  1194 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
  4854 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
  1195 			'capability' => 'switch_themes',
  4855 			'capability' => 'switch_themes',
  1196 		) ) );
  4856 		) ) );
  1197 
  4857 
  1198 		require_once( ABSPATH . 'wp-admin/includes/theme.php' );
  4858 		/* Site Identity */
  1199 
       
  1200 		// Theme Controls.
       
  1201 
       
  1202 		// Add a control for the active/original theme.
       
  1203 		if ( ! $this->is_theme_active() ) {
       
  1204 			$themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
       
  1205 			$active_theme = current( $themes );
       
  1206 			$active_theme['isActiveTheme'] = true;
       
  1207 			$this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
       
  1208 				'theme'    => $active_theme,
       
  1209 				'section'  => 'themes',
       
  1210 				'settings' => 'active_theme',
       
  1211 			) ) );
       
  1212 		}
       
  1213 
       
  1214 		$themes = wp_prepare_themes_for_js();
       
  1215 		foreach ( $themes as $theme ) {
       
  1216 			if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
       
  1217 				continue;
       
  1218 			}
       
  1219 
       
  1220 			$theme_id = 'theme_' . $theme['id'];
       
  1221 			$theme['isActiveTheme'] = false;
       
  1222 			$this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
       
  1223 				'theme'    => $theme,
       
  1224 				'section'  => 'themes',
       
  1225 				'settings' => 'active_theme',
       
  1226 			) ) );
       
  1227 		}
       
  1228 
       
  1229 		/* Site Title & Tagline */
       
  1230 
  4859 
  1231 		$this->add_section( 'title_tagline', array(
  4860 		$this->add_section( 'title_tagline', array(
  1232 			'title'    => __( 'Site Title & Tagline' ),
  4861 			'title'    => __( 'Site Identity' ),
  1233 			'priority' => 20,
  4862 			'priority' => 20,
  1234 		) );
  4863 		) );
  1235 
  4864 
  1236 		$this->add_setting( 'blogname', array(
  4865 		$this->add_setting( 'blogname', array(
  1237 			'default'    => get_option( 'blogname' ),
  4866 			'default'    => get_option( 'blogname' ),
  1253 		$this->add_control( 'blogdescription', array(
  4882 		$this->add_control( 'blogdescription', array(
  1254 			'label'      => __( 'Tagline' ),
  4883 			'label'      => __( 'Tagline' ),
  1255 			'section'    => 'title_tagline',
  4884 			'section'    => 'title_tagline',
  1256 		) );
  4885 		) );
  1257 
  4886 
       
  4887 		// Add a setting to hide header text if the theme doesn't support custom headers.
       
  4888 		if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
       
  4889 			$this->add_setting( 'header_text', array(
       
  4890 				'theme_supports'    => array( 'custom-logo', 'header-text' ),
       
  4891 				'default'           => 1,
       
  4892 				'sanitize_callback' => 'absint',
       
  4893 			) );
       
  4894 
       
  4895 			$this->add_control( 'header_text', array(
       
  4896 				'label'    => __( 'Display Site Title and Tagline' ),
       
  4897 				'section'  => 'title_tagline',
       
  4898 				'settings' => 'header_text',
       
  4899 				'type'     => 'checkbox',
       
  4900 			) );
       
  4901 		}
       
  4902 
       
  4903 		$this->add_setting( 'site_icon', array(
       
  4904 			'type'       => 'option',
       
  4905 			'capability' => 'manage_options',
       
  4906 			'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
       
  4907 		) );
       
  4908 
       
  4909 		$this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
       
  4910 			'label'       => __( 'Site Icon' ),
       
  4911 			'description' => sprintf(
       
  4912 				'<p>' . __( 'Site Icons are what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. Upload one here!' ) . '</p>' .
       
  4913 				/* translators: %s: site icon size in pixels */
       
  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 
  1258 		/* Colors */
  4955 		/* Colors */
  1259 
  4956 
  1260 		$this->add_section( 'colors', array(
  4957 		$this->add_section( 'colors', array(
  1261 			'title'          => __( 'Colors' ),
  4958 			'title'          => __( 'Colors' ),
  1262 			'priority'       => 40,
  4959 			'priority'       => 40,
  1272 
  4969 
  1273 		// Input type: checkbox
  4970 		// Input type: checkbox
  1274 		// With custom value
  4971 		// With custom value
  1275 		$this->add_control( 'display_header_text', array(
  4972 		$this->add_control( 'display_header_text', array(
  1276 			'settings' => 'header_textcolor',
  4973 			'settings' => 'header_textcolor',
  1277 			'label'    => __( 'Display Header Text' ),
  4974 			'label'    => __( 'Display Site Title and Tagline' ),
  1278 			'section'  => 'title_tagline',
  4975 			'section'  => 'title_tagline',
  1279 			'type'     => 'checkbox',
  4976 			'type'     => 'checkbox',
       
  4977 			'priority' => 40,
  1280 		) );
  4978 		) );
  1281 
  4979 
  1282 		$this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
  4980 		$this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
  1283 			'label'   => __( 'Header Text Color' ),
  4981 			'label'   => __( 'Header Text Color' ),
  1284 			'section' => 'colors',
  4982 			'section' => 'colors',
  1297 		$this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
  4995 		$this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
  1298 			'label'   => __( 'Background Color' ),
  4996 			'label'   => __( 'Background Color' ),
  1299 			'section' => 'colors',
  4997 			'section' => 'colors',
  1300 		) ) );
  4998 		) ) );
  1301 
  4999 
  1302 
       
  1303 		/* Custom Header */
  5000 		/* Custom Header */
  1304 
  5001 
       
  5002 		if ( current_theme_supports( 'custom-header', 'video' ) ) {
       
  5003 			$title = __( 'Header Media' );
       
  5004 			$description = '<p>' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '</p>';
       
  5005 
       
  5006 			$width = absint( get_theme_support( 'custom-header', 'width' ) );
       
  5007 			$height = absint( get_theme_support( 'custom-header', 'height' ) );
       
  5008 			if ( $width && $height ) {
       
  5009 				$control_description = sprintf(
       
  5010 					/* 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.' ),
       
  5012 					'<code>.mp4</code>',
       
  5013 					sprintf( '<strong>%s &times; %s</strong>', $width, $height )
       
  5014 				);
       
  5015 			} elseif ( $width ) {
       
  5016 				$control_description = sprintf(
       
  5017 					/* translators: 1: .mp4, 2: header width in pixels */
       
  5018 					__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a width of %2$s pixels.' ),
       
  5019 					'<code>.mp4</code>',
       
  5020 					sprintf( '<strong>%s</strong>', $width )
       
  5021 				);
       
  5022 			} else {
       
  5023 				$control_description = sprintf(
       
  5024 					/* translators: 1: .mp4, 2: header height in pixels */
       
  5025 					__( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a height of %2$s pixels.' ),
       
  5026 					'<code>.mp4</code>',
       
  5027 					sprintf( '<strong>%s</strong>', $height )
       
  5028 				);
       
  5029 			}
       
  5030 		} else {
       
  5031 			$title = __( 'Header Image' );
       
  5032 			$description = '';
       
  5033 			$control_description = '';
       
  5034 		}
       
  5035 
  1305 		$this->add_section( 'header_image', array(
  5036 		$this->add_section( 'header_image', array(
  1306 			'title'          => __( 'Header Image' ),
  5037 			'title'          => $title,
       
  5038 			'description'    => $description,
  1307 			'theme_supports' => 'custom-header',
  5039 			'theme_supports' => 'custom-header',
  1308 			'priority'       => 60,
  5040 			'priority'       => 60,
  1309 		) );
  5041 		) );
  1310 
  5042 
       
  5043 		$this->add_setting( 'header_video', array(
       
  5044 			'theme_supports'    => array( 'custom-header', 'video' ),
       
  5045 			'transport'         => 'postMessage',
       
  5046 			'sanitize_callback' => 'absint',
       
  5047 			'validate_callback' => array( $this, '_validate_header_video' ),
       
  5048 		) );
       
  5049 
       
  5050 		$this->add_setting( 'external_header_video', array(
       
  5051 			'theme_supports'    => array( 'custom-header', 'video' ),
       
  5052 			'transport'         => 'postMessage',
       
  5053 			'sanitize_callback' => array( $this, '_sanitize_external_header_video' ),
       
  5054 			'validate_callback' => array( $this, '_validate_external_header_video' ),
       
  5055 		) );
       
  5056 
  1311 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
  5057 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
  1312 			'default'        => get_theme_support( 'custom-header', 'default-image' ),
  5058 			'default'        => sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() ),
  1313 			'theme_supports' => 'custom-header',
  5059 			'theme_supports' => 'custom-header',
  1314 		) ) );
  5060 		) ) );
  1315 
  5061 
  1316 		$this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
  5062 		$this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
  1317 			// 'default'        => get_theme_support( 'custom-header', 'default-image' ),
       
  1318 			'theme_supports' => 'custom-header',
  5063 			'theme_supports' => 'custom-header',
  1319 		) ) );
  5064 		) ) );
  1320 
  5065 
       
  5066 		/*
       
  5067 		 * 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
       
  5069 		 * refresh can be utilized.
       
  5070 		 */
       
  5071 		if ( current_theme_supports( 'custom-header', 'video' ) ) {
       
  5072 			$this->get_setting( 'header_image' )->transport = 'postMessage';
       
  5073 			$this->get_setting( 'header_image_data' )->transport = 'postMessage';
       
  5074 		}
       
  5075 
       
  5076 		$this->add_control( new WP_Customize_Media_Control( $this, 'header_video', array(
       
  5077 			'theme_supports' => array( 'custom-header', 'video' ),
       
  5078 			'label'          => __( 'Header Video' ),
       
  5079 			'description'    => $control_description,
       
  5080 			'section'        => 'header_image',
       
  5081 			'mime_type'      => 'video',
       
  5082 			'active_callback' => 'is_header_video_active',
       
  5083 		) ) );
       
  5084 
       
  5085 		$this->add_control( 'external_header_video', array(
       
  5086 			'theme_supports' => array( 'custom-header', 'video' ),
       
  5087 			'type'           => 'url',
       
  5088 			'description'    => __( 'Or, enter a YouTube URL:' ),
       
  5089 			'section'        => 'header_image',
       
  5090 			'active_callback' => 'is_header_video_active',
       
  5091 		) );
       
  5092 
  1321 		$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
  5093 		$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
       
  5094 
       
  5095 		$this->selective_refresh->add_partial( 'custom_header', array(
       
  5096 			'selector'            => '#wp-custom-header',
       
  5097 			'render_callback'     => 'the_custom_header_markup',
       
  5098 			'settings'            => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
       
  5099 			'container_inclusive' => true,
       
  5100 		) );
  1322 
  5101 
  1323 		/* Custom Background */
  5102 		/* Custom Background */
  1324 
  5103 
  1325 		$this->add_section( 'background_image', array(
  5104 		$this->add_section( 'background_image', array(
  1326 			'title'          => __( 'Background Image' ),
  5105 			'title'          => __( 'Background Image' ),
  1329 		) );
  5108 		) );
  1330 
  5109 
  1331 		$this->add_setting( 'background_image', array(
  5110 		$this->add_setting( 'background_image', array(
  1332 			'default'        => get_theme_support( 'custom-background', 'default-image' ),
  5111 			'default'        => get_theme_support( 'custom-background', 'default-image' ),
  1333 			'theme_supports' => 'custom-background',
  5112 			'theme_supports' => 'custom-background',
       
  5113 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  1334 		) );
  5114 		) );
  1335 
  5115 
  1336 		$this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
  5116 		$this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
  1337 			'theme_supports' => 'custom-background',
  5117 			'theme_supports' => 'custom-background',
       
  5118 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  1338 		) ) );
  5119 		) ) );
  1339 
  5120 
  1340 		$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
  5121 		$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
  1341 
  5122 
  1342 		$this->add_setting( 'background_repeat', array(
  5123 		$this->add_setting( 'background_preset', array(
  1343 			'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
  5124 			'default'        => get_theme_support( 'custom-background', 'default-preset' ),
  1344 			'theme_supports' => 'custom-background',
  5125 			'theme_supports' => 'custom-background',
       
  5126 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  1345 		) );
  5127 		) );
  1346 
  5128 
  1347 		$this->add_control( 'background_repeat', array(
  5129 		$this->add_control( 'background_preset', array(
  1348 			'label'      => __( 'Background Repeat' ),
  5130 			'label'      => _x( 'Preset', 'Background Preset' ),
  1349 			'section'    => 'background_image',
  5131 			'section'    => 'background_image',
  1350 			'type'       => 'radio',
  5132 			'type'       => 'select',
  1351 			'choices'    => array(
  5133 			'choices'    => array(
  1352 				'no-repeat'  => __('No Repeat'),
  5134 				'default' => _x( 'Default', 'Default Preset' ),
  1353 				'repeat'     => __('Tile'),
  5135 				'fill'    => __( 'Fill Screen' ),
  1354 				'repeat-x'   => __('Tile Horizontally'),
  5136 				'fit'     => __( 'Fit to Screen' ),
  1355 				'repeat-y'   => __('Tile Vertically'),
  5137 				'repeat'  => _x( 'Repeat', 'Repeat Image' ),
       
  5138 				'custom'  => _x( 'Custom', 'Custom Preset' ),
  1356 			),
  5139 			),
  1357 		) );
  5140 		) );
  1358 
  5141 
  1359 		$this->add_setting( 'background_position_x', array(
  5142 		$this->add_setting( 'background_position_x', array(
  1360 			'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
  5143 			'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
  1361 			'theme_supports' => 'custom-background',
  5144 			'theme_supports' => 'custom-background',
       
  5145 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
  1362 		) );
  5146 		) );
  1363 
  5147 
  1364 		$this->add_control( 'background_position_x', array(
  5148 		$this->add_setting( 'background_position_y', array(
  1365 			'label'      => __( 'Background Position' ),
  5149 			'default'        => get_theme_support( 'custom-background', 'default-position-y' ),
       
  5150 			'theme_supports' => 'custom-background',
       
  5151 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5152 		) );
       
  5153 
       
  5154 		$this->add_control( new WP_Customize_Background_Position_Control( $this, 'background_position', array(
       
  5155 			'label'    => __( 'Image Position' ),
       
  5156 			'section'  => 'background_image',
       
  5157 			'settings' => array(
       
  5158 				'x' => 'background_position_x',
       
  5159 				'y' => 'background_position_y',
       
  5160 			),
       
  5161 		) ) );
       
  5162 
       
  5163 		$this->add_setting( 'background_size', array(
       
  5164 			'default'        => get_theme_support( 'custom-background', 'default-size' ),
       
  5165 			'theme_supports' => 'custom-background',
       
  5166 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5167 		) );
       
  5168 
       
  5169 		$this->add_control( 'background_size', array(
       
  5170 			'label'      => __( 'Image Size' ),
  1366 			'section'    => 'background_image',
  5171 			'section'    => 'background_image',
  1367 			'type'       => 'radio',
  5172 			'type'       => 'select',
  1368 			'choices'    => array(
  5173 			'choices'    => array(
  1369 				'left'       => __('Left'),
  5174 				'auto'    => __( 'Original' ),
  1370 				'center'     => __('Center'),
  5175 				'contain' => __( 'Fit to Screen' ),
  1371 				'right'      => __('Right'),
  5176 				'cover'   => __( 'Fill Screen' ),
  1372 			),
  5177 			),
  1373 		) );
  5178 		) );
  1374 
  5179 
       
  5180 		$this->add_setting( 'background_repeat', array(
       
  5181 			'default'           => get_theme_support( 'custom-background', 'default-repeat' ),
       
  5182 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5183 			'theme_supports'    => 'custom-background',
       
  5184 		) );
       
  5185 
       
  5186 		$this->add_control( 'background_repeat', array(
       
  5187 			'label'    => __( 'Repeat Background Image' ),
       
  5188 			'section'  => 'background_image',
       
  5189 			'type'     => 'checkbox',
       
  5190 		) );
       
  5191 
  1375 		$this->add_setting( 'background_attachment', array(
  5192 		$this->add_setting( 'background_attachment', array(
  1376 			'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
  5193 			'default'           => get_theme_support( 'custom-background', 'default-attachment' ),
  1377 			'theme_supports' => 'custom-background',
  5194 			'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
       
  5195 			'theme_supports'    => 'custom-background',
  1378 		) );
  5196 		) );
  1379 
  5197 
  1380 		$this->add_control( 'background_attachment', array(
  5198 		$this->add_control( 'background_attachment', array(
  1381 			'label'      => __( 'Background Attachment' ),
  5199 			'label'    => __( 'Scroll with Page' ),
  1382 			'section'    => 'background_image',
  5200 			'section'  => 'background_image',
  1383 			'type'       => 'radio',
  5201 			'type'     => 'checkbox',
  1384 			'choices'    => array(
       
  1385 				'scroll'     => __('Scroll'),
       
  1386 				'fixed'      => __('Fixed'),
       
  1387 			),
       
  1388 		) );
  5202 		) );
       
  5203 
  1389 
  5204 
  1390 		// If the theme is using the default background callback, we can update
  5205 		// If the theme is using the default background callback, we can update
  1391 		// the background CSS using postMessage.
  5206 		// the background CSS using postMessage.
  1392 		if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
  5207 		if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
  1393 			foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
  5208 			foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) {
  1394 				$this->get_setting( 'background_' . $prop )->transport = 'postMessage';
  5209 				$this->get_setting( 'background_' . $prop )->transport = 'postMessage';
  1395 			}
  5210 			}
  1396 		}
  5211 		}
  1397 
  5212 
  1398 		/* Nav Menus */
  5213 		/*
  1399 
  5214 		 * Static Front Page
  1400 		$locations      = get_registered_nav_menus();
  5215 		 * See also https://core.trac.wordpress.org/ticket/19627 which introduces the static-front-page theme_support.
  1401 		$menus          = wp_get_nav_menus();
  5216 		 * The following replicates behavior from options-reading.php.
  1402 		$num_locations  = count( array_keys( $locations ) );
  5217 		 */
  1403 
  5218 
  1404 		if ( 1 == $num_locations ) {
  5219 		$this->add_section( 'static_front_page', array(
  1405 			$description = __( 'Your theme supports one menu. Select which menu you would like to use.' );
  5220 			'title' => __( 'Homepage Settings' ),
       
  5221 			'priority' => 120,
       
  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.' ),
       
  5223 			'active_callback' => array( $this, 'has_published_pages' ),
       
  5224 		) );
       
  5225 
       
  5226 		$this->add_setting( 'show_on_front', array(
       
  5227 			'default' => get_option( 'show_on_front' ),
       
  5228 			'capability' => 'manage_options',
       
  5229 			'type' => 'option',
       
  5230 		) );
       
  5231 
       
  5232 		$this->add_control( 'show_on_front', array(
       
  5233 			'label' => __( 'Your homepage displays' ),
       
  5234 			'section' => 'static_front_page',
       
  5235 			'type' => 'radio',
       
  5236 			'choices' => array(
       
  5237 				'posts' => __( 'Your latest posts' ),
       
  5238 				'page'  => __( 'A static page' ),
       
  5239 			),
       
  5240 		) );
       
  5241 
       
  5242 		$this->add_setting( 'page_on_front', array(
       
  5243 			'type'       => 'option',
       
  5244 			'capability' => 'manage_options',
       
  5245 		) );
       
  5246 
       
  5247 		$this->add_control( 'page_on_front', array(
       
  5248 			'label' => __( 'Homepage' ),
       
  5249 			'section' => 'static_front_page',
       
  5250 			'type' => 'dropdown-pages',
       
  5251 			'allow_addition' => true,
       
  5252 		) );
       
  5253 
       
  5254 		$this->add_setting( 'page_for_posts', array(
       
  5255 			'type' => 'option',
       
  5256 			'capability' => 'manage_options',
       
  5257 		) );
       
  5258 
       
  5259 		$this->add_control( 'page_for_posts', array(
       
  5260 			'label' => __( 'Posts page' ),
       
  5261 			'section' => 'static_front_page',
       
  5262 			'type' => 'dropdown-pages',
       
  5263 			'allow_addition' => true,
       
  5264 		) );
       
  5265 
       
  5266 		/* Custom CSS */
       
  5267 		$section_description = '<p>';
       
  5268 		$section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.' );
       
  5269 		$section_description .= sprintf(
       
  5270 			' <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' ) ),
       
  5272 			__( 'Learn more about CSS' ),
       
  5273 			/* translators: accessibility text */
       
  5274 			__( '(opens in a new window)' )
       
  5275 		);
       
  5276 		$section_description .= '</p>';
       
  5277 
       
  5278 		$section_description .= '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>';
       
  5279 		$section_description .= '<ul>';
       
  5280 		$section_description .= '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>';
       
  5281 		$section_description .= '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>';
       
  5282 		$section_description .= '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the escape key twice.' ) . '</li>';
       
  5283 		$section_description .= '</ul>';
       
  5284 
       
  5285 		if ( 'false' !== wp_get_current_user()->syntax_highlighting ) {
       
  5286 			$section_description .= '<p>';
       
  5287 			$section_description .= sprintf(
       
  5288 				/* 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.' ),
       
  5290 				esc_url( get_edit_profile_url() ),
       
  5291 				'class="external-link" target="_blank"',
       
  5292 				sprintf( '<span class="screen-reader-text"> %s</span>',
       
  5293 					/* translators: accessibility text */
       
  5294 					__( '(opens in a new window)' )
       
  5295 				)
       
  5296 			);
       
  5297 			$section_description .= '</p>';
       
  5298 		}
       
  5299 
       
  5300 		$section_description .= '<p class="section-description-buttons">';
       
  5301 		$section_description .= '<button type="button" class="button-link section-description-close">' . __( 'Close' ) . '</button>';
       
  5302 		$section_description .= '</p>';
       
  5303 
       
  5304 		$this->add_section( 'custom_css', array(
       
  5305 			'title'              => __( 'Additional CSS' ),
       
  5306 			'priority'           => 200,
       
  5307 			'description_hidden' => true,
       
  5308 			'description'        => $section_description,
       
  5309 		) );
       
  5310 
       
  5311 		$custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array(
       
  5312 			'capability' => 'edit_css',
       
  5313 			'default' => '',
       
  5314 		) );
       
  5315 		$this->add_setting( $custom_css_setting );
       
  5316 
       
  5317 		$this->add_control( new WP_Customize_Code_Editor_Control( $this, 'custom_css', array(
       
  5318 			'label'       => __( 'CSS code' ),
       
  5319 			'section'     => 'custom_css',
       
  5320 			'settings'    => array( 'default' => $custom_css_setting->id ),
       
  5321 			'code_type'   => 'text/css',
       
  5322 			'input_attrs' => array(
       
  5323 				'aria-describedby' => 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4',
       
  5324 			),
       
  5325 		) ) );
       
  5326 	}
       
  5327 
       
  5328 	/**
       
  5329 	 * Return whether there are published pages.
       
  5330 	 *
       
  5331 	 * Used as active callback for static front page section and controls.
       
  5332 	 *
       
  5333 	 * @since 4.7.0
       
  5334 	 *
       
  5335 	 * @returns bool Whether there are published (or to be published) pages.
       
  5336 	 */
       
  5337 	public function has_published_pages() {
       
  5338 
       
  5339 		$setting = $this->get_setting( 'nav_menus_created_posts' );
       
  5340 		if ( $setting ) {
       
  5341 			foreach ( $setting->value() as $post_id ) {
       
  5342 				if ( 'page' === get_post_type( $post_id ) ) {
       
  5343 					return true;
       
  5344 				}
       
  5345 			}
       
  5346 		}
       
  5347 		return 0 !== count( get_pages() );
       
  5348 	}
       
  5349 
       
  5350 	/**
       
  5351 	 * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
       
  5352 	 *
       
  5353 	 * @since 4.2.0
       
  5354 	 *
       
  5355 	 * @see add_dynamic_settings()
       
  5356 	 */
       
  5357 	public function register_dynamic_settings() {
       
  5358 		$setting_ids = array_keys( $this->unsanitized_post_values() );
       
  5359 		$this->add_dynamic_settings( $setting_ids );
       
  5360 	}
       
  5361 
       
  5362 	/**
       
  5363 	 * Load themes into the theme browsing/installation UI.
       
  5364 	 *
       
  5365 	 * @since 4.9.0
       
  5366 	 */
       
  5367 	public function handle_load_themes_request() {
       
  5368 		check_ajax_referer( 'switch_themes', 'nonce' );
       
  5369 
       
  5370 		if ( ! current_user_can( 'switch_themes' ) ) {
       
  5371 			wp_die( -1 );
       
  5372 		}
       
  5373 
       
  5374 		if ( empty( $_POST['theme_action'] ) ) {
       
  5375 			wp_send_json_error( 'missing_theme_action' );
       
  5376 		}
       
  5377 		$theme_action = sanitize_key( $_POST['theme_action'] );
       
  5378 		$themes = array();
       
  5379 		$args = array();
       
  5380 
       
  5381 		// Define query filters based on user input.
       
  5382 		if ( ! array_key_exists( 'search', $_POST ) ) {
       
  5383 			$args['search'] = '';
  1406 		} else {
  5384 		} else {
  1407 			$description = sprintf( _n( 'Your theme supports %s menu. Select which menu appears in each location.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) );
  5385 			$args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) );
  1408 		}
  5386 		}
  1409 
  5387 
  1410 		$this->add_section( 'nav', array(
  5388 		if ( ! array_key_exists( 'tags', $_POST ) ) {
  1411 			'title'          => __( 'Navigation' ),
  5389 			$args['tag'] = '';
  1412 			'theme_supports' => 'menus',
  5390 		} else {
  1413 			'priority'       => 100,
  5391 			$args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) );
  1414 			'description'    => $description . "\n\n" . __( 'You can edit your menu content on the Menus screen in the Appearance section.' ),
  5392 		}
  1415 		) );
  5393 
  1416 
  5394 		if ( ! array_key_exists( 'page', $_POST ) ) {
  1417 		if ( $menus ) {
  5395 			$args['page'] = 1;
  1418 			$choices = array( '' => __( '&mdash; Select &mdash;' ) );
  5396 		} else {
  1419 			foreach ( $menus as $menu ) {
  5397 			$args['page'] = absint( $_POST['page'] );
  1420 				$choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
  5398 		}
  1421 			}
  5399 
  1422 
  5400 		require_once ABSPATH . 'wp-admin/includes/theme.php';
  1423 			foreach ( $locations as $location => $description ) {
  5401 
  1424 				$menu_setting_id = "nav_menu_locations[{$location}]";
  5402 		if ( 'installed' === $theme_action ) {
  1425 
  5403 
  1426 				$this->add_setting( $menu_setting_id, array(
  5404 			// Load all installed themes from wp_prepare_themes_for_js().
  1427 					'sanitize_callback' => 'absint',
  5405 			$themes = array( 'themes' => wp_prepare_themes_for_js() );
  1428 					'theme_supports'    => 'menus',
  5406 			foreach ( $themes['themes'] as &$theme ) {
       
  5407 				$theme['type'] = 'installed';
       
  5408 				$theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] );
       
  5409 			}
       
  5410 
       
  5411 		} elseif ( 'wporg' === $theme_action ) {
       
  5412 
       
  5413 			// Load WordPress.org themes from the .org API and normalize data to match installed theme objects.
       
  5414 			if ( ! current_user_can( 'install_themes' ) ) {
       
  5415 				wp_die( -1 );
       
  5416 			}
       
  5417 
       
  5418 			// Arguments for all queries.
       
  5419 			$wporg_args = array(
       
  5420 				'per_page' => 100,
       
  5421 				'fields' => array(
       
  5422 					'screenshot_url' => true,
       
  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 				),
       
  5434 			);
       
  5435 
       
  5436 			$args = array_merge( $wporg_args, $args );
       
  5437 
       
  5438 			if ( '' === $args['search'] && '' === $args['tag'] ) {
       
  5439 				$args['browse'] = 'new'; // Sort by latest themes by default.
       
  5440 			}
       
  5441 
       
  5442 			// Load themes from the .org API.
       
  5443 			$themes = themes_api( 'query_themes', $args );
       
  5444 			if ( is_wp_error( $themes ) ) {
       
  5445 				wp_send_json_error();
       
  5446 			}
       
  5447 
       
  5448 			// This list matches the allowed tags in wp-admin/includes/theme-install.php.
       
  5449 			$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' ),
       
  5451 				array()
       
  5452 			);
       
  5453 			$themes_allowedtags['a'] = array_fill_keys( array( 'href', 'title', 'target' ), true );
       
  5454 			$themes_allowedtags['acronym']['title'] = true;
       
  5455 			$themes_allowedtags['abbr']['title'] = true;
       
  5456 			$themes_allowedtags['img'] = array_fill_keys( array( 'src', 'class', 'alt' ), true );
       
  5457 
       
  5458 			// Prepare a list of installed themes to check against before the loop.
       
  5459 			$installed_themes = array();
       
  5460 			$wp_themes = wp_get_themes();
       
  5461 			foreach ( $wp_themes as $theme ) {
       
  5462 				$installed_themes[] = $theme->get_stylesheet();
       
  5463 			}
       
  5464 			$update_php = network_admin_url( 'update.php?action=install-theme' );
       
  5465 
       
  5466 			// Set up properties for themes available on WordPress.org.
       
  5467 			foreach ( $themes->themes as &$theme ) {
       
  5468 				$theme->install_url = add_query_arg( array(
       
  5469 					'theme'    => $theme->slug,
       
  5470 					'_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
       
  5471 				), $update_php );
       
  5472 
       
  5473 				$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 );
       
  5476 				$theme->description = wp_kses( $theme->description, $themes_allowedtags );
       
  5477 				$theme->tags        = implode( ', ', $theme->tags );
       
  5478 				$theme->stars       = wp_star_rating( array(
       
  5479 					'rating' => $theme->rating,
       
  5480 					'type' => 'percent',
       
  5481 					'number' => $theme->num_ratings,
       
  5482 					'echo' => false,
  1429 				) );
  5483 				) );
  1430 
  5484 				$theme->num_ratings = number_format_i18n( $theme->num_ratings );
  1431 				$this->add_control( $menu_setting_id, array(
  5485 				$theme->preview_url = set_url_scheme( $theme->preview_url );
  1432 					'label'   => $description,
  5486 
  1433 					'section' => 'nav',
  5487 				// Handle themes that are already installed as installed themes.
  1434 					'type'    => 'select',
  5488 				if ( in_array( $theme->slug, $installed_themes, true ) ) {
  1435 					'choices' => $choices,
  5489 					$theme->type = 'installed';
  1436 				) );
  5490 				} else {
  1437 			}
  5491 					$theme->type = $theme_action;
  1438 		}
  5492 				}
  1439 
  5493 
  1440 		/* Static Front Page */
  5494 				// Set active based on customized theme.
  1441 		// #WP19627
  5495 				$theme->active = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme->slug );
  1442 
  5496 
  1443 		// Replicate behavior from options-reading.php and hide front page options if there are no pages
  5497 				// Map available theme properties to installed theme properties.
  1444 		if ( get_pages() ) {
  5498 				$theme->id           = $theme->slug;
  1445 			$this->add_section( 'static_front_page', array(
  5499 				$theme->screenshot   = array( $theme->screenshot_url );
  1446 				'title'          => __( 'Static Front Page' ),
  5500 				$theme->authorAndUri = $theme->author;
  1447 			//	'theme_supports' => 'static-front-page',
  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.
  1448 				'priority'       => 120,
  5502 				if ( isset( $theme->parent ) ) {
  1449 				'description'    => __( 'Your theme supports a static front page.' ),
  5503 					$theme->parent = $theme->parent['slug'];
  1450 			) );
  5504 				} else {
  1451 
  5505 					$theme->parent = false;
  1452 			$this->add_setting( 'show_on_front', array(
  5506 				}
  1453 				'default'        => get_option( 'show_on_front' ),
  5507 				unset( $theme->slug );
  1454 				'capability'     => 'manage_options',
  5508 				unset( $theme->screenshot_url );
  1455 				'type'           => 'option',
  5509 				unset( $theme->author );
  1456 			//	'theme_supports' => 'static-front-page',
  5510 			} // End foreach().
  1457 			) );
  5511 		} // End if().
  1458 
  5512 
  1459 			$this->add_control( 'show_on_front', array(
  5513 		/**
  1460 				'label'   => __( 'Front page displays' ),
  5514 		 * Filters the theme data loaded in the customizer.
  1461 				'section' => 'static_front_page',
  5515 		 *
  1462 				'type'    => 'radio',
  5516 		 * This allows theme data to be loading from an external source,
  1463 				'choices' => array(
  5517 		 * or modification of data loaded from `wp_prepare_themes_for_js()`
  1464 					'posts' => __( 'Your latest posts' ),
  5518 		 * or WordPress.org via `themes_api()`.
  1465 					'page'  => __( 'A static page' ),
  5519 		 *
  1466 				),
  5520 		 * @since 4.9.0
  1467 			) );
  5521 		 *
  1468 
  5522 		 * @see wp_prepare_themes_for_js()
  1469 			$this->add_setting( 'page_on_front', array(
  5523 		 * @see themes_api()
  1470 				'type'       => 'option',
  5524 		 * @see WP_Customize_Manager::__construct()
  1471 				'capability' => 'manage_options',
  5525 		 *
  1472 			//	'theme_supports' => 'static-front-page',
  5526 		 * @param array                $themes  Nested array of theme data.
  1473 			) );
  5527 		 * @param array                $args    List of arguments, such as page, search term, and tags to query for.
  1474 
  5528 		 * @param WP_Customize_Manager $manager Instance of Customize manager.
  1475 			$this->add_control( 'page_on_front', array(
  5529 		 */
  1476 				'label'      => __( 'Front page' ),
  5530 		$themes = apply_filters( 'customize_load_themes', $themes, $args, $this );
  1477 				'section'    => 'static_front_page',
  5531 
  1478 				'type'       => 'dropdown-pages',
  5532 		wp_send_json_success( $themes );
  1479 			) );
  5533 	}
  1480 
  5534 
  1481 			$this->add_setting( 'page_for_posts', array(
       
  1482 				'type'           => 'option',
       
  1483 				'capability'     => 'manage_options',
       
  1484 			//	'theme_supports' => 'static-front-page',
       
  1485 			) );
       
  1486 
       
  1487 			$this->add_control( 'page_for_posts', array(
       
  1488 				'label'      => __( 'Posts page' ),
       
  1489 				'section'    => 'static_front_page',
       
  1490 				'type'       => 'dropdown-pages',
       
  1491 			) );
       
  1492 		}
       
  1493 	}
       
  1494 
       
  1495 	/**
       
  1496 	 * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
       
  1497 	 *
       
  1498 	 * @since 4.2.0
       
  1499 	 * @access public
       
  1500 	 *
       
  1501 	 * @see add_dynamic_settings()
       
  1502 	 */
       
  1503 	public function register_dynamic_settings() {
       
  1504 		$this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
       
  1505 	}
       
  1506 
  5535 
  1507 	/**
  5536 	/**
  1508 	 * Callback for validating the header_textcolor value.
  5537 	 * Callback for validating the header_textcolor value.
  1509 	 *
  5538 	 *
  1510 	 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
  5539 	 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
  1511 	 * Returns default text color if hex color is empty.
  5540 	 * Returns default text color if hex color is empty.
  1512 	 *
  5541 	 *
  1513 	 * @since 3.4.0
  5542 	 * @since 3.4.0
  1514 	 *
  5543 	 *
  1515 	 * @param string $color
  5544 	 * @param string $color
  1516 	 * @return string
  5545 	 * @return mixed
  1517 	 */
  5546 	 */
  1518 	public function _sanitize_header_textcolor( $color ) {
  5547 	public function _sanitize_header_textcolor( $color ) {
  1519 		if ( 'blank' === $color )
  5548 		if ( 'blank' === $color )
  1520 			return 'blank';
  5549 			return 'blank';
  1521 
  5550 
  1523 		if ( empty( $color ) )
  5552 		if ( empty( $color ) )
  1524 			$color = get_theme_support( 'custom-header', 'default-text-color' );
  5553 			$color = get_theme_support( 'custom-header', 'default-text-color' );
  1525 
  5554 
  1526 		return $color;
  5555 		return $color;
  1527 	}
  5556 	}
       
  5557 
       
  5558 	/**
       
  5559 	 * Callback for validating a background setting value.
       
  5560 	 *
       
  5561 	 * @since 4.7.0
       
  5562 	 *
       
  5563 	 * @param string $value Repeat value.
       
  5564 	 * @param WP_Customize_Setting $setting Setting.
       
  5565 	 * @return string|WP_Error Background value or validation error.
       
  5566 	 */
       
  5567 	public function _sanitize_background_setting( $value, $setting ) {
       
  5568 		if ( 'background_repeat' === $setting->id ) {
       
  5569 			if ( ! in_array( $value, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ) ) ) {
       
  5570 				return new WP_Error( 'invalid_value', __( 'Invalid value for background repeat.' ) );
       
  5571 			}
       
  5572 		} elseif ( 'background_attachment' === $setting->id ) {
       
  5573 			if ( ! in_array( $value, array( 'fixed', 'scroll' ) ) ) {
       
  5574 				return new WP_Error( 'invalid_value', __( 'Invalid value for background attachment.' ) );
       
  5575 			}
       
  5576 		} elseif ( 'background_position_x' === $setting->id ) {
       
  5577 			if ( ! in_array( $value, array( 'left', 'center', 'right' ), true ) ) {
       
  5578 				return new WP_Error( 'invalid_value', __( 'Invalid value for background position X.' ) );
       
  5579 			}
       
  5580 		} elseif ( 'background_position_y' === $setting->id ) {
       
  5581 			if ( ! in_array( $value, array( 'top', 'center', 'bottom' ), true ) ) {
       
  5582 				return new WP_Error( 'invalid_value', __( 'Invalid value for background position Y.' ) );
       
  5583 			}
       
  5584 		} elseif ( 'background_size' === $setting->id ) {
       
  5585 			if ( ! in_array( $value, array( 'auto', 'contain', 'cover' ), true ) ) {
       
  5586 				return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) );
       
  5587 			}
       
  5588 		} elseif ( 'background_preset' === $setting->id ) {
       
  5589 			if ( ! in_array( $value, array( 'default', 'fill', 'fit', 'repeat', 'custom' ), true ) ) {
       
  5590 				return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) );
       
  5591 			}
       
  5592 		} elseif ( 'background_image' === $setting->id || 'background_image_thumb' === $setting->id ) {
       
  5593 			$value = empty( $value ) ? '' : esc_url_raw( $value );
       
  5594 		} else {
       
  5595 			return new WP_Error( 'unrecognized_setting', __( 'Unrecognized background setting.' ) );
       
  5596 		}
       
  5597 		return $value;
       
  5598 	}
       
  5599 
       
  5600 	/**
       
  5601 	 * Export header video settings to facilitate selective refresh.
       
  5602 	 *
       
  5603 	 * @since 4.7.0
       
  5604 	 *
       
  5605 	 * @param array $response Response.
       
  5606 	 * @param WP_Customize_Selective_Refresh $selective_refresh Selective refresh component.
       
  5607 	 * @param array $partials Array of partials.
       
  5608 	 * @return array
       
  5609 	 */
       
  5610 	public function export_header_video_settings( $response, $selective_refresh, $partials ) {
       
  5611 		if ( isset( $partials['custom_header'] ) ) {
       
  5612 			$response['custom_header_settings'] = get_header_video_settings();
       
  5613 		}
       
  5614 
       
  5615 		return $response;
       
  5616 	}
       
  5617 
       
  5618 	/**
       
  5619 	 * Callback for validating the header_video value.
       
  5620 	 *
       
  5621 	 * Ensures that the selected video is less than 8MB and provides an error message.
       
  5622 	 *
       
  5623 	 * @since 4.7.0
       
  5624 	 *
       
  5625 	 * @param WP_Error $validity
       
  5626 	 * @param mixed $value
       
  5627 	 * @return mixed
       
  5628 	 */
       
  5629 	public function _validate_header_video( $validity, $value ) {
       
  5630 		$video = get_attached_file( absint( $value ) );
       
  5631 		if ( $video ) {
       
  5632 			$size = filesize( $video );
       
  5633 			if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB.
       
  5634 				$validity->add( '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.' )
       
  5636 				);
       
  5637 			}
       
  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.
       
  5639 				$validity->add( 'invalid_file_type', sprintf(
       
  5640 					/* translators: 1: .mp4, 2: .mov */
       
  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.' ),
       
  5642 					'<code>.mp4</code>',
       
  5643 					'<code>.mov</code>'
       
  5644 				) );
       
  5645 			}
       
  5646 		}
       
  5647 		return $validity;
       
  5648 	}
       
  5649 
       
  5650 	/**
       
  5651 	 * Callback for validating the external_header_video value.
       
  5652 	 *
       
  5653 	 * Ensures that the provided URL is supported.
       
  5654 	 *
       
  5655 	 * @since 4.7.0
       
  5656 	 *
       
  5657 	 * @param WP_Error $validity
       
  5658 	 * @param mixed $value
       
  5659 	 * @return mixed
       
  5660 	 */
       
  5661 	public function _validate_external_header_video( $validity, $value ) {
       
  5662 		$video = esc_url_raw( $value );
       
  5663 		if ( $video ) {
       
  5664 			if ( ! preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video ) ) {
       
  5665 				$validity->add( 'invalid_url', __( 'Please enter a valid YouTube URL.' ) );
       
  5666 			}
       
  5667 		}
       
  5668 		return $validity;
       
  5669 	}
       
  5670 
       
  5671 	/**
       
  5672 	 * Callback for sanitizing the external_header_video value.
       
  5673 	 *
       
  5674 	 * @since 4.7.1
       
  5675 	 *
       
  5676 	 * @param string $value URL.
       
  5677 	 * @return string Sanitized URL.
       
  5678 	 */
       
  5679 	public function _sanitize_external_header_video( $value ) {
       
  5680 		return esc_url_raw( trim( $value ) );
       
  5681 	}
       
  5682 
       
  5683 	/**
       
  5684 	 * Callback for rendering the custom logo, used in the custom_logo partial.
       
  5685 	 *
       
  5686 	 * This method exists because the partial object and context data are passed
       
  5687 	 * into a partial's render_callback so we cannot use get_custom_logo() as
       
  5688 	 * the render_callback directly since it expects a blog ID as the first
       
  5689 	 * argument. When WP no longer supports PHP 5.3, this method can be removed
       
  5690 	 * in favor of an anonymous function.
       
  5691 	 *
       
  5692 	 * @see WP_Customize_Manager::register_controls()
       
  5693 	 *
       
  5694 	 * @since 4.5.0
       
  5695 	 *
       
  5696 	 * @return string Custom logo.
       
  5697 	 */
       
  5698 	public function _render_custom_logo_partial() {
       
  5699 		return get_custom_logo();
       
  5700 	}
  1528 }
  5701 }
  1529 
       
  1530 /**
       
  1531  * Sanitizes a hex color.
       
  1532  *
       
  1533  * Returns either '', a 3 or 6 digit hex color (with #), or null.
       
  1534  * For sanitizing values without a #, see sanitize_hex_color_no_hash().
       
  1535  *
       
  1536  * @since 3.4.0
       
  1537  *
       
  1538  * @param string $color
       
  1539  * @return string|null
       
  1540  */
       
  1541 function sanitize_hex_color( $color ) {
       
  1542 	if ( '' === $color )
       
  1543 		return '';
       
  1544 
       
  1545 	// 3 or 6 hex digits, or the empty string.
       
  1546 	if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
       
  1547 		return $color;
       
  1548 
       
  1549 	return null;
       
  1550 }
       
  1551 
       
  1552 /**
       
  1553  * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
       
  1554  *
       
  1555  * Saving hex colors without a hash puts the burden of adding the hash on the
       
  1556  * UI, which makes it difficult to use or upgrade to other color types such as
       
  1557  * rgba, hsl, rgb, and html color names.
       
  1558  *
       
  1559  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
       
  1560  *
       
  1561  * @since 3.4.0
       
  1562  *
       
  1563  * @param string $color
       
  1564  * @return string|null
       
  1565  */
       
  1566 function sanitize_hex_color_no_hash( $color ) {
       
  1567 	$color = ltrim( $color, '#' );
       
  1568 
       
  1569 	if ( '' === $color )
       
  1570 		return '';
       
  1571 
       
  1572 	return sanitize_hex_color( '#' . $color ) ? $color : null;
       
  1573 }
       
  1574 
       
  1575 /**
       
  1576  * Ensures that any hex color is properly hashed.
       
  1577  * Otherwise, returns value untouched.
       
  1578  *
       
  1579  * This method should only be necessary if using sanitize_hex_color_no_hash().
       
  1580  *
       
  1581  * @since 3.4.0
       
  1582  *
       
  1583  * @param string $color
       
  1584  * @return string
       
  1585  */
       
  1586 function maybe_hash_hex_color( $color ) {
       
  1587 	if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
       
  1588 		return '#' . $unhashed;
       
  1589 
       
  1590 	return $color;
       
  1591 }