Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/class-wp-customize-manager.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * WordPress Customize Manager classes
4
 *
5
 * @package WordPress
6
 * @subpackage Customize
7
 * @since 3.4.0
8
 */
9
10
/**
11
 * Customize Manager class.
12
 *
13
 * Bootstraps the Customize experience on the server-side.
14
 *
15
 * Sets up the theme-switching process if a theme other than the active one is
16
 * being previewed and customized.
17
 *
18
 * Serves as a factory for Customize Controls and Settings, and
19
 * instantiates default Customize Controls and Settings.
20
 *
21
 * @since 3.4.0
22
 */
23
final class WP_Customize_Manager {
24
	/**
25
	 * An instance of the theme being previewed.
26
	 *
27
	 * @since 3.4.0
28
	 * @access protected
29
	 * @var WP_Theme
30
	 */
31
	protected $theme;
32
33
	/**
34
	 * The directory name of the previously active theme (within the theme_root).
35
	 *
36
	 * @since 3.4.0
37
	 * @access protected
38
	 * @var string
39
	 */
40
	protected $original_stylesheet;
41
42
	/**
43
	 * Whether this is a Customizer pageload.
44
	 *
45
	 * @since 3.4.0
46
	 * @access protected
47
	 * @var bool
48
	 */
49
	protected $previewing = false;
50
51
	/**
52
	 * Methods and properties dealing with managing widgets in the Customizer.
53
	 *
54
	 * @since 3.9.0
55
	 * @access public
56
	 * @var WP_Customize_Widgets
57
	 */
58
	public $widgets;
59
60
	/**
61
	 * Methods and properties dealing with managing nav menus in the Customizer.
62
	 *
63
	 * @since 4.3.0
64
	 * @access public
65
	 * @var WP_Customize_Nav_Menus
66
	 */
67
	public $nav_menus;
68
69
	/**
70
	 * Methods and properties dealing with selective refresh in the Customizer preview.
71
	 *
72
	 * @since 4.5.0
73
	 * @access public
74
	 * @var WP_Customize_Selective_Refresh
75
	 */
76
	public $selective_refresh;
77
78
	/**
79
	 * Registered instances of WP_Customize_Setting.
80
	 *
81
	 * @since 3.4.0
82
	 * @access protected
83
	 * @var array
84
	 */
85
	protected $settings = array();
86
87
	/**
88
	 * Sorted top-level instances of WP_Customize_Panel and WP_Customize_Section.
89
	 *
90
	 * @since 4.0.0
91
	 * @access protected
92
	 * @var array
93
	 */
94
	protected $containers = array();
95
96
	/**
97
	 * Registered instances of WP_Customize_Panel.
98
	 *
99
	 * @since 4.0.0
100
	 * @access protected
101
	 * @var array
102
	 */
103
	protected $panels = array();
104
105
	/**
106
	 * List of core components.
107
	 *
108
	 * @since 4.5.0
109
	 * @access protected
110
	 * @var array
111
	 */
112
	protected $components = array( 'widgets', 'nav_menus' );
113
114
	/**
115
	 * Registered instances of WP_Customize_Section.
116
	 *
117
	 * @since 3.4.0
118
	 * @access protected
119
	 * @var array
120
	 */
121
	protected $sections = array();
122
123
	/**
124
	 * Registered instances of WP_Customize_Control.
125
	 *
126
	 * @since 3.4.0
127
	 * @access protected
128
	 * @var array
129
	 */
130
	protected $controls = array();
131
132
	/**
133
	 * Return value of check_ajax_referer() in customize_preview_init() method.
134
	 *
135
	 * @since 3.5.0
136
	 * @access protected
137
	 * @var false|int
138
	 */
139
	protected $nonce_tick;
140
141
	/**
142
	 * Panel types that may be rendered from JS templates.
143
	 *
144
	 * @since 4.3.0
145
	 * @access protected
146
	 * @var array
147
	 */
148
	protected $registered_panel_types = array();
149
150
	/**
151
	 * Section types that may be rendered from JS templates.
152
	 *
153
	 * @since 4.3.0
154
	 * @access protected
155
	 * @var array
156
	 */
157
	protected $registered_section_types = array();
158
159
	/**
160
	 * Control types that may be rendered from JS templates.
161
	 *
162
	 * @since 4.1.0
163
	 * @access protected
164
	 * @var array
165
	 */
166
	protected $registered_control_types = array();
167
168
	/**
169
	 * Initial URL being previewed.
170
	 *
171
	 * @since 4.4.0
172
	 * @access protected
173
	 * @var string
174
	 */
175
	protected $preview_url;
176
177
	/**
178
	 * URL to link the user to when closing the Customizer.
179
	 *
180
	 * @since 4.4.0
181
	 * @access protected
182
	 * @var string
183
	 */
184
	protected $return_url;
185
186
	/**
187
	 * Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
188
	 *
189
	 * @since 4.4.0
190
	 * @access protected
191
	 * @var array
192
	 */
193
	protected $autofocus = array();
194
195
	/**
196
	 * Unsanitized values for Customize Settings parsed from $_POST['customized'].
197
	 *
198
	 * @var array
199
	 */
200
	private $_post_values;
201
202
	/**
203
	 * Constructor.
204
	 *
205
	 * @since 3.4.0
206
	 */
207
	public function __construct() {
208
		require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
209
		require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
210
		require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
211
		require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
212
213
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-color-control.php' );
214
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-media-control.php' );
215
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php' );
216
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php' );
217
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' );
218
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' );
219
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
220
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
221
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
222
		require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
223
		require_once( ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php' );
224
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-control.php' );
225
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php' );
226
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php' );
227
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
228
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
229
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );
230
231
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
232
233
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
234
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
235
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' );
236
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-section.php' );
237
238
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
239
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
240
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
241
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
242
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
243
244
		/**
245
		 * Filters the core Customizer components to load.
246
		 *
247
		 * This allows Core components to be excluded from being instantiated by
248
		 * filtering them out of the array. Note that this filter generally runs
249
		 * during the {@see 'plugins_loaded'} action, so it cannot be added
250
		 * in a theme.
251
		 *
252
		 * @since 4.4.0
253
		 *
254
		 * @see WP_Customize_Manager::__construct()
255
		 *
256
		 * @param array                $components List of core components to load.
257
		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
258
		 */
259
		$components = apply_filters( 'customize_loaded_components', $this->components, $this );
260
261
		require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
262
		$this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
263
264 View Code Duplication
		if ( in_array( 'widgets', $components, true ) ) {
265
			require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
266
			$this->widgets = new WP_Customize_Widgets( $this );
267
		}
268
269 View Code Duplication
		if ( in_array( 'nav_menus', $components, true ) ) {
270
			require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
271
			$this->nav_menus = new WP_Customize_Nav_Menus( $this );
272
		}
273
274
		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
275
276
		add_action( 'setup_theme', array( $this, 'setup_theme' ) );
277
		add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
278
279
		// Run wp_redirect_status late to make sure we override the status last.
280
		add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
281
282
		// Do not spawn cron (especially the alternate cron) while running the Customizer.
283
		remove_action( 'init', 'wp_cron' );
284
285
		// Do not run update checks when rendering the controls.
286
		remove_action( 'admin_init', '_maybe_update_core' );
287
		remove_action( 'admin_init', '_maybe_update_plugins' );
288
		remove_action( 'admin_init', '_maybe_update_themes' );
289
290
		add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
291
		add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
292
293
		add_action( 'customize_register',                 array( $this, 'register_controls' ) );
294
		add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
295
		add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
296
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
297
298
		// Render Panel, Section, and Control templates.
299
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
300
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
301
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
302
303
		// Export the settings to JS via the _wpCustomizeSettings variable.
304
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
305
	}
306
307
	/**
308
	 * Return true if it's an Ajax request.
309
	 *
310
	 * @since 3.4.0
311
	 * @since 4.2.0 Added `$action` param.
312
	 * @access public
313
	 *
314
	 * @param string|null $action Whether the supplied Ajax action is being run.
315
	 * @return bool True if it's an Ajax request, false otherwise.
316
	 */
317
	public function doing_ajax( $action = null ) {
318
		$doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
319
		if ( ! $doing_ajax ) {
320
			return false;
321
		}
322
323
		if ( ! $action ) {
324
			return true;
325
		} else {
326
			/*
327
			 * Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need
328
			 * to check before admin-ajax.php gets to that point.
329
			 */
330
			return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
331
		}
332
	}
333
334
	/**
335
	 * Custom wp_die wrapper. Returns either the standard message for UI
336
	 * or the Ajax message.
337
	 *
338
	 * @since 3.4.0
339
	 *
340
	 * @param mixed $ajax_message Ajax return
341
	 * @param mixed $message UI message
342
	 */
343
	protected function wp_die( $ajax_message, $message = null ) {
344
		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
345
			wp_die( $ajax_message );
346
		}
347
348
		if ( ! $message ) {
349
			$message = __( 'Cheatin&#8217; uh?' );
350
		}
351
352
		wp_die( $message );
353
	}
354
355
	/**
356
	 * Return the Ajax wp_die() handler if it's a customized request.
357
	 *
358
	 * @since 3.4.0
359
	 *
360
	 * @return string
361
	 */
362
	public function wp_die_handler() {
363
		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
364
			return '_ajax_wp_die_handler';
365
		}
366
367
		return '_default_wp_die_handler';
368
	}
