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


Generated: Thu Jul 27 01:00:04 2017 Cross-referenced by PHPXref 0.7.1