wp/wp-includes/class-wp-customize-manager.php
changeset 0 d970ebf37754
child 5 5e2f62d02dcd
equal deleted inserted replaced
-1:000000000000 0:d970ebf37754
       
     1 <?php
       
     2 /**
       
     3  * Customize Manager.
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage Customize
       
     7  * @since 3.4.0
       
     8  */
       
     9 final class WP_Customize_Manager {
       
    10 	protected $theme;
       
    11 	protected $original_stylesheet;
       
    12 
       
    13 	protected $previewing = false;
       
    14 
       
    15 	protected $settings = array();
       
    16 	protected $sections = array();
       
    17 	protected $controls = array();
       
    18 
       
    19 	protected $nonce_tick;
       
    20 
       
    21 	protected $customized;
       
    22 
       
    23 	private $_post_values;
       
    24 
       
    25 	/**
       
    26 	 * Constructor.
       
    27 	 *
       
    28 	 * @since 3.4.0
       
    29 	 */
       
    30 	public function __construct() {
       
    31 		require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
       
    32 		require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
       
    33 		require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
       
    34 
       
    35 		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
       
    36 
       
    37 		add_action( 'setup_theme',  array( $this, 'setup_theme' ) );
       
    38 		add_action( 'wp_loaded',    array( $this, 'wp_loaded' ) );
       
    39 
       
    40 		// Run wp_redirect_status late to make sure we override the status last.
       
    41 		add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
       
    42 
       
    43 		// Do not spawn cron (especially the alternate cron) while running the customizer.
       
    44 		remove_action( 'init', 'wp_cron' );
       
    45 
       
    46 		// Do not run update checks when rendering the controls.
       
    47 		remove_action( 'admin_init', '_maybe_update_core' );
       
    48 		remove_action( 'admin_init', '_maybe_update_plugins' );
       
    49 		remove_action( 'admin_init', '_maybe_update_themes' );
       
    50 
       
    51 		add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
       
    52 
       
    53 		add_action( 'customize_register',                 array( $this, 'register_controls' ) );
       
    54 		add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
       
    55 		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
       
    56 	}
       
    57 
       
    58 	/**
       
    59 	 * Return true if it's an AJAX request.
       
    60 	 *
       
    61 	 * @since 3.4.0
       
    62 	 *
       
    63 	 * @return bool
       
    64 	 */
       
    65 	public function doing_ajax() {
       
    66 		return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
       
    67 	}
       
    68 
       
    69 	/**
       
    70 	 * Custom wp_die wrapper. Returns either the standard message for UI
       
    71 	 * or the AJAX message.
       
    72 	 *
       
    73 	 * @since 3.4.0
       
    74 	 *
       
    75 	 * @param mixed $ajax_message AJAX return
       
    76 	 * @param mixed $message UI message
       
    77 	 */
       
    78 	protected function wp_die( $ajax_message, $message = null ) {
       
    79 		if ( $this->doing_ajax() )
       
    80 			wp_die( $ajax_message );
       
    81 
       
    82 		if ( ! $message )
       
    83 			$message = __( 'Cheatin&#8217; uh?' );
       
    84 
       
    85 		wp_die( $message );
       
    86 	}
       
    87 
       
    88 	/**
       
    89 	 * Return the AJAX wp_die() handler if it's a customized request.
       
    90 	 *
       
    91 	 * @since 3.4.0
       
    92 	 *
       
    93 	 * @return string
       
    94 	 */
       
    95 	public function wp_die_handler() {
       
    96 		if ( $this->doing_ajax() )
       
    97 			return '_ajax_wp_die_handler';
       
    98 
       
    99 		return '_default_wp_die_handler';
       
   100 	}
       
   101 
       
   102 	/**
       
   103 	 * Start preview and customize theme.
       
   104 	 *
       
   105 	 * Check if customize query variable exist. Init filters to filter the current theme.
       
   106 	 *
       
   107 	 * @since 3.4.0
       
   108 	 */
       
   109 	public function setup_theme() {
       
   110 		send_origin_headers();
       
   111 
       
   112 		if ( is_admin() && ! $this->doing_ajax() )
       
   113 		    auth_redirect();
       
   114 		elseif ( $this->doing_ajax() && ! is_user_logged_in() )
       
   115 		    $this->wp_die( 0 );
       
   116 
       
   117 		show_admin_bar( false );
       
   118 
       
   119 		if ( ! current_user_can( 'edit_theme_options' ) )
       
   120 			$this->wp_die( -1 );
       
   121 
       
   122 		$this->original_stylesheet = get_stylesheet();
       
   123 
       
   124 		$this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
       
   125 
       
   126 		if ( $this->is_theme_active() ) {
       
   127 			// Once the theme is loaded, we'll validate it.
       
   128 			add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
       
   129 		} else {
       
   130 			if ( ! current_user_can( 'switch_themes' ) )
       
   131 				$this->wp_die( -1 );
       
   132 
       
   133 			// If the theme isn't active, you can't preview it if it is not allowed or has errors.
       
   134 			if ( $this->theme()->errors() )
       
   135 				$this->wp_die( -1 );
       
   136 
       
   137 			if ( ! $this->theme()->is_allowed() )
       
   138 				$this->wp_die( -1 );
       
   139 		}
       
   140 
       
   141 		$this->start_previewing_theme();
       
   142 	}
       
   143 
       
   144 	/**
       
   145 	 * Callback to validate a theme once it is loaded
       
   146 	 *
       
   147 	 * @since 3.4.0
       
   148 	 */
       
   149 	function after_setup_theme() {
       
   150 		if ( ! $this->doing_ajax() && ! validate_current_theme() ) {
       
   151 			wp_redirect( 'themes.php?broken=true' );
       
   152 			exit;
       
   153 		}
       
   154 	}
       
   155 
       
   156 	/**
       
   157 	 * Start previewing the selected theme.
       
   158 	 *
       
   159 	 * Adds filters to change the current theme.
       
   160 	 *
       
   161 	 * @since 3.4.0
       
   162 	 */
       
   163 	public function start_previewing_theme() {
       
   164 		// Bail if we're already previewing.
       
   165 		if ( $this->is_preview() )
       
   166 			return;
       
   167 
       
   168 		$this->previewing = true;
       
   169 
       
   170 		if ( ! $this->is_theme_active() ) {
       
   171 			add_filter( 'template', array( $this, 'get_template' ) );
       
   172 			add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
       
   173 			add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
       
   174 
       
   175 			// @link: http://core.trac.wordpress.org/ticket/20027
       
   176 			add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
       
   177 			add_filter( 'pre_option_template', array( $this, 'get_template' ) );
       
   178 
       
   179 			// Handle custom theme roots.
       
   180 			add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
       
   181 			add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
       
   182 		}
       
   183 
       
   184 		do_action( 'start_previewing_theme', $this );
       
   185 	}
       
   186 
       
   187 	/**
       
   188 	 * Stop previewing the selected theme.
       
   189 	 *
       
   190 	 * Removes filters to change the current theme.
       
   191 	 *
       
   192 	 * @since 3.4.0
       
   193 	 */
       
   194 	public function stop_previewing_theme() {
       
   195 		if ( ! $this->is_preview() )
       
   196 			return;
       
   197 
       
   198 		$this->previewing = false;
       
   199 
       
   200 		if ( ! $this->is_theme_active() ) {
       
   201 			remove_filter( 'template', array( $this, 'get_template' ) );
       
   202 			remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
       
   203 			remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
       
   204 
       
   205 			// @link: http://core.trac.wordpress.org/ticket/20027
       
   206 			remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
       
   207 			remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
       
   208 
       
   209 			// Handle custom theme roots.
       
   210 			remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
       
   211 			remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
       
   212 		}
       
   213 
       
   214 		do_action( 'stop_previewing_theme', $this );
       
   215 	}
       
   216 
       
   217 	/**
       
   218 	 * Get the theme being customized.
       
   219 	 *
       
   220 	 * @since 3.4.0
       
   221 	 *
       
   222 	 * @return WP_Theme
       
   223 	 */
       
   224 	public function theme() {
       
   225 		return $this->theme;
       
   226 	}
       
   227 
       
   228 	/**
       
   229 	 * Get the registered settings.
       
   230 	 *
       
   231 	 * @since 3.4.0
       
   232 	 *
       
   233 	 * @return array
       
   234 	 */
       
   235 	public function settings() {
       
   236 		return $this->settings;
       
   237 	}
       
   238 
       
   239 	/**
       
   240 	 * Get the registered controls.
       
   241 	 *
       
   242 	 * @since 3.4.0
       
   243 	 *
       
   244 	 * @return array
       
   245 	 */
       
   246 	public function controls() {
       
   247 		return $this->controls;
       
   248 	}
       
   249 
       
   250 	/**
       
   251 	 * Get the registered sections.
       
   252 	 *
       
   253 	 * @since 3.4.0
       
   254 	 *
       
   255 	 * @return array
       
   256 	 */
       
   257 	public function sections() {
       
   258 		return $this->sections;
       
   259 	}
       
   260 
       
   261 	/**
       
   262 	 * Checks if the current theme is active.
       
   263 	 *
       
   264 	 * @since 3.4.0
       
   265 	 *
       
   266 	 * @return bool
       
   267 	 */
       
   268 	public function is_theme_active() {
       
   269 		return $this->get_stylesheet() == $this->original_stylesheet;
       
   270 	}
       
   271 
       
   272 	/**
       
   273 	 * Register styles/scripts and initialize the preview of each setting
       
   274 	 *
       
   275 	 * @since 3.4.0
       
   276 	 */
       
   277 	public function wp_loaded() {
       
   278 		do_action( 'customize_register', $this );
       
   279 
       
   280 		if ( $this->is_preview() && ! is_admin() )
       
   281 			$this->customize_preview_init();
       
   282 	}
       
   283 
       
   284 	/**
       
   285 	 * Prevents AJAX requests from following redirects when previewing a theme
       
   286 	 * by issuing a 200 response instead of a 30x.
       
   287 	 *
       
   288 	 * Instead, the JS will sniff out the location header.
       
   289 	 *
       
   290 	 * @since 3.4.0
       
   291 	 *
       
   292 	 * @param $status
       
   293 	 * @return int
       
   294 	 */
       
   295 	public function wp_redirect_status( $status ) {
       
   296 		if ( $this->is_preview() && ! is_admin() )
       
   297 			return 200;
       
   298 
       
   299 		return $status;
       
   300 	}
       
   301 
       
   302 	/**
       
   303 	 * Decode the $_POST attribute used to override the WP_Customize_Setting values.
       
   304 	 *
       
   305 	 * @since 3.4.0
       
   306 	 *
       
   307 	 * @param mixed $setting A WP_Customize_Setting derived object
       
   308 	 * @return string Sanitized attribute
       
   309 	 */
       
   310 	public function post_value( $setting ) {
       
   311 		if ( ! isset( $this->_post_values ) ) {
       
   312 			if ( isset( $_POST['customized'] ) )
       
   313 				$this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
       
   314 			else
       
   315 				$this->_post_values = false;
       
   316 		}
       
   317 
       
   318 		if ( isset( $this->_post_values[ $setting->id ] ) )
       
   319 			return $setting->sanitize( $this->_post_values[ $setting->id ] );
       
   320 	}
       
   321 
       
   322 	/**
       
   323 	 * Print javascript settings.
       
   324 	 *
       
   325 	 * @since 3.4.0
       
   326 	 */
       
   327 	public function customize_preview_init() {
       
   328 		$this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
       
   329 
       
   330 		$this->prepare_controls();
       
   331 
       
   332 		wp_enqueue_script( 'customize-preview' );
       
   333 		add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
       
   334 		add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
       
   335 		add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
       
   336 		add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
       
   337 		add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
       
   338 
       
   339 		foreach ( $this->settings as $setting ) {
       
   340 			$setting->preview();
       
   341 		}
       
   342 
       
   343 		do_action( 'customize_preview_init', $this );
       
   344 	}
       
   345 
       
   346 	/**
       
   347 	 * Print base element for preview frame.
       
   348 	 *
       
   349 	 * @since 3.4.0
       
   350 	 */
       
   351 	public function customize_preview_base() {
       
   352 		?><base href="<?php echo home_url( '/' ); ?>" /><?php
       
   353 	}
       
   354 
       
   355 	/**
       
   356 	 * Print a workaround to handle HTML5 tags in IE < 9
       
   357 	 *
       
   358 	 * @since 3.4.0
       
   359 	 */
       
   360 	public function customize_preview_html5() { ?>
       
   361 		<!--[if lt IE 9]>
       
   362 		<script type="text/javascript">
       
   363 			var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
       
   364 				'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
       
   365 				'output', 'progress', 'section', 'time', 'video' ];
       
   366 			for ( var i = 0; i < e.length; i++ ) {
       
   367 				document.createElement( e[i] );
       
   368 			}
       
   369 		</script>
       
   370 		<![endif]--><?php
       
   371 	}
       
   372 
       
   373 	/**
       
   374 	 * Print javascript settings for preview frame.
       
   375 	 *
       
   376 	 * @since 3.4.0
       
   377 	 */
       
   378 	public function customize_preview_settings() {
       
   379 		$settings = array(
       
   380 			'values'  => array(),
       
   381 			'channel' => esc_js( $_POST['customize_messenger_channel'] ),
       
   382 		);
       
   383 
       
   384 		if ( 2 == $this->nonce_tick ) {
       
   385 			$settings['nonce'] = array(
       
   386 				'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
       
   387 				'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
       
   388 			);
       
   389 		}
       
   390 
       
   391 		foreach ( $this->settings as $id => $setting ) {
       
   392 			$settings['values'][ $id ] = $setting->js_value();
       
   393 		}
       
   394 
       
   395 		?>
       
   396 		<script type="text/javascript">
       
   397 			var _wpCustomizeSettings = <?php echo json_encode( $settings ); ?>;
       
   398 		</script>
       
   399 		<?php
       
   400 	}
       
   401 
       
   402 	/**
       
   403 	 * Prints a signature so we can ensure the customizer was properly executed.
       
   404 	 *
       
   405 	 * @since 3.4.0
       
   406 	 */
       
   407 	public function customize_preview_signature() {
       
   408 		echo 'WP_CUSTOMIZER_SIGNATURE';
       
   409 	}
       
   410 
       
   411 	/**
       
   412 	 * Removes the signature in case we experience a case where the customizer was not properly executed.
       
   413 	 *
       
   414 	 * @since 3.4.0
       
   415 	 */
       
   416 	public function remove_preview_signature( $return = null ) {
       
   417 		remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
       
   418 
       
   419 		return $return;
       
   420 	}
       
   421 
       
   422 	/**
       
   423 	 * Is it a theme preview?
       
   424 	 *
       
   425 	 * @since 3.4.0
       
   426 	 *
       
   427 	 * @return bool True if it's a preview, false if not.
       
   428 	 */
       
   429 	public function is_preview() {
       
   430 		return (bool) $this->previewing;
       
   431 	}
       
   432 
       
   433 	/**
       
   434 	 * Retrieve the template name of the previewed theme.
       
   435 	 *
       
   436 	 * @since 3.4.0
       
   437 	 *
       
   438 	 * @return string Template name.
       
   439 	 */
       
   440 	public function get_template() {
       
   441 		return $this->theme()->get_template();
       
   442 	}
       
   443 
       
   444 	/**
       
   445 	 * Retrieve the stylesheet name of the previewed theme.
       
   446 	 *
       
   447 	 * @since 3.4.0
       
   448 	 *
       
   449 	 * @return string Stylesheet name.
       
   450 	 */
       
   451 	public function get_stylesheet() {
       
   452 		return $this->theme()->get_stylesheet();
       
   453 	}
       
   454 
       
   455 	/**
       
   456 	 * Retrieve the template root of the previewed theme.
       
   457 	 *
       
   458 	 * @since 3.4.0
       
   459 	 *
       
   460 	 * @return string Theme root.
       
   461 	 */
       
   462 	public function get_template_root() {
       
   463 		return get_raw_theme_root( $this->get_template(), true );
       
   464 	}
       
   465 
       
   466 	/**
       
   467 	 * Retrieve the stylesheet root of the previewed theme.
       
   468 	 *
       
   469 	 * @since 3.4.0
       
   470 	 *
       
   471 	 * @return string Theme root.
       
   472 	 */
       
   473 	public function get_stylesheet_root() {
       
   474 		return get_raw_theme_root( $this->get_stylesheet(), true );
       
   475 	}
       
   476 
       
   477 	/**
       
   478 	 * Filter the current theme and return the name of the previewed theme.
       
   479 	 *
       
   480 	 * @since 3.4.0
       
   481 	 *
       
   482 	 * @param $current_theme {@internal Parameter is not used}
       
   483 	 * @return string Theme name.
       
   484 	 */
       
   485 	public function current_theme( $current_theme ) {
       
   486 		return $this->theme()->display('Name');
       
   487 	}
       
   488 
       
   489 	/**
       
   490 	 * Switch the theme and trigger the save action of each setting.
       
   491 	 *
       
   492 	 * @since 3.4.0
       
   493 	 */
       
   494 	public function save() {
       
   495 		if ( ! $this->is_preview() )
       
   496 			die;
       
   497 
       
   498 		check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
       
   499 
       
   500 		// Do we have to switch themes?
       
   501 		if ( ! $this->is_theme_active() ) {
       
   502 			// Temporarily stop previewing the theme to allow switch_themes()
       
   503 			// to operate properly.
       
   504 			$this->stop_previewing_theme();
       
   505 			switch_theme( $this->get_stylesheet() );
       
   506 			$this->start_previewing_theme();
       
   507 		}
       
   508 
       
   509 		do_action( 'customize_save', $this );
       
   510 
       
   511 		foreach ( $this->settings as $setting ) {
       
   512 			$setting->save();
       
   513 		}
       
   514 
       
   515 		do_action( 'customize_save_after', $this );
       
   516 
       
   517 		die;
       
   518 	}
       
   519 
       
   520 	/**
       
   521 	 * Add a customize setting.
       
   522 	 *
       
   523 	 * @since 3.4.0
       
   524 	 *
       
   525 	 * @param string $id A specific ID of the setting. Can be a
       
   526 	 *                   theme mod or option name.
       
   527 	 * @param array $args Setting arguments.
       
   528 	 */
       
   529 	public function add_setting( $id, $args = array() ) {
       
   530 		if ( is_a( $id, 'WP_Customize_Setting' ) )
       
   531 			$setting = $id;
       
   532 		else
       
   533 			$setting = new WP_Customize_Setting( $this, $id, $args );
       
   534 
       
   535 		$this->settings[ $setting->id ] = $setting;
       
   536 	}
       
   537 
       
   538 	/**
       
   539 	 * Retrieve a customize setting.
       
   540 	 *
       
   541 	 * @since 3.4.0
       
   542 	 *
       
   543 	 * @param string $id A specific ID of the setting.
       
   544 	 * @return object The settings object.
       
   545 	 */
       
   546 	public function get_setting( $id ) {
       
   547 		if ( isset( $this->settings[ $id ] ) )
       
   548 			return $this->settings[ $id ];
       
   549 	}
       
   550 
       
   551 	/**
       
   552 	 * Remove a customize setting.
       
   553 	 *
       
   554 	 * @since 3.4.0
       
   555 	 *
       
   556 	 * @param string $id A specific ID of the setting.
       
   557 	 */
       
   558 	public function remove_setting( $id ) {
       
   559 		unset( $this->settings[ $id ] );
       
   560 	}
       
   561 
       
   562 	/**
       
   563 	 * Add a customize section.
       
   564 	 *
       
   565 	 * @since 3.4.0
       
   566 	 *
       
   567 	 * @param string $id A specific ID of the section.
       
   568 	 * @param array $args Section arguments.
       
   569 	 */
       
   570 	public function add_section( $id, $args = array() ) {
       
   571 		if ( is_a( $id, 'WP_Customize_Section' ) )
       
   572 			$section = $id;
       
   573 		else
       
   574 			$section = new WP_Customize_Section( $this, $id, $args );
       
   575 
       
   576 		$this->sections[ $section->id ] = $section;
       
   577 	}
       
   578 
       
   579 	/**
       
   580 	 * Retrieve a customize section.
       
   581 	 *
       
   582 	 * @since 3.4.0
       
   583 	 *
       
   584 	 * @param string $id A specific ID of the section.
       
   585 	 * @return object The section object.
       
   586 	 */
       
   587 	public function get_section( $id ) {
       
   588 		if ( isset( $this->sections[ $id ] ) )
       
   589 			return $this->sections[ $id ];
       
   590 	}
       
   591 
       
   592 	/**
       
   593 	 * Remove a customize section.
       
   594 	 *
       
   595 	 * @since 3.4.0
       
   596 	 *
       
   597 	 * @param string $id A specific ID of the section.
       
   598 	 */
       
   599 	public function remove_section( $id ) {
       
   600 		unset( $this->sections[ $id ] );
       
   601 	}
       
   602 
       
   603 	/**
       
   604 	 * Add a customize control.
       
   605 	 *
       
   606 	 * @since 3.4.0
       
   607 	 *
       
   608 	 * @param string $id A specific ID of the control.
       
   609 	 * @param array $args Setting arguments.
       
   610 	 */
       
   611 	public function add_control( $id, $args = array() ) {
       
   612 		if ( is_a( $id, 'WP_Customize_Control' ) )
       
   613 			$control = $id;
       
   614 		else
       
   615 			$control = new WP_Customize_Control( $this, $id, $args );
       
   616 
       
   617 		$this->controls[ $control->id ] = $control;
       
   618 	}
       
   619 
       
   620 	/**
       
   621 	 * Retrieve a customize control.
       
   622 	 *
       
   623 	 * @since 3.4.0
       
   624 	 *
       
   625 	 * @param string $id A specific ID of the control.
       
   626 	 * @return object The settings object.
       
   627 	 */
       
   628 	public function get_control( $id ) {
       
   629 		if ( isset( $this->controls[ $id ] ) )
       
   630 			return $this->controls[ $id ];
       
   631 	}
       
   632 
       
   633 	/**
       
   634 	 * Remove a customize setting.
       
   635 	 *
       
   636 	 * @since 3.4.0
       
   637 	 *
       
   638 	 * @param string $id A specific ID of the control.
       
   639 	 */
       
   640 	public function remove_control( $id ) {
       
   641 		unset( $this->controls[ $id ] );
       
   642 	}
       
   643 
       
   644 	/**
       
   645 	 * Helper function to compare two objects by priority.
       
   646 	 *
       
   647 	 * @since 3.4.0
       
   648 	 *
       
   649 	 * @param object $a Object A.
       
   650 	 * @param object $b Object B.
       
   651 	 * @return int
       
   652 	 */
       
   653 	protected final function _cmp_priority( $a, $b ) {
       
   654 		$ap = $a->priority;
       
   655 		$bp = $b->priority;
       
   656 
       
   657 		if ( $ap == $bp )
       
   658 			return 0;
       
   659 		return ( $ap > $bp ) ? 1 : -1;
       
   660 	}
       
   661 
       
   662 	/**
       
   663 	 * Prepare settings and sections.
       
   664 	 *
       
   665 	 * @since 3.4.0
       
   666 	 */
       
   667 	public function prepare_controls() {
       
   668 		// Prepare controls
       
   669 		// Reversing makes uasort sort by time added when conflicts occur.
       
   670 
       
   671 		$this->controls = array_reverse( $this->controls );
       
   672 		$controls = array();
       
   673 
       
   674 		foreach ( $this->controls as $id => $control ) {
       
   675 			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() )
       
   676 				continue;
       
   677 
       
   678 			$this->sections[ $control->section ]->controls[] = $control;
       
   679 			$controls[ $id ] = $control;
       
   680 		}
       
   681 		$this->controls = $controls;
       
   682 
       
   683 		// Prepare sections
       
   684 		$this->sections = array_reverse( $this->sections );
       
   685 		uasort( $this->sections, array( $this, '_cmp_priority' ) );
       
   686 		$sections = array();
       
   687 
       
   688 		foreach ( $this->sections as $section ) {
       
   689 			if ( ! $section->check_capabilities() || ! $section->controls )
       
   690 				continue;
       
   691 
       
   692 			usort( $section->controls, array( $this, '_cmp_priority' ) );
       
   693 			$sections[] = $section;
       
   694 		}
       
   695 		$this->sections = $sections;
       
   696 	}
       
   697 
       
   698 	/**
       
   699 	 * Enqueue scripts for customize controls.
       
   700 	 *
       
   701 	 * @since 3.4.0
       
   702 	 */
       
   703 	public function enqueue_control_scripts() {
       
   704 		foreach ( $this->controls as $control ) {
       
   705 			$control->enqueue();
       
   706 		}
       
   707 	}
       
   708 
       
   709 	/**
       
   710 	 * Register some default controls.
       
   711 	 *
       
   712 	 * @since 3.4.0
       
   713 	 */
       
   714 	public function register_controls() {
       
   715 
       
   716 		/* Site Title & Tagline */
       
   717 
       
   718 		$this->add_section( 'title_tagline', array(
       
   719 			'title'    => __( 'Site Title & Tagline' ),
       
   720 			'priority' => 20,
       
   721 		) );
       
   722 
       
   723 		$this->add_setting( 'blogname', array(
       
   724 			'default'    => get_option( 'blogname' ),
       
   725 			'type'       => 'option',
       
   726 			'capability' => 'manage_options',
       
   727 		) );
       
   728 
       
   729 		$this->add_control( 'blogname', array(
       
   730 			'label'      => __( 'Site Title' ),
       
   731 			'section'    => 'title_tagline',
       
   732 		) );
       
   733 
       
   734 		$this->add_setting( 'blogdescription', array(
       
   735 			'default'    => get_option( 'blogdescription' ),
       
   736 			'type'       => 'option',
       
   737 			'capability' => 'manage_options',
       
   738 		) );
       
   739 
       
   740 		$this->add_control( 'blogdescription', array(
       
   741 			'label'      => __( 'Tagline' ),
       
   742 			'section'    => 'title_tagline',
       
   743 		) );
       
   744 
       
   745 		/* Colors */
       
   746 
       
   747 		$this->add_section( 'colors', array(
       
   748 			'title'          => __( 'Colors' ),
       
   749 			'priority'       => 40,
       
   750 		) );
       
   751 
       
   752 		$this->add_setting( 'header_textcolor', array(
       
   753 			'theme_supports' => array( 'custom-header', 'header-text' ),
       
   754 			'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
       
   755 
       
   756 			'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
       
   757 			'sanitize_js_callback' => 'maybe_hash_hex_color',
       
   758 		) );
       
   759 
       
   760 		// Input type: checkbox
       
   761 		// With custom value
       
   762 		$this->add_control( 'display_header_text', array(
       
   763 			'settings' => 'header_textcolor',
       
   764 			'label'    => __( 'Display Header Text' ),
       
   765 			'section'  => 'title_tagline',
       
   766 			'type'     => 'checkbox',
       
   767 		) );
       
   768 
       
   769 		$this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
       
   770 			'label'   => __( 'Header Text Color' ),
       
   771 			'section' => 'colors',
       
   772 		) ) );
       
   773 
       
   774 		// Input type: Color
       
   775 		// With sanitize_callback
       
   776 		$this->add_setting( 'background_color', array(
       
   777 			'default'        => get_theme_support( 'custom-background', 'default-color' ),
       
   778 			'theme_supports' => 'custom-background',
       
   779 
       
   780 			'sanitize_callback'    => 'sanitize_hex_color_no_hash',
       
   781 			'sanitize_js_callback' => 'maybe_hash_hex_color',
       
   782 		) );
       
   783 
       
   784 		$this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
       
   785 			'label'   => __( 'Background Color' ),
       
   786 			'section' => 'colors',
       
   787 		) ) );
       
   788 
       
   789 
       
   790 		/* Custom Header */
       
   791 
       
   792 		$this->add_section( 'header_image', array(
       
   793 			'title'          => __( 'Header Image' ),
       
   794 			'theme_supports' => 'custom-header',
       
   795 			'priority'       => 60,
       
   796 		) );
       
   797 
       
   798 		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
       
   799 			'default'        => get_theme_support( 'custom-header', 'default-image' ),
       
   800 			'theme_supports' => 'custom-header',
       
   801 		) ) );
       
   802 
       
   803 		$this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
       
   804 			// 'default'        => get_theme_support( 'custom-header', 'default-image' ),
       
   805 			'theme_supports' => 'custom-header',
       
   806 		) ) );
       
   807 
       
   808 		$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
       
   809 
       
   810 		/* Custom Background */
       
   811 
       
   812 		$this->add_section( 'background_image', array(
       
   813 			'title'          => __( 'Background Image' ),
       
   814 			'theme_supports' => 'custom-background',
       
   815 			'priority'       => 80,
       
   816 		) );
       
   817 
       
   818 		$this->add_setting( 'background_image', array(
       
   819 			'default'        => get_theme_support( 'custom-background', 'default-image' ),
       
   820 			'theme_supports' => 'custom-background',
       
   821 		) );
       
   822 
       
   823 		$this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
       
   824 			'theme_supports' => 'custom-background',
       
   825 		) ) );
       
   826 
       
   827 		$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
       
   828 
       
   829 		$this->add_setting( 'background_repeat', array(
       
   830 			'default'        => 'repeat',
       
   831 			'theme_supports' => 'custom-background',
       
   832 		) );
       
   833 
       
   834 		$this->add_control( 'background_repeat', array(
       
   835 			'label'      => __( 'Background Repeat' ),
       
   836 			'section'    => 'background_image',
       
   837 			'type'       => 'radio',
       
   838 			'choices'    => array(
       
   839 				'no-repeat'  => __('No Repeat'),
       
   840 				'repeat'     => __('Tile'),
       
   841 				'repeat-x'   => __('Tile Horizontally'),
       
   842 				'repeat-y'   => __('Tile Vertically'),
       
   843 			),
       
   844 		) );
       
   845 
       
   846 		$this->add_setting( 'background_position_x', array(
       
   847 			'default'        => 'left',
       
   848 			'theme_supports' => 'custom-background',
       
   849 		) );
       
   850 
       
   851 		$this->add_control( 'background_position_x', array(
       
   852 			'label'      => __( 'Background Position' ),
       
   853 			'section'    => 'background_image',
       
   854 			'type'       => 'radio',
       
   855 			'choices'    => array(
       
   856 				'left'       => __('Left'),
       
   857 				'center'     => __('Center'),
       
   858 				'right'      => __('Right'),
       
   859 			),
       
   860 		) );
       
   861 
       
   862 		$this->add_setting( 'background_attachment', array(
       
   863 			'default'        => 'fixed',
       
   864 			'theme_supports' => 'custom-background',
       
   865 		) );
       
   866 
       
   867 		$this->add_control( 'background_attachment', array(
       
   868 			'label'      => __( 'Background Attachment' ),
       
   869 			'section'    => 'background_image',
       
   870 			'type'       => 'radio',
       
   871 			'choices'    => array(
       
   872 				'fixed'      => __('Fixed'),
       
   873 				'scroll'     => __('Scroll'),
       
   874 			),
       
   875 		) );
       
   876 
       
   877 		// If the theme is using the default background callback, we can update
       
   878 		// the background CSS using postMessage.
       
   879 		if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
       
   880 			foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
       
   881 				$this->get_setting( 'background_' . $prop )->transport = 'postMessage';
       
   882 			}
       
   883 		}
       
   884 
       
   885 		/* Nav Menus */
       
   886 
       
   887 		$locations      = get_registered_nav_menus();
       
   888 		$menus          = wp_get_nav_menus();
       
   889 		$menu_locations = get_nav_menu_locations();
       
   890 		$num_locations  = count( array_keys( $locations ) );
       
   891 
       
   892 		$this->add_section( 'nav', array(
       
   893 			'title'          => __( 'Navigation' ),
       
   894 			'theme_supports' => 'menus',
       
   895 			'priority'       => 100,
       
   896 			'description'    => sprintf( _n('Your theme supports %s menu. Select which menu you would like to use.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) ) . "\n\n" . __('You can edit your menu content on the Menus screen in the Appearance section.'),
       
   897 		) );
       
   898 
       
   899 		if ( $menus ) {
       
   900 			$choices = array( 0 => __( '&mdash; Select &mdash;' ) );
       
   901 			foreach ( $menus as $menu ) {
       
   902 				$choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
       
   903 			}
       
   904 
       
   905 			foreach ( $locations as $location => $description ) {
       
   906 				$menu_setting_id = "nav_menu_locations[{$location}]";
       
   907 
       
   908 				$this->add_setting( $menu_setting_id, array(
       
   909 					'sanitize_callback' => 'absint',
       
   910 					'theme_supports'    => 'menus',
       
   911 				) );
       
   912 
       
   913 				$this->add_control( $menu_setting_id, array(
       
   914 					'label'   => $description,
       
   915 					'section' => 'nav',
       
   916 					'type'    => 'select',
       
   917 					'choices' => $choices,
       
   918 				) );
       
   919 			}
       
   920 		}
       
   921 
       
   922 		/* Static Front Page */
       
   923 		// #WP19627
       
   924 
       
   925 		$this->add_section( 'static_front_page', array(
       
   926 			'title'          => __( 'Static Front Page' ),
       
   927 		//	'theme_supports' => 'static-front-page',
       
   928 			'priority'       => 120,
       
   929 			'description'    => __( 'Your theme supports a static front page.' ),
       
   930 		) );
       
   931 
       
   932 		$this->add_setting( 'show_on_front', array(
       
   933 			'default'        => get_option( 'show_on_front' ),
       
   934 			'capability'     => 'manage_options',
       
   935 			'type'           => 'option',
       
   936 		//	'theme_supports' => 'static-front-page',
       
   937 		) );
       
   938 
       
   939 		$this->add_control( 'show_on_front', array(
       
   940 			'label'   => __( 'Front page displays' ),
       
   941 			'section' => 'static_front_page',
       
   942 			'type'    => 'radio',
       
   943 			'choices' => array(
       
   944 				'posts' => __( 'Your latest posts' ),
       
   945 				'page'  => __( 'A static page' ),
       
   946 			),
       
   947 		) );
       
   948 
       
   949 		$this->add_setting( 'page_on_front', array(
       
   950 			'type'       => 'option',
       
   951 			'capability' => 'manage_options',
       
   952 		//	'theme_supports' => 'static-front-page',
       
   953 		) );
       
   954 
       
   955 		$this->add_control( 'page_on_front', array(
       
   956 			'label'      => __( 'Front page' ),
       
   957 			'section'    => 'static_front_page',
       
   958 			'type'       => 'dropdown-pages',
       
   959 		) );
       
   960 
       
   961 		$this->add_setting( 'page_for_posts', array(
       
   962 			'type'           => 'option',
       
   963 			'capability'     => 'manage_options',
       
   964 		//	'theme_supports' => 'static-front-page',
       
   965 		) );
       
   966 
       
   967 		$this->add_control( 'page_for_posts', array(
       
   968 			'label'      => __( 'Posts page' ),
       
   969 			'section'    => 'static_front_page',
       
   970 			'type'       => 'dropdown-pages',
       
   971 		) );
       
   972 	}
       
   973 
       
   974 	/**
       
   975 	 * Callback for validating the header_textcolor value.
       
   976 	 *
       
   977 	 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
       
   978 	 * Returns default text color if hex color is empty.
       
   979 	 *
       
   980 	 * @since 3.4.0
       
   981 	 *
       
   982 	 * @param string $color
       
   983 	 * @return string
       
   984 	 */
       
   985 	public function _sanitize_header_textcolor( $color ) {
       
   986 		if ( 'blank' === $color )
       
   987 			return 'blank';
       
   988 
       
   989 		$color = sanitize_hex_color_no_hash( $color );
       
   990 		if ( empty( $color ) )
       
   991 			$color = get_theme_support( 'custom-header', 'default-text-color' );
       
   992 
       
   993 		return $color;
       
   994 	}
       
   995 };
       
   996 
       
   997 /**
       
   998  * Validates a hex color.
       
   999  *
       
  1000  * Returns either '', a 3 or 6 digit hex color (with #), or null.
       
  1001  * For validating values without a #, see sanitize_hex_color_no_hash().
       
  1002  *
       
  1003  * @since 3.4.0
       
  1004  *
       
  1005  * @param string $color
       
  1006  * @return string|null
       
  1007  */
       
  1008 function sanitize_hex_color( $color ) {
       
  1009 	if ( '' === $color )
       
  1010 		return '';
       
  1011 
       
  1012 	// 3 or 6 hex digits, or the empty string.
       
  1013 	if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
       
  1014 		return $color;
       
  1015 
       
  1016 	return null;
       
  1017 }
       
  1018 
       
  1019 /**
       
  1020  * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
       
  1021  *
       
  1022  * Saving hex colors without a hash puts the burden of adding the hash on the
       
  1023  * UI, which makes it difficult to use or upgrade to other color types such as
       
  1024  * rgba, hsl, rgb, and html color names.
       
  1025  *
       
  1026  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
       
  1027  *
       
  1028  * @since 3.4.0
       
  1029  * @uses sanitize_hex_color()
       
  1030  *
       
  1031  * @param string $color
       
  1032  * @return string|null
       
  1033  */
       
  1034 function sanitize_hex_color_no_hash( $color ) {
       
  1035 	$color = ltrim( $color, '#' );
       
  1036 
       
  1037 	if ( '' === $color )
       
  1038 		return '';
       
  1039 
       
  1040 	return sanitize_hex_color( '#' . $color ) ? $color : null;
       
  1041 }
       
  1042 
       
  1043 /**
       
  1044  * Ensures that any hex color is properly hashed.
       
  1045  * Otherwise, returns value untouched.
       
  1046  *
       
  1047  * This method should only be necessary if using sanitize_hex_color_no_hash().
       
  1048  *
       
  1049  * @since 3.4.0
       
  1050  *
       
  1051  * @param string $color
       
  1052  * @return string
       
  1053  */
       
  1054 function maybe_hash_hex_color( $color ) {
       
  1055 	if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
       
  1056 		return '#' . $unhashed;
       
  1057 
       
  1058 	return $color;
       
  1059 }