369
370
	/**
371
	 * Start preview and customize theme.
372
	 *
373
	 * Check if customize query variable exist. Init filters to filter the current theme.
374
	 *
375
	 * @since 3.4.0
376
	 */
377
	public function setup_theme() {
378
		send_origin_headers();
379
380
		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
381
		if ( is_admin() && ! $doing_ajax_or_is_customized ) {
382
			auth_redirect();
383
		} elseif ( $doing_ajax_or_is_customized && ! is_user_logged_in() ) {
384
			$this->wp_die( 0, __( 'You must be logged in to complete this action.' ) );
385
		}
386
387
		show_admin_bar( false );
388
389
		if ( ! current_user_can( 'customize' ) ) {
390
			$this->wp_die( -1, __( 'Sorry, you are not allowed to customize this site.' ) );
391
		}
392
393
		$this->original_stylesheet = get_stylesheet();
394
395
		$this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
396
397
		if ( $this->is_theme_active() ) {
398
			// Once the theme is loaded, we'll validate it.
399
			add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
400
		} else {
401
			// If the requested theme is not the active theme and the user doesn't have the
402
			// switch_themes cap, bail.
403
			if ( ! current_user_can( 'switch_themes' ) ) {
404
				$this->wp_die( -1, __( 'Sorry, you are not allowed to edit theme options on this site.' ) );
405
			}
406
407
			// If the theme has errors while loading, bail.
408
			if ( $this->theme()->errors() ) {
409
				$this->wp_die( -1, $this->theme()->errors()->get_error_message() );
410
			}
411
412
			// If the theme isn't allowed per multisite settings, bail.
413
			if ( ! $this->theme()->is_allowed() ) {
414
				$this->wp_die( -1, __( 'The requested theme does not exist.' ) );
415
			}
416
		}
417
418
		$this->start_previewing_theme();
419
	}
420
421
	/**
422
	 * Callback to validate a theme once it is loaded
423
	 *
424
	 * @since 3.4.0
425
	 */
426
	public function after_setup_theme() {
427
		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
428
		if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
429
			wp_redirect( 'themes.php?broken=true' );
430
			exit;
431
		}
432
	}
433
434
	/**
435
	 * If the theme to be previewed isn't the active theme, add filter callbacks
436
	 * to swap it out at runtime.
437
	 *
438
	 * @since 3.4.0
439
	 */
440 View Code Duplication
	public function start_previewing_theme() {
441
		// Bail if we're already previewing.
442
		if ( $this->is_preview() ) {
443
			return;
444
		}
445
446
		$this->previewing = true;
447
448
		if ( ! $this->is_theme_active() ) {
449
			add_filter( 'template', array( $this, 'get_template' ) );
450
			add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
451
			add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
452
453
			// @link: https://core.trac.wordpress.org/ticket/20027
454
			add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
455
			add_filter( 'pre_option_template', array( $this, 'get_template' ) );
456
457
			// Handle custom theme roots.
458
			add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
459
			add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
460
		}
461
462
		/**
463
		 * Fires once the Customizer theme preview has started.
464
		 *
465
		 * @since 3.4.0
466
		 *
467
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
468
		 */
469
		do_action( 'start_previewing_theme', $this );
470
	}
471
472
	/**
473
	 * Stop previewing the selected theme.
474
	 *
475
	 * Removes filters to change the current theme.
476
	 *
477
	 * @since 3.4.0
478
	 */
479 View Code Duplication
	public function stop_previewing_theme() {
480
		if ( ! $this->is_preview() ) {
481
			return;
482
		}
483
484
		$this->previewing = false;
485
486
		if ( ! $this->is_theme_active() ) {
487
			remove_filter( 'template', array( $this, 'get_template' ) );
488
			remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
489
			remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
490
491
			// @link: https://core.trac.wordpress.org/ticket/20027
492
			remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
493
			remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
494
495
			// Handle custom theme roots.
496
			remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
497
			remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
498
		}
499
500
		/**
501
		 * Fires once the Customizer theme preview has stopped.
502
		 *
503
		 * @since 3.4.0
504
		 *
505
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
506
		 */
507
		do_action( 'stop_previewing_theme', $this );
508
	}
509
510
	/**
511
	 * Get the theme being customized.
512
	 *
513
	 * @since 3.4.0
514
	 *
515
	 * @return WP_Theme
516
	 */
517
	public function theme() {
518
		if ( ! $this->theme ) {
519
			$this->theme = wp_get_theme();
520
		}
521
		return $this->theme;
522
	}
523
524
	/**
525
	 * Get the registered settings.
526
	 *
527
	 * @since 3.4.0
528
	 *
529
	 * @return array
530
	 */
531
	public function settings() {
532
		return $this->settings;
533
	}
534
535
	/**
536
	 * Get the registered controls.
537
	 *
538
	 * @since 3.4.0
539
	 *
540
	 * @return array
541
	 */
542
	public function controls() {
543
		return $this->controls;
544
	}
545
546
	/**
547
	 * Get the registered containers.
548
	 *
549
	 * @since 4.0.0
550
	 *
551
	 * @return array
552
	 */
553
	public function containers() {
554
		return $this->containers;
555
	}
556
557
	/**
558
	 * Get the registered sections.
559
	 *
560
	 * @since 3.4.0
561
	 *
562
	 * @return array
563
	 */
564
	public function sections() {
565
		return $this->sections;
566
	}
567
568
	/**
569
	 * Get the registered panels.
570
	 *
571
	 * @since 4.0.0
572
	 * @access public
573
	 *
574
	 * @return array Panels.
575
	 */
576
	public function panels() {
577
		return $this->panels;
578
	}
579
580
	/**
581
	 * Checks if the current theme is active.
582
	 *
583
	 * @since 3.4.0
584
	 *
585
	 * @return bool
586
	 */
587
	public function is_theme_active() {
588
		return $this->get_stylesheet() == $this->original_stylesheet;
589
	}
590
591
	/**
592
	 * Register styles/scripts and initialize the preview of each setting
593
	 *
594
	 * @since 3.4.0
595
	 */
596
	public function wp_loaded() {
597
598
		/**
599
		 * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
600
		 *
601
		 * @since 3.4.0
602
		 *
603
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
604
		 */
605
		do_action( 'customize_register', $this );
606
607
		if ( $this->is_preview() && ! is_admin() )
608
			$this->customize_preview_init();
609
	}
610
611
	/**
612
	 * Prevents Ajax requests from following redirects when previewing a theme
613
	 * by issuing a 200 response instead of a 30x.
614
	 *
615
	 * Instead, the JS will sniff out the location header.
616
	 *
617
	 * @since 3.4.0
618
	 *
619
	 * @param $status
620
	 * @return int
621
	 */
622
	public function wp_redirect_status( $status ) {
623
		if ( $this->is_preview() && ! is_admin() )
624
			return 200;
625
626
		return $status;
627
	}
628
629
	/**
630
	 * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
631
	 * settings for subsequent post_value() lookups.
632
	 *
633
	 * @since 4.1.1
634
	 *
635
	 * @return array
636
	 */
637
	public function unsanitized_post_values() {
638
		if ( ! isset( $this->_post_values ) ) {
639
			if ( isset( $_POST['customized'] ) ) {
640
				$this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
641
			}
642
			if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
643
				$this->_post_values = array();
644
			}
645
		}
646
		if ( empty( $this->_post_values ) ) {
647
			return array();
648
		} else {
649
			return $this->_post_values;
650
		}
651
	}
652
653
	/**
654
	 * Returns the sanitized value for a given setting from the request's POST data.
655
	 *
656
	 * @since 3.4.0
657
	 * @since 4.1.1 Introduced the `$default` parameter.
658
	 * @since 4.6.0 `$default` is now returned early when the setting post value is invalid.
659
	 * @access public
660
	 *
661
	 * @see WP_REST_Server::dispatch()
662
	 * @see WP_Rest_Request::sanitize_params()
663
	 * @see WP_Rest_Request::has_valid_params()
664
	 *
665
	 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object.
666
	 * @param mixed                $default Value returned $setting has no post value (added in 4.2.0)
667
	 *                                      or the post value is invalid (added in 4.6.0).
668
	 * @return string|mixed $post_value Sanitized value or the $default provided.
669
	 */
670
	public function post_value( $setting, $default = null ) {
671
		$post_values = $this->unsanitized_post_values();
672
		if ( ! array_key_exists( $setting->id, $post_values ) ) {
673
			return $default;
674
		}
675
		$value = $post_values[ $setting->id ];
676
		$valid = $setting->validate( $value );
677
		if ( is_wp_error( $valid ) ) {
678
			return $default;
679
		}
680
		$value = $setting->sanitize( $value );
681
		if ( is_null( $value ) || is_wp_error( $value ) ) {
682
			return $default;
683
		}
684
		return $value;
685
	}
686
687
	/**
688
	 * Override a setting's (unsanitized) value as found in any incoming $_POST['customized'].
689
	 *
690
	 * @since 4.2.0
691
	 * @access public
692
	 *
693
	 * @param string $setting_id ID for the WP_Customize_Setting instance.
694
	 * @param mixed  $value      Post value.
695
	 */
