[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-wp-customize-manager.php (source)

   1  <?php
   2  /**
   3   * WordPress Customize Manager classes
   4   *
   5   * @package WordPress
   6   * @subpackage Customize
   7   * @since 3.4.0
   8   */
   9  
  10  /**
  11   * Customize Manager class.
  12   *
  13   * Bootstraps the Customize experience on the server-side.
  14   *
  15   * Sets up the theme-switching process if a theme other than the active one is
  16   * being previewed and customized.
  17   *
  18   * Serves as a factory for Customize Controls and Settings, and
  19   * instantiates default Customize Controls and Settings.
  20   *
  21   * @since 3.4.0
  22   */
  23  final class WP_Customize_Manager {
  24      /**
  25       * An instance of the theme being previewed.
  26       *
  27       * @since 3.4.0
  28       * @access protected
  29       * @var WP_Theme
  30       */
  31      protected $theme;
  32  
  33      /**
  34       * The directory name of the previously active theme (within the theme_root).
  35       *
  36       * @since 3.4.0
  37       * @access protected
  38       * @var string
  39       */
  40      protected $original_stylesheet;
  41  
  42      /**
  43       * Whether this is a Customizer pageload.
  44       *
  45       * @since 3.4.0
  46       * @access protected
  47       * @var bool
  48       */
  49      protected $previewing = false;
  50  
  51      /**
  52       * Methods and properties dealing with managing widgets in the Customizer.
  53       *
  54       * @since 3.9.0
  55       * @access public
  56       * @var WP_Customize_Widgets
  57       */
  58      public $widgets;
  59  
  60      /**
  61       * Methods and properties dealing with managing nav menus in the Customizer.
  62       *
  63       * @since 4.3.0
  64       * @access public
  65       * @var WP_Customize_Nav_Menus
  66       */
  67      public $nav_menus;
  68  
  69      /**
  70       * Methods and properties dealing with selective refresh in the Customizer preview.
  71       *
  72       * @since 4.5.0
  73       * @access public
  74       * @var WP_Customize_Selective_Refresh
  75       */
  76      public $selective_refresh;
  77  
  78      /**
  79       * Registered instances of WP_Customize_Setting.
  80       *
  81       * @since 3.4.0
  82       * @access protected
  83       * @var array
  84       */
  85      protected $settings = array();
  86  
  87      /**
  88       * Sorted top-level instances of WP_Customize_Panel and WP_Customize_Section.
  89       *
  90       * @since 4.0.0
  91       * @access protected
  92       * @var array
  93       */
  94      protected $containers = array();
  95  
  96      /**
  97       * Registered instances of WP_Customize_Panel.
  98       *
  99       * @since 4.0.0
 100       * @access protected
 101       * @var array
 102       */
 103      protected $panels = array();
 104  
 105      /**
 106       * List of core components.
 107       *
 108       * @since 4.5.0
 109       * @access protected
 110       * @var array
 111       */
 112      protected $components = array( 'widgets', 'nav_menus' );
 113  
 114      /**
 115       * Registered instances of WP_Customize_Section.
 116       *
 117       * @since 3.4.0
 118       * @access protected
 119       * @var array
 120       */
 121      protected $sections = array();
 122  
 123      /**
 124       * Registered instances of WP_Customize_Control.
 125       *
 126       * @since 3.4.0
 127       * @access protected
 128       * @var array
 129       */
 130      protected $controls = array();
 131  
 132      /**
 133       * Panel types that may be rendered from JS templates.
 134       *
 135       * @since 4.3.0
 136       * @access protected
 137       * @var array
 138       */
 139      protected $registered_panel_types = array();
 140  
 141      /**
 142       * Section types that may be rendered from JS templates.
 143       *
 144       * @since 4.3.0
 145       * @access protected
 146       * @var array
 147       */
 148      protected $registered_section_types = array();
 149  
 150      /**
 151       * Control types that may be rendered from JS templates.
 152       *
 153       * @since 4.1.0
 154       * @access protected
 155       * @var array
 156       */
 157      protected $registered_control_types = array();
 158  
 159      /**
 160       * Initial URL being previewed.
 161       *
 162       * @since 4.4.0
 163       * @access protected
 164       * @var string
 165       */
 166      protected $preview_url;
 167  
 168      /**
 169       * URL to link the user to when closing the Customizer.
 170       *
 171       * @since 4.4.0
 172       * @access protected
 173       * @var string
 174       */
 175      protected $return_url;
 176  
 177      /**
 178       * Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
 179       *
 180       * @since 4.4.0
 181       * @access protected
 182       * @var array
 183       */
 184      protected $autofocus = array();
 185  
 186      /**
 187       * Messenger channel.
 188       *
 189       * @since 4.7.0
 190       * @access protected
 191       * @var string
 192       */
 193      protected $messenger_channel;
 194  
 195      /**
 196       * Unsanitized values for Customize Settings parsed from $_POST['customized'].
 197       *
 198       * @var array
 199       */
 200      private $_post_values;
 201  
 202      /**
 203       * Changeset UUID.
 204       *
 205       * @since 4.7.0
 206       * @access private
 207       * @var string
 208       */
 209      private $_changeset_uuid;
 210  
 211      /**
 212       * Changeset post ID.
 213       *
 214       * @since 4.7.0
 215       * @access private
 216       * @var int|false
 217       */
 218      private $_changeset_post_id;
 219  
 220      /**
 221       * Changeset data loaded from a customize_changeset post.
 222       *
 223       * @since 4.7.0
 224       * @access private
 225       * @var array
 226       */
 227      private $_changeset_data;
 228  
 229      /**
 230       * Constructor.
 231       *
 232       * @since 3.4.0
 233       * @since 4.7.0 Added $args param.
 234       *
 235       * @param array $args {
 236       *     Args.
 237       *
 238       *     @type string $changeset_uuid    Changeset UUID, the post_name for the customize_changeset post containing the customized state. Defaults to new UUID.
 239       *     @type string $theme             Theme to be previewed (for theme switch). Defaults to customize_theme or theme query params.
 240       *     @type string $messenger_channel Messenger channel. Defaults to customize_messenger_channel query param.
 241       * }
 242       */
 243  	public function __construct( $args = array() ) {
 244  
 245          $args = array_merge(
 246              array_fill_keys( array( 'changeset_uuid', 'theme', 'messenger_channel' ), null ),
 247              $args
 248          );
 249  
 250          // Note that the UUID format will be validated in the setup_theme() method.
 251          if ( ! isset( $args['changeset_uuid'] ) ) {
 252              $args['changeset_uuid'] = wp_generate_uuid4();
 253          }
 254  
 255          // The theme and messenger_channel should be supplied via $args, but they are also looked at in the $_REQUEST global here for back-compat.
 256          if ( ! isset( $args['theme'] ) ) {
 257              if ( isset( $_REQUEST['customize_theme'] ) ) {
 258                  $args['theme'] = wp_unslash( $_REQUEST['customize_theme'] );
 259              } elseif ( isset( $_REQUEST['theme'] ) ) { // Deprecated.
 260                  $args['theme'] = wp_unslash( $_REQUEST['theme'] );
 261              }
 262          }
 263          if ( ! isset( $args['messenger_channel'] ) && isset( $_REQUEST['customize_messenger_channel'] ) ) {
 264              $args['messenger_channel'] = sanitize_key( wp_unslash( $_REQUEST['customize_messenger_channel'] ) );
 265          }
 266  
 267          $this->original_stylesheet = get_stylesheet();
 268          $this->theme = wp_get_theme( $args['theme'] );
 269          $this->messenger_channel = $args['messenger_channel'];
 270          $this->_changeset_uuid = $args['changeset_uuid'];
 271  
 272          require_once ( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
 273          require_once ( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
 274          require_once ( ABSPATH . WPINC . '/class-wp-customize-section.php' );
 275          require_once ( ABSPATH . WPINC . '/class-wp-customize-control.php' );
 276  
 277          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-color-control.php' );
 278          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-media-control.php' );
 279          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php' );
 280          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php' );
 281          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' );
 282          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-background-position-control.php' );
 283          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' );
 284          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
 285          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
 286          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
 287          require_once ( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
 288          require_once ( ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php' );
 289          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-control.php' );
 290          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php' );
 291          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php' );
 292          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
 293          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
 294          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );
 295  
 296          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
 297  
 298          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
 299          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
 300          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' );
 301          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-section.php' );
 302  
 303          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-custom-css-setting.php' );
 304          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
 305          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
 306          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
 307          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
 308          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
 309  
 310          /**
 311           * Filters the core Customizer components to load.
 312           *
 313           * This allows Core components to be excluded from being instantiated by
 314           * filtering them out of the array. Note that this filter generally runs
 315           * during the {@see 'plugins_loaded'} action, so it cannot be added
 316           * in a theme.
 317           *
 318           * @since 4.4.0
 319           *
 320           * @see WP_Customize_Manager::__construct()
 321           *
 322           * @param array                $components List of core components to load.
 323           * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
 324           */
 325          $components = apply_filters( 'customize_loaded_components', $this->components, $this );
 326  
 327          require_once ( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
 328          $this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
 329  
 330          if ( in_array( 'widgets', $components, true ) ) {
 331              require_once ( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
 332              $this->widgets = new WP_Customize_Widgets( $this );
 333          }
 334  
 335          if ( in_array( 'nav_menus', $components, true ) ) {
 336              require_once ( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
 337              $this->nav_menus = new WP_Customize_Nav_Menus( $this );
 338          }
 339  
 340          add_action( 'setup_theme', array( $this, 'setup_theme' ) );
 341          add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
 342  
 343          // Do not spawn cron (especially the alternate cron) while running the Customizer.
 344          remove_action( 'init', 'wp_cron' );
 345  
 346          // Do not run update checks when rendering the controls.
 347          remove_action( 'admin_init', '_maybe_update_core' );
 348          remove_action( 'admin_init', '_maybe_update_plugins' );
 349          remove_action( 'admin_init', '_maybe_update_themes' );
 350  
 351          add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
 352          add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
 353  
 354          add_action( 'customize_register',                 array( $this, 'register_controls' ) );
 355          add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
 356          add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
 357          add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
 358  
 359          // Render Panel, Section, and Control templates.
 360          add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
 361          add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
 362          add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
 363  
 364          // Export header video settings with the partial response.
 365          add_filter( 'customize_render_partials_response', array( $this, 'export_header_video_settings' ), 10, 3 );
 366  
 367          // Export the settings to JS via the _wpCustomizeSettings variable.
 368          add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
 369      }
 370  
 371      /**
 372       * Return true if it's an Ajax request.
 373       *
 374       * @since 3.4.0
 375       * @since 4.2.0 Added `$action` param.
 376       * @access public
 377       *
 378       * @param string|null $action Whether the supplied Ajax action is being run.
 379       * @return bool True if it's an Ajax request, false otherwise.
 380       */
 381  	public function doing_ajax( $action = null ) {
 382          if ( ! wp_doing_ajax() ) {
 383              return false;
 384          }
 385  
 386          if ( ! $action ) {
 387              return true;
 388          } else {
 389              /*
 390               * Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need
 391               * to check before admin-ajax.php gets to that point.
 392               */
 393              return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
 394          }
 395      }
 396  
 397      /**
 398       * Custom wp_die wrapper. Returns either the standard message for UI
 399       * or the Ajax message.
 400       *
 401       * @since 3.4.0
 402       *
 403       * @param mixed $ajax_message Ajax return
 404       * @param mixed $message UI message
 405       */
 406  	protected function wp_die( $ajax_message, $message = null ) {
 407          if ( $this->doing_ajax() ) {
 408              wp_die( $ajax_message );
 409          }
 410  
 411          if ( ! $message ) {
 412              $message = __( 'Cheatin&#8217; uh?' );
 413          }
 414  
 415          if ( $this->messenger_channel ) {
 416              ob_start();
 417              wp_enqueue_scripts();
 418              wp_print_scripts( array( 'customize-base' ) );
 419  
 420              $settings = array(
 421                  'messengerArgs' => array(
 422                      'channel' => $this->messenger_channel,
 423                      'url' => wp_customize_url(),
 424                  ),
 425                  'error' => $ajax_message,
 426              );
 427              ?>
 428              <script>
 429              ( function( api, settings ) {
 430                  var preview = new api.Messenger( settings.messengerArgs );
 431                  preview.send( 'iframe-loading-error', settings.error );
 432              } )( wp.customize, <?php echo wp_json_encode( $settings ) ?> );
 433              </script>
 434              <?php
 435              $message .= ob_get_clean();
 436          }
 437  
 438          wp_die( $message );
 439      }
 440  
 441      /**
 442       * Return the Ajax wp_die() handler if it's a customized request.
 443       *
 444       * @since 3.4.0
 445       * @deprecated 4.7.0
 446       *
 447       * @return callable Die handler.
 448       */
 449  	public function wp_die_handler() {
 450          _deprecated_function( __METHOD__, '4.7.0' );
 451  
 452          if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
 453              return '_ajax_wp_die_handler';
 454          }
 455  
 456          return '_default_wp_die_handler';
 457      }
 458  
 459      /**
 460       * Start preview and customize theme.
 461       *
 462       * Check if customize query variable exist. Init filters to filter the current theme.
 463       *
 464       * @since 3.4.0
 465       *
 466       * @global string $pagenow
 467       */
 468  	public function setup_theme() {
 469          global $pagenow;
 470  
 471          // Check permissions for customize.php access since this method is called before customize.php can run any code,
 472          if ( 'customize.php' === $pagenow && ! current_user_can( 'customize' ) ) {
 473              if ( ! is_user_logged_in() ) {
 474                  auth_redirect();
 475              } else {
 476                  wp_die(
 477                      '<h1>' . __( 'Cheatin&#8217; uh?' ) . '</h1>' .
 478                      '<p>' . __( 'Sorry, you are not allowed to customize this site.' ) . '</p>',
 479                      403
 480                  );
 481              }
 482              return;
 483          }
 484  
 485          if ( ! preg_match( '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/', $this->_changeset_uuid ) ) {
 486              $this->wp_die( -1, __( 'Invalid changeset UUID' ) );
 487          }
 488  
 489          /*
 490           * If unauthenticated then require a valid changeset UUID to load the preview.
 491           * In this way, the UUID serves as a secret key. If the messenger channel is present,
 492           * then send unauthenticated code to prompt re-auth.
 493           */
 494          if ( ! current_user_can( 'customize' ) && ! $this->changeset_post_id() ) {
 495              $this->wp_die( $this->messenger_channel ? 0 : -1, __( 'Non-existent changeset UUID.' ) );
 496          }
 497  
 498          if ( ! headers_sent() ) {
 499              send_origin_headers();
 500          }
 501  
 502          // Hide the admin bar if we're embedded in the customizer iframe.
 503          if ( $this->messenger_channel ) {
 504              show_admin_bar( false );
 505          }
 506  
 507          if ( $this->is_theme_active() ) {
 508              // Once the theme is loaded, we'll validate it.
 509              add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
 510          } else {
 511              // If the requested theme is not the active theme and the user doesn't have the
 512              // switch_themes cap, bail.
 513              if ( ! current_user_can( 'switch_themes' ) ) {
 514                  $this->wp_die( -1, __( 'Sorry, you are not allowed to edit theme options on this site.' ) );
 515              }
 516  
 517              // If the theme has errors while loading, bail.
 518              if ( $this->theme()->errors() ) {
 519                  $this->wp_die( -1, $this->theme()->errors()->get_error_message() );
 520              }
 521  
 522              // If the theme isn't allowed per multisite settings, bail.
 523              if ( ! $this->theme()->is_allowed() ) {
 524                  $this->wp_die( -1, __( 'The requested theme does not exist.' ) );
 525              }
 526          }
 527  
 528          /*
 529           * Import theme starter content for fresh installs when landing in the customizer.
 530           * Import starter content at after_setup_theme:100 so that any
 531           * add_theme_support( 'starter-content' ) calls will have been made.
 532           */
 533          if ( get_option( 'fresh_site' ) && 'customize.php' === $pagenow ) {
 534              add_action( 'after_setup_theme', array( $this, 'import_theme_starter_content' ), 100 );
 535          }
 536  
 537          $this->start_previewing_theme();
 538      }
 539  
 540      /**
 541       * Callback to validate a theme once it is loaded
 542       *
 543       * @since 3.4.0
 544       */
 545  	public function after_setup_theme() {
 546          $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
 547          if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
 548              wp_redirect( 'themes.php?broken=true' );
 549              exit;
 550          }
 551      }
 552  
 553      /**
 554       * If the theme to be previewed isn't the active theme, add filter callbacks
 555       * to swap it out at runtime.
 556       *
 557       * @since 3.4.0
 558       */
 559  	public function start_previewing_theme() {
 560          // Bail if we're already previewing.
 561          if ( $this->is_preview() ) {
 562              return;
 563          }
 564  
 565          $this->previewing = true;
 566  
 567          if ( ! $this->is_theme_active() ) {
 568              add_filter( 'template', array( $this, 'get_template' ) );
 569              add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
 570              add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
 571  
 572              // @link: https://core.trac.wordpress.org/ticket/20027
 573              add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
 574              add_filter( 'pre_option_template', array( $this, 'get_template' ) );
 575  
 576              // Handle custom theme roots.
 577              add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
 578              add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
 579          }
 580  
 581          /**
 582           * Fires once the Customizer theme preview has started.
 583           *
 584           * @since 3.4.0
 585           *
 586           * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 587           */
 588          do_action( 'start_previewing_theme', $this );
 589      }
 590  
 591      /**
 592       * Stop previewing the selected theme.
 593       *
 594       * Removes filters to change the current theme.
 595       *
 596       * @since 3.4.0
 597       */
 598  	public function stop_previewing_theme() {
 599          if ( ! $this->is_preview() ) {
 600              return;
 601          }
 602  
 603          $this->previewing = false;
 604  
 605          if ( ! $this->is_theme_active() ) {
 606              remove_filter( 'template', array( $this, 'get_template' ) );
 607              remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
 608              remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
 609  
 610              // @link: https://core.trac.wordpress.org/ticket/20027
 611              remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
 612              remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
 613  
 614              // Handle custom theme roots.
 615              remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
 616              remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
 617          }
 618  
 619          /**
 620           * Fires once the Customizer theme preview has stopped.
 621           *
 622           * @since 3.4.0
 623           *
 624           * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 625           */
 626          do_action( 'stop_previewing_theme', $this );
 627      }
 628  
 629      /**
 630       * Get the changeset UUID.
 631       *
 632       * @since 4.7.0
 633       * @access public
 634       *
 635       * @return string UUID.
 636       */
 637  	public function changeset_uuid() {
 638          return $this->_changeset_uuid;
 639      }
 640  
 641      /**
 642       * Get the theme being customized.
 643       *
 644       * @since 3.4.0
 645       *
 646       * @return WP_Theme
 647       */
 648  	public function theme() {
 649          if ( ! $this->theme ) {
 650              $this->theme = wp_get_theme();
 651          }
 652          return $this->theme;
 653      }
 654  
 655      /**
 656       * Get the registered settings.
 657       *
 658       * @since 3.4.0
 659       *
 660       * @return array
 661       */
 662  	public function settings() {
 663          return $this->settings;
 664      }
 665  
 666      /**
 667       * Get the registered controls.
 668       *
 669       * @since 3.4.0
 670       *
 671       * @return array
 672       */
 673  	public function controls() {
 674          return $this->controls;
 675      }
 676  
 677      /**
 678       * Get the registered containers.
 679       *
 680       * @since 4.0.0
 681       *
 682       * @return array
 683       */
 684  	public function containers() {
 685          return $this->containers;
 686      }
 687  
 688      /**
 689       * Get the registered sections.
 690       *
 691       * @since 3.4.0
 692       *
 693       * @return array
 694       */
 695  	public function sections() {
 696          return $this->sections;
 697      }
 698  
 699      /**
 700       * Get the registered panels.
 701       *
 702       * @since 4.0.0
 703       * @access public
 704       *
 705       * @return array Panels.
 706       */
 707  	public function panels() {
 708          return $this->panels;
 709      }
 710  
 711      /**
 712       * Checks if the current theme is active.
 713       *
 714       * @since 3.4.0
 715       *
 716       * @return bool
 717       */
 718  	public function is_theme_active() {
 719          return $this->get_stylesheet() == $this->original_stylesheet;
 720      }
 721  
 722      /**
 723       * Register styles/scripts and initialize the preview of each setting
 724       *
 725       * @since 3.4.0
 726       */
 727  	public function wp_loaded() {
 728  
 729          /**
 730           * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
 731           *
 732           * @since 3.4.0
 733           *
 734           * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 735           */
 736          do_action( 'customize_register', $this );
 737  
 738          /*
 739           * Note that settings must be previewed here even outside the customizer preview
 740           * and also in the customizer pane itself. This is to enable loading an existing
 741           * changeset into the customizer. Previewing the settings only has to be prevented
 742           * in the case of a customize_save action because then update_option()
 743           * may short-circuit because it will detect that there are no changes to
 744           * make.
 745           */
 746          if ( ! $this->doing_ajax( 'customize_save' ) ) {
 747              foreach ( $this->settings as $setting ) {
 748                  $setting->preview();
 749              }
 750          }
 751  
 752          if ( $this->is_preview() && ! is_admin() ) {
 753              $this->customize_preview_init();
 754          }
 755      }
 756  
 757      /**
 758       * Prevents Ajax requests from following redirects when previewing a theme
 759       * by issuing a 200 response instead of a 30x.
 760       *
 761       * Instead, the JS will sniff out the location header.
 762       *
 763       * @since 3.4.0
 764       * @deprecated 4.7.0
 765       *
 766       * @param int $status Status.
 767       * @return int
 768       */
 769  	public function wp_redirect_status( $status ) {
 770          _deprecated_function( __FUNCTION__, '4.7.0' );
 771  
 772          if ( $this->is_preview() && ! is_admin() ) {
 773              return 200;
 774          }
 775  
 776          return $status;
 777      }
 778  
 779      /**
 780       * Find the changeset post ID for a given changeset UUID.
 781       *
 782       * @since 4.7.0
 783       * @access public
 784       *
 785       * @param string $uuid Changeset UUID.
 786       * @return int|null Returns post ID on success and null on failure.
 787       */
 788  	public function find_changeset_post_id( $uuid ) {
 789          $cache_group = 'customize_changeset_post';
 790          $changeset_post_id = wp_cache_get( $uuid, $cache_group );
 791          if ( $changeset_post_id && 'customize_changeset' === get_post_type( $changeset_post_id ) ) {
 792              return $changeset_post_id;
 793          }
 794  
 795          $changeset_post_query = new WP_Query( array(
 796              'post_type' => 'customize_changeset',
 797              'post_status' => get_post_stati(),
 798              'name' => $uuid,
 799              'posts_per_page' => 1,
 800              'no_found_rows' => true,
 801              'cache_results' => true,
 802              'update_post_meta_cache' => false,
 803              'update_post_term_cache' => false,
 804              'lazy_load_term_meta' => false,
 805          ) );
 806          if ( ! empty( $changeset_post_query->posts ) ) {
 807              // Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed.
 808              $changeset_post_id = $changeset_post_query->posts[0]->ID;
 809              wp_cache_set( $this->_changeset_uuid, $changeset_post_id, $cache_group );
 810              return $changeset_post_id;
 811          }
 812  
 813          return null;
 814      }
 815  
 816      /**
 817       * Get the changeset post id for the loaded changeset.
 818       *
 819       * @since 4.7.0
 820       * @access public
 821       *
 822       * @return int|null Post ID on success or null if there is no post yet saved.
 823       */
 824  	public function changeset_post_id() {
 825          if ( ! isset( $this->_changeset_post_id ) ) {
 826              $post_id = $this->find_changeset_post_id( $this->_changeset_uuid );
 827              if ( ! $post_id ) {
 828                  $post_id = false;
 829              }
 830              $this->_changeset_post_id = $post_id;
 831          }
 832          if ( false === $this->_changeset_post_id ) {
 833              return null;
 834          }
 835          return $this->_changeset_post_id;
 836      }
 837  
 838      /**
 839       * Get the data stored in a changeset post.
 840       *
 841       * @since 4.7.0
 842       * @access protected
 843       *
 844       * @param int $post_id Changeset post ID.
 845       * @return array|WP_Error Changeset data or WP_Error on error.
 846       */
 847  	protected function get_changeset_post_data( $post_id ) {
 848          if ( ! $post_id ) {
 849              return new WP_Error( 'empty_post_id' );
 850          }
 851          $changeset_post = get_post( $post_id );
 852          if ( ! $changeset_post ) {
 853              return new WP_Error( 'missing_post' );
 854          }
 855          if ( 'customize_changeset' !== $changeset_post->post_type ) {
 856              return new WP_Error( 'wrong_post_type' );
 857          }
 858          $changeset_data = json_decode( $changeset_post->post_content, true );
 859          if ( function_exists( 'json_last_error' ) && json_last_error() ) {
 860              return new WP_Error( 'json_parse_error', '', json_last_error() );
 861          }
 862          if ( ! is_array( $changeset_data ) ) {
 863              return new WP_Error( 'expected_array' );
 864          }
 865          return $changeset_data;
 866      }
 867  
 868      /**
 869       * Get changeset data.
 870       *
 871       * @since 4.7.0
 872       * @access public
 873       *
 874       * @return array Changeset data.
 875       */
 876  	public function changeset_data() {
 877          if ( isset( $this->_changeset_data ) ) {
 878              return $this->_changeset_data;
 879          }
 880          $changeset_post_id = $this->changeset_post_id();
 881          if ( ! $changeset_post_id ) {
 882              $this->_changeset_data = array();
 883          } else {
 884              $data = $this->get_changeset_post_data( $changeset_post_id );
 885              if ( ! is_wp_error( $data ) ) {
 886                  $this->_changeset_data = $data;
 887              } else {
 888                  $this->_changeset_data = array();
 889              }
 890          }
 891          return $this->_changeset_data;
 892      }
 893  
 894      /**
 895       * Starter content setting IDs.
 896       *
 897       * @since 4.7.0
 898       * @access private
 899       * @var array
 900       */
 901      protected $pending_starter_content_settings_ids = array();
 902  
 903      /**
 904       * Import theme starter content into the customized state.
 905       *
 906       * @since 4.7.0
 907       * @access public
 908       *
 909       * @param array $starter_content Starter content. Defaults to `get_theme_starter_content()`.
 910       */
 911  	function import_theme_starter_content( $starter_content = array() ) {
 912          if ( empty( $starter_content ) ) {
 913              $starter_content = get_theme_starter_content();
 914          }
 915  
 916          $changeset_data = array();
 917          if ( $this->changeset_post_id() ) {
 918              $changeset_data = $this->get_changeset_post_data( $this->changeset_post_id() );
 919          }
 920  
 921          $sidebars_widgets = isset( $starter_content['widgets'] ) && ! empty( $this->widgets ) ? $starter_content['widgets'] : array();
 922          $attachments = isset( $starter_content['attachments'] ) && ! empty( $this->nav_menus ) ? $starter_content['attachments'] : array();
 923          $posts = isset( $starter_content['posts'] ) && ! empty( $this->nav_menus ) ? $starter_content['posts'] : array();
 924          $options = isset( $starter_content['options'] ) ? $starter_content['options'] : array();
 925          $nav_menus = isset( $starter_content['nav_menus'] ) && ! empty( $this->nav_menus ) ? $starter_content['nav_menus'] : array();
 926          $theme_mods = isset( $starter_content['theme_mods'] ) ? $starter_content['theme_mods'] : array();
 927  
 928          // Widgets.
 929          $max_widget_numbers = array();
 930          foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
 931              $sidebar_widget_ids = array();
 932              foreach ( $widgets as $widget ) {
 933                  list( $id_base, $instance ) = $widget;
 934  
 935                  if ( ! isset( $max_widget_numbers[ $id_base ] ) ) {
 936  
 937                      // When $settings is an array-like object, get an intrinsic array for use with array_keys().
 938                      $settings = get_option( "widget_{$id_base}", array() );
 939                      if ( $settings instanceof ArrayObject || $settings instanceof ArrayIterator ) {
 940                          $settings = $settings->getArrayCopy();
 941                      }
 942  
 943                      // Find the max widget number for this type.
 944                      $widget_numbers = array_keys( $settings );
 945                      if ( count( $widget_numbers ) > 0 ) {
 946                          $widget_numbers[] = 1;
 947                          $max_widget_numbers[ $id_base ] = call_user_func_array( 'max', $widget_numbers );
 948                      } else {
 949                          $max_widget_numbers[ $id_base ] = 1;
 950                      }
 951                  }
 952                  $max_widget_numbers[ $id_base ] += 1;
 953  
 954                  $widget_id = sprintf( '%s-%d', $id_base, $max_widget_numbers[ $id_base ] );
 955                  $setting_id = sprintf( 'widget_%s[%d]', $id_base, $max_widget_numbers[ $id_base ] );
 956  
 957                  $setting_value = $this->widgets->sanitize_widget_js_instance( $instance );
 958                  if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
 959                      $this->set_post_value( $setting_id, $setting_value );
 960                      $this->pending_starter_content_settings_ids[] = $setting_id;
 961                  }
 962                  $sidebar_widget_ids[] = $widget_id;
 963              }
 964  
 965              $setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
 966              if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
 967                  $this->set_post_value( $setting_id, $sidebar_widget_ids );
 968                  $this->pending_starter_content_settings_ids[] = $setting_id;
 969              }
 970          }
 971  
 972          $starter_content_auto_draft_post_ids = array();
 973          if ( ! empty( $changeset_data['nav_menus_created_posts']['value'] ) ) {
 974              $starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, $changeset_data['nav_menus_created_posts']['value'] );
 975          }
 976  
 977          // Make an index of all the posts needed and what their slugs are.
 978          $needed_posts = array();
 979          $attachments = $this->prepare_starter_content_attachments( $attachments );
 980          foreach ( $attachments as $attachment ) {
 981              $key = 'attachment:' . $attachment['post_name'];
 982              $needed_posts[ $key ] = true;
 983          }
 984          foreach ( array_keys( $posts ) as $post_symbol ) {
 985              if ( empty( $posts[ $post_symbol ]['post_name'] ) && empty( $posts[ $post_symbol ]['post_title'] ) ) {
 986                  unset( $posts[ $post_symbol ] );
 987                  continue;
 988              }
 989              if ( empty( $posts[ $post_symbol ]['post_name'] ) ) {
 990                  $posts[ $post_symbol ]['post_name'] = sanitize_title( $posts[ $post_symbol ]['post_title'] );
 991              }
 992              if ( empty( $posts[ $post_symbol ]['post_type'] ) ) {
 993                  $posts[ $post_symbol ]['post_type'] = 'post';
 994              }
 995              $needed_posts[ $posts[ $post_symbol ]['post_type'] . ':' . $posts[ $post_symbol ]['post_name'] ] = true;
 996          }
 997          $all_post_slugs = array_merge(
 998              wp_list_pluck( $attachments, 'post_name' ),
 999              wp_list_pluck( $posts, 'post_name' )
1000          );
1001  
1002          /*
1003           * Obtain all post types referenced in starter content to use in query.
1004           * This is needed because 'any' will not account for post types not yet registered.
1005           */
1006          $post_types = array_filter( array_merge( array( 'attachment' ), wp_list_pluck( $posts, 'post_type' ) ) );
1007  
1008          // Re-use auto-draft starter content posts referenced in the current customized state.
1009          $existing_starter_content_posts = array();
1010          if ( ! empty( $starter_content_auto_draft_post_ids ) ) {
1011              $existing_posts_query = new WP_Query( array(
1012                  'post__in' => $starter_content_auto_draft_post_ids,
1013                  'post_status' => 'auto-draft',
1014                  'post_type' => $post_types,
1015                  'posts_per_page' => -1,
1016              ) );
1017              foreach ( $existing_posts_query->posts as $existing_post ) {
1018                  $post_name = $existing_post->post_name;
1019                  if ( empty( $post_name ) ) {
1020                      $post_name = get_post_meta( $existing_post->ID, '_customize_draft_post_name', true );
1021                  }
1022                  $existing_starter_content_posts[ $existing_post->post_type . ':' . $post_name ] = $existing_post;
1023              }
1024          }
1025  
1026          // Re-use non-auto-draft posts.
1027          if ( ! empty( $all_post_slugs ) ) {
1028              $existing_posts_query = new WP_Query( array(
1029                  'post_name__in' => $all_post_slugs,
1030                  'post_status' => array_diff( get_post_stati(), array( 'auto-draft' ) ),
1031                  'post_type' => 'any',
1032                  'posts_per_page' => -1,
1033              ) );
1034              foreach ( $existing_posts_query->posts as $existing_post ) {
1035                  $key = $existing_post->post_type . ':' . $existing_post->post_name;
1036                  if ( isset( $needed_posts[ $key ] ) && ! isset( $existing_starter_content_posts[ $key ] ) ) {
1037                      $existing_starter_content_posts[ $key ] = $existing_post;
1038                  }
1039              }
1040          }
1041  
1042          // Attachments are technically posts but handled differently.
1043          if ( ! empty( $attachments ) ) {
1044  
1045              $attachment_ids = array();
1046  
1047              foreach ( $attachments as $symbol => $attachment ) {
1048                  $file_array = array(
1049                      'name' => $attachment['file_name'],
1050                  );
1051                  $file_path = $attachment['file_path'];
1052                  $attachment_id = null;
1053                  $attached_file = null;
1054                  if ( isset( $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ] ) ) {
1055                      $attachment_post = $existing_starter_content_posts[ 'attachment:' . $attachment['post_name'] ];
1056                      $attachment_id = $attachment_post->ID;
1057                      $attached_file = get_attached_file( $attachment_id );
1058                      if ( empty( $attached_file ) || ! file_exists( $attached_file ) ) {
1059                          $attachment_id = null;
1060                          $attached_file = null;
1061                      } elseif ( $this->get_stylesheet() !== get_post_meta( $attachment_post->ID, '_starter_content_theme', true ) ) {
1062  
1063                          // Re-generate attachment metadata since it was previously generated for a different theme.
1064                          $metadata = wp_generate_attachment_metadata( $attachment_post->ID, $attached_file );
1065                          wp_update_attachment_metadata( $attachment_id, $metadata );
1066                          update_post_meta( $attachment_id, '_starter_content_theme', $this->get_stylesheet() );
1067                      }
1068                  }
1069  
1070                  // Insert the attachment auto-draft because it doesn't yet exist or the attached file is gone.
1071                  if ( ! $attachment_id ) {
1072  
1073                      // Copy file to temp location so that original file won't get deleted from theme after sideloading.
1074                      $temp_file_name = wp_tempnam( basename( $file_path ) );
1075                      if ( $temp_file_name && copy( $file_path, $temp_file_name ) ) {
1076                          $file_array['tmp_name'] = $temp_file_name;
1077                      }
1078                      if ( empty( $file_array['tmp_name'] ) ) {
1079                          continue;
1080                      }
1081  
1082                      $attachment_post_data = array_merge(
1083                          wp_array_slice_assoc( $attachment, array( 'post_title', 'post_content', 'post_excerpt' ) ),
1084                          array(
1085                              'post_status' => 'auto-draft', // So attachment will be garbage collected in a week if changeset is never published.
1086                          )
1087                      );
1088  
1089                      // In PHP < 5.6 filesize() returns 0 for the temp files unless we clear the file status cache.
1090                      // Technically, PHP < 5.6.0 || < 5.5.13 || < 5.4.29 but no need to be so targeted.
1091                      // See https://bugs.php.net/bug.php?id=65701
1092                      if ( version_compare( PHP_VERSION, '5.6', '<' ) ) {
1093                          clearstatcache();
1094                      }
1095  
1096                      $attachment_id = media_handle_sideload( $file_array, 0, null, $attachment_post_data );
1097                      if ( is_wp_error( $attachment_id ) ) {
1098                          continue;
1099                      }
1100                      update_post_meta( $attachment_id, '_starter_content_theme', $this->get_stylesheet() );
1101                      update_post_meta( $attachment_id, '_customize_draft_post_name', $attachment['post_name'] );
1102                  }
1103  
1104                  $attachment_ids[ $symbol ] = $attachment_id;
1105              }
1106              $starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, array_values( $attachment_ids ) );
1107          }
1108  
1109          // Posts & pages.
1110          if ( ! empty( $posts ) ) {
1111              foreach ( array_keys( $posts ) as $post_symbol ) {
1112                  if ( empty( $posts[ $post_symbol ]['post_type'] ) || empty( $posts[ $post_symbol ]['post_name'] ) ) {
1113                      continue;
1114                  }
1115                  $post_type = $posts[ $post_symbol ]['post_type'];
1116                  if ( ! empty( $posts[ $post_symbol ]['post_name'] ) ) {
1117                      $post_name = $posts[ $post_symbol ]['post_name'];
1118                  } elseif ( ! empty( $posts[ $post_symbol ]['post_title'] ) ) {
1119                      $post_name = sanitize_title( $posts[ $post_symbol ]['post_title'] );
1120                  } else {
1121                      continue;
1122                  }
1123  
1124                  // Use existing auto-draft post if one already exists with the same type and name.
1125                  if ( isset( $existing_starter_content_posts[ $post_type . ':' . $post_name ] ) ) {
1126                      $posts[ $post_symbol ]['ID'] = $existing_starter_content_posts[ $post_type . ':' . $post_name ]->ID;
1127                      continue;
1128                  }
1129  
1130                  // Translate the featured image symbol.
1131                  if ( ! empty( $posts[ $post_symbol ]['thumbnail'] )
1132                      && preg_match( '/^{{(?P<symbol>.+)}}$/', $posts[ $post_symbol ]['thumbnail'], $matches )
1133                      && isset( $attachment_ids[ $matches['symbol'] ] ) ) {
1134                      $posts[ $post_symbol ]['meta_input']['_thumbnail_id'] = $attachment_ids[ $matches['symbol'] ];
1135                  }
1136  
1137                  if ( ! empty( $posts[ $post_symbol ]['template'] ) ) {
1138                      $posts[ $post_symbol ]['meta_input']['_wp_page_template'] = $posts[ $post_symbol ]['template'];
1139                  }
1140  
1141                  $r = $this->nav_menus->insert_auto_draft_post( $posts[ $post_symbol ] );
1142                  if ( $r instanceof WP_Post ) {
1143                      $posts[ $post_symbol ]['ID'] = $r->ID;
1144                  }
1145              }
1146  
1147              $starter_content_auto_draft_post_ids = array_merge( $starter_content_auto_draft_post_ids, wp_list_pluck( $posts, 'ID' ) );
1148          }
1149  
1150          // The nav_menus_created_posts setting is why nav_menus component is dependency for adding posts.
1151          if ( ! empty( $this->nav_menus ) && ! empty( $starter_content_auto_draft_post_ids ) ) {
1152              $setting_id = 'nav_menus_created_posts';
1153              $this->set_post_value( $setting_id, array_unique( array_values( $starter_content_auto_draft_post_ids ) ) );
1154              $this->pending_starter_content_settings_ids[] = $setting_id;
1155          }
1156  
1157          // Nav menus.
1158          $placeholder_id = -1;
1159          $reused_nav_menu_setting_ids = array();
1160          foreach ( $nav_menus as $nav_menu_location => $nav_menu ) {
1161  
1162              $nav_menu_term_id = null;
1163              $nav_menu_setting_id = null;
1164              $matches = array();
1165  
1166              // Look for an existing placeholder menu with starter content to re-use.
1167              foreach ( $changeset_data as $setting_id => $setting_params ) {
1168                  $can_reuse = (
1169                      ! empty( $setting_params['starter_content'] )
1170                      &&
1171                      ! in_array( $setting_id, $reused_nav_menu_setting_ids, true )
1172                      &&
1173                      preg_match( '#^nav_menu\[(?P<nav_menu_id>-?\d+)\]$#', $setting_id, $matches )
1174                  );
1175                  if ( $can_reuse ) {
1176                      $nav_menu_term_id = intval( $matches['nav_menu_id'] );
1177                      $nav_menu_setting_id = $setting_id;
1178                      $reused_nav_menu_setting_ids[] = $setting_id;
1179                      break;
1180                  }
1181              }
1182  
1183              if ( ! $nav_menu_term_id ) {
1184                  while ( isset( $changeset_data[ sprintf( 'nav_menu[%d]', $placeholder_id ) ] ) ) {
1185                      $placeholder_id--;
1186                  }
1187                  $nav_menu_term_id = $placeholder_id;
1188                  $nav_menu_setting_id = sprintf( 'nav_menu[%d]', $placeholder_id );
1189              }
1190  
1191              $this->set_post_value( $nav_menu_setting_id, array(
1192                  'name' => isset( $nav_menu['name'] ) ? $nav_menu['name'] : $nav_menu_location,
1193              ) );
1194              $this->pending_starter_content_settings_ids[] = $nav_menu_setting_id;
1195  
1196              // @todo Add support for menu_item_parent.
1197              $position = 0;
1198              foreach ( $nav_menu['items'] as $nav_menu_item ) {
1199                  $nav_menu_item_setting_id = sprintf( 'nav_menu_item[%d]', $placeholder_id-- );
1200                  if ( ! isset( $nav_menu_item['position'] ) ) {
1201                      $nav_menu_item['position'] = $position++;
1202                  }
1203                  $nav_menu_item['nav_menu_term_id'] = $nav_menu_term_id;
1204  
1205                  if ( isset( $nav_menu_item['object_id'] ) ) {
1206                      if ( 'post_type' === $nav_menu_item['type'] && preg_match( '/^{{(?P<symbol>.+)}}$/', $nav_menu_item['object_id'], $matches ) && isset( $posts[ $matches['symbol'] ] ) ) {
1207                          $nav_menu_item['object_id'] = $posts[ $matches['symbol'] ]['ID'];
1208                          if ( empty( $nav_menu_item['title'] ) ) {
1209                              $original_object = get_post( $nav_menu_item['object_id'] );
1210                              $nav_menu_item['title'] = $original_object->post_title;
1211                          }
1212                      } else {
1213                          continue;
1214                      }
1215                  } else {
1216                      $nav_menu_item['object_id'] = 0;
1217                  }
1218  
1219                  if ( empty( $changeset_data[ $nav_menu_item_setting_id ] ) || ! empty( $changeset_data[ $nav_menu_item_setting_id ]['starter_content'] ) ) {
1220                      $this->set_post_value( $nav_menu_item_setting_id, $nav_menu_item );
1221                      $this->pending_starter_content_settings_ids[] = $nav_menu_item_setting_id;
1222                  }
1223              }
1224  
1225              $setting_id = sprintf( 'nav_menu_locations[%s]', $nav_menu_location );
1226              if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
1227                  $this->set_post_value( $setting_id, $nav_menu_term_id );
1228                  $this->pending_starter_content_settings_ids[] = $setting_id;
1229              }
1230          }
1231  
1232          // Options.
1233          foreach ( $options as $name => $value ) {
1234              if ( preg_match( '/^{{(?P<symbol>.+)}}$/', $value, $matches ) ) {
1235                  if ( isset( $posts[ $matches['symbol'] ] ) ) {
1236                      $value = $posts[ $matches['symbol'] ]['ID'];
1237                  } elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) {
1238                      $value = $attachment_ids[ $matches['symbol'] ];
1239                  } else {
1240                      continue;
1241                  }
1242              }
1243  
1244              if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) {
1245                  $this->set_post_value( $name, $value );
1246                  $this->pending_starter_content_settings_ids[] = $name;
1247              }
1248          }
1249  
1250          // Theme mods.
1251          foreach ( $theme_mods as $name => $value ) {
1252              if ( preg_match( '/^{{(?P<symbol>.+)}}$/', $value, $matches ) ) {
1253                  if ( isset( $posts[ $matches['symbol'] ] ) ) {
1254                      $value = $posts[ $matches['symbol'] ]['ID'];
1255                  } elseif ( isset( $attachment_ids[ $matches['symbol'] ] ) ) {
1256                      $value = $attachment_ids[ $matches['symbol'] ];
1257                  } else {
1258                      continue;
1259                  }
1260              }
1261  
1262              // Handle header image as special case since setting has a legacy format.
1263              if ( 'header_image' === $name ) {
1264                  $name = 'header_image_data';
1265                  $metadata = wp_get_attachment_metadata( $value );
1266                  if ( empty( $metadata ) ) {
1267                      continue;
1268                  }
1269                  $value = array(
1270                      'attachment_id' => $value,
1271                      'url' => wp_get_attachment_url( $value ),
1272                      'height' => $metadata['height'],
1273                      'width' => $metadata['width'],
1274                  );
1275              } elseif ( 'background_image' === $name ) {
1276                  $value = wp_get_attachment_url( $value );
1277              }
1278  
1279              if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) {
1280                  $this->set_post_value( $name, $value );
1281                  $this->pending_starter_content_settings_ids[] = $name;
1282              }
1283          }
1284  
1285          if ( ! empty( $this->pending_starter_content_settings_ids ) ) {
1286              if ( did_action( 'customize_register' ) ) {
1287                  $this->_save_starter_content_changeset();
1288              } else {
1289                  add_action( 'customize_register', array( $this, '_save_starter_content_changeset' ), 1000 );
1290              }
1291          }
1292      }
1293  
1294      /**
1295       * Prepare starter content attachments.
1296       *
1297       * Ensure that the attachments are valid and that they have slugs and file name/path.
1298       *
1299       * @since 4.7.0
1300       * @access private
1301       *
1302       * @param array $attachments Attachments.
1303       * @return array Prepared attachments.
1304       */
1305  	protected function prepare_starter_content_attachments( $attachments ) {
1306          $prepared_attachments = array();
1307          if ( empty( $attachments ) ) {
1308              return $prepared_attachments;
1309          }
1310  
1311          // Such is The WordPress Way.
1312          require_once( ABSPATH . 'wp-admin/includes/file.php' );
1313          require_once( ABSPATH . 'wp-admin/includes/media.php' );
1314          require_once( ABSPATH . 'wp-admin/includes/image.php' );
1315  
1316          foreach ( $attachments as $symbol => $attachment ) {
1317  
1318              // A file is required and URLs to files are not currently allowed.
1319              if ( empty( $attachment['file'] ) || preg_match( '#^https?://$#', $attachment['file'] ) ) {
1320                  continue;
1321              }
1322  
1323              $file_path = null;
1324              if ( file_exists( $attachment['file'] ) ) {
1325                  $file_path = $attachment['file']; // Could be absolute path to file in plugin.
1326              } elseif ( is_child_theme() && file_exists( get_stylesheet_directory() . '/' . $attachment['file'] ) ) {
1327                  $file_path = get_stylesheet_directory() . '/' . $attachment['file'];
1328              } elseif ( file_exists( get_template_directory() . '/' . $attachment['file'] ) ) {
1329                  $file_path = get_template_directory() . '/' . $attachment['file'];
1330              } else {
1331                  continue;
1332              }
1333              $file_name = basename( $attachment['file'] );
1334  
1335              // Skip file types that are not recognized.
1336              $checked_filetype = wp_check_filetype( $file_name );
1337              if ( empty( $checked_filetype['type'] ) ) {
1338                  continue;
1339              }
1340  
1341              // Ensure post_name is set since not automatically derived from post_title for new auto-draft posts.
1342              if ( empty( $attachment['post_name'] ) ) {
1343                  if ( ! empty( $attachment['post_title'] ) ) {
1344                      $attachment['post_name'] = sanitize_title( $attachment['post_title'] );
1345                  } else {
1346                      $attachment['post_name'] = sanitize_title( preg_replace( '/\.\w+$/', '', $file_name ) );
1347                  }
1348              }
1349  
1350              $attachment['file_name'] = $file_name;
1351              $attachment['file_path'] = $file_path;
1352              $prepared_attachments[ $symbol ] = $attachment;
1353          }
1354          return $prepared_attachments;
1355      }
1356  
1357      /**
1358       * Save starter content changeset.
1359       *
1360       * @since 4.7.0
1361       * @access private
1362       */
1363  	public function _save_starter_content_changeset() {
1364  
1365          if ( empty( $this->pending_starter_content_settings_ids ) ) {
1366              return;
1367          }
1368  
1369          $this->save_changeset_post( array(
1370              'data' => array_fill_keys( $this->pending_starter_content_settings_ids, array( 'starter_content' => true ) ),
1371              'starter_content' => true,
1372          ) );
1373  
1374          $this->pending_starter_content_settings_ids = array();
1375      }
1376  
1377      /**
1378       * Get dirty pre-sanitized setting values in the current customized state.
1379       *
1380       * The returned array consists of a merge of three sources:
1381       * 1. If the theme is not currently active, then the base array is any stashed
1382       *    theme mods that were modified previously but never published.
1383       * 2. The values from the current changeset, if it exists.
1384       * 3. If the user can customize, the values parsed from the incoming
1385       *    `$_POST['customized']` JSON data.
1386       * 4. Any programmatically-set post values via `WP_Customize_Manager::set_post_value()`.
1387       *
1388       * The name "unsanitized_post_values" is a carry-over from when the customized
1389       * state was exclusively sourced from `$_POST['customized']`. Nevertheless,
1390       * the value returned will come from the current changeset post and from the
1391       * incoming post data.
1392       *
1393       * @since 4.1.1
1394       * @since 4.7.0 Added $args param and merging with changeset values and stashed theme mods.
1395       *
1396       * @param array $args {
1397       *     Args.
1398       *
1399       *     @type bool $exclude_changeset Whether the changeset values should also be excluded. Defaults to false.
1400       *     @type bool $exclude_post_data Whether the post input values should also be excluded. Defaults to false when lacking the customize capability.
1401       * }
1402       * @return array
1403       */
1404  	public function unsanitized_post_values( $args = array() ) {
1405          $args = array_merge(
1406              array(
1407                  'exclude_changeset' => false,
1408                  'exclude_post_data' => ! current_user_can( 'customize' ),
1409              ),
1410              $args
1411          );
1412  
1413          $values = array();
1414  
1415          // Let default values be from the stashed theme mods if doing a theme switch and if no changeset is present.
1416          if ( ! $this->is_theme_active() ) {
1417              $stashed_theme_mods = get_option( 'customize_stashed_theme_mods' );
1418              $stylesheet = $this->get_stylesheet();
1419              if ( isset( $stashed_theme_mods[ $stylesheet ] ) ) {
1420                  $values = array_merge( $values, wp_list_pluck( $stashed_theme_mods[ $stylesheet ], 'value' ) );
1421              }
1422          }
1423  
1424          if ( ! $args['exclude_changeset'] ) {
1425              foreach ( $this->changeset_data() as $setting_id => $setting_params ) {
1426                  if ( ! array_key_exists( 'value', $setting_params ) ) {
1427                      continue;
1428                  }
1429                  if ( isset( $setting_params['type'] ) && 'theme_mod' === $setting_params['type'] ) {
1430  
1431                      // Ensure that theme mods values are only used if they were saved under the current theme.
1432                      $namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/';
1433                      if ( preg_match( $namespace_pattern, $setting_id, $matches ) && $this->get_stylesheet() === $matches['stylesheet'] ) {
1434                          $values[ $matches['setting_id'] ] = $setting_params['value'];
1435                      }
1436                  } else {
1437                      $values[ $setting_id ] = $setting_params['value'];
1438                  }
1439              }
1440          }
1441  
1442          if ( ! $args['exclude_post_data'] ) {
1443              if ( ! isset( $this->_post_values ) ) {
1444                  if ( isset( $_POST['customized'] ) ) {
1445                      $post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
1446                  } else {
1447                      $post_values = array();
1448                  }
1449                  if ( is_array( $post_values ) ) {
1450                      $this->_post_values = $post_values;
1451                  } else {
1452                      $this->_post_values = array();
1453                  }
1454              }
1455              $values = array_merge( $values, $this->_post_values );
1456          }
1457          return $values;
1458      }
1459  
1460      /**
1461       * Returns the sanitized value for a given setting from the current customized state.
1462       *
1463       * The name "post_value" is a carry-over from when the customized state was exclusively
1464       * sourced from `$_POST['customized']`. Nevertheless, the value returned will come
1465       * from the current changeset post and from the incoming post data.
1466       *
1467       * @since 3.4.0
1468       * @since 4.1.1 Introduced the `$default` parameter.
1469       * @since 4.6.0 `$default` is now returned early when the setting post value is invalid.
1470       * @access public
1471       *
1472       * @see WP_REST_Server::dispatch()
1473       * @see WP_Rest_Request::sanitize_params()
1474       * @see WP_Rest_Request::has_valid_params()
1475       *
1476       * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object.
1477       * @param mixed                $default Value returned $setting has no post value (added in 4.2.0)
1478       *                                      or the post value is invalid (added in 4.6.0).
1479       * @return string|mixed $post_value Sanitized value or the $default provided.
1480       */
1481  	public function post_value( $setting, $default = null ) {
1482          $post_values = $this->unsanitized_post_values();
1483          if ( ! array_key_exists( $setting->id, $post_values ) ) {
1484              return $default;
1485          }
1486          $value = $post_values[ $setting->id ];
1487          $valid = $setting->validate( $value );
1488          if ( is_wp_error( $valid ) ) {
1489              return $default;
1490          }
1491          $value = $setting->sanitize( $value );
1492          if ( is_null( $value ) || is_wp_error( $value ) ) {
1493              return $default;
1494          }
1495          return $value;
1496      }
1497  
1498      /**
1499       * Override a setting's value in the current customized state.
1500       *
1501       * The name "post_value" is a carry-over from when the customized state was
1502       * exclusively sourced from `$_POST['customized']`.
1503       *
1504       * @since 4.2.0
1505       * @access public
1506       *
1507       * @param string $setting_id ID for the WP_Customize_Setting instance.
1508       * @param mixed  $value      Post value.
1509       */
1510  	public function set_post_value( $setting_id, $value ) {
1511          $this->unsanitized_post_values(); // Populate _post_values from $_POST['customized'].
1512          $this->_post_values[ $setting_id ] = $value;
1513  
1514          /**
1515           * Announce when a specific setting's unsanitized post value has been set.
1516           *
1517           * Fires when the WP_Customize_Manager::set_post_value() method is called.
1518           *
1519           * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID.
1520           *
1521           * @since 4.4.0
1522           *
1523           * @param mixed                $value Unsanitized setting post value.
1524           * @param WP_Customize_Manager $this  WP_Customize_Manager instance.
1525           */
1526          do_action( "customize_post_value_set_{$setting_id}", $value, $this );
1527  
1528          /**
1529           * Announce when any setting's unsanitized post value has been set.
1530           *
1531           * Fires when the WP_Customize_Manager::set_post_value() method is called.
1532           *
1533           * This is useful for `WP_Customize_Setting` instances to watch
1534           * in order to update a cached previewed value.
1535           *
1536           * @since 4.4.0
1537           *
1538           * @param string               $setting_id Setting ID.
1539           * @param mixed                $value      Unsanitized setting post value.
1540           * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
1541           */
1542          do_action( 'customize_post_value_set', $setting_id, $value, $this );
1543      }
1544  
1545      /**
1546       * Print JavaScript settings.
1547       *
1548       * @since 3.4.0
1549       */
1550  	public function customize_preview_init() {
1551  
1552          /*
1553           * Now that Customizer previews are loaded into iframes via GET requests
1554           * and natural URLs with transaction UUIDs added, we need to ensure that
1555           * the responses are never cached by proxies. In practice, this will not
1556           * be needed if the user is logged-in anyway. But if anonymous access is
1557           * allowed then the auth cookies would not be sent and WordPress would
1558           * not send no-cache headers by default.
1559           */
1560          if ( ! headers_sent() ) {
1561              nocache_headers();
1562              header( 'X-Robots: noindex, nofollow, noarchive' );
1563          }
1564          add_action( 'wp_head', 'wp_no_robots' );
1565          add_filter( 'wp_headers', array( $this, 'filter_iframe_security_headers' ) );
1566  
1567          /*
1568           * If preview is being served inside the customizer preview iframe, and
1569           * if the user doesn't have customize capability, then it is assumed
1570           * that the user's session has expired and they need to re-authenticate.
1571           */
1572          if ( $this->messenger_channel && ! current_user_can( 'customize' ) ) {
1573              $this->wp_die( -1, __( 'Unauthorized. You may remove the customize_messenger_channel param to preview as frontend.' ) );
1574              return;
1575          }
1576  
1577          $this->prepare_controls();
1578  
1579          add_filter( 'wp_redirect', array( $this, 'add_state_query_params' ) );
1580  
1581          wp_enqueue_script( 'customize-preview' );
1582          wp_enqueue_style( 'customize-preview' );
1583          add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
1584          add_action( 'wp_head', array( $this, 'remove_frameless_preview_messenger_channel' ) );
1585          add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
1586          add_filter( 'get_edit_post_link', '__return_empty_string' );
1587  
1588          /**
1589           * Fires once the Customizer preview has initialized and JavaScript
1590           * settings have been printed.
1591           *
1592           * @since 3.4.0
1593           *
1594           * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1595           */
1596          do_action( 'customize_preview_init', $this );
1597      }
1598  
1599      /**
1600       * Filter the X-Frame-Options and Content-Security-Policy headers to ensure frontend can load in customizer.
1601       *
1602       * @since 4.7.0
1603       * @access public
1604       *
1605       * @param array $headers Headers.
1606       * @return array Headers.
1607       */
1608  	public function filter_iframe_security_headers( $headers ) {
1609          $customize_url = admin_url( 'customize.php' );
1610          $headers['X-Frame-Options'] = 'ALLOW-FROM ' . $customize_url;
1611          $headers['Content-Security-Policy'] = 'frame-ancestors ' . preg_replace( '#^(\w+://[^/]+).+?$#', '$1', $customize_url );
1612          return $headers;
1613      }
1614  
1615      /**
1616       * Add customize state query params to a given URL if preview is allowed.
1617       *
1618       * @since 4.7.0
1619       * @access public
1620       * @see wp_redirect()
1621       * @see WP_Customize_Manager::get_allowed_url()
1622       *
1623       * @param string $url URL.
1624       * @return string URL.
1625       */
1626  	public function add_state_query_params( $url ) {
1627          $parsed_original_url = wp_parse_url( $url );
1628          $is_allowed = false;
1629          foreach ( $this->get_allowed_urls() as $allowed_url ) {
1630              $parsed_allowed_url = wp_parse_url( $allowed_url );
1631              $is_allowed = (
1632                  $parsed_allowed_url['scheme'] === $parsed_original_url['scheme']
1633                  &&
1634                  $parsed_allowed_url['host'] === $parsed_original_url['host']
1635                  &&
1636                  0 === strpos( $parsed_original_url['path'], $parsed_allowed_url['path'] )
1637              );
1638              if ( $is_allowed ) {
1639                  break;
1640              }
1641          }
1642  
1643          if ( $is_allowed ) {
1644              $query_params = array(
1645                  'customize_changeset_uuid' => $this->changeset_uuid(),
1646              );
1647              if ( ! $this->is_theme_active() ) {
1648                  $query_params['customize_theme'] = $this->get_stylesheet();
1649              }
1650              if ( $this->messenger_channel ) {
1651                  $query_params['customize_messenger_channel'] = $this->messenger_channel;
1652              }
1653              $url = add_query_arg( $query_params, $url );
1654          }
1655  
1656          return $url;
1657      }
1658  
1659      /**
1660       * Prevent sending a 404 status when returning the response for the customize
1661       * preview, since it causes the jQuery Ajax to fail. Send 200 instead.
1662       *
1663       * @since 4.0.0
1664       * @deprecated 4.7.0
1665       * @access public
1666       */
1667  	public function customize_preview_override_404_status() {
1668          _deprecated_function( __METHOD__, '4.7.0' );
1669      }
1670  
1671      /**
1672       * Print base element for preview frame.
1673       *
1674       * @since 3.4.0
1675       * @deprecated 4.7.0
1676       */
1677  	public function customize_preview_base() {
1678          _deprecated_function( __METHOD__, '4.7.0' );
1679      }
1680  
1681      /**
1682       * Print a workaround to handle HTML5 tags in IE < 9.
1683       *
1684       * @since 3.4.0
1685       * @deprecated 4.7.0 Customizer no longer supports IE8, so all supported browsers recognize HTML5.
1686       */
1687  	public function customize_preview_html5() {
1688          _deprecated_function( __FUNCTION__, '4.7.0' );
1689      }
1690  
1691      /**
1692       * Print CSS for loading indicators for the Customizer preview.
1693       *
1694       * @since 4.2.0
1695       * @access public
1696       */
1697  	public function customize_preview_loading_style() {
1698          ?><style>
1699              body.wp-customizer-unloading {
1700                  opacity: 0.25;
1701                  cursor: progress !important;
1702                  -webkit-transition: opacity 0.5s;
1703                  transition: opacity 0.5s;
1704              }
1705              body.wp-customizer-unloading * {
1706                  pointer-events: none !important;
1707              }
1708              form.customize-unpreviewable,
1709              form.customize-unpreviewable input,
1710              form.customize-unpreviewable select,
1711              form.customize-unpreviewable button,
1712              a.customize-unpreviewable,
1713              area.customize-unpreviewable {
1714                  cursor: not-allowed !important;
1715              }
1716          </style><?php
1717      }
1718  
1719      /**
1720       * Remove customize_messenger_channel query parameter from the preview window when it is not in an iframe.
1721       *
1722       * This ensures that the admin bar will be shown. It also ensures that link navigation will
1723       * work as expected since the parent frame is not being sent the URL to navigate to.
1724       *
1725       * @since 4.7.0
1726       * @access public
1727       */
1728  	public function remove_frameless_preview_messenger_channel() {
1729          if ( ! $this->messenger_channel ) {
1730              return;
1731          }
1732          ?>
1733          <script>
1734          ( function() {
1735              var urlParser, oldQueryParams, newQueryParams, i;
1736              if ( parent !== window ) {
1737                  return;
1738              }
1739              urlParser = document.createElement( 'a' );
1740              urlParser.href = location.href;
1741              oldQueryParams = urlParser.search.substr( 1 ).split( /&/ );
1742              newQueryParams = [];
1743              for ( i = 0; i < oldQueryParams.length; i += 1 ) {
1744                  if ( ! /^customize_messenger_channel=/.test( oldQueryParams[ i ] ) ) {
1745                      newQueryParams.push( oldQueryParams[ i ] );
1746                  }
1747              }
1748              urlParser.search = newQueryParams.join( '&' );
1749              if ( urlParser.search !== location.search ) {
1750                  location.replace( urlParser.href );
1751              }
1752          } )();
1753          </script>
1754          <?php
1755      }
1756  
1757      /**
1758       * Print JavaScript settings for preview frame.
1759       *
1760       * @since 3.4.0
1761       */
1762  	public function customize_preview_settings() {
1763          $post_values = $this->unsanitized_post_values( array( 'exclude_changeset' => true ) );
1764          $setting_validities = $this->validate_setting_values( $post_values );
1765          $exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities );
1766  
1767          // Note that the REQUEST_URI is not passed into home_url() since this breaks subdirectory installs.
1768          $self_url = empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
1769          $state_query_params = array(
1770              'customize_theme',
1771              'customize_changeset_uuid',
1772              'customize_messenger_channel',
1773          );
1774          $self_url = remove_query_arg( $state_query_params, $self_url );
1775  
1776          $allowed_urls = $this->get_allowed_urls();
1777          $allowed_hosts = array();
1778          foreach ( $allowed_urls as $allowed_url ) {
1779              $parsed = wp_parse_url( $allowed_url );
1780              if ( empty( $parsed['host'] ) ) {
1781                  continue;
1782              }
1783              $host = $parsed['host'];
1784              if ( ! empty( $parsed['port'] ) ) {
1785                  $host .= ':' . $parsed['port'];
1786              }
1787              $allowed_hosts[] = $host;
1788          }
1789  
1790          $switched_locale = switch_to_locale( get_user_locale() );
1791          $l10n = array(
1792              'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
1793              'linkUnpreviewable' => __( 'This link is not live-previewable.' ),
1794              'formUnpreviewable' => __( 'This form is not live-previewable.' ),
1795          );
1796          if ( $switched_locale ) {
1797              restore_previous_locale();
1798          }
1799  
1800          $settings = array(
1801              'changeset' => array(
1802                  'uuid' => $this->_changeset_uuid,
1803              ),
1804              'timeouts' => array(
1805                  'selectiveRefresh' => 250,
1806                  'keepAliveSend' => 1000,
1807              ),
1808              'theme' => array(
1809                  'stylesheet' => $this->get_stylesheet(),
1810                  'active'     => $this->is_theme_active(),
1811              ),
1812              'url' => array(
1813                  'self' => $self_url,
1814                  'allowed' => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
1815                  'allowedHosts' => array_unique( $allowed_hosts ),
1816                  'isCrossDomain' => $this->is_cross_domain(),
1817              ),
1818              'channel' => $this->messenger_channel,
1819              'activePanels' => array(),
1820              'activeSections' => array(),
1821              'activeControls' => array(),
1822              'settingValidities' => $exported_setting_validities,
1823              'nonce' => current_user_can( 'customize' ) ? $this->get_nonces() : array(),
1824              'l10n' => $l10n,
1825              '_dirty' => array_keys( $post_values ),
1826          );
1827  
1828          foreach ( $this->panels as $panel_id => $panel ) {
1829              if ( $panel->check_capabilities() ) {
1830                  $settings['activePanels'][ $panel_id ] = $panel->active();
1831                  foreach ( $panel->sections as $section_id => $section ) {
1832                      if ( $section->check_capabilities() ) {
1833                          $settings['activeSections'][ $section_id ] = $section->active();
1834                      }
1835                  }
1836              }
1837          }
1838          foreach ( $this->sections as $id => $section ) {
1839              if ( $section->check_capabilities() ) {
1840                  $settings['activeSections'][ $id ] = $section->active();
1841              }
1842          }
1843          foreach ( $this->controls as $id => $control ) {
1844              if ( $control->check_capabilities() ) {
1845                  $settings['activeControls'][ $id ] = $control->active();
1846              }
1847          }
1848  
1849          ?>
1850          <script type="text/javascript">
1851              var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
1852              _wpCustomizeSettings.values = {};
1853              (function( v ) {
1854                  <?php
1855                  /*
1856                   * Serialize settings separately from the initial _wpCustomizeSettings
1857                   * serialization in order to avoid a peak memory usage spike.
1858                   * @todo We may not even need to export the values at all since the pane syncs them anyway.
1859                   */
1860                  foreach ( $this->settings as $id => $setting ) {
1861                      if ( $setting->check_capabilities() ) {
1862                          printf(
1863                              "v[%s] = %s;\n",
1864                              wp_json_encode( $id ),
1865                              wp_json_encode( $setting->js_value() )
1866                          );
1867                      }
1868                  }
1869                  ?>
1870              })( _wpCustomizeSettings.values );
1871          </script>
1872          <?php
1873      }
1874  
1875      /**
1876       * Prints a signature so we can ensure the Customizer was properly executed.
1877       *
1878       * @since 3.4.0
1879       * @deprecated 4.7.0
1880       */
1881  	public function customize_preview_signature() {
1882          _deprecated_function( __METHOD__, '4.7.0' );
1883      }
1884  
1885      /**
1886       * Removes the signature in case we experience a case where the Customizer was not properly executed.
1887       *
1888       * @since 3.4.0
1889       * @deprecated 4.7.0
1890       *
1891       * @param mixed $return Value passed through for {@see 'wp_die_handler'} filter.
1892       * @return mixed Value passed through for {@see 'wp_die_handler'} filter.
1893       */
1894  	public function remove_preview_signature( $return = null ) {
1895          _deprecated_function( __METHOD__, '4.7.0' );
1896  
1897          return $return;
1898      }
1899  
1900      /**
1901       * Is it a theme preview?
1902       *
1903       * @since 3.4.0
1904       *
1905       * @return bool True if it's a preview, false if not.
1906       */
1907  	public function is_preview() {
1908          return (bool) $this->previewing;
1909      }
1910  
1911      /**
1912       * Retrieve the template name of the previewed theme.
1913       *
1914       * @since 3.4.0
1915       *
1916       * @return string Template name.
1917       */
1918  	public function get_template() {
1919          return $this->theme()->get_template();
1920      }
1921  
1922      /**
1923       * Retrieve the stylesheet name of the previewed theme.
1924       *
1925       * @since 3.4.0
1926       *
1927       * @return string Stylesheet name.
1928       */
1929  	public function get_stylesheet() {
1930          return $this->theme()->get_stylesheet();
1931      }
1932  
1933      /**
1934       * Retrieve the template root of the previewed theme.
1935       *
1936       * @since 3.4.0
1937       *
1938       * @return string Theme root.
1939       */
1940  	public function get_template_root() {
1941          return get_raw_theme_root( $this->get_template(), true );
1942      }
1943  
1944      /**
1945       * Retrieve the stylesheet root of the previewed theme.
1946       *
1947       * @since 3.4.0
1948       *
1949       * @return string Theme root.
1950       */
1951  	public function get_stylesheet_root() {
1952          return get_raw_theme_root( $this->get_stylesheet(), true );
1953      }
1954  
1955      /**
1956       * Filters the current theme and return the name of the previewed theme.
1957       *
1958       * @since 3.4.0
1959       *
1960       * @param $current_theme {@internal Parameter is not used}
1961       * @return string Theme name.
1962       */
1963  	public function current_theme( $current_theme ) {
1964          return $this->theme()->display('Name');
1965      }
1966  
1967      /**
1968       * Validates setting values.
1969       *
1970       * Validation is skipped for unregistered settings or for values that are
1971       * already null since they will be skipped anyway. Sanitization is applied
1972       * to values that pass validation, and values that become null or `WP_Error`
1973       * after sanitizing are marked invalid.
1974       *
1975       * @since 4.6.0
1976       * @access public
1977       *
1978       * @see WP_REST_Request::has_valid_params()
1979       * @see WP_Customize_Setting::validate()
1980       *
1981       * @param array $setting_values Mapping of setting IDs to values to validate and sanitize.
1982       * @param array $options {
1983       *     Options.
1984       *
1985       *     @type bool $validate_existence  Whether a setting's existence will be checked.
1986       *     @type bool $validate_capability Whether the setting capability will be checked.
1987       * }
1988       * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`.
1989       */
1990  	public function validate_setting_values( $setting_values, $options = array() ) {
1991          $options = wp_parse_args( $options, array(
1992              'validate_capability' => false,
1993              'validate_existence' => false,
1994          ) );
1995  
1996          $validities = array();
1997          foreach ( $setting_values as $setting_id => $unsanitized_value ) {
1998              $setting = $this->get_setting( $setting_id );
1999              if ( ! $setting ) {
2000                  if ( $options['validate_existence'] ) {
2001                      $validities[ $setting_id ] = new WP_Error( 'unrecognized', __( 'Setting does not exist or is unrecognized.' ) );
2002                  }
2003                  continue;
2004              }
2005              if ( $options['validate_capability'] && ! current_user_can( $setting->capability ) ) {
2006                  $validity = new WP_Error( 'unauthorized', __( 'Unauthorized to modify setting due to capability.' ) );
2007              } else {
2008                  if ( is_null( $unsanitized_value ) ) {
2009                      continue;
2010                  }
2011                  $validity = $setting->validate( $unsanitized_value );
2012              }
2013              if ( ! is_wp_error( $validity ) ) {
2014                  /** This filter is documented in wp-includes/class-wp-customize-setting.php */
2015                  $late_validity = apply_filters( "customize_validate_{$setting->id}", new WP_Error(), $unsanitized_value, $setting );
2016                  if ( ! empty( $late_validity->errors ) ) {
2017                      $validity = $late_validity;
2018                  }
2019              }
2020              if ( ! is_wp_error( $validity ) ) {
2021                  $value = $setting->sanitize( $unsanitized_value );
2022                  if ( is_null( $value ) ) {
2023                      $validity = false;
2024                  } elseif ( is_wp_error( $value ) ) {
2025                      $validity = $value;
2026                  }
2027              }
2028              if ( false === $validity ) {
2029                  $validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
2030              }
2031              $validities[ $setting_id ] = $validity;
2032          }
2033          return $validities;
2034      }
2035  
2036      /**
2037       * Prepares setting validity for exporting to the client (JS).
2038       *
2039       * Converts `WP_Error` instance into array suitable for passing into the
2040       * `wp.customize.Notification` JS model.
2041       *
2042       * @since 4.6.0
2043       * @access public
2044       *
2045       * @param true|WP_Error $validity Setting validity.
2046       * @return true|array If `$validity` was a WP_Error, the error codes will be array-mapped
2047       *                    to their respective `message` and `data` to pass into the
2048       *                    `wp.customize.Notification` JS model.
2049       */
2050  	public function prepare_setting_validity_for_js( $validity ) {
2051          if ( is_wp_error( $validity ) ) {
2052              $notification = array();
2053              foreach ( $validity->errors as $error_code => $error_messages ) {
2054                  $notification[ $error_code ] = array(
2055                      'message' => join( ' ', $error_messages ),
2056                      'data' => $validity->get_error_data( $error_code ),
2057                  );
2058              }
2059              return $notification;
2060          } else {
2061              return true;
2062          }
2063      }
2064  
2065      /**
2066       * Handle customize_save WP Ajax request to save/update a changeset.
2067       *
2068       * @since 3.4.0
2069       * @since 4.7.0 The semantics of this method have changed to update a changeset, optionally to also change the status and other attributes.
2070       */
2071  	public function save() {
2072          if ( ! is_user_logged_in() ) {
2073              wp_send_json_error( 'unauthenticated' );
2074          }
2075  
2076          if ( ! $this->is_preview() ) {
2077              wp_send_json_error( 'not_preview' );
2078          }
2079  
2080          $action = 'save-customize_' . $this->get_stylesheet();
2081          if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
2082              wp_send_json_error( 'invalid_nonce' );
2083          }
2084  
2085          $changeset_post_id = $this->changeset_post_id();
2086          if ( empty( $changeset_post_id ) ) {
2087              if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->create_posts ) ) {
2088                  wp_send_json_error( 'cannot_create_changeset_post' );
2089              }
2090          } else {
2091              if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post_id ) ) {
2092                  wp_send_json_error( 'cannot_edit_changeset_post' );
2093              }
2094          }
2095  
2096          if ( ! empty( $_POST['customize_changeset_data'] ) ) {
2097              $input_changeset_data = json_decode( wp_unslash( $_POST['customize_changeset_data'] ), true );
2098              if ( ! is_array( $input_changeset_data ) ) {
2099                  wp_send_json_error( 'invalid_customize_changeset_data' );
2100              }
2101          } else {
2102              $input_changeset_data = array();
2103          }
2104  
2105          // Validate title.
2106          $changeset_title = null;
2107          if ( isset( $_POST['customize_changeset_title'] ) ) {
2108              $changeset_title = sanitize_text_field( wp_unslash( $_POST['customize_changeset_title'] ) );
2109          }
2110  
2111          // Validate changeset status param.
2112          $is_publish = null;
2113          $changeset_status = null;
2114          if ( isset( $_POST['customize_changeset_status'] ) ) {
2115              $changeset_status = wp_unslash( $_POST['customize_changeset_status'] );
2116              if ( ! get_post_status_object( $changeset_status ) || ! in_array( $changeset_status, array( 'draft', 'pending', 'publish', 'future' ), true ) ) {
2117                  wp_send_json_error( 'bad_customize_changeset_status', 400 );
2118              }
2119              $is_publish = ( 'publish' === $changeset_status || 'future' === $changeset_status );
2120              if ( $is_publish && ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) {
2121                  wp_send_json_error( 'changeset_publish_unauthorized', 403 );
2122              }
2123          }
2124  
2125          /*
2126           * Validate changeset date param. Date is assumed to be in local time for
2127           * the WP if in MySQL format (YYYY-MM-DD HH:MM:SS). Otherwise, the date
2128           * is parsed with strtotime() so that ISO date format may be supplied
2129           * or a string like "+10 minutes".
2130           */
2131          $changeset_date_gmt = null;
2132          if ( isset( $_POST['customize_changeset_date'] ) ) {
2133              $changeset_date = wp_unslash( $_POST['customize_changeset_date'] );
2134              if ( preg_match( '/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/', $changeset_date ) ) {
2135                  $mm = substr( $changeset_date, 5, 2 );
2136                  $jj = substr( $changeset_date, 8, 2 );
2137                  $aa = substr( $changeset_date, 0, 4 );
2138                  $valid_date = wp_checkdate( $mm, $jj, $aa, $changeset_date );
2139                  if ( ! $valid_date ) {
2140                      wp_send_json_error( 'bad_customize_changeset_date', 400 );
2141                  }
2142                  $changeset_date_gmt = get_gmt_from_date( $changeset_date );
2143              } else {
2144                  $timestamp = strtotime( $changeset_date );
2145                  if ( ! $timestamp ) {
2146                      wp_send_json_error( 'bad_customize_changeset_date', 400 );
2147                  }
2148                  $changeset_date_gmt = gmdate( 'Y-m-d H:i:s', $timestamp );
2149              }
2150          }
2151  
2152          $r = $this->save_changeset_post( array(
2153              'status' => $changeset_status,
2154              'title' => $changeset_title,
2155              'date_gmt' => $changeset_date_gmt,
2156              'data' => $input_changeset_data,
2157          ) );
2158          if ( is_wp_error( $r ) ) {
2159              $response = array(
2160                  'message' => $r->get_error_message(),
2161                  'code' => $r->get_error_code(),
2162              );
2163              if ( is_array( $r->get_error_data() ) ) {
2164                  $response = array_merge( $response, $r->get_error_data() );
2165              } else {
2166                  $response['data'] = $r->get_error_data();
2167              }
2168          } else {
2169              $response = $r;
2170  
2171              // Note that if the changeset status was publish, then it will get set to trash if revisions are not supported.
2172              $response['changeset_status'] = get_post_status( $this->changeset_post_id() );
2173              if ( $is_publish && 'trash' === $response['changeset_status'] ) {
2174                  $response['changeset_status'] = 'publish';
2175              }
2176  
2177              if ( 'publish' === $response['changeset_status'] ) {
2178                  $response['next_changeset_uuid'] = wp_generate_uuid4();
2179              }
2180          }
2181  
2182          if ( isset( $response['setting_validities'] ) ) {
2183              $response['setting_validities'] = array_map( array( $this, 'prepare_setting_validity_for_js' ), $response['setting_validities'] );
2184          }
2185  
2186          /**
2187           * Filters response data for a successful customize_save Ajax request.
2188           *
2189           * This filter does not apply if there was a nonce or authentication failure.
2190           *
2191           * @since 4.2.0
2192           *
2193           * @param array                $response Additional information passed back to the 'saved'
2194           *                                       event on `wp.customize`.
2195           * @param WP_Customize_Manager $this     WP_Customize_Manager instance.
2196           */
2197          $response = apply_filters( 'customize_save_response', $response, $this );
2198  
2199          if ( is_wp_error( $r ) ) {
2200              wp_send_json_error( $response );
2201          } else {
2202              wp_send_json_success( $response );
2203          }
2204      }
2205  
2206      /**
2207       * Save the post for the loaded changeset.
2208       *
2209       * @since 4.7.0
2210       * @access public
2211       *
2212       * @param array $args {
2213       *     Args for changeset post.
2214       *
2215       *     @type array  $data            Optional additional changeset data. Values will be merged on top of any existing post values.
2216       *     @type string $status          Post status. Optional. If supplied, the save will be transactional and a post revision will be allowed.
2217       *     @type string $title           Post title. Optional.
2218       *     @type string $date_gmt        Date in GMT. Optional.
2219       *     @type int    $user_id         ID for user who is saving the changeset. Optional, defaults to the current user ID.
2220       *     @type bool   $starter_content Whether the data is starter content. If false (default), then $starter_content will be cleared for any $data being saved.
2221       * }
2222       *
2223       * @return array|WP_Error Returns array on success and WP_Error with array data on error.
2224       */
2225  	function save_changeset_post( $args = array() ) {
2226  
2227          $args = array_merge(
2228              array(
2229                  'status' => null,
2230                  'title' => null,
2231                  'data' => array(),
2232                  'date_gmt' => null,
2233                  'user_id' => get_current_user_id(),
2234                  'starter_content' => false,
2235              ),
2236              $args
2237          );
2238  
2239          $changeset_post_id = $this->changeset_post_id();
2240          $existing_changeset_data = array();
2241          if ( $changeset_post_id ) {
2242              $existing_status = get_post_status( $changeset_post_id );
2243              if ( 'publish' === $existing_status || 'trash' === $existing_status ) {
2244                  return new WP_Error( 'changeset_already_published' );
2245              }
2246  
2247              $existing_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
2248          }
2249  
2250          // Fail if attempting to publish but publish hook is missing.
2251          if ( 'publish' === $args['status'] && false === has_action( 'transition_post_status', '_wp_customize_publish_changeset' ) ) {
2252              return new WP_Error( 'missing_publish_callback' );
2253          }
2254  
2255          // Validate date.
2256          $now = gmdate( 'Y-m-d H:i:59' );
2257          if ( $args['date_gmt'] ) {
2258              $is_future_dated = ( mysql2date( 'U', $args['date_gmt'], false ) > mysql2date( 'U', $now, false ) );
2259              if ( ! $is_future_dated ) {
2260                  return new WP_Error( 'not_future_date' ); // Only future dates are allowed.
2261              }
2262  
2263              if ( ! $this->is_theme_active() && ( 'future' === $args['status'] || $is_future_dated ) ) {
2264                  return new WP_Error( 'cannot_schedule_theme_switches' ); // This should be allowed in the future, when theme is a regular setting.
2265              }
2266              $will_remain_auto_draft = ( ! $args['status'] && ( ! $changeset_post_id || 'auto-draft' === get_post_status( $changeset_post_id ) ) );
2267              if ( $will_remain_auto_draft ) {
2268                  return new WP_Error( 'cannot_supply_date_for_auto_draft_changeset' );
2269              }
2270          } elseif ( $changeset_post_id && 'future' === $args['status'] ) {
2271  
2272              // Fail if the new status is future but the existing post's date is not in the future.
2273              $changeset_post = get_post( $changeset_post_id );
2274              if ( mysql2date( 'U', $changeset_post->post_date_gmt, false ) <= mysql2date( 'U', $now, false ) ) {
2275                  return new WP_Error( 'not_future_date' );
2276              }
2277          }
2278  
2279          // The request was made via wp.customize.previewer.save().
2280          $update_transactionally = (bool) $args['status'];
2281          $allow_revision = (bool) $args['status'];
2282  
2283          // Amend post values with any supplied data.
2284          foreach ( $args['data'] as $setting_id => $setting_params ) {
2285              if ( array_key_exists( 'value', $setting_params ) ) {
2286                  $this->set_post_value( $setting_id, $setting_params['value'] ); // Add to post values so that they can be validated and sanitized.
2287              }
2288          }
2289  
2290          // Note that in addition to post data, this will include any stashed theme mods.
2291          $post_values = $this->unsanitized_post_values( array(
2292              'exclude_changeset' => true,
2293              'exclude_post_data' => false,
2294          ) );
2295          $this->add_dynamic_settings( array_keys( $post_values ) ); // Ensure settings get created even if they lack an input value.
2296  
2297          /*
2298           * Get list of IDs for settings that have values different from what is currently
2299           * saved in the changeset. By skipping any values that are already the same, the
2300           * subset of changed settings can be passed into validate_setting_values to prevent
2301           * an underprivileged modifying a single setting for which they have the capability
2302           * from being blocked from saving. This also prevents a user from touching of the
2303           * previous saved settings and overriding the associated user_id if they made no change.
2304           */
2305          $changed_setting_ids = array();
2306          foreach ( $post_values as $setting_id => $setting_value ) {
2307              $setting = $this->get_setting( $setting_id );
2308  
2309              if ( $setting && 'theme_mod' === $setting->type ) {
2310                  $prefixed_setting_id = $this->get_stylesheet() . '::' . $setting->id;
2311              } else {
2312                  $prefixed_setting_id = $setting_id;
2313              }
2314  
2315              $is_value_changed = (
2316                  ! isset( $existing_changeset_data[ $prefixed_setting_id ] )
2317                  ||
2318                  ! array_key_exists( 'value', $existing_changeset_data[ $prefixed_setting_id ] )
2319                  ||
2320                  $existing_changeset_data[ $prefixed_setting_id ]['value'] !== $setting_value
2321              );
2322              if ( $is_value_changed ) {
2323                  $changed_setting_ids[] = $setting_id;
2324              }
2325          }
2326  
2327          /**
2328           * Fires before save validation happens.
2329           *
2330           * Plugins can add just-in-time {@see 'customize_validate_{$this->ID}'} filters
2331           * at this point to catch any settings registered after `customize_register`.
2332           * The dynamic portion of the hook name, `$this->ID` refers to the setting ID.
2333           *
2334           * @since 4.6.0
2335           *
2336           * @param WP_Customize_Manager $this WP_Customize_Manager instance.
2337           */
2338          do_action( 'customize_save_validation_before', $this );
2339  
2340          // Validate settings.
2341          $validated_values = array_merge(
2342              array_fill_keys( array_keys( $args['data'] ), null ), // Make sure existence/capability checks are done on value-less setting updates.
2343              $post_values
2344          );
2345          $setting_validities = $this->validate_setting_values( $validated_values, array(
2346              'validate_capability' => true,
2347              'validate_existence' => true,
2348          ) );
2349          $invalid_setting_count = count( array_filter( $setting_validities, 'is_wp_error' ) );
2350  
2351          /*
2352           * Short-circuit if there are invalid settings the update is transactional.
2353           * A changeset update is transactional when a status is supplied in the request.
2354           */
2355          if ( $update_transactionally && $invalid_setting_count > 0 ) {
2356              $response = array(
2357                  'setting_validities' => $setting_validities,
2358                  'message' => sprintf( _n( 'There is %s invalid setting.', 'There are %s invalid settings.', $invalid_setting_count ), number_format_i18n( $invalid_setting_count ) ),
2359              );
2360              return new WP_Error( 'transaction_fail', '', $response );
2361          }
2362  
2363          // Obtain/merge data for changeset.
2364          $original_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
2365          $data = $original_changeset_data;
2366          if ( is_wp_error( $data ) ) {
2367              $data = array();
2368          }
2369  
2370          // Ensure that all post values are included in the changeset data.
2371          foreach ( $post_values as $setting_id => $post_value ) {
2372              if ( ! isset( $args['data'][ $setting_id ] ) ) {
2373                  $args['data'][ $setting_id ] = array();
2374              }
2375              if ( ! isset( $args['data'][ $setting_id ]['value'] ) ) {
2376                  $args['data'][ $setting_id ]['value'] = $post_value;
2377              }
2378          }
2379  
2380          foreach ( $args['data'] as $setting_id => $setting_params ) {
2381              $setting = $this->get_setting( $setting_id );
2382              if ( ! $setting || ! $setting->check_capabilities() ) {
2383                  continue;
2384              }
2385  
2386              // Skip updating changeset for invalid setting values.
2387              if ( isset( $setting_validities[ $setting_id ] ) && is_wp_error( $setting_validities[ $setting_id ] ) ) {
2388                  continue;
2389              }
2390  
2391              $changeset_setting_id = $setting_id;
2392              if ( 'theme_mod' === $setting->type ) {
2393                  $changeset_setting_id = sprintf( '%s::%s', $this->get_stylesheet(), $setting_id );
2394              }
2395  
2396              if ( null === $setting_params ) {
2397                  // Remove setting from changeset entirely.
2398                  unset( $data[ $changeset_setting_id ] );
2399              } else {
2400  
2401                  if ( ! isset( $data[ $changeset_setting_id ] ) ) {
2402                      $data[ $changeset_setting_id ] = array();
2403                  }
2404  
2405                  // Merge any additional setting params that have been supplied with the existing params.
2406                  $merged_setting_params = array_merge( $data[ $changeset_setting_id ], $setting_params );
2407  
2408                  // Skip updating setting params if unchanged (ensuring the user_id is not overwritten).
2409                  if ( $data[ $changeset_setting_id ] === $merged_setting_params ) {
2410                      continue;
2411                  }
2412  
2413                  $data[ $changeset_setting_id ] = array_merge(
2414                      $merged_setting_params,
2415                      array(
2416                          'type' => $setting->type,
2417                          'user_id' => $args['user_id'],
2418                      )
2419                  );
2420  
2421                  // Clear starter_content flag in data if changeset is not explicitly being updated for starter content.
2422                  if ( empty( $args['starter_content'] ) ) {
2423                      unset( $data[ $changeset_setting_id ]['starter_content'] );
2424                  }
2425              }
2426          }
2427  
2428          $filter_context = array(
2429              'uuid' => $this->changeset_uuid(),
2430              'title' => $args['title'],
2431              'status' => $args['status'],
2432              'date_gmt' => $args['date_gmt'],
2433              'post_id' => $changeset_post_id,
2434              'previous_data' => is_wp_error( $original_changeset_data ) ? array() : $original_changeset_data,
2435              'manager' => $this,
2436          );
2437  
2438          /**
2439           * Filters the settings' data that will be persisted into the changeset.
2440           *
2441           * Plugins may amend additional data (such as additional meta for settings) into the changeset with this filter.
2442           *
2443           * @since 4.7.0
2444           *
2445           * @param array $data Updated changeset data, mapping setting IDs to arrays containing a $value item and optionally other metadata.
2446           * @param array $context {
2447           *     Filter context.
2448           *
2449           *     @type string               $uuid          Changeset UUID.
2450           *     @type string               $title         Requested title for the changeset post.
2451           *     @type string               $status        Requested status for the changeset post.
2452           *     @type string               $date_gmt      Requested date for the changeset post in MySQL format and GMT timezone.
2453           *     @type int|false            $post_id       Post ID for the changeset, or false if it doesn't exist yet.
2454           *     @type array                $previous_data Previous data contained in the changeset.
2455           *     @type WP_Customize_Manager $manager       Manager instance.
2456           * }
2457           */
2458          $data = apply_filters( 'customize_changeset_save_data', $data, $filter_context );
2459  
2460          // Switch theme if publishing changes now.
2461          if ( 'publish' === $args['status'] && ! $this->is_theme_active() ) {
2462              // Temporarily stop previewing the theme to allow switch_themes() to operate properly.
2463              $this->stop_previewing_theme();
2464              switch_theme( $this->get_stylesheet() );
2465              update_option( 'theme_switched_via_customizer', true );
2466              $this->start_previewing_theme();
2467          }
2468  
2469          // Gather the data for wp_insert_post()/wp_update_post().
2470          $json_options = 0;
2471          if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
2472              $json_options |= JSON_UNESCAPED_SLASHES; // Introduced in PHP 5.4. This is only to improve readability as slashes needn't be escaped in storage.
2473          }
2474          $json_options |= JSON_PRETTY_PRINT; // Also introduced in PHP 5.4, but WP defines constant for back compat. See WP Trac #30139.
2475          $post_array = array(
2476              'post_content' => wp_json_encode( $data, $json_options ),
2477          );
2478          if ( $args['title'] ) {
2479              $post_array['post_title'] = $args['title'];
2480          }
2481          if ( $changeset_post_id ) {
2482              $post_array['ID'] = $changeset_post_id;
2483          } else {
2484              $post_array['post_type'] = 'customize_changeset';
2485              $post_array['post_name'] = $this->changeset_uuid();
2486              $post_array['post_status'] = 'auto-draft';
2487          }
2488          if ( $args['status'] ) {
2489              $post_array['post_status'] = $args['status'];
2490          }
2491  
2492          // Reset post date to now if we are publishing, otherwise pass post_date_gmt and translate for post_date.
2493          if ( 'publish' === $args['status'] ) {
2494              $post_array['post_date_gmt'] = '0000-00-00 00:00:00';
2495              $post_array['post_date'] = '0000-00-00 00:00:00';
2496          } elseif ( $args['date_gmt'] ) {
2497              $post_array['post_date_gmt'] = $args['date_gmt'];
2498              $post_array['post_date'] = get_date_from_gmt( $args['date_gmt'] );
2499          } elseif ( $changeset_post_id && 'auto-draft' === get_post_status( $changeset_post_id ) ) {
2500              /*
2501               * Keep bumping the date for the auto-draft whenever it is modified;
2502               * this extends its life, preserving it from garbage-collection via
2503               * wp_delete_auto_drafts().
2504               */
2505              $post_array['post_date'] = current_time( 'mysql' );
2506              $post_array['post_date_gmt'] = '';
2507          }
2508  
2509          $this->store_changeset_revision = $allow_revision;
2510          add_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ), 5, 3 );
2511  
2512          // Update the changeset post. The publish_customize_changeset action will cause the settings in the changeset to be saved via WP_Customize_Setting::save().
2513          $has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
2514          if ( $has_kses ) {
2515              kses_remove_filters(); // Prevent KSES from corrupting JSON in post_content.
2516          }
2517  
2518          // Note that updating a post with publish status will trigger WP_Customize_Manager::publish_changeset_values().
2519          if ( $changeset_post_id ) {
2520              $post_array['edit_date'] = true; // Prevent date clearing.
2521              $r = wp_update_post( wp_slash( $post_array ), true );
2522          } else {
2523              $r = wp_insert_post( wp_slash( $post_array ), true );
2524              if ( ! is_wp_error( $r ) ) {
2525                  $this->_changeset_post_id = $r; // Update cached post ID for the loaded changeset.
2526              }
2527          }
2528          if ( $has_kses ) {
2529              kses_init_filters();
2530          }
2531          $this->_changeset_data = null; // Reset so WP_Customize_Manager::changeset_data() will re-populate with updated contents.
2532  
2533          remove_filter( 'wp_save_post_revision_post_has_changed', array( $this, '_filter_revision_post_has_changed' ) );
2534  
2535          $response = array(
2536              'setting_validities' => $setting_validities,
2537          );
2538  
2539          if ( is_wp_error( $r ) ) {
2540              $response['changeset_post_save_failure'] = $r->get_error_code();
2541              return new WP_Error( 'changeset_post_save_failure', '', $response );
2542          }
2543  
2544          return $response;
2545      }
2546  
2547      /**
2548       * Whether a changeset revision should be made.
2549       *
2550       * @since 4.7.0
2551       * @access private
2552       * @var bool
2553       */
2554      protected $store_changeset_revision;
2555  
2556      /**
2557       * Filters whether a changeset has changed to create a new revision.
2558       *
2559       * Note that this will not be called while a changeset post remains in auto-draft status.
2560       *
2561       * @since 4.7.0
2562       * @access private
2563       *
2564       * @param bool    $post_has_changed Whether the post has changed.
2565       * @param WP_Post $last_revision    The last revision post object.
2566       * @param WP_Post $post             The post object.
2567       *
2568       * @return bool Whether a revision should be made.
2569       */
2570  	public function _filter_revision_post_has_changed( $post_has_changed, $last_revision, $post ) {
2571          unset( $last_revision );
2572          if ( 'customize_changeset' === $post->post_type ) {
2573              $post_has_changed = $this->store_changeset_revision;
2574          }
2575          return $post_has_changed;
2576      }
2577  
2578      /**
2579       * Publish changeset values.
2580       *
2581       * This will the values contained in a changeset, even changesets that do not
2582       * correspond to current manager instance. This is called by
2583       * `_wp_customize_publish_changeset()` when a customize_changeset post is
2584       * transitioned to the `publish` status. As such, this method should not be
2585       * called directly and instead `wp_publish_post()` should be used.
2586       *
2587       * Please note that if the settings in the changeset are for a non-activated
2588       * theme, the theme must first be switched to (via `switch_theme()`) before
2589       * invoking this method.
2590       *
2591       * @since 4.7.0
2592       * @access private
2593       * @see _wp_customize_publish_changeset()
2594       *
2595       * @param int $changeset_post_id ID for customize_changeset post. Defaults to the changeset for the current manager instance.
2596       * @return true|WP_Error True or error info.
2597       */
2598  	public function _publish_changeset_values( $changeset_post_id ) {
2599          $publishing_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
2600          if ( is_wp_error( $publishing_changeset_data ) ) {
2601              return $publishing_changeset_data;
2602          }
2603  
2604          $changeset_post = get_post( $changeset_post_id );
2605  
2606          /*
2607           * Temporarily override the changeset context so that it will be read
2608           * in calls to unsanitized_post_values() and so that it will be available
2609           * on the $wp_customize object passed to hooks during the save logic.
2610           */
2611          $previous_changeset_post_id = $this->_changeset_post_id;
2612          $this->_changeset_post_id   = $changeset_post_id;
2613          $previous_changeset_uuid    = $this->_changeset_uuid;
2614          $this->_changeset_uuid      = $changeset_post->post_name;
2615          $previous_changeset_data    = $this->_changeset_data;
2616          $this->_changeset_data      = $publishing_changeset_data;
2617  
2618          // Parse changeset data to identify theme mod settings and user IDs associated with settings to be saved.
2619          $setting_user_ids = array();
2620          $theme_mod_settings = array();
2621          $namespace_pattern = '/^(?P<stylesheet>.+?)::(?P<setting_id>.+)$/';
2622          $matches = array();
2623          foreach ( $this->_changeset_data as $raw_setting_id => $setting_params ) {
2624              $actual_setting_id = null;
2625              $is_theme_mod_setting = (
2626                  isset( $setting_params['value'] )
2627                  &&
2628                  isset( $setting_params['type'] )
2629                  &&
2630                  'theme_mod' === $setting_params['type']
2631                  &&
2632                  preg_match( $namespace_pattern, $raw_setting_id, $matches )
2633              );
2634              if ( $is_theme_mod_setting ) {
2635                  if ( ! isset( $theme_mod_settings[ $matches['stylesheet'] ] ) ) {
2636                      $theme_mod_settings[ $matches['stylesheet'] ] = array();
2637                  }
2638                  $theme_mod_settings[ $matches['stylesheet'] ][ $matches['setting_id'] ] = $setting_params;
2639  
2640                  if ( $this->get_stylesheet() === $matches['stylesheet'] ) {
2641                      $actual_setting_id = $matches['setting_id'];
2642                  }
2643              } else {
2644                  $actual_setting_id = $raw_setting_id;
2645              }
2646  
2647              // Keep track of the user IDs for settings actually for this theme.
2648              if ( $actual_setting_id && isset( $setting_params['user_id'] ) ) {
2649                  $setting_user_ids[ $actual_setting_id ] = $setting_params['user_id'];
2650              }
2651          }
2652  
2653          $changeset_setting_values = $this->unsanitized_post_values( array(
2654              'exclude_post_data' => true,
2655              'exclude_changeset' => false,
2656          ) );
2657          $changeset_setting_ids = array_keys( $changeset_setting_values );
2658          $this->add_dynamic_settings( $changeset_setting_ids );
2659  
2660          /**
2661           * Fires once the theme has switched in the Customizer, but before settings
2662           * have been saved.
2663           *
2664           * @since 3.4.0
2665           *
2666           * @param WP_Customize_Manager $manager WP_Customize_Manager instance.
2667           */
2668          do_action( 'customize_save', $this );
2669  
2670          /*
2671           * Ensure that all settings will allow themselves to be saved. Note that
2672           * this is safe because the setting would have checked the capability
2673           * when the setting value was written into the changeset. So this is why
2674           * an additional capability check is not required here.
2675           */
2676          $original_setting_capabilities = array();
2677          foreach ( $changeset_setting_ids as $setting_id ) {
2678              $setting = $this->get_setting( $setting_id );
2679              if ( $setting && ! isset( $setting_user_ids[ $setting_id ] ) ) {
2680                  $original_setting_capabilities[ $setting->id ] = $setting->capability;
2681                  $setting->capability = 'exist';
2682              }
2683          }
2684  
2685          $original_user_id = get_current_user_id();
2686          foreach ( $changeset_setting_ids as $setting_id ) {
2687              $setting = $this->get_setting( $setting_id );
2688              if ( $setting ) {
2689                  /*
2690                   * Set the current user to match the user who saved the value into
2691                   * the changeset so that any filters that apply during the save
2692                   * process will respect the original user's capabilities. This
2693                   * will ensure, for example, that KSES won't strip unsafe HTML
2694                   * when a scheduled changeset publishes via WP Cron.
2695                   */
2696                  if ( isset( $setting_user_ids[ $setting_id ] ) ) {
2697                      wp_set_current_user( $setting_user_ids[ $setting_id ] );
2698                  } else {
2699                      wp_set_current_user( $original_user_id );
2700                  }
2701  
2702                  $setting->save();
2703              }
2704          }
2705          wp_set_current_user( $original_user_id );
2706  
2707          // Update the stashed theme mod settings, removing the active theme's stashed settings, if activated.
2708          if ( did_action( 'switch_theme' ) ) {
2709              $other_theme_mod_settings = $theme_mod_settings;
2710              unset( $other_theme_mod_settings[ $this->get_stylesheet() ] );
2711              $this->update_stashed_theme_mod_settings( $other_theme_mod_settings );
2712          }
2713  
2714          /**
2715           * Fires after Customize settings have been saved.
2716           *
2717           * @since 3.6.0
2718           *
2719           * @param WP_Customize_Manager $manager WP_Customize_Manager instance.
2720           */
2721          do_action( 'customize_save_after', $this );
2722  
2723          // Restore original capabilities.
2724          foreach ( $original_setting_capabilities as $setting_id => $capability ) {
2725              $setting = $this->get_setting( $setting_id );
2726              if ( $setting ) {
2727                  $setting->capability = $capability;
2728              }
2729          }
2730  
2731          // Restore original changeset data.
2732          $this->_changeset_data    = $previous_changeset_data;
2733          $this->_changeset_post_id = $previous_changeset_post_id;
2734          $this->_changeset_uuid    = $previous_changeset_uuid;
2735  
2736          return true;
2737      }
2738  
2739      /**
2740       * Update stashed theme mod settings.
2741       *
2742       * @since 4.7.0
2743       * @access private
2744       *
2745       * @param array $inactive_theme_mod_settings Mapping of stylesheet to arrays of theme mod settings.
2746       * @return array|false Returns array of updated stashed theme mods or false if the update failed or there were no changes.
2747       */
2748  	protected function update_stashed_theme_mod_settings( $inactive_theme_mod_settings ) {
2749          $stashed_theme_mod_settings = get_option( 'customize_stashed_theme_mods' );
2750          if ( empty( $stashed_theme_mod_settings ) ) {
2751              $stashed_theme_mod_settings = array();
2752          }
2753  
2754          // Delete any stashed theme mods for the active theme since since they would have been loaded and saved upon activation.
2755          unset( $stashed_theme_mod_settings[ $this->get_stylesheet() ] );
2756  
2757          // Merge inactive theme mods with the stashed theme mod settings.
2758          foreach ( $inactive_theme_mod_settings as $stylesheet => $theme_mod_settings ) {
2759              if ( ! isset( $stashed_theme_mod_settings[ $stylesheet ] ) ) {
2760                  $stashed_theme_mod_settings[ $stylesheet ] = array();
2761              }
2762  
2763              $stashed_theme_mod_settings[ $stylesheet ] = array_merge(
2764                  $stashed_theme_mod_settings[ $stylesheet ],
2765                  $theme_mod_settings
2766              );
2767          }
2768  
2769          $autoload = false;
2770          $result = update_option( 'customize_stashed_theme_mods', $stashed_theme_mod_settings, $autoload );
2771          if ( ! $result ) {
2772              return false;
2773          }
2774          return $stashed_theme_mod_settings;
2775      }
2776  
2777      /**
2778       * Refresh nonces for the current preview.
2779       *
2780       * @since 4.2.0
2781       */
2782  	public function refresh_nonces() {
2783          if ( ! $this->is_preview() ) {
2784              wp_send_json_error( 'not_preview' );
2785          }
2786  
2787          wp_send_json_success( $this->get_nonces() );
2788      }
2789  
2790      /**
2791       * Add a customize setting.
2792       *
2793       * @since 3.4.0
2794       * @since 4.5.0 Return added WP_Customize_Setting instance.
2795       * @access public
2796       *
2797       * @param WP_Customize_Setting|string $id   Customize Setting object, or ID.
2798       * @param array                       $args Setting arguments; passed to WP_Customize_Setting
2799       *                                          constructor.
2800       * @return WP_Customize_Setting             The instance of the setting that was added.
2801       */
2802  	public function add_setting( $id, $args = array() ) {
2803          if ( $id instanceof WP_Customize_Setting ) {
2804              $setting = $id;
2805          } else {
2806              $class = 'WP_Customize_Setting';
2807  
2808              /** This filter is documented in wp-includes/class-wp-customize-manager.php */
2809              $args = apply_filters( 'customize_dynamic_setting_args', $args, $id );
2810  
2811              /** This filter is documented in wp-includes/class-wp-customize-manager.php */
2812              $class = apply_filters( 'customize_dynamic_setting_class', $class, $id, $args );
2813  
2814              $setting = new $class( $this, $id, $args );
2815          }
2816  
2817          $this->settings[ $setting->id ] = $setting;
2818          return $setting;
2819      }
2820  
2821      /**
2822       * Register any dynamically-created settings, such as those from $_POST['customized']
2823       * that have no corresponding setting created.
2824       *
2825       * This is a mechanism to "wake up" settings that have been dynamically created
2826       * on the front end and have been sent to WordPress in `$_POST['customized']`. When WP
2827       * loads, the dynamically-created settings then will get created and previewed
2828       * even though they are not directly created statically with code.
2829       *
2830       * @since 4.2.0
2831       * @access public
2832       *
2833       * @param array $setting_ids The setting IDs to add.
2834       * @return array The WP_Customize_Setting objects added.
2835       */
2836  	public function add_dynamic_settings( $setting_ids ) {
2837          $new_settings = array();
2838          foreach ( $setting_ids as $setting_id ) {
2839              // Skip settings already created
2840              if ( $this->get_setting( $setting_id ) ) {
2841                  continue;
2842              }
2843  
2844              $setting_args = false;
2845              $setting_class = 'WP_Customize_Setting';
2846  
2847              /**
2848               * Filters a dynamic setting's constructor args.
2849               *
2850               * For a dynamic setting to be registered, this filter must be employed
2851               * to override the default false value with an array of args to pass to
2852               * the WP_Customize_Setting constructor.
2853               *
2854               * @since 4.2.0
2855               *
2856               * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
2857               * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
2858               */
2859              $setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
2860              if ( false === $setting_args ) {
2861                  continue;
2862              }
2863  
2864              /**
2865               * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
2866               *
2867               * @since 4.2.0
2868               *
2869               * @param string $setting_class WP_Customize_Setting or a subclass.
2870               * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
2871               * @param array  $setting_args  WP_Customize_Setting or a subclass.
2872               */
2873              $setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
2874  
2875              $setting = new $setting_class( $this, $setting_id, $setting_args );
2876  
2877              $this->add_setting( $setting );
2878              $new_settings[] = $setting;
2879          }
2880          return $new_settings;
2881      }
2882  
2883      /**
2884       * Retrieve a customize setting.
2885       *
2886       * @since 3.4.0
2887       *
2888       * @param string $id Customize Setting ID.
2889       * @return WP_Customize_Setting|void The setting, if set.
2890       */
2891  	public function get_setting( $id ) {
2892          if ( isset( $this->settings[ $id ] ) ) {
2893              return $this->settings[ $id ];
2894          }
2895      }
2896  
2897      /**
2898       * Remove a customize setting.
2899       *
2900       * @since 3.4.0
2901       *
2902       * @param string $id Customize Setting ID.
2903       */
2904  	public function remove_setting( $id ) {
2905          unset( $this->settings[ $id ] );
2906      }
2907  
2908      /**
2909       * Add a customize panel.
2910       *
2911       * @since 4.0.0
2912       * @since 4.5.0 Return added WP_Customize_Panel instance.
2913       * @access public
2914       *
2915       * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
2916       * @param array                     $args Optional. Panel arguments. Default empty array.
2917       *
2918       * @return WP_Customize_Panel             The instance of the panel that was added.
2919       */
2920  	public function add_panel( $id, $args = array() ) {
2921          if ( $id instanceof WP_Customize_Panel ) {
2922              $panel = $id;
2923          } else {
2924              $panel = new WP_Customize_Panel( $this, $id, $args );
2925          }
2926  
2927          $this->panels[ $panel->id ] = $panel;
2928          return $panel;
2929      }
2930  
2931      /**
2932       * Retrieve a customize panel.
2933       *
2934       * @since 4.0.0
2935       * @access public
2936       *
2937       * @param string $id Panel ID to get.
2938       * @return WP_Customize_Panel|void Requested panel instance, if set.
2939       */
2940  	public function get_panel( $id ) {
2941          if ( isset( $this->panels[ $id ] ) ) {
2942              return $this->panels[ $id ];
2943          }
2944      }
2945  
2946      /**
2947       * Remove a customize panel.
2948       *
2949       * @since 4.0.0
2950       * @access public
2951       *
2952       * @param string $id Panel ID to remove.
2953       */
2954  	public function remove_panel( $id ) {
2955          // Removing core components this way is _doing_it_wrong().
2956          if ( in_array( $id, $this->components, true ) ) {
2957              /* translators: 1: panel id, 2: link to 'customize_loaded_components' filter reference */
2958              $message = sprintf( __( 'Removing %1$s manually will cause PHP warnings. Use the %2$s filter instead.' ),
2959                  $id,
2960                  '<a href="' . esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ) . '"><code>customize_loaded_components</code></a>'
2961              );
2962  
2963              _doing_it_wrong( __METHOD__, $message, '4.5.0' );
2964          }
2965          unset( $this->panels[ $id ] );
2966      }
2967  
2968      /**
2969       * Register a customize panel type.
2970       *
2971       * Registered types are eligible to be rendered via JS and created dynamically.
2972       *
2973       * @since 4.3.0
2974       * @access public
2975       *
2976       * @see WP_Customize_Panel
2977       *
2978       * @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
2979       */
2980  	public function register_panel_type( $panel ) {
2981          $this->registered_panel_types[] = $panel;
2982      }
2983  
2984      /**
2985       * Render JS templates for all registered panel types.
2986       *
2987       * @since 4.3.0
2988       * @access public
2989       */
2990  	public function render_panel_templates() {
2991          foreach ( $this->registered_panel_types as $panel_type ) {
2992              $panel = new $panel_type( $this, 'temp', array() );
2993              $panel->print_template();
2994          }
2995      }
2996  
2997      /**
2998       * Add a customize section.
2999       *
3000       * @since 3.4.0
3001       * @since 4.5.0 Return added WP_Customize_Section instance.
3002       * @access public
3003       *
3004       * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
3005       * @param array                       $args Section arguments.
3006       *
3007       * @return WP_Customize_Section             The instance of the section that was added.
3008       */
3009  	public function add_section( $id, $args = array() ) {
3010          if ( $id instanceof WP_Customize_Section ) {
3011              $section = $id;
3012          } else {
3013              $section = new WP_Customize_Section( $this, $id, $args );
3014          }
3015  
3016          $this->sections[ $section->id ] = $section;
3017          return $section;
3018      }
3019  
3020      /**
3021       * Retrieve a customize section.
3022       *
3023       * @since 3.4.0
3024       *
3025       * @param string $id Section ID.
3026       * @return WP_Customize_Section|void The section, if set.
3027       */
3028  	public function get_section( $id ) {
3029          if ( isset( $this->sections[ $id ] ) )
3030              return $this->sections[ $id ];
3031      }
3032  
3033      /**
3034       * Remove a customize section.
3035       *
3036       * @since 3.4.0
3037       *
3038       * @param string $id Section ID.
3039       */
3040  	public function remove_section( $id ) {
3041          unset( $this->sections[ $id ] );
3042      }
3043  
3044      /**
3045       * Register a customize section type.
3046       *
3047       * Registered types are eligible to be rendered via JS and created dynamically.
3048       *
3049       * @since 4.3.0
3050       * @access public
3051       *
3052       * @see WP_Customize_Section
3053       *
3054       * @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
3055       */
3056  	public function register_section_type( $section ) {
3057          $this->registered_section_types[] = $section;
3058      }
3059  
3060      /**
3061       * Render JS templates for all registered section types.
3062       *
3063       * @since 4.3.0
3064       * @access public
3065       */
3066  	public function render_section_templates() {
3067          foreach ( $this->registered_section_types as $section_type ) {
3068              $section = new $section_type( $this, 'temp', array() );
3069              $section->print_template();
3070          }
3071      }
3072  
3073      /**
3074       * Add a customize control.
3075       *
3076       * @since 3.4.0
3077       * @since 4.5.0 Return added WP_Customize_Control instance.
3078       * @access public
3079       *
3080       * @param WP_Customize_Control|string $id   Customize Control object, or ID.
3081       * @param array                       $args Control arguments; passed to WP_Customize_Control
3082       *                                          constructor.
3083       * @return WP_Customize_Control             The instance of the control that was added.
3084       */
3085  	public function add_control( $id, $args = array() ) {
3086          if ( $id instanceof WP_Customize_Control ) {
3087              $control = $id;
3088          } else {
3089              $control = new WP_Customize_Control( $this, $id, $args );
3090          }
3091  
3092          $this->controls[ $control->id ] = $control;
3093          return $control;
3094      }
3095  
3096      /**
3097       * Retrieve a customize control.
3098       *
3099       * @since 3.4.0
3100       *
3101       * @param string $id ID of the control.
3102       * @return WP_Customize_Control|void The control object, if set.
3103       */
3104  	public function get_control( $id ) {
3105          if ( isset( $this->controls[ $id ] ) )
3106              return $this->controls[ $id ];
3107      }
3108  
3109      /**
3110       * Remove a customize control.
3111       *
3112       * @since 3.4.0
3113       *
3114       * @param string $id ID of the control.
3115       */
3116  	public function remove_control( $id ) {
3117          unset( $this->controls[ $id ] );
3118      }
3119  
3120      /**
3121       * Register a customize control type.
3122       *
3123       * Registered types are eligible to be rendered via JS and created dynamically.
3124       *
3125       * @since 4.1.0
3126       * @access public
3127       *
3128       * @param string $control Name of a custom control which is a subclass of
3129       *                        WP_Customize_Control.
3130       */
3131  	public function register_control_type( $control ) {
3132          $this->registered_control_types[] = $control;
3133      }
3134  
3135      /**
3136       * Render JS templates for all registered control types.
3137       *
3138       * @since 4.1.0
3139       * @access public
3140       */
3141  	public function render_control_templates() {
3142          foreach ( $this->registered_control_types as $control_type ) {
3143              $control = new $control_type( $this, 'temp', array(
3144                  'settings' => array(),
3145              ) );
3146              $control->print_template();
3147          }
3148          ?>
3149          <script type="text/html" id="tmpl-customize-control-notifications">
3150              <ul>
3151                  <# _.each( data.notifications, function( notification ) { #>
3152                      <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{{ notification.message || notification.code }}}</li>
3153                  <# } ); #>
3154              </ul>
3155          </script>
3156          <?php
3157      }
3158  
3159      /**
3160       * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
3161       *
3162       * @since 3.4.0
3163       * @deprecated 4.7.0 Use wp_list_sort()
3164       *
3165       * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
3166       * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
3167       * @return int
3168       */
3169  	protected function _cmp_priority( $a, $b ) {
3170          _deprecated_function( __METHOD__, '4.7.0', 'wp_list_sort' );
3171  
3172          if ( $a->priority === $b->priority ) {
3173              return $a->instance_number - $b->instance_number;
3174          } else {
3175              return $a->priority - $b->priority;
3176          }
3177      }
3178  
3179      /**
3180       * Prepare panels, sections, and controls.
3181       *
3182       * For each, check if required related components exist,
3183       * whether the user has the necessary capabilities,
3184       * and sort by priority.
3185       *
3186       * @since 3.4.0
3187       */
3188  	public function prepare_controls() {
3189  
3190          $controls = array();
3191          $this->controls = wp_list_sort( $this->controls, array(
3192              'priority'        => 'ASC',
3193              'instance_number' => 'ASC',
3194          ), 'ASC', true );
3195  
3196          foreach ( $this->controls as $id => $control ) {
3197              if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
3198                  continue;
3199              }
3200  
3201              $this->sections[ $control->section ]->controls[] = $control;
3202              $controls[ $id ] = $control;
3203          }
3204          $this->controls = $controls;
3205  
3206          // Prepare sections.
3207          $this->sections = wp_list_sort( $this->sections, array(
3208              'priority'        => 'ASC',
3209              'instance_number' => 'ASC',
3210          ), 'ASC', true );
3211          $sections = array();
3212  
3213          foreach ( $this->sections as $section ) {
3214              if ( ! $section->check_capabilities() ) {
3215                  continue;
3216              }
3217  
3218  
3219              $section->controls = wp_list_sort( $section->controls, array(
3220                  'priority'        => 'ASC',
3221                  'instance_number' => 'ASC',
3222              ) );
3223  
3224              if ( ! $section->panel ) {
3225                  // Top-level section.
3226                  $sections[ $section->id ] = $section;
3227              } else {
3228                  // This section belongs to a panel.
3229                  if ( isset( $this->panels [ $section->panel ] ) ) {
3230                      $this->panels[ $section->panel ]->sections[ $section->id ] = $section;
3231                  }
3232              }
3233          }
3234          $this->sections = $sections;
3235  
3236          // Prepare panels.
3237          $this->panels = wp_list_sort( $this->panels, array(
3238              'priority'        => 'ASC',
3239              'instance_number' => 'ASC',
3240          ), 'ASC', true );
3241          $panels = array();
3242  
3243          foreach ( $this->panels as $panel ) {
3244              if ( ! $panel->check_capabilities() ) {
3245                  continue;
3246              }
3247  
3248              $panel->sections = wp_list_sort( $panel->sections, array(
3249                  'priority'        => 'ASC',
3250                  'instance_number' => 'ASC',
3251              ), 'ASC', true );
3252              $panels[ $panel->id ] = $panel;
3253          }
3254          $this->panels = $panels;
3255  
3256          // Sort panels and top-level sections together.
3257          $this->containers = array_merge( $this->panels, $this->sections );
3258          $this->containers = wp_list_sort( $this->containers, array(
3259              'priority'        => 'ASC',
3260              'instance_number' => 'ASC',
3261          ), 'ASC', true );
3262      }
3263  
3264      /**
3265       * Enqueue scripts for customize controls.
3266       *
3267       * @since 3.4.0
3268       */
3269  	public function enqueue_control_scripts() {
3270          foreach ( $this->controls as $control ) {
3271              $control->enqueue();
3272          }
3273      }
3274  
3275      /**
3276       * Determine whether the user agent is iOS.
3277       *
3278       * @since 4.4.0
3279       * @access public
3280       *
3281       * @return bool Whether the user agent is iOS.
3282       */
3283  	public function is_ios() {
3284          return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
3285      }
3286  
3287      /**
3288       * Get the template string for the Customizer pane document title.
3289       *
3290       * @since 4.4.0
3291       * @access public
3292       *
3293       * @return string The template string for the document title.
3294       */
3295  	public function get_document_title_template() {
3296          if ( $this->is_theme_active() ) {
3297              /* translators: %s: document title from the preview */
3298              $document_title_tmpl = __( 'Customize: %s' );
3299          } else {
3300              /* translators: %s: document title from the preview */
3301              $document_title_tmpl = __( 'Live Preview: %s' );
3302          }
3303          $document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
3304          return $document_title_tmpl;
3305      }
3306  
3307      /**
3308       * Set the initial URL to be previewed.
3309       *
3310       * URL is validated.
3311       *
3312       * @since 4.4.0
3313       * @access public
3314       *
3315       * @param string $preview_url URL to be previewed.
3316       */
3317  	public function set_preview_url( $preview_url ) {
3318          $preview_url = esc_url_raw( $preview_url );
3319          $this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
3320      }
3321  
3322      /**
3323       * Get the initial URL to be previewed.
3324       *
3325       * @since 4.4.0
3326       * @access public
3327       *
3328       * @return string URL being previewed.
3329       */
3330  	public function get_preview_url() {
3331          if ( empty( $this->preview_url ) ) {
3332              $preview_url = home_url( '/' );
3333          } else {
3334              $preview_url = $this->preview_url;
3335          }
3336          return $preview_url;
3337      }
3338  
3339      /**
3340       * Determines whether the admin and the frontend are on different domains.
3341       *
3342       * @since 4.7.0
3343       * @access public
3344       *
3345       * @return bool Whether cross-domain.
3346       */
3347  	public function is_cross_domain() {
3348          $admin_origin = wp_parse_url( admin_url() );
3349          $home_origin = wp_parse_url( home_url() );
3350          $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
3351          return $cross_domain;
3352      }
3353  
3354      /**
3355       * Get URLs allowed to be previewed.
3356       *
3357       * If the front end and the admin are served from the same domain, load the
3358       * preview over ssl if the Customizer is being loaded over ssl. This avoids
3359       * insecure content warnings. This is not attempted if the admin and front end
3360       * are on different domains to avoid the case where the front end doesn't have
3361       * ssl certs. Domain mapping plugins can allow other urls in these conditions
3362       * using the customize_allowed_urls filter.
3363       *
3364       * @since 4.7.0
3365       * @access public
3366       *
3367       * @returns array Allowed URLs.
3368       */
3369  	public function get_allowed_urls() {
3370          $allowed_urls = array( home_url( '/' ) );
3371  
3372          if ( is_ssl() && ! $this->is_cross_domain() ) {
3373              $allowed_urls[] = home_url( '/', 'https' );
3374          }
3375  
3376          /**
3377           * Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
3378           *
3379           * @since 3.4.0
3380           *
3381           * @param array $allowed_urls An array of allowed URLs.
3382           */
3383          $allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
3384  
3385          return $allowed_urls;
3386      }
3387  
3388      /**
3389       * Get messenger channel.
3390       *
3391       * @since 4.7.0
3392       * @access public
3393       *
3394       * @return string Messenger channel.
3395       */
3396  	public function get_messenger_channel() {
3397          return $this->messenger_channel;
3398      }
3399  
3400      /**
3401       * Set URL to link the user to when closing the Customizer.
3402       *
3403       * URL is validated.
3404       *
3405       * @since 4.4.0
3406       * @access public
3407       *
3408       * @param string $return_url URL for return link.
3409       */
3410  	public function set_return_url( $return_url ) {
3411          $return_url = esc_url_raw( $return_url );
3412          $return_url = remove_query_arg( wp_removable_query_args(), $return_url );
3413          $return_url = wp_validate_redirect( $return_url );
3414          $this->return_url = $return_url;
3415      }
3416  
3417      /**
3418       * Get URL to link the user to when closing the Customizer.
3419       *
3420       * @since 4.4.0
3421       * @access public
3422       *
3423       * @return string URL for link to close Customizer.
3424       */
3425  	public function get_return_url() {
3426          $referer = wp_get_referer();
3427          $excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
3428  
3429          if ( $this->return_url ) {
3430              $return_url = $this->return_url;
3431          } else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
3432              $return_url = $referer;
3433          } else if ( $this->preview_url ) {
3434              $return_url = $this->preview_url;
3435          } else {
3436              $return_url = home_url( '/' );
3437          }
3438          return $return_url;
3439      }
3440  
3441      /**
3442       * Set the autofocused constructs.
3443       *
3444       * @since 4.4.0
3445       * @access public
3446       *
3447       * @param array $autofocus {
3448       *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
3449       *
3450       *     @type string [$control]  ID for control to be autofocused.
3451       *     @type string [$section]  ID for section to be autofocused.
3452       *     @type string [$panel]    ID for panel to be autofocused.
3453       * }
3454       */
3455  	public function set_autofocus( $autofocus ) {
3456          $this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
3457      }
3458  
3459      /**
3460       * Get the autofocused constructs.
3461       *
3462       * @since 4.4.0
3463       * @access public
3464       *
3465       * @return array {
3466       *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
3467       *
3468       *     @type string [$control]  ID for control to be autofocused.
3469       *     @type string [$section]  ID for section to be autofocused.
3470       *     @type string [$panel]    ID for panel to be autofocused.
3471       * }
3472       */
3473  	public function get_autofocus() {
3474          return $this->autofocus;
3475      }
3476  
3477      /**
3478       * Get nonces for the Customizer.
3479       *
3480       * @since 4.5.0
3481       * @return array Nonces.
3482       */
3483  	public function get_nonces() {
3484          $nonces = array(
3485              'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
3486              'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
3487          );
3488  
3489          /**
3490           * Filters nonces for Customizer.
3491           *
3492           * @since 4.2.0
3493           *
3494           * @param array                $nonces Array of refreshed nonces for save and
3495           *                                     preview actions.
3496           * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
3497           */
3498          $nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
3499  
3500          return $nonces;
3501      }
3502  
3503      /**
3504       * Print JavaScript settings for parent window.
3505       *
3506       * @since 4.4.0
3507       */
3508  	public function customize_pane_settings() {
3509  
3510          $login_url = add_query_arg( array(
3511              'interim-login' => 1,
3512              'customize-login' => 1,
3513          ), wp_login_url() );
3514  
3515          // Ensure dirty flags are set for modified settings.
3516          foreach ( array_keys( $this->unsanitized_post_values() ) as $setting_id ) {
3517              $setting = $this->get_setting( $setting_id );
3518              if ( $setting ) {
3519                  $setting->dirty = true;
3520              }
3521          }
3522  
3523          // Prepare Customizer settings to pass to JavaScript.
3524          $settings = array(
3525              'changeset' => array(
3526                  'uuid' => $this->changeset_uuid(),
3527                  'status' => $this->changeset_post_id() ? get_post_status( $this->changeset_post_id() ) : '',
3528              ),
3529              'timeouts' => array(
3530                  'windowRefresh' => 250,
3531                  'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000,
3532                  'keepAliveCheck' => 2500,
3533                  'reflowPaneContents' => 100,
3534                  'previewFrameSensitivity' => 2000,
3535              ),
3536              'theme'    => array(
3537                  'stylesheet' => $this->get_stylesheet(),
3538                  'active'     => $this->is_theme_active(),
3539              ),
3540              'url'      => array(
3541                  'preview'       => esc_url_raw( $this->get_preview_url() ),
3542                  'parent'        => esc_url_raw( admin_url() ),
3543                  'activated'     => esc_url_raw( home_url( '/' ) ),
3544                  'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
3545                  'allowed'       => array_map( 'esc_url_raw', $this->get_allowed_urls() ),
3546                  'isCrossDomain' => $this->is_cross_domain(),
3547                  'home'          => esc_url_raw( home_url( '/' ) ),
3548                  'login'         => esc_url_raw( $login_url ),
3549              ),
3550              'browser'  => array(
3551                  'mobile' => wp_is_mobile(),
3552                  'ios'    => $this->is_ios(),
3553              ),
3554              'panels'   => array(),
3555              'sections' => array(),
3556              'nonce'    => $this->get_nonces(),
3557              'autofocus' => $this->get_autofocus(),
3558              'documentTitleTmpl' => $this->get_document_title_template(),
3559              'previewableDevices' => $this->get_previewable_devices(),
3560          );
3561  
3562          // Prepare Customize Section objects to pass to JavaScript.
3563          foreach ( $this->sections() as $id => $section ) {
3564              if ( $section->check_capabilities() ) {
3565                  $settings['sections'][ $id ] = $section->json();
3566              }
3567          }
3568  
3569          // Prepare Customize Panel objects to pass to JavaScript.
3570          foreach ( $this->panels() as $panel_id => $panel ) {
3571              if ( $panel->check_capabilities() ) {
3572                  $settings['panels'][ $panel_id ] = $panel->json();
3573                  foreach ( $panel->sections as $section_id => $section ) {
3574                      if ( $section->check_capabilities() ) {
3575                          $settings['sections'][ $section_id ] = $section->json();
3576                      }
3577                  }
3578              }
3579          }
3580  
3581          ?>
3582          <script type="text/javascript">
3583              var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
3584              _wpCustomizeSettings.controls = {};
3585              _wpCustomizeSettings.settings = {};
3586              <?php
3587  
3588              // Serialize settings one by one to improve memory usage.
3589              echo "(function ( s ){\n";
3590              foreach ( $this->settings() as $setting ) {
3591                  if ( $setting->check_capabilities() ) {
3592                      printf(
3593                          "s[%s] = %s;\n",
3594                          wp_json_encode( $setting->id ),
3595                          wp_json_encode( $setting->json() )
3596                      );
3597                  }
3598              }
3599              echo "})( _wpCustomizeSettings.settings );\n";
3600  
3601              // Serialize controls one by one to improve memory usage.
3602              echo "(function ( c ){\n";
3603              foreach ( $this->controls() as $control ) {
3604                  if ( $control->check_capabilities() ) {
3605                      printf(
3606                          "c[%s] = %s;\n",
3607                          wp_json_encode( $control->id ),
3608                          wp_json_encode( $control->json() )
3609                      );
3610                  }
3611              }
3612              echo "})( _wpCustomizeSettings.controls );\n";
3613          ?>
3614          </script>
3615          <?php
3616      }
3617  
3618      /**
3619       * Returns a list of devices to allow previewing.
3620       *
3621       * @access public
3622       * @since 4.5.0
3623       *
3624       * @return array List of devices with labels and default setting.
3625       */
3626  	public function get_previewable_devices() {
3627          $devices = array(
3628              'desktop' => array(
3629                  'label' => __( 'Enter desktop preview mode' ),
3630                  'default' => true,
3631              ),
3632              'tablet' => array(
3633                  'label' => __( 'Enter tablet preview mode' ),
3634              ),
3635              'mobile' => array(
3636                  'label' => __( 'Enter mobile preview mode' ),
3637              ),
3638          );
3639  
3640          /**
3641           * Filters the available devices to allow previewing in the Customizer.
3642           *
3643           * @since 4.5.0
3644           *
3645           * @see WP_Customize_Manager::get_previewable_devices()
3646           *
3647           * @param array $devices List of devices with labels and default setting.
3648           */
3649          $devices = apply_filters( 'customize_previewable_devices', $devices );
3650  
3651          return $devices;
3652      }
3653  
3654      /**
3655       * Register some default controls.
3656       *
3657       * @since 3.4.0
3658       */
3659  	public function register_controls() {
3660  
3661          /* Panel, Section, and Control Types */
3662          $this->register_panel_type( 'WP_Customize_Panel' );
3663          $this->register_section_type( 'WP_Customize_Section' );
3664          $this->register_section_type( 'WP_Customize_Sidebar_Section' );
3665          $this->register_control_type( 'WP_Customize_Color_Control' );
3666          $this->register_control_type( 'WP_Customize_Media_Control' );
3667          $this->register_control_type( 'WP_Customize_Upload_Control' );
3668          $this->register_control_type( 'WP_Customize_Image_Control' );
3669          $this->register_control_type( 'WP_Customize_Background_Image_Control' );
3670          $this->register_control_type( 'WP_Customize_Background_Position_Control' );
3671          $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
3672          $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
3673          $this->register_control_type( 'WP_Customize_Theme_Control' );
3674  
3675          /* Themes */
3676  
3677          $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
3678              'title'      => $this->theme()->display( 'Name' ),
3679              'capability' => 'switch_themes',
3680              'priority'   => 0,
3681          ) ) );
3682  
3683          // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
3684          $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
3685              'capability' => 'switch_themes',
3686          ) ) );
3687  
3688          require_once( ABSPATH . 'wp-admin/includes/theme.php' );
3689  
3690          // Theme Controls.
3691  
3692          // Add a control for the active/original theme.
3693          if ( ! $this->is_theme_active() ) {
3694              $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
3695              $active_theme = current( $themes );
3696              $active_theme['isActiveTheme'] = true;
3697              $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
3698                  'theme'    => $active_theme,
3699                  'section'  => 'themes',
3700                  'settings' => 'active_theme',
3701              ) ) );
3702          }
3703  
3704          $themes = wp_prepare_themes_for_js();
3705          foreach ( $themes as $theme ) {
3706              if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
3707                  continue;
3708              }
3709  
3710              $theme_id = 'theme_' . $theme['id'];
3711              $theme['isActiveTheme'] = false;
3712              $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
3713                  'theme'    => $theme,
3714                  'section'  => 'themes',
3715                  'settings' => 'active_theme',
3716              ) ) );
3717          }
3718  
3719          /* Site Identity */
3720  
3721          $this->add_section( 'title_tagline', array(
3722              'title'    => __( 'Site Identity' ),
3723              'priority' => 20,
3724          ) );
3725  
3726          $this->add_setting( 'blogname', array(
3727              'default'    => get_option( 'blogname' ),
3728              'type'       => 'option',
3729              'capability' => 'manage_options',
3730          ) );
3731  
3732          $this->add_control( 'blogname', array(
3733              'label'      => __( 'Site Title' ),
3734              'section'    => 'title_tagline',
3735          ) );
3736  
3737          $this->add_setting( 'blogdescription', array(
3738              'default'    => get_option( 'blogdescription' ),
3739              'type'       => 'option',
3740              'capability' => 'manage_options',
3741          ) );
3742  
3743          $this->add_control( 'blogdescription', array(
3744              'label'      => __( 'Tagline' ),
3745              'section'    => 'title_tagline',
3746          ) );
3747  
3748          // Add a setting to hide header text if the theme doesn't support custom headers.
3749          if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
3750              $this->add_setting( 'header_text', array(
3751                  'theme_supports'    => array( 'custom-logo', 'header-text' ),
3752                  'default'           => 1,
3753                  'sanitize_callback' => 'absint',
3754              ) );
3755  
3756              $this->add_control( 'header_text', array(
3757                  'label'    => __( 'Display Site Title and Tagline' ),
3758                  'section'  => 'title_tagline',
3759                  'settings' => 'header_text',
3760                  'type'     => 'checkbox',
3761              ) );
3762          }
3763  
3764          $this->add_setting( 'site_icon', array(
3765              'type'       => 'option',
3766              'capability' => 'manage_options',
3767              'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
3768          ) );
3769  
3770          $this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
3771              'label'       => __( 'Site Icon' ),
3772              'description' => sprintf(
3773                  /* translators: %s: site icon size in pixels */
3774                  __( 'The Site Icon is used as a browser and app icon for your site. Icons must be square, and at least %s pixels wide and tall.' ),
3775                  '<strong>512</strong>'
3776              ),
3777              'section'     => 'title_tagline',
3778              'priority'    => 60,
3779              'height'      => 512,
3780              'width'       => 512,
3781          ) ) );
3782  
3783          $this->add_setting( 'custom_logo', array(
3784              'theme_supports' => array( 'custom-logo' ),
3785              'transport'      => 'postMessage',
3786          ) );
3787  
3788          $custom_logo_args = get_theme_support( 'custom-logo' );
3789          $this->add_control( new WP_Customize_Cropped_Image_Control( $this, 'custom_logo', array(
3790              'label'         => __( 'Logo' ),
3791              'section'       => 'title_tagline',
3792              'priority'      => 8,
3793              'height'        => $custom_logo_args[0]['height'],
3794              'width'         => $custom_logo_args[0]['width'],
3795              'flex_height'   => $custom_logo_args[0]['flex-height'],
3796              'flex_width'    => $custom_logo_args[0]['flex-width'],
3797              'button_labels' => array(
3798                  'select'       => __( 'Select logo' ),
3799                  'change'       => __( 'Change logo' ),
3800                  'remove'       => __( 'Remove' ),
3801                  'default'      => __( 'Default' ),
3802                  'placeholder'  => __( 'No logo selected' ),
3803                  'frame_title'  => __( 'Select logo' ),
3804                  'frame_button' => __( 'Choose logo' ),
3805              ),
3806          ) ) );
3807  
3808          $this->selective_refresh->add_partial( 'custom_logo', array(
3809              'settings'            => array( 'custom_logo' ),
3810              'selector'            => '.custom-logo-link',
3811              'render_callback'     => array( $this, '_render_custom_logo_partial' ),
3812              'container_inclusive' => true,
3813          ) );
3814  
3815          /* Colors */
3816  
3817          $this->add_section( 'colors', array(
3818              'title'          => __( 'Colors' ),
3819              'priority'       => 40,
3820          ) );
3821  
3822          $this->add_setting( 'header_textcolor', array(
3823              'theme_supports' => array( 'custom-header', 'header-text' ),
3824              'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
3825  
3826              'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
3827              'sanitize_js_callback' => 'maybe_hash_hex_color',
3828          ) );
3829  
3830          // Input type: checkbox
3831          // With custom value
3832          $this->add_control( 'display_header_text', array(
3833              'settings' => 'header_textcolor',
3834              'label'    => __( 'Display Site Title and Tagline' ),
3835              'section'  => 'title_tagline',
3836              'type'     => 'checkbox',
3837              'priority' => 40,
3838          ) );
3839  
3840          $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
3841              'label'   => __( 'Header Text Color' ),
3842              'section' => 'colors',
3843          ) ) );
3844  
3845          // Input type: Color
3846          // With sanitize_callback
3847          $this->add_setting( 'background_color', array(
3848              'default'        => get_theme_support( 'custom-background', 'default-color' ),
3849              'theme_supports' => 'custom-background',
3850  
3851              'sanitize_callback'    => 'sanitize_hex_color_no_hash',
3852              'sanitize_js_callback' => 'maybe_hash_hex_color',
3853          ) );
3854  
3855          $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
3856              'label'   => __( 'Background Color' ),
3857              'section' => 'colors',
3858          ) ) );
3859  
3860          /* Custom Header */
3861  
3862          if ( current_theme_supports( 'custom-header', 'video' ) ) {
3863              $title = __( 'Header Media' );
3864              $description = '<p>' . __( 'If you add a video, the image will be used as a fallback while the video loads.' ) . '</p>';
3865  
3866              // @todo Customizer sections should support having notifications just like controls do. See <https://core.trac.wordpress.org/ticket/38794>.
3867              $description .= '<div class="customize-control-notifications-container header-video-not-currently-previewable" style="display: none"><ul>';
3868              $description .= '<li class="notice notice-info">' . __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ) . '</li>';
3869              $description .= '</ul></div>';
3870              $width = absint( get_theme_support( 'custom-header', 'width' ) );
3871              $height = absint( get_theme_support( 'custom-header', 'height' ) );
3872              if ( $width && $height ) {
3873                  $control_description = sprintf(
3874                      /* translators: 1: .mp4, 2: header size in pixels */
3875                      __( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends dimensions of %2$s pixels.' ),
3876                      '<code>.mp4</code>',
3877                      sprintf( '<strong>%s &times; %s</strong>', $width, $height )
3878                  );
3879              } elseif ( $width ) {
3880                  $control_description = sprintf(
3881                      /* translators: 1: .mp4, 2: header width in pixels */
3882                      __( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a width of %2$s pixels.' ),
3883                      '<code>.mp4</code>',
3884                      sprintf( '<strong>%s</strong>', $width )
3885                  );
3886              } else {
3887                  $control_description = sprintf(
3888                      /* translators: 1: .mp4, 2: header height in pixels */
3889                      __( 'Upload your video in %1$s format and minimize its file size for best results. Your theme recommends a height of %2$s pixels.' ),
3890                      '<code>.mp4</code>',
3891                      sprintf( '<strong>%s</strong>', $height )
3892                  );
3893              }
3894          } else {
3895              $title = __( 'Header Image' );
3896              $description = '';
3897              $control_description = '';
3898          }
3899  
3900          $this->add_section( 'header_image', array(
3901              'title'          => $title,
3902              'description'    => $description,
3903              'theme_supports' => 'custom-header',
3904              'priority'       => 60,
3905          ) );
3906  
3907          $this->add_setting( 'header_video', array(
3908              'theme_supports'    => array( 'custom-header', 'video' ),
3909              'transport'         => 'postMessage',
3910              'sanitize_callback' => 'absint',
3911              'validate_callback' => array( $this, '_validate_header_video' ),
3912          ) );
3913  
3914          $this->add_setting( 'external_header_video', array(
3915              'theme_supports'    => array( 'custom-header', 'video' ),
3916              'transport'         => 'postMessage',
3917              'sanitize_callback' => array( $this, '_sanitize_external_header_video' ),
3918              'validate_callback' => array( $this, '_validate_external_header_video' ),
3919          ) );
3920  
3921          $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
3922              'default'        => sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() ),
3923              'theme_supports' => 'custom-header',
3924          ) ) );
3925  
3926          $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
3927              'theme_supports' => 'custom-header',
3928          ) ) );
3929  
3930          /*
3931           * Switch image settings to postMessage when video support is enabled since
3932           * it entails that the_custom_header_markup() will be used, and thus selective
3933           * refresh can be utilized.
3934           */
3935          if ( current_theme_supports( 'custom-header', 'video' ) ) {
3936              $this->get_setting( 'header_image' )->transport = 'postMessage';
3937              $this->get_setting( 'header_image_data' )->transport = 'postMessage';
3938          }
3939  
3940          $this->add_control( new WP_Customize_Media_Control( $this, 'header_video', array(
3941              'theme_supports' => array( 'custom-header', 'video' ),
3942              'label'          => __( 'Header Video' ),
3943              'description'    => $control_description,
3944              'section'        => 'header_image',
3945              'mime_type'      => 'video',
3946              // @todo These button_labels can be removed once WP_Customize_Media_Control provides mime_type-specific labels automatically. See <https://core.trac.wordpress.org/ticket/38796>.
3947              'button_labels'  => array(
3948                  'select'       => __( 'Select Video' ),
3949                  'change'       => __( 'Change Video' ),
3950                  'placeholder'  => __( 'No video selected' ),
3951                  'frame_title'  => __( 'Select Video' ),
3952                  'frame_button' => __( 'Choose Video' ),
3953              ),
3954              'active_callback' => 'is_header_video_active',
3955          ) ) );
3956  
3957          $this->add_control( 'external_header_video', array(
3958              'theme_supports' => array( 'custom-header', 'video' ),
3959              'type'           => 'url',
3960              'description'    => __( 'Or, enter a YouTube URL:' ),
3961              'section'        => 'header_image',
3962              'active_callback' => 'is_header_video_active',
3963          ) );
3964  
3965          $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
3966  
3967          $this->selective_refresh->add_partial( 'custom_header', array(
3968              'selector'            => '#wp-custom-header',
3969              'render_callback'     => 'the_custom_header_markup',
3970              'settings'            => array( 'header_video', 'external_header_video', 'header_image' ), // The image is used as a video fallback here.
3971              'container_inclusive' => true,
3972          ) );
3973  
3974          /* Custom Background */
3975  
3976          $this->add_section( 'background_image', array(
3977              'title'          => __( 'Background Image' ),
3978              'theme_supports' => 'custom-background',
3979              'priority'       => 80,
3980          ) );
3981  
3982          $this->add_setting( 'background_image', array(
3983              'default'        => get_theme_support( 'custom-background', 'default-image' ),
3984              'theme_supports' => 'custom-background',
3985              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
3986          ) );
3987  
3988          $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
3989              'theme_supports' => 'custom-background',
3990              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
3991          ) ) );
3992  
3993          $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
3994  
3995          $this->add_setting( 'background_preset', array(
3996              'default'        => get_theme_support( 'custom-background', 'default-preset' ),
3997              'theme_supports' => 'custom-background',
3998              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
3999          ) );
4000  
4001          $this->add_control( 'background_preset', array(
4002              'label'      => _x( 'Preset', 'Background Preset' ),
4003              'section'    => 'background_image',
4004              'type'       => 'select',
4005              'choices'    => array(
4006                  'default' => _x( 'Default', 'Default Preset' ),
4007                  'fill'    => __( 'Fill Screen' ),
4008                  'fit'     => __( 'Fit to Screen' ),
4009                  'repeat'  => _x( 'Repeat', 'Repeat Image' ),
4010                  'custom'  => _x( 'Custom', 'Custom Preset' ),
4011              ),
4012          ) );
4013  
4014          $this->add_setting( 'background_position_x', array(
4015              'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
4016              'theme_supports' => 'custom-background',
4017              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
4018          ) );
4019  
4020          $this->add_setting( 'background_position_y', array(
4021              'default'        => get_theme_support( 'custom-background', 'default-position-y' ),
4022              'theme_supports' => 'custom-background',
4023              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
4024          ) );
4025  
4026          $this->add_control( new WP_Customize_Background_Position_Control( $this, 'background_position', array(
4027              'label'    => __( 'Image Position' ),
4028              'section'  => 'background_image',
4029              'settings' => array(
4030                  'x' => 'background_position_x',
4031                  'y' => 'background_position_y',
4032              ),
4033          ) ) );
4034  
4035          $this->add_setting( 'background_size', array(
4036              'default'        => get_theme_support( 'custom-background', 'default-size' ),
4037              'theme_supports' => 'custom-background',
4038              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
4039          ) );
4040  
4041          $this->add_control( 'background_size', array(
4042              'label'      => __( 'Image Size' ),
4043              'section'    => 'background_image',
4044              'type'       => 'select',
4045              'choices'    => array(
4046                  'auto'    => __( 'Original' ),
4047                  'contain' => __( 'Fit to Screen' ),
4048                  'cover'   => __( 'Fill Screen' ),
4049              ),
4050          ) );
4051  
4052          $this->add_setting( 'background_repeat', array(
4053              'default'           => get_theme_support( 'custom-background', 'default-repeat' ),
4054              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
4055              'theme_supports'    => 'custom-background',
4056          ) );
4057  
4058          $this->add_control( 'background_repeat', array(
4059              'label'    => __( 'Repeat Background Image' ),
4060              'section'  => 'background_image',
4061              'type'     => 'checkbox',
4062          ) );
4063  
4064          $this->add_setting( 'background_attachment', array(
4065              'default'           => get_theme_support( 'custom-background', 'default-attachment' ),
4066              'sanitize_callback' => array( $this, '_sanitize_background_setting' ),
4067              'theme_supports'    => 'custom-background',
4068          ) );
4069  
4070          $this->add_control( 'background_attachment', array(
4071              'label'    => __( 'Scroll with Page' ),
4072              'section'  => 'background_image',
4073              'type'     => 'checkbox',
4074          ) );
4075  
4076  
4077          // If the theme is using the default background callback, we can update
4078          // the background CSS using postMessage.
4079          if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
4080              foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) {
4081                  $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
4082              }
4083          }
4084  
4085          /*
4086           * Static Front Page
4087           * See also https://core.trac.wordpress.org/ticket/19627 which introduces the the static-front-page theme_support.
4088           * The following replicates behavior from options-reading.php.
4089           */
4090  
4091          $this->add_section( 'static_front_page', array(
4092              'title' => __( 'Static Front Page' ),
4093              'priority' => 120,
4094              'description' => __( 'Your theme supports a static front page.' ),
4095              'active_callback' => array( $this, 'has_published_pages' ),
4096          ) );
4097  
4098          $this->add_setting( 'show_on_front', array(
4099              'default' => get_option( 'show_on_front' ),
4100              'capability' => 'manage_options',
4101              'type' => 'option',
4102          ) );
4103  
4104          $this->add_control( 'show_on_front', array(
4105              'label' => __( 'Front page displays' ),
4106              'section' => 'static_front_page',
4107              'type' => 'radio',
4108              'choices' => array(
4109                  'posts' => __( 'Your latest posts' ),
4110                  'page'  => __( 'A static page' ),
4111              ),
4112          ) );
4113  
4114          $this->add_setting( 'page_on_front', array(
4115              'type'       => 'option',
4116              'capability' => 'manage_options',
4117          ) );
4118  
4119          $this->add_control( 'page_on_front', array(
4120              'label' => __( 'Front page' ),
4121              'section' => 'static_front_page',
4122              'type' => 'dropdown-pages',
4123              'allow_addition' => true,
4124          ) );
4125  
4126          $this->add_setting( 'page_for_posts', array(
4127              'type' => 'option',
4128              'capability' => 'manage_options',
4129          ) );
4130  
4131          $this->add_control( 'page_for_posts', array(
4132              'label' => __( 'Posts page' ),
4133              'section' => 'static_front_page',
4134              'type' => 'dropdown-pages',
4135              'allow_addition' => true,
4136          ) );
4137  
4138          /* Custom CSS */
4139          $this->add_section( 'custom_css', array(
4140              'title'              => __( 'Additional CSS' ),
4141              'priority'           => 200,
4142              'description_hidden' => true,
4143              'description'        => sprintf( '%s<br /><a href="%s" class="external-link" target="_blank">%s<span class="screen-reader-text">%s</span></a>',
4144                  __( 'CSS allows you to customize the appearance and layout of your site with code. Separate CSS is saved for each of your themes. In the editing area the Tab key enters a tab character. To move below this area by pressing Tab, press the Esc key followed by the Tab key.' ),
4145                  esc_url( __( 'https://codex.wordpress.org/CSS' ) ),
4146                  __( 'Learn more about CSS' ),
4147                  __( '(link opens in a new window)' )
4148              ),
4149          ) );
4150  
4151          $custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array(
4152              'capability' => 'edit_css',
4153              'default' => sprintf( "/*\n%s\n*/", __( "You can add your own CSS here.\n\nClick the help icon above to learn more." ) ),
4154          ) );
4155          $this->add_setting( $custom_css_setting );
4156  
4157          $this->add_control( 'custom_css', array(
4158              'type'     => 'textarea',
4159              'section'  => 'custom_css',
4160              'settings' => array( 'default' => $custom_css_setting->id ),
4161              'input_attrs' => array(
4162                  'class' => 'code', // Ensures contents displayed as LTR instead of RTL.
4163              ),
4164          ) );
4165      }
4166  
4167      /**
4168       * Return whether there are published pages.
4169       *
4170       * Used as active callback for static front page section and controls.
4171       *
4172       * @access private
4173       * @since 4.7.0
4174       *
4175       * @returns bool Whether there are published (or to be published) pages.
4176       */
4177  	public function has_published_pages() {
4178  
4179          $setting = $this->get_setting( 'nav_menus_created_posts' );
4180          if ( $setting ) {
4181              foreach ( $setting->value() as $post_id ) {
4182                  if ( 'page' === get_post_type( $post_id ) ) {
4183                      return true;
4184                  }
4185              }
4186          }
4187          return 0 !== count( get_pages() );
4188      }
4189  
4190      /**
4191       * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
4192       *
4193       * @since 4.2.0
4194       * @access public
4195       *
4196       * @see add_dynamic_settings()
4197       */
4198  	public function register_dynamic_settings() {
4199          $setting_ids = array_keys( $this->unsanitized_post_values() );
4200          $this->add_dynamic_settings( $setting_ids );
4201      }
4202  
4203      /**
4204       * Callback for validating the header_textcolor value.
4205       *
4206       * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
4207       * Returns default text color if hex color is empty.
4208       *
4209       * @since 3.4.0
4210       *
4211       * @param string $color
4212       * @return mixed
4213       */
4214  	public function _sanitize_header_textcolor( $color ) {
4215          if ( 'blank' === $color )
4216              return 'blank';
4217  
4218          $color = sanitize_hex_color_no_hash( $color );
4219          if ( empty( $color ) )
4220              $color = get_theme_support( 'custom-header', 'default-text-color' );
4221  
4222          return $color;
4223      }
4224  
4225      /**
4226       * Callback for validating a background setting value.
4227       *
4228       * @since 4.7.0
4229       * @access private
4230       *
4231       * @param string $value Repeat value.
4232       * @param WP_Customize_Setting $setting Setting.
4233       * @return string|WP_Error Background value or validation error.
4234       */
4235  	public function _sanitize_background_setting( $value, $setting ) {
4236          if ( 'background_repeat' === $setting->id ) {
4237              if ( ! in_array( $value, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ) ) ) {
4238                  return new WP_Error( 'invalid_value', __( 'Invalid value for background repeat.' ) );
4239              }
4240          } elseif ( 'background_attachment' === $setting->id ) {
4241              if ( ! in_array( $value, array( 'fixed', 'scroll' ) ) ) {
4242                  return new WP_Error( 'invalid_value', __( 'Invalid value for background attachment.' ) );
4243              }
4244          } elseif ( 'background_position_x' === $setting->id ) {
4245              if ( ! in_array( $value, array( 'left', 'center', 'right' ), true ) ) {
4246                  return new WP_Error( 'invalid_value', __( 'Invalid value for background position X.' ) );
4247              }
4248          } elseif ( 'background_position_y' === $setting->id ) {
4249              if ( ! in_array( $value, array( 'top', 'center', 'bottom' ), true ) ) {
4250                  return new WP_Error( 'invalid_value', __( 'Invalid value for background position Y.' ) );
4251              }
4252          } elseif ( 'background_size' === $setting->id ) {
4253              if ( ! in_array( $value, array( 'auto', 'contain', 'cover' ), true ) ) {
4254                  return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) );
4255              }
4256          } elseif ( 'background_preset' === $setting->id ) {
4257              if ( ! in_array( $value, array( 'default', 'fill', 'fit', 'repeat', 'custom' ), true ) ) {
4258                  return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) );
4259              }
4260          } elseif ( 'background_image' === $setting->id || 'background_image_thumb' === $setting->id ) {
4261              $value = empty( $value ) ? '' : esc_url_raw( $value );
4262          } else {
4263              return new WP_Error( 'unrecognized_setting', __( 'Unrecognized background setting.' ) );
4264          }
4265          return $value;
4266      }
4267  
4268      /**
4269       * Export header video settings to facilitate selective refresh.
4270       *
4271       * @since 4.7.0
4272       *
4273       * @param array $response Response.
4274       * @param WP_Customize_Selective_Refresh $selective_refresh Selective refresh component.
4275       * @param array $partials Array of partials.
4276       * @return array
4277       */
4278  	public function export_header_video_settings( $response, $selective_refresh, $partials ) {
4279          if ( isset( $partials['custom_header'] ) ) {
4280              $response['custom_header_settings'] = get_header_video_settings();
4281          }
4282  
4283          return $response;
4284      }
4285  
4286      /**
4287       * Callback for validating the header_video value.
4288       *
4289       * Ensures that the selected video is less than 8MB and provides an error message.
4290       *
4291       * @since 4.7.0
4292       *
4293       * @param WP_Error $validity
4294       * @param mixed $value
4295       * @return mixed
4296       */
4297  	public function _validate_header_video( $validity, $value ) {
4298          $video = get_attached_file( absint( $value ) );
4299          if ( $video ) {
4300              $size = filesize( $video );
4301              if ( 8 < $size / pow( 1024, 2 ) ) { // Check whether the size is larger than 8MB.
4302                  $validity->add( 'size_too_large',
4303                      __( '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.' )
4304                  );
4305              }
4306              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.
4307                  $validity->add( 'invalid_file_type', sprintf(
4308                      /* translators: 1: .mp4, 2: .mov */
4309                      __( '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.' ),
4310                      '<code>.mp4</code>',
4311                      '<code>.mov</code>'
4312                  ) );
4313              }
4314          }
4315          return $validity;
4316      }
4317  
4318      /**
4319       * Callback for validating the external_header_video value.
4320       *
4321       * Ensures that the provided URL is supported.
4322       *
4323       * @since 4.7.0
4324       *
4325       * @param WP_Error $validity
4326       * @param mixed $value
4327       * @return mixed
4328       */
4329  	public function _validate_external_header_video( $validity, $value ) {
4330          $video = esc_url_raw( $value );
4331          if ( $video ) {
4332              if ( ! preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video ) ) {
4333                  $validity->add( 'invalid_url', __( 'Please enter a valid YouTube URL.' ) );
4334              }
4335          }
4336          return $validity;
4337      }
4338  
4339      /**
4340       * Callback for sanitizing the external_header_video value.
4341       *
4342       * @since 4.7.1
4343       *
4344       * @param string $value URL.
4345       * @return string Sanitized URL.
4346       */
4347  	public function _sanitize_external_header_video( $value ) {
4348          return esc_url_raw( trim( $value ) );
4349      }
4350  
4351      /**
4352       * Callback for rendering the custom logo, used in the custom_logo partial.
4353       *
4354       * This method exists because the partial object and context data are passed
4355       * into a partial's render_callback so we cannot use get_custom_logo() as
4356       * the render_callback directly since it expects a blog ID as the first
4357       * argument. When WP no longer supports PHP 5.3, this method can be removed
4358       * in favor of an anonymous function.
4359       *
4360       * @see WP_Customize_Manager::register_controls()
4361       *
4362       * @since 4.5.0
4363       * @access private
4364       *
4365       * @return string Custom logo.
4366       */
4367  	public function _render_custom_logo_partial() {
4368          return get_custom_logo();
4369      }
4370  }


Generated: Thu Apr 27 01:00:02 2017 Cross-referenced by PHPXref 0.7.1