696
	public function set_post_value( $setting_id, $value ) {
697
		$this->unsanitized_post_values();
698
		$this->_post_values[ $setting_id ] = $value;
699
700
		/**
701
		 * Announce when a specific setting's unsanitized post value has been set.
702
		 *
703
		 * Fires when the WP_Customize_Manager::set_post_value() method is called.
704
		 *
705
		 * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID.
706
		 *
707
		 * @since 4.4.0
708
		 *
709
		 * @param mixed                $value Unsanitized setting post value.
710
		 * @param WP_Customize_Manager $this  WP_Customize_Manager instance.
711
		 */
712
		do_action( "customize_post_value_set_{$setting_id}", $value, $this );
713
714
		/**
715
		 * Announce when any setting's unsanitized post value has been set.
716
		 *
717
		 * Fires when the WP_Customize_Manager::set_post_value() method is called.
718
		 *
719
		 * This is useful for `WP_Customize_Setting` instances to watch
720
		 * in order to update a cached previewed value.
721
		 *
722
		 * @since 4.4.0
723
		 *
724
		 * @param string               $setting_id Setting ID.
725
		 * @param mixed                $value      Unsanitized setting post value.
726
		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
727
		 */
728
		do_action( 'customize_post_value_set', $setting_id, $value, $this );
729
	}
730
731
	/**
732
	 * Print JavaScript settings.
733
	 *
734
	 * @since 3.4.0
735
	 */
736
	public function customize_preview_init() {
737
		$this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
738
739
		$this->prepare_controls();
740
741
		wp_enqueue_script( 'customize-preview' );
742
		add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
743
		add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
744
		add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
745
		add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
746
		add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
747
		add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
748
		add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
749
750
		foreach ( $this->settings as $setting ) {
751
			$setting->preview();
752
		}
753
754
		/**
755
		 * Fires once the Customizer preview has initialized and JavaScript
756
		 * settings have been printed.
757
		 *
758
		 * @since 3.4.0
759
		 *
760
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
761
		 */
762
		do_action( 'customize_preview_init', $this );
763
	}
764
765
	/**
766
	 * Prevent sending a 404 status when returning the response for the customize
767
	 * preview, since it causes the jQuery Ajax to fail. Send 200 instead.
768
	 *
769
	 * @since 4.0.0
770
	 * @access public
771
	 */
772
	public function customize_preview_override_404_status() {
773
		if ( is_404() ) {
774
			status_header( 200 );
775
		}
776
	}
777
778
	/**
779
	 * Print base element for preview frame.
780
	 *
781
	 * @since 3.4.0
782
	 */
783
	public function customize_preview_base() {
784
		?><base href="<?php echo home_url( '/' ); ?>" /><?php
785
	}
786
787
	/**
788
	 * Print a workaround to handle HTML5 tags in IE < 9.
789
	 *
790
	 * @since 3.4.0
791
	 */
792
	public function customize_preview_html5() { ?>
793
		<!--[if lt IE 9]>
794
		<script type="text/javascript">
795
			var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
796
				'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
797
				'output', 'progress', 'section', 'time', 'video' ];
798
			for ( var i = 0; i < e.length; i++ ) {
799
				document.createElement( e[i] );
800
			}
801
		</script>
802
		<![endif]--><?php
803
	}
804
805
	/**
806
	 * Print CSS for loading indicators for the Customizer preview.
807
	 *
808
	 * @since 4.2.0
809
	 * @access public
810
	 */
811
	public function customize_preview_loading_style() {
812
		?><style>
813
			body.wp-customizer-unloading {
814
				opacity: 0.25;
815
				cursor: progress !important;
816
				-webkit-transition: opacity 0.5s;
817
				transition: opacity 0.5s;
818
			}
819
			body.wp-customizer-unloading * {
820
				pointer-events: none !important;
821
			}
822
		</style><?php
823
	}
824
825
	/**
826
	 * Print JavaScript settings for preview frame.
827
	 *
828
	 * @since 3.4.0
829
	 */
830
	public function customize_preview_settings() {
831
		$setting_validities = $this->validate_setting_values( $this->unsanitized_post_values() );
832
		$exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities );
833
834
		$settings = array(
835
			'theme' => array(
836
				'stylesheet' => $this->get_stylesheet(),
837
				'active'     => $this->is_theme_active(),
838
			),
839
			'url' => array(
840
				'self' => empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ),
841
			),
842
			'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
843
			'activePanels' => array(),
844
			'activeSections' => array(),
845
			'activeControls' => array(),
846
			'settingValidities' => $exported_setting_validities,
847
			'nonce' => $this->get_nonces(),
848
			'l10n' => array(
849
				'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
850
			),
851
			'_dirty' => array_keys( $this->unsanitized_post_values() ),
852
		);
853
854 View Code Duplication
		foreach ( $this->panels as $panel_id => $panel ) {
855
			if ( $panel->check_capabilities() ) {
856
				$settings['activePanels'][ $panel_id ] = $panel->active();
857
				foreach ( $panel->sections as $section_id => $section ) {
858
					if ( $section->check_capabilities() ) {
859
						$settings['activeSections'][ $section_id ] = $section->active();
860
					}
861
				}
862
			}
863
		}
864
		foreach ( $this->sections as $id => $section ) {
865
			if ( $section->check_capabilities() ) {
866
				$settings['activeSections'][ $id ] = $section->active();
867
			}
868
		}
869
		foreach ( $this->controls as $id => $control ) {
870
			if ( $control->check_capabilities() ) {
871
				$settings['activeControls'][ $id ] = $control->active();
872
			}
873
		}
874
875
		?>
876
		<script type="text/javascript">
877
			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
878
			_wpCustomizeSettings.values = {};
879
			(function( v ) {
880
				<?php
881
				/*
882
				 * Serialize settings separately from the initial _wpCustomizeSettings
883
				 * serialization in order to avoid a peak memory usage spike.
884
				 * @todo We may not even need to export the values at all since the pane syncs them anyway.
885
				 */
886 View Code Duplication
				foreach ( $this->settings as $id => $setting ) {
887
					if ( $setting->check_capabilities() ) {
888
						printf(
889
							"v[%s] = %s;\n",
890
							wp_json_encode( $id ),
891
							wp_json_encode( $setting->js_value() )
892
						);
893
					}
894
				}
895
				?>
896
			})( _wpCustomizeSettings.values );
897
		</script>
898
		<?php
899
	}
900
901
	/**
902
	 * Prints a signature so we can ensure the Customizer was properly executed.
903
	 *
904
	 * @since 3.4.0
905
	 */
906
	public function customize_preview_signature() {
907
		echo 'WP_CUSTOMIZER_SIGNATURE';
908
	}
909
910
	/**
911
	 * Removes the signature in case we experience a case where the Customizer was not properly executed.
912
	 *
913
	 * @since 3.4.0
914
	 *
915
	 * @param mixed $return Value passed through for {@see 'wp_die_handler'} filter.
916
	 * @return mixed Value passed through for {@see 'wp_die_handler'} filter.
917
	 */
918
	public function remove_preview_signature( $return = null ) {
919
		remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
920
921
		return $return;
922
	}
923
924
	/**
925
	 * Is it a theme preview?
926
	 *
927
	 * @since 3.4.0
928
	 *
929
	 * @return bool True if it's a preview, false if not.
930
	 */
931
	public function is_preview() {
932
		return (bool) $this->previewing;
933
	}
934
935
	/**
936
	 * Retrieve the template name of the previewed theme.
937
	 *
938
	 * @since 3.4.0
939
	 *
940
	 * @return string Template name.
941
	 */
942
	public function get_template() {
943
		return $this->theme()->get_template();
944
	}
945
946
	/**
947
	 * Retrieve the stylesheet name of the previewed theme.
948
	 *
949
	 * @since 3.4.0
950
	 *
951
	 * @return string Stylesheet name.
952
	 */
953
	public function get_stylesheet() {
954
		return $this->theme()->get_stylesheet();
955
	}
956
957
	/**
958
	 * Retrieve the template root of the previewed theme.
959
	 *
960
	 * @since 3.4.0
961
	 *
962
	 * @return string Theme root.
963
	 */
964
	public function get_template_root() {
965
		return get_raw_theme_root( $this->get_template(), true );
966
	}
967
968
	/**
969
	 * Retrieve the stylesheet root of the previewed theme.
970
	 *
971
	 * @since 3.4.0
972
	 *
973
	 * @return string Theme root.
974
	 */
975
	public function get_stylesheet_root() {
976
		return get_raw_theme_root( $this->get_stylesheet(), true );
977
	}
978
979
	/**
980
	 * Filters the current theme and return the name of the previewed theme.
981
	 *
982
	 * @since 3.4.0
983
	 *
984
	 * @param $current_theme {@internal Parameter is not used}
985
	 * @return string Theme name.
986
	 */
987
	public function current_theme( $current_theme ) {
988
		return $this->theme()->display('Name');
989
	}
990
991
	/**
992
	 * Validates setting values.
993
	 *
994
	 * Validation is skipped for unregistered settings or for values that are
995
	 * already null since they will be skipped anyway. Sanitization is applied
996
	 * to values that pass validation, and values that become null or `WP_Error`
997
	 * after sanitizing are marked invalid.
998
	 *
999
	 * @since 4.6.0
1000
	 * @access public
1001
	 *
1002
	 * @see WP_REST_Request::has_valid_params()
1003
	 * @see WP_Customize_Setting::validate()
1004
	 *
1005
	 * @param array $setting_values Mapping of setting IDs to values to validate and sanitize.
1006
	 * @return array Mapping of setting IDs to return value of validate method calls, either `true` or `WP_Error`.
1007
	 */
1008
	public function validate_setting_values( $setting_values ) {
1009
		$validities = array();
1010
		foreach ( $setting_values as $setting_id => $unsanitized_value ) {
1011
			$setting = $this->get_setting( $setting_id );
1012
			if ( ! $setting || is_null( $unsanitized_value ) ) {
1013
				continue;
1014
			}
1015
			$validity = $setting->validate( $unsanitized_value );
1016
			if ( ! is_wp_error( $validity ) ) {
1017
				$value = $setting->sanitize( $unsanitized_value );
1018
				if ( is_null( $value ) ) {
1019
					$validity = false;
1020
				} elseif ( is_wp_error( $value ) ) {
1021
					$validity = $value;
1022
				}
1023
			}
1024
			if ( false === $validity ) {
1025
				$validity = new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
1026
			}
1027
			$validities[ $setting_id ] = $validity;
1028
		}
1029
		return $validities;
1030
	}
1031
1032
	/**
1033
	 * Prepares setting validity for exporting to the client (JS).
1034
	 *
1035
	 * Converts `WP_Error` instance into array suitable for passing into the
1036
	 * `wp.customize.Notification` JS model.
1037
	 *
1038
	 * @since 4.6.0
1039
	 * @access public
1040
	 *
1041
	 * @param true|WP_Error $validity Setting validity.
1042
	 * @return true|array If `$validity` was a WP_Error, the error codes will be array-mapped
1043
	 *                    to their respective `message` and `data` to pass into the
1044
	 *                    `wp.customize.Notification` JS model.
1045
	 */
1046
	public function prepare_setting_validity_for_js( $validity ) {
1047
		if ( is_wp_error( $validity ) ) {
1048
			$notification = array();
1049
			foreach ( $validity->errors as $error_code => $error_messages ) {
1050
				$error_data = $validity->get_error_data( $error_code );
1051
				if ( is_null( $error_data ) ) {
1052
					$error_data = array();
1053
				}
1054
				$error_data = array_merge(
1055
					$error_data,
1056
					array( 'from_server' => true )
1057
				);
1058
				$notification[ $error_code ] = array(
1059
					'message' => join( ' ', $error_messages ),
1060
					'data' => $error_data,
1061
				);
1062
			}
1063
			return $notification;
1064
		} else {
1065
			return true;
1066
		}
1067
	}
1068
1069
	/**
1070
	 * Switch the theme and trigger the save() method on each setting.
1071
	 *
1072
	 * @since 3.4.0
1073
	 */
1074
	public function save() {
1075
		if ( ! $this->is_preview() ) {
1076
			wp_send_json_error( 'not_preview' );
1077
		}
1078
1079
		$action = 'save-customize_' . $this->get_stylesheet();
1080
		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
1081
			wp_send_json_error( 'invalid_nonce' );
1082
		}
1083
1084
		/**
1085
		 * Fires before save validation happens.
1086
		 *
1087
		 * Plugins can add just-in-time {@see 'customize_validate_{$this->ID}'} filters
1088
		 * at this point to catch any settings registered after `customize_register`.
1089
		 * The dynamic portion of the hook name, `$this->ID` refers to the setting ID.
1090
		 *
1091
		 * @since 4.6.0
1092
		 *
1093
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1094
		 */
1095
		do_action( 'customize_save_validation_before', $this );
1096
1097
		// Validate settings.
1098
		$setting_validities = $this->validate_setting_values( $this->unsanitized_post_values() );
1099
		$invalid_setting_count = count( array_filter( $setting_validities, 'is_wp_error' ) );
1100
		$exported_setting_validities = array_map( array( $this, 'prepare_setting_validity_for_js' ), $setting_validities );
1101
		if ( $invalid_setting_count > 0 ) {
1102
			$response = array(
1103
				'setting_validities' => $exported_setting_validities,
1104
				'message' => sprintf( _n( 'There is %s invalid setting.', 'There are %s invalid settings.', $invalid_setting_count ), number_format_i18n( $invalid_setting_count ) ),
1105
			);
1106
1107
			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
1108
			$response = apply_filters( 'customize_save_response', $response, $this );
1109
			wp_send_json_error( $response );
1110
		}
1111
1112
		// Do we have to switch themes?
1113
		if ( ! $this->is_theme_active() ) {
1114
			// Temporarily stop previewing the theme to allow switch_themes()
1115
			// to operate properly.
1116
			$this->stop_previewing_theme();
1117
			switch_theme( $this->get_stylesheet() );
1118
			update_option( 'theme_switched_via_customizer', true );
1119
			$this->start_previewing_theme();
1120
		}
1121
1122
		/**
1123
		 * Fires once the theme has switched in the Customizer, but before settings
1124
		 * have been saved.
1125
		 *
1126
		 * @since 3.4.0
1127
		 *
1128
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1129
		 */
1130
		do_action( 'customize_save', $this );
1131
1132
		foreach ( $this->settings as $setting ) {
1133
			$setting->save();
1134
		}
1135
1136
		/**
1137
		 * Fires after Customize settings have been saved.
1138
		 *
1139
		 * @since 3.6.0
1140
		 *
1141
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1142
		 */
1143
		do_action( 'customize_save_after', $this );
1144
1145
		$data = array(
1146
			'setting_validities' => $exported_setting_validities,
1147
		);
1148
1149
		/**
1150
		 * Filters response data for a successful customize_save Ajax request.
1151
		 *
1152
		 * This filter does not apply if there was a nonce or authentication failure.
1153
		 *
1154
		 * @since 4.2.0
1155
		 *
1156
		 * @param array                $data Additional information passed back to the 'saved'
1157
		 *                                   event on `wp.customize`.
1158
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1159
		 */
1160
		$response = apply_filters( 'customize_save_response', $data, $this );
1161
		wp_send_json_success( $response );
1162
	}
1163
1164
	/**
1165
	 * Refresh nonces for the current preview.
1166
	 *
1167
	 * @since 4.2.0
1168
	 */
1169
	public function refresh_nonces() {
1170
		if ( ! $this->is_preview() ) {
1171
			wp_send_json_error( 'not_preview' );
1172
		}
1173
1174
		wp_send_json_success( $this->get_nonces() );
1175
	}
1176
1177
	/**
1178
	 * Add a customize setting.
1179
	 *
1180
	 * @since 3.4.0
1181
	 * @since 4.5.0 Return added WP_Customize_Setting instance.
1182
	 * @access public
1183
	 *
1184
	 * @param WP_Customize_Setting|string $id   Customize Setting object, or ID.
1185
	 * @param array                       $args Setting arguments; passed to WP_Customize_Setting
1186
	 *                                          constructor.
1187
	 * @return WP_Customize_Setting             The instance of the setting that was added.
1188
	 */
1189
	public function add_setting( $id, $args = array() ) {
1190
		if ( $id instanceof WP_Customize_Setting ) {
1191
			$setting = $id;
1192
		} else {
1193
			$class = 'WP_Customize_Setting';
1194
1195
			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
1196
			$args = apply_filters( 'customize_dynamic_setting_args', $args, $id );
1197
1198
			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
1199
			$class = apply_filters( 'customize_dynamic_setting_class', $class, $id, $args );
1200
1201
			$setting = new $class( $this, $id, $args );
1202
		}
1203
1204
		$this->settings[ $setting->id ] = $setting;
1205
		return $setting;
1206
	}
1207
1208
	/**
1209
	 * Register any dynamically-created settings, such as those from $_POST['customized']
1210
	 * that have no corresponding setting created.
1211
	 *
1212
	 * This is a mechanism to "wake up" settings that have been dynamically created
1213
	 * on the front end and have been sent to WordPress in `$_POST['customized']`. When WP
1214
	 * loads, the dynamically-created settings then will get created and previewed
1215
	 * even though they are not directly created statically with code.
1216
	 *
1217
	 * @since 4.2.0
1218
	 * @access public
1219
	 *
1220
	 * @param array $setting_ids The setting IDs to add.
1221
	 * @return array The WP_Customize_Setting objects added.
1222
	 */
1223
	public function add_dynamic_settings( $setting_ids ) {
1224
		$new_settings = array();
1225
		foreach ( $setting_ids as $setting_id ) {
1226
			// Skip settings already created
1227
			if ( $this->get_setting( $setting_id ) ) {
1228
				continue;
1229
			}
1230
1231
			$setting_args = false;
1232
			$setting_class = 'WP_Customize_Setting';
1233
1234
			/**
1235
			 * Filters a dynamic setting's constructor args.
1236
			 *
1237
			 * For a dynamic setting to be registered, this filter must be employed
1238
			 * to override the default false value with an array of args to pass to
1239
			 * the WP_Customize_Setting constructor.
1240
			 *
1241
			 * @since 4.2.0
1242
			 *
1243
			 * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
1244
			 * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
1245
			 */
1246
			$setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
1247
			if ( false === $setting_args ) {
1248
				continue;
1249
			}
1250
1251
			/**
1252
			 * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
1253
			 *
1254
			 * @since 4.2.0
1255
			 *
1256
			 * @param string $setting_class WP_Customize_Setting or a subclass.
1257
			 * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
1258
			 * @param array  $setting_args  WP_Customize_Setting or a subclass.
1259
			 */
1260
			$setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
1261
1262
			$setting = new $setting_class( $this, $setting_id, $setting_args );
1263
1264
			$this->add_setting( $setting );
1265
			$new_settings[] = $setting;
1266
		}
1267
		return $new_settings;
1268
	}
1269
1270
	/**
1271
	 * Retrieve a customize setting.
1272
	 *
1273
	 * @since 3.4.0
1274
	 *
1275
	 * @param string $id Customize Setting ID.
1276
	 * @return WP_Customize_Setting|void The setting, if set.
1277
	 */
1278
	public function get_setting( $id ) {
1279
		if ( isset( $this->settings[ $id ] ) ) {
1280
			return $this->settings[ $id ];
1281
		}
1282
	}
1283
1284
	/**
1285
	 * Remove a customize setting.
1286
	 *
1287
	 * @since 3.4.0
1288
	 *
1289
	 * @param string $id Customize Setting ID.
1290
	 */
1291
	public function remove_setting( $id ) {
1292
		unset( $this->settings[ $id ] );
1293
	}
1294
1295
	/**
1296
	 * Add a customize panel.
1297
	 *
1298
	 * @since 4.0.0
1299
	 * @since 4.5.0 Return added WP_Customize_Panel instance.
1300
	 * @access public
1301
	 *
1302
	 * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
1303
	 * @param array                     $args Optional. Panel arguments. Default empty array.
1304
	 *
1305
	 * @return WP_Customize_Panel             The instance of the panel that was added.
1306
	 */
1307
	public function add_panel( $id, $args = array() ) {
1308
		if ( $id instanceof WP_Customize_Panel ) {
1309
			$panel = $id;
1310
		} else {
1311
			$panel = new WP_Customize_Panel( $this, $id, $args );
1312
		}
1313
1314
		$this->panels[ $panel->id ] = $panel;
1315
		return $panel;
1316
	}
1317
1318
	/**
1319
	 * Retrieve a customize panel.
1320
	 *
1321
	 * @since 4.0.0
1322
	 * @access public
1323
	 *
1324
	 * @param string $id Panel ID to get.
1325
	 * @return WP_Customize_Panel|void Requested panel instance, if set.
1326
	 */
1327
	public function get_panel( $id ) {
1328
		if ( isset( $this->panels[ $id ] ) ) {
1329
			return $this->panels[ $id ];
1330
		}
1331
	}
1332
1333
	/**
1334
	 * Remove a customize panel.
1335
	 *
1336
	 * @since 4.0.0
1337
	 * @access public
1338
	 *
1339
	 * @param string $id Panel ID to remove.
1340
	 */
1341
	public function remove_panel( $id ) {
1342
		// Removing core components this way is _doing_it_wrong().
1343
		if ( in_array( $id, $this->components, true ) ) {
1344
			/* translators: 1: panel id, 2: link to 'customize_loaded_components' filter reference */
1345
			$message = sprintf( __( 'Removing %1$s manually will cause PHP warnings. Use the %2$s filter instead.' ),
1346
				$id,
1347
				'<a href="' . esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ) . '"><code>customize_loaded_components</code></a>'
1348
			);
1349
1350
			_doing_it_wrong( __METHOD__, $message, '4.5.0' );
1351
		}
1352
		unset( $this->panels[ $id ] );
1353
	}
1354
1355
	/**
1356
	 * Register a customize panel type.
1357
	 *
1358
	 * Registered types are eligible to be rendered via JS and created dynamically.
1359
	 *
1360
	 * @since 4.3.0
1361
	 * @access public
1362
	 *
1363
	 * @see WP_Customize_Panel
1364
	 *
1365
	 * @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
1366
	 */
1367
	public function register_panel_type( $panel ) {
1368
		$this->registered_panel_types[] = $panel;
1369
	}
1370
1371
	/**
1372
	 * Render JS templates for all registered panel types.
1373
	 *
1374
	 * @since 4.3.0
1375
	 * @access public
1376
	 */
1377
	public function render_panel_templates() {
1378
		foreach ( $this->registered_panel_types as $panel_type ) {
1379
			$panel = new $panel_type( $this, 'temp', array() );
1380
			$panel->print_template();
1381
		}
1382
	}
1383
1384
	/**
1385
	 * Add a customize section.
1386
	 *
1387
	 * @since 3.4.0
1388
	 * @since 4.5.0 Return added WP_Customize_Section instance.
1389
	 * @access public
1390
	 *
1391
	 * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
1392
	 * @param array                       $args Section arguments.
1393
	 *
1394
	 * @return WP_Customize_Section             The instance of the section that was added.
1395
	 */
1396
	public function add_section( $id, $args = array() ) {
1397
		if ( $id instanceof WP_Customize_Section ) {
1398
			$section = $id;
1399
		} else {
1400
			$section = new WP_Customize_Section( $this, $id, $args );
1401
		}
1402
1403
		$this->sections[ $section->id ] = $section;
1404
		return $section;
1405
	}
1406
1407
	/**
1408
	 * Retrieve a customize section.
1409
	 *
1410
	 * @since 3.4.0
1411
	 *
1412
	 * @param string $id Section ID.
1413
	 * @return WP_Customize_Section|void The section, if set.
1414
	 */
1415
	public function get_section( $id ) {
1416
		if ( isset( $this->sections[ $id ] ) )
1417
			return $this->sections[ $id ];
1418
	}
1419
1420
	/**
1421
	 * Remove a customize section.
1422
	 *
1423
	 * @since 3.4.0
1424
	 *
1425
	 * @param string $id Section ID.
1426
	 */
1427
	public function remove_section( $id ) {
1428
		unset( $this->sections[ $id ] );
1429
	}
1430
1431
	/**
1432
	 * Register a customize section type.
1433
	 *
1434
	 * Registered types are eligible to be rendered via JS and created dynamically.
1435
	 *
1436
	 * @since 4.3.0
1437
	 * @access public
1438
	 *
1439
	 * @see WP_Customize_Section
1440
	 *
1441
	 * @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
1442
	 */
1443
	public function register_section_type( $section ) {
1444
		$this->registered_section_types[] = $section;
1445
	}
1446
1447
	/**
1448
	 * Render JS templates for all registered section types.
1449
	 *
1450
	 * @since 4.3.0
1451
	 * @access public
1452
	 */
1453
	public function render_section_templates() {
1454
		foreach ( $this->registered_section_types as $section_type ) {
1455
			$section = new $section_type( $this, 'temp', array() );
1456
			$section->print_template();
1457
		}
1458
	}
1459
1460
	/**
1461
	 * Add a customize control.
1462
	 *
1463
	 * @since 3.4.0
1464
	 * @since 4.5.0 Return added WP_Customize_Control instance.
1465
	 * @access public
1466
	 *
1467
	 * @param WP_Customize_Control|string $id   Customize Control object, or ID.
1468
	 * @param array                       $args Control arguments; passed to WP_Customize_Control
1469
	 *                                          constructor.
1470
	 * @return WP_Customize_Control             The instance of the control that was added.
1471
	 */
1472
	public function add_control( $id, $args = array() ) {
1473
		if ( $id instanceof WP_Customize_Control ) {
1474
			$control = $id;
1475
		} else {
1476
			$control = new WP_Customize_Control( $this, $id, $args );
1477
		}
1478
1479
		$this->controls[ $control->id ] = $control;
1480
		return $control;
1481
	}
1482
1483
	/**
1484
	 * Retrieve a customize control.
1485
	 *
1486
	 * @since 3.4.0
1487
	 *
1488
	 * @param string $id ID of the control.
1489
	 * @return WP_Customize_Control|void The control object, if set.
1490
	 */
1491
	public function get_control( $id ) {
1492
		if ( isset( $this->controls[ $id ] ) )
1493
			return $this->controls[ $id ];
1494
	}
1495
1496
	/**
1497
	 * Remove a customize control.
1498
	 *
1499
	 * @since 3.4.0
1500
	 *
1501
	 * @param string $id ID of the control.
1502
	 */
1503
	public function remove_control( $id ) {
1504
		unset( $this->controls[ $id ] );
1505
	}
1506
1507
	/**
1508
	 * Register a customize control type.
1509
	 *
1510
	 * Registered types are eligible to be rendered via JS and created dynamically.
1511
	 *
1512
	 * @since 4.1.0
1513
	 * @access public
1514
	 *
1515
	 * @param string $control Name of a custom control which is a subclass of
1516
	 *                        WP_Customize_Control.
1517
	 */
1518
	public function register_control_type( $control ) {
1519
		$this->registered_control_types[] = $control;
1520
	}
1521
1522
	/**
1523
	 * Render JS templates for all registered control types.
1524
	 *
1525
	 * @since 4.1.0
1526
	 * @access public
1527
	 */
1528
	public function render_control_templates() {
1529
		foreach ( $this->registered_control_types as $control_type ) {
1530
			$control = new $control_type( $this, 'temp', array(
1531
				'settings' => array(),
1532
			) );
1533
			$control->print_template();
1534
		}
1535
		?>
1536
		<script type="text/html" id="tmpl-customize-control-notifications">
1537
			<ul>
1538
				<# _.each( data.notifications, function( notification ) { #>
1539
					<li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li>
1540
				<# } ); #>
1541
			</ul>
1542
		</script>
1543
		<?php
1544
	}
1545
1546
	/**
1547
	 * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
1548
	 *
1549
	 * @since 3.4.0
1550
	 *
1551
	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
1552
	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
1553
	 * @return int
1554
	 */
1555
	protected function _cmp_priority( $a, $b ) {
1556
		if ( $a->priority === $b->priority ) {
1557
			return $a->instance_number - $b->instance_number;
1558
		} else {
1559
			return $a->priority - $b->priority;
1560
		}
1561
	}
1562
1563
	/**
1564
	 * Prepare panels, sections, and controls.
1565
	 *
1566
	 * For each, check if required related components exist,
1567
	 * whether the user has the necessary capabilities,
1568
	 * and sort by priority.
1569
	 *
1570
	 * @since 3.4.0
1571
	 */
1572
	public function prepare_controls() {
1573
1574
		$controls = array();
1575
		uasort( $this->controls, array( $this, '_cmp_priority' ) );
1576
1577
		foreach ( $this->controls as $id => $control ) {
1578
			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
1579
				continue;
1580
			}
1581
1582
			$this->sections[ $control->section ]->controls[] = $control;
1583
			$controls[ $id ] = $control;
1584
		}
1585
		$this->controls = $controls;
1586
1587
		// Prepare sections.
1588
		uasort( $this->sections, array( $this, '_cmp_priority' ) );
1589
		$sections = array();
1590
1591
		foreach ( $this->sections as $section ) {
1592
			if ( ! $section->check_capabilities() ) {
1593
				continue;
1594
			}
1595
1596
			usort( $section->controls, array( $this, '_cmp_priority' ) );
1597
1598
			if ( ! $section->panel ) {
1599
				// Top-level section.
1600
				$sections[ $section->id ] = $section;
1601
			} else {
1602
				// This section belongs to a panel.
1603
				if ( isset( $this->panels [ $section->panel ] ) ) {
1604
					$this->panels[ $section->panel ]->sections[ $section->id ] = $section;
1605
				}
1606
			}
1607
		}
1608
		$this->sections = $sections;
1609
1610
		// Prepare panels.
1611
		uasort( $this->panels, array( $this, '_cmp_priority' ) );
1612
		$panels = array();
1613
1614
		foreach ( $this->panels as $panel ) {
1615
			if ( ! $panel->check_capabilities() ) {
1616
				continue;
1617
			}
1618
1619
			uasort( $panel->sections, array( $this, '_cmp_priority' ) );
1620
			$panels[ $panel->id ] = $panel;
1621
		}
1622
		$this->panels = $panels;
1623
1624
		// Sort panels and top-level sections together.
1625
		$this->containers = array_merge( $this->panels, $this->sections );
1626
		uasort( $this->containers, array( $this, '_cmp_priority' ) );
1627
	}
1628
1629
	/**
1630
	 * Enqueue scripts for customize controls.
1631
	 *
1632
	 * @since 3.4.0
1633
	 */
1634
	public function enqueue_control_scripts() {
1635
		foreach ( $this->controls as $control ) {
1636
			$control->enqueue();
1637
		}
1638
	}
1639
1640
	/**
1641
	 * Determine whether the user agent is iOS.
1642
	 *
1643
	 * @since 4.4.0
1644
	 * @access public
1645
	 *
1646
	 * @return bool Whether the user agent is iOS.
1647
	 */
1648
	public function is_ios() {
1649
		return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
1650
	}
1651
1652
	/**
1653
	 * Get the template string for the Customizer pane document title.
1654
	 *
1655
	 * @since 4.4.0
1656
	 * @access public
1657
	 *
1658
	 * @return string The template string for the document title.
1659
	 */
1660
	public function get_document_title_template() {
1661
		if ( $this->is_theme_active() ) {
1662
			/* translators: %s: document title from the preview */
1663
			$document_title_tmpl = __( 'Customize: %s' );
1664
		} else {
1665
			/* translators: %s: document title from the preview */
1666
			$document_title_tmpl = __( 'Live Preview: %s' );
1667
		}
1668
		$document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
1669
		return $document_title_tmpl;
1670
	}
1671
1672
	/**
1673
	 * Set the initial URL to be previewed.
1674
	 *
1675
	 * URL is validated.
1676
	 *
1677
	 * @since 4.4.0
1678
	 * @access public
1679
	 *
1680
	 * @param string $preview_url URL to be previewed.
1681
	 */
1682
	public function set_preview_url( $preview_url ) {
1683
		$preview_url = esc_url_raw( $preview_url );
1684
		$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
1685
	}
1686
1687
	/**
1688
	 * Get the initial URL to be previewed.
1689
	 *
1690
	 * @since 4.4.0
1691
	 * @access public
1692
	 *
1693
	 * @return string URL being previewed.
1694
	 */
1695
	public function get_preview_url() {
1696
		if ( empty( $this->preview_url ) ) {
1697
			$preview_url = home_url( '/' );
1698
		} else {
1699
			$preview_url = $this->preview_url;
1700
		}
1701
		return $preview_url;
1702
	}
1703
1704
	/**
1705
	 * Set URL to link the user to when closing the Customizer.
1706
	 *
1707
	 * URL is validated.
1708
	 *
1709
	 * @since 4.4.0
1710
	 * @access public
1711
	 *
1712
	 * @param string $return_url URL for return link.
1713
	 */
1714
	public function set_return_url( $return_url ) {
1715
		$return_url = esc_url_raw( $return_url );
1716
		$return_url = remove_query_arg( wp_removable_query_args(), $return_url );
1717
		$return_url = wp_validate_redirect( $return_url );
0 ignored issues
show
It seems like $return_url can also be of type boolean; however, wp_validate_redirect() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1718
		$this->return_url = $return_url;
1719
	}
1720
1721
	/**
1722
	 * Get URL to link the user to when closing the Customizer.
1723
	 *
1724
	 * @since 4.4.0
1725
	 * @access public
1726
	 *
1727
	 * @return string URL for link to close Customizer.
1728
	 */
1729
	public function get_return_url() {
1730
		$referer = wp_get_referer();
1731
		$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
1732
1733
		if ( $this->return_url ) {
1734
			$return_url = $this->return_url;
1735
		} else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
1736
			$return_url = $referer;
1737
		} else if ( $this->preview_url ) {
1738
			$return_url = $this->preview_url;
1739
		} else {
1740
			$return_url = home_url( '/' );
1741
		}
1742
		return $return_url;
1743
	}
1744
1745
	/**
1746
	 * Set the autofocused constructs.
1747
	 *
1748
	 * @since 4.4.0
1749
	 * @access public
1750
	 *
1751
	 * @param array $autofocus {
1752
	 *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
1753
	 *
1754
	 *     @type string [$control]  ID for control to be autofocused.
1755
	 *     @type string [$section]  ID for section to be autofocused.
1756
	 *     @type string [$panel]    ID for panel to be autofocused.
1757
	 * }
1758
	 */
1759
	public function set_autofocus( $autofocus ) {
1760
		$this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
1761
	}
1762
1763
	/**
1764
	 * Get the autofocused constructs.
1765
	 *
1766
	 * @since 4.4.0
1767
	 * @access public
1768
	 *
1769
	 * @return array {
1770
	 *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
1771
	 *
1772
	 *     @type string [$control]  ID for control to be autofocused.
1773
	 *     @type string [$section]  ID for section to be autofocused.
1774
	 *     @type string [$panel]    ID for panel to be autofocused.
1775
	 * }
1776
	 */
1777
	public function get_autofocus() {
1778
		return $this->autofocus;
1779
	}
1780
1781
	/**
1782
	 * Get nonces for the Customizer.
1783
	 *
1784
	 * @since 4.5.0
1785
	 * @return array Nonces.
1786
	 */
1787
	public function get_nonces() {
1788
		$nonces = array(
1789
			'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
1790
			'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
1791
		);
1792
1793
		/**
1794
		 * Filters nonces for Customizer.
1795
		 *
1796
		 * @since 4.2.0
1797
		 *
1798
		 * @param array                $nonces Array of refreshed nonces for save and
1799
		 *                                     preview actions.
1800
		 * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
1801
		 */
1802
		$nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
1803
1804
		return $nonces;
1805
	}
1806
1807
	/**
1808
	 * Print JavaScript settings for parent window.
1809
	 *
1810
	 * @since 4.4.0
1811
	 */
1812
	public function customize_pane_settings() {
1813
		/*
1814
		 * If the front end and the admin are served from the same domain, load the
1815
		 * preview over ssl if the Customizer is being loaded over ssl. This avoids
1816
		 * insecure content warnings. This is not attempted if the admin and front end
1817
		 * are on different domains to avoid the case where the front end doesn't have
1818
		 * ssl certs. Domain mapping plugins can allow other urls in these conditions
1819
		 * using the customize_allowed_urls filter.
1820
		 */
1821
1822
		$allowed_urls = array( home_url( '/' ) );
1823
		$admin_origin = parse_url( admin_url() );
1824
		$home_origin  = parse_url( home_url() );
1825
		$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
1826
1827
		if ( is_ssl() && ! $cross_domain ) {
1828
			$allowed_urls[] = home_url( '/', 'https' );
1829
		}
1830
1831
		/**
1832
		 * Filters the list of URLs allowed to be clicked and followed in the Customizer preview.
1833
		 *
1834
		 * @since 3.4.0
1835
		 *
1836
		 * @param array $allowed_urls An array of allowed URLs.
1837
		 */
1838
		$allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
1839
1840
		$login_url = add_query_arg( array(
1841
			'interim-login' => 1,
1842
			'customize-login' => 1,
1843
		), wp_login_url() );
1844
1845
		// Prepare Customizer settings to pass to JavaScript.
1846
		$settings = array(
1847
			'theme'    => array(
1848
				'stylesheet' => $this->get_stylesheet(),
1849
				'active'     => $this->is_theme_active(),
1850
			),
1851
			'url'      => array(
1852
				'preview'       => esc_url_raw( $this->get_preview_url() ),
1853
				'parent'        => esc_url_raw( admin_url() ),
1854
				'activated'     => esc_url_raw( home_url( '/' ) ),
1855
				'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
1856
				'allowed'       => array_map( 'esc_url_raw', $allowed_urls ),
1857
				'isCrossDomain' => $cross_domain,
1858
				'home'          => esc_url_raw( home_url( '/' ) ),
1859
				'login'         => esc_url_raw( $login_url ),
1860
			),
1861
			'browser'  => array(
1862
				'mobile' => wp_is_mobile(),
1863
				'ios'    => $this->is_ios(),
1864
			),
1865
			'panels'   => array(),
1866
			'sections' => array(),
1867
			'nonce'    => $this->get_nonces(),
1868
			'autofocus' => $this->get_autofocus(),
1869
			'documentTitleTmpl' => $this->get_document_title_template(),
1870
			'previewableDevices' => $this->get_previewable_devices(),
1871
		);
1872
1873
		// Prepare Customize Section objects to pass to JavaScript.
1874
		foreach ( $this->sections() as $id => $section ) {
1875
			if ( $section->check_capabilities() ) {
1876
				$settings['sections'][ $id ] = $section->json();
1877
			}
1878
		}
1879
1880
		// Prepare Customize Panel objects to pass to JavaScript.
1881 View Code Duplication
		foreach ( $this->panels() as $panel_id => $panel ) {
1882
			if ( $panel->check_capabilities() ) {
1883
				$settings['panels'][ $panel_id ] = $panel->json();
1884
				foreach ( $panel->sections as $section_id => $section ) {
1885
					if ( $section->check_capabilities() ) {
1886
						$settings['sections'][ $section_id ] = $section->json();
1887
					}
1888
				}
1889
			}
1890
		}
1891
1892
		?>
1893
		<script type="text/javascript">
1894
			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
1895
			_wpCustomizeSettings.controls = {};
1896
			_wpCustomizeSettings.settings = {};
1897
			<?php
1898
1899
			// Serialize settings one by one to improve memory usage.
1900
			echo "(function ( s ){\n";
1901 View Code Duplication
			foreach ( $this->settings() as $setting ) {
1902
				if ( $setting->check_capabilities() ) {
1903
					printf(
1904
						"s[%s] = %s;\n",
1905
						wp_json_encode( $setting->id ),
1906
						wp_json_encode( $setting->json() )
1907
					);
1908
				}
1909
			}
1910
			echo "})( _wpCustomizeSettings.settings );\n";
1911
1912
			// Serialize controls one by one to improve memory usage.
1913
			echo "(function ( c ){\n";
1914
			foreach ( $this->controls() as $control ) {
1915
				if ( $control->check_capabilities() ) {
1916
					printf(
1917
						"c[%s] = %s;\n",
1918
						wp_json_encode( $control->id ),
1919
						wp_json_encode( $control->json() )
1920
					);
1921
				}
1922
			}
1923
			echo "})( _wpCustomizeSettings.controls );\n";
1924
		?>
1925
		</script>
1926
		<?php
1927
	}
1928
1929
	/**
1930
	 * Returns a list of devices to allow previewing.
1931
	 *
1932
	 * @access public
1933
	 * @since 4.5.0
1934
	 *
1935
	 * @return array List of devices with labels and default setting.
1936
	 */
1937
	public function get_previewable_devices() {
1938
		$devices = array(
1939
			'desktop' => array(
1940
				'label' => __( 'Enter desktop preview mode' ),
1941
				'default' => true,
1942
			),
1943
			'tablet' => array(
1944
				'label' => __( 'Enter tablet preview mode' ),
1945
			),
1946
			'mobile' => array(
1947
				'label' => __( 'Enter mobile preview mode' ),
1948
			),
1949
		);
1950
1951
		/**
1952
		 * Filters the available devices to allow previewing in the Customizer.
1953
		 *
1954
		 * @since 4.5.0
1955
		 *
1956
		 * @see WP_Customize_Manager::get_previewable_devices()
1957
		 *
1958
		 * @param array $devices List of devices with labels and default setting.
1959
		 */
1960
		$devices = apply_filters( 'customize_previewable_devices', $devices );
1961
1962
		return $devices;
1963
	}
1964
1965
	/**
1966
	 * Register some default controls.
1967
	 *
1968
	 * @since 3.4.0
1969
	 */
1970
	public function register_controls() {
1971
1972
		/* Panel, Section, and Control Types */
1973
		$this->register_panel_type( 'WP_Customize_Panel' );
1974
		$this->register_section_type( 'WP_Customize_Section' );
1975
		$this->register_section_type( 'WP_Customize_Sidebar_Section' );
1976
		$this->register_control_type( 'WP_Customize_Color_Control' );
1977
		$this->register_control_type( 'WP_Customize_Media_Control' );
1978
		$this->register_control_type( 'WP_Customize_Upload_Control' );
1979
		$this->register_control_type( 'WP_Customize_Image_Control' );
1980
		$this->register_control_type( 'WP_Customize_Background_Image_Control' );
1981
		$this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
1982
		$this->register_control_type( 'WP_Customize_Site_Icon_Control' );
1983
		$this->register_control_type( 'WP_Customize_Theme_Control' );
1984
1985
		/* Themes */
1986
1987
		$this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
1988
			'title'      => $this->theme()->display( 'Name' ),
1989
			'capability' => 'switch_themes',
1990
			'priority'   => 0,
1991
		) ) );
1992
1993
		// Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
1994
		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
1995
			'capability' => 'switch_themes',
1996
		) ) );
1997
1998
		require_once( ABSPATH . 'wp-admin/includes/theme.php' );
1999
2000
		// Theme Controls.
2001
2002
		// Add a control for the active/original theme.
2003
		if ( ! $this->is_theme_active() ) {
2004
			$themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
2005
			$active_theme = current( $themes );
2006
			$active_theme['isActiveTheme'] = true;
2007
			$this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
2008
				'theme'    => $active_theme,
2009
				'section'  => 'themes',
2010
				'settings' => 'active_theme',
2011
			) ) );
2012
		}
2013
2014
		$themes = wp_prepare_themes_for_js();
2015
		foreach ( $themes as $theme ) {
2016
			if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
2017
				continue;
2018
			}
2019
2020
			$theme_id = 'theme_' . $theme['id'];
2021
			$theme['isActiveTheme'] = false;
2022
			$this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
2023
				'theme'    => $theme,
2024
				'section'  => 'themes',
2025
				'settings' => 'active_theme',
2026
			) ) );
2027
		}
2028
2029
		/* Site Identity */
2030
2031
		$this->add_section( 'title_tagline', array(
2032
			'title'    => __( 'Site Identity' ),
2033
			'priority' => 20,
2034
		) );
2035
2036
		$this->add_setting( 'blogname', array(
2037
			'default'    => get_option( 'blogname' ),
2038
			'type'       => 'option',
2039
			'capability' => 'manage_options',
2040
		) );
2041
2042
		$this->add_control( 'blogname', array(
2043
			'label'      => __( 'Site Title' ),
2044
			'section'    => 'title_tagline',
2045
		) );
2046
2047
		$this->add_setting( 'blogdescription', array(
2048
			'default'    => get_option( 'blogdescription' ),
2049
			'type'       => 'option',
2050
			'capability' => 'manage_options',
2051
		) );
2052
2053
		$this->add_control( 'blogdescription', array(
2054
			'label'      => __( 'Tagline' ),
2055
			'section'    => 'title_tagline',
2056
		) );
2057
2058
		// Add a setting to hide header text if the theme doesn't support custom headers.
2059
		if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
2060
			$this->add_setting( 'header_text', array(
2061
				'theme_supports'    => array( 'custom-logo', 'header-text' ),
2062
				'default'           => 1,
2063
				'sanitize_callback' => 'absint',
2064
			) );
2065
2066
			$this->add_control( 'header_text', array(
2067
				'label'    => __( 'Display Site Title and Tagline' ),
2068
				'section'  => 'title_tagline',
2069
				'settings' => 'header_text',
2070
				'type'     => 'checkbox',
2071
			) );
2072
		}
2073
2074
		$this->add_setting( 'site_icon', array(
2075
			'type'       => 'option',
2076
			'capability' => 'manage_options',
2077
			'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
2078
		) );
2079
2080
		$this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
2081
			'label'       => __( 'Site Icon' ),
2082
			'description' => sprintf(
2083
				/* translators: %s: site icon size in pixels */
2084
				__( '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.' ),
2085
				'<strong>512</strong>'
2086
			),
2087
			'section'     => 'title_tagline',
2088
			'priority'    => 60,
2089
			'height'      => 512,
2090
			'width'       => 512,
2091
		) ) );
2092
2093
		$this->add_setting( 'custom_logo', array(
2094
			'theme_supports' => array( 'custom-logo' ),
2095
			'transport'      => 'postMessage',
2096
		) );
2097
2098
		$custom_logo_args = get_theme_support( 'custom-logo' );
2099
		$this->add_control( new WP_Customize_Cropped_Image_Control( $this, 'custom_logo', array(
2100
			'label'         => __( 'Logo' ),
2101
			'section'       => 'title_tagline',
2102
			'priority'      => 8,
2103
			'height'        => $custom_logo_args[0]['height'],
2104
			'width'         => $custom_logo_args[0]['width'],
2105
			'flex_height'   => $custom_logo_args[0]['flex-height'],
2106
			'flex_width'    => $custom_logo_args[0]['flex-width'],
2107
			'button_labels' => array(
2108
				'select'       => __( 'Select logo' ),
2109
				'change'       => __( 'Change logo' ),
2110
				'remove'       => __( 'Remove' ),
2111
				'default'      => __( 'Default' ),
2112
				'placeholder'  => __( 'No logo selected' ),
2113
				'frame_title'  => __( 'Select logo' ),
2114
				'frame_button' => __( 'Choose logo' ),
2115
			),
2116
		) ) );
2117
2118
		$this->selective_refresh->add_partial( 'custom_logo', array(
2119
			'settings'            => array( 'custom_logo' ),
2120
			'selector'            => '.custom-logo-link',
2121
			'render_callback'     => array( $this, '_render_custom_logo_partial' ),
2122
			'container_inclusive' => true,
2123
		) );
2124
2125
		/* Colors */
2126
2127
		$this->add_section( 'colors', array(
2128
			'title'          => __( 'Colors' ),
2129
			'priority'       => 40,
2130
		) );
2131
2132
		$this->add_setting( 'header_textcolor', array(
2133
			'theme_supports' => array( 'custom-header', 'header-text' ),
2134
			'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
2135
2136
			'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
2137
			'sanitize_js_callback' => 'maybe_hash_hex_color',
2138
		) );
2139
2140
		// Input type: checkbox
2141
		// With custom value
2142
		$this->add_control( 'display_header_text', array(
2143
			'settings' => 'header_textcolor',
2144
			'label'    => __( 'Display Site Title and Tagline' ),
2145
			'section'  => 'title_tagline',
2146
			'type'     => 'checkbox',
2147
			'priority' => 40,
2148
		) );
2149
2150
		$this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
2151
			'label'   => __( 'Header Text Color' ),
2152
			'section' => 'colors',
2153
		) ) );
2154
2155
		// Input type: Color
2156
		// With sanitize_callback
2157
		$this->add_setting( 'background_color', array(
2158
			'default'        => get_theme_support( 'custom-background', 'default-color' ),
2159
			'theme_supports' => 'custom-background',
2160
2161
			'sanitize_callback'    => 'sanitize_hex_color_no_hash',
2162
			'sanitize_js_callback' => 'maybe_hash_hex_color',
2163
		) );
2164
2165
		$this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
2166
			'label'   => __( 'Background Color' ),
2167
			'section' => 'colors',
2168
		) ) );
2169
2170
2171
		/* Custom Header */
2172
2173
		$this->add_section( 'header_image', array(
2174
			'title'          => __( 'Header Image' ),
2175
			'theme_supports' => 'custom-header',
2176
			'priority'       => 60,
2177
		) );
2178
2179
		$this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
2180
			'default'        => get_theme_support( 'custom-header', 'default-image' ),
2181
			'theme_supports' => 'custom-header',
2182
		) ) );
2183
2184
		$this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
2185
			// 'default'        => get_theme_support( 'custom-header', 'default-image' ),
2186
			'theme_supports' => 'custom-header',
2187
		) ) );
2188
2189
		$this->add_control( new WP_Customize_Header_Image_Control( $this ) );
2190
2191
		/* Custom Background */
2192
2193
		$this->add_section( 'background_image', array(
2194
			'title'          => __( 'Background Image' ),
2195
			'theme_supports' => 'custom-background',
2196
			'priority'       => 80,
2197
		) );
2198
2199
		$this->add_setting( 'background_image', array(
2200
			'default'        => get_theme_support( 'custom-background', 'default-image' ),
2201
			'theme_supports' => 'custom-background',
2202
		) );
2203
2204
		$this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
2205
			'theme_supports' => 'custom-background',
2206
		) ) );
2207
2208
		$this->add_control( new WP_Customize_Background_Image_Control( $this ) );
2209
2210
		$this->add_setting( 'background_repeat', array(
2211
			'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
2212
			'theme_supports' => 'custom-background',
2213
		) );
2214
2215
		$this->add_control( 'background_repeat', array(
2216
			'label'      => __( 'Background Repeat' ),
2217
			'section'    => 'background_image',
2218
			'type'       => 'radio',
2219
			'choices'    => array(
2220
				'no-repeat'  => __('No Repeat'),
2221
				'repeat'     => __('Tile'),
2222
				'repeat-x'   => __('Tile Horizontally'),
2223
				'repeat-y'   => __('Tile Vertically'),
2224
			),
2225
		) );
2226
2227
		$this->add_setting( 'background_position_x', array(
2228
			'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
2229
			'theme_supports' => 'custom-background',
2230
		) );
2231
2232
		$this->add_control( 'background_position_x', array(
2233
			'label'      => __( 'Background Position' ),
2234
			'section'    => 'background_image',
2235
			'type'       => 'radio',
2236
			'choices'    => array(
2237
				'left'       => __('Left'),
2238
				'center'     => __('Center'),
2239
				'right'      => __('Right'),
2240
			),
2241
		) );
2242
2243
		$this->add_setting( 'background_attachment', array(
2244
			'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
2245
			'theme_supports' => 'custom-background',
2246
		) );
2247
2248
		$this->add_control( 'background_attachment', array(
2249
			'label'      => __( 'Background Attachment' ),
2250
			'section'    => 'background_image',
2251
			'type'       => 'radio',
2252
			'choices'    => array(
2253
				'scroll'     => __('Scroll'),
2254
				'fixed'      => __('Fixed'),
2255
			),
2256
		) );
2257
2258
		// If the theme is using the default background callback, we can update
2259
		// the background CSS using postMessage.
2260
		if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
2261
			foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
2262
				$this->get_setting( 'background_' . $prop )->transport = 'postMessage';
2263
			}
2264
		}
2265
2266
		/* Static Front Page */
2267
		// #WP19627
2268
2269
		// Replicate behavior from options-reading.php and hide front page options if there are no pages
2270
		if ( get_pages() ) {
2271
			$this->add_section( 'static_front_page', array(
2272
				'title'          => __( 'Static Front Page' ),
2273
			//	'theme_supports' => 'static-front-page',
2274
				'priority'       => 120,
2275
				'description'    => __( 'Your theme supports a static front page.' ),
2276
			) );
2277
2278
			$this->add_setting( 'show_on_front', array(
2279
				'default'        => get_option( 'show_on_front' ),
2280
				'capability'     => 'manage_options',
2281
				'type'           => 'option',
2282
			//	'theme_supports' => 'static-front-page',
2283
			) );
2284
2285
			$this->add_control( 'show_on_front', array(
2286
				'label'   => __( 'Front page displays' ),
2287
				'section' => 'static_front_page',
2288
				'type'    => 'radio',
2289
				'choices' => array(
2290
					'posts' => __( 'Your latest posts' ),
2291
					'page'  => __( 'A static page' ),
2292
				),
2293
			) );
2294
2295
			$this->add_setting( 'page_on_front', array(
2296
				'type'       => 'option',
2297
				'capability' => 'manage_options',
2298
			//	'theme_supports' => 'static-front-page',
2299
			) );
2300
2301
			$this->add_control( 'page_on_front', array(
2302
				'label'      => __( 'Front page' ),
2303
				'section'    => 'static_front_page',
2304
				'type'       => 'dropdown-pages',
2305
			) );
2306
2307
			$this->add_setting( 'page_for_posts', array(
2308
				'type'           => 'option',
2309
				'capability'     => 'manage_options',
2310
			//	'theme_supports' => 'static-front-page',
2311
			) );
2312
2313
			$this->add_control( 'page_for_posts', array(
2314
				'label'      => __( 'Posts page' ),
2315
				'section'    => 'static_front_page',
2316
				'type'       => 'dropdown-pages',
2317
			) );
2318
		}
2319
	}
2320
2321
	/**
2322
	 * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
2323
	 *
2324
	 * @since 4.2.0
2325
	 * @access public
2326
	 *
2327
	 * @see add_dynamic_settings()
2328
	 */
2329
	public function register_dynamic_settings() {
2330
		$this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
2331
	}
2332
2333
	/**
2334
	 * Callback for validating the header_textcolor value.
2335
	 *
2336
	 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
2337
	 * Returns default text color if hex color is empty.
2338
	 *
2339
	 * @since 3.4.0
2340
	 *
2341
	 * @param string $color
2342
	 * @return mixed
2343
	 */
2344
	public function _sanitize_header_textcolor( $color ) {
2345
		if ( 'blank' === $color )
2346
			return 'blank';
2347
2348
		$color = sanitize_hex_color_no_hash( $color );
2349
		if ( empty( $color ) )
2350
			$color = get_theme_support( 'custom-header', 'default-text-color' );
2351
2352
		return $color;
2353
	}
2354
2355
	/**
2356
	 * Callback for rendering the custom logo, used in the custom_logo partial.
2357
	 *
2358
	 * This method exists because the partial object and context data are passed
2359
	 * into a partial's render_callback so we cannot use get_custom_logo() as
2360
	 * the render_callback directly since it expects a blog ID as the first
2361
	 * argument. When WP no longer supports PHP 5.3, this method can be removed
2362
	 * in favor of an anonymous function.
2363
	 *
2364
	 * @see WP_Customize_Manager::register_controls()
2365
	 *
2366
	 * @since 4.5.0
2367
	 * @access private
2368
	 *
2369
	 * @return string Custom logo.
2370
	 */
2371
	public function _render_custom_logo_partial() {
2372
		return get_custom_logo();
2373
	}
2374
}
2375