Completed
Push — master ( a7cd2a...eabd6c )
by Stephen
38:42
created

WP_Customize_Manager   F

Complexity

Total Complexity 187

Size/Duplication

Total Lines 2217
Duplicated Lines 4.19 %

Coupling/Cohesion

Components 2
Dependencies 19

Importance

Changes 3
Bugs 0 Features 1
Metric Value
wmc 187
lcom 2
cbo 19
dl 93
loc 2217
rs 0.5217
c 3
b 0
f 1

71 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 12 101 4
B doing_ajax() 0 16 5
A wp_die() 0 11 4
A wp_die_handler() 0 7 3
C setup_theme() 0 43 12
A after_setup_theme() 0 7 4
B start_previewing_theme() 31 31 3
B stop_previewing_theme() 30 30 3
A theme() 0 6 2
A settings() 0 3 1
A controls() 0 3 1
A containers() 0 3 1
A sections() 0 3 1
A panels() 0 3 1
A is_theme_active() 0 3 1
A wp_loaded() 0 14 3
A wp_redirect_status() 0 6 3
B unsanitized_post_values() 0 15 5
A post_value() 0 8 2
B set_post_value() 0 34 1
B customize_preview_init() 0 28 2
A customize_preview_override_404_status() 0 5 2
A customize_preview_base() 0 3 1
A customize_preview_html5() 0 12 1
A customize_preview_loading_style() 0 13 1
D customize_preview_settings() 10 66 12
A customize_preview_signature() 0 3 1
A remove_preview_signature() 0 5 1
A is_preview() 0 3 1
A get_template() 0 3 1
A get_stylesheet() 0 3 1
A get_template_root() 0 3 1
A get_stylesheet_root() 0 3 1
A current_theme() 0 3 1
B save() 0 57 5
A refresh_nonces() 0 7 2
A add_setting() 0 18 2
B add_dynamic_settings() 0 46 4
A get_setting() 0 5 2
A remove_setting() 0 3 1
A add_panel() 0 10 2
A get_panel() 0 5 2
A remove_panel() 0 13 2
A register_panel_type() 0 3 1
A render_panel_templates() 0 6 2
A add_section() 0 10 2
A get_section() 0 4 2
A remove_section() 0 3 1
A register_section_type() 0 3 1
A render_section_templates() 0 6 2
A add_control() 0 10 2
A get_control() 0 4 2
A remove_control() 0 3 1
A register_control_type() 0 3 1
A render_control_templates() 0 8 2
A _cmp_priority() 0 7 2
C prepare_controls() 0 56 10
A enqueue_control_scripts() 0 5 2
A is_ios() 0 3 2
A get_document_title_template() 0 11 2
A set_preview_url() 0 3 1
A get_preview_url() 0 8 2
A set_return_url() 0 5 1
B get_return_url() 0 15 5
A set_autofocus() 0 3 1
A get_autofocus() 0 3 1
F customize_pane_settings() 10 121 13
D register_controls() 0 348 10
A register_dynamic_settings() 0 3 1
A _sanitize_header_textcolor() 0 10 3
A _render_custom_logo_partial() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_Customize_Manager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WP_Customize_Manager, and based on these observations, apply Extract Interface, too.

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', 'selective_refresh' );
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
		 * Filter 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 View Code Duplication
		if ( in_array( 'widgets', $components, true ) ) {
262
			require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
263
			$this->widgets = new WP_Customize_Widgets( $this );
264
		}
265
266 View Code Duplication
		if ( in_array( 'nav_menus', $components, true ) ) {
267
			require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
268
			$this->nav_menus = new WP_Customize_Nav_Menus( $this );
269
		}
270
271 View Code Duplication
		if ( in_array( 'selective_refresh', $components, true ) ) {
272
			require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
273
			$this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
274
		}
275
276
		add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
277
278
		add_action( 'setup_theme', array( $this, 'setup_theme' ) );
279
		add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
280
281
		// Run wp_redirect_status late to make sure we override the status last.
282
		add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
283
284
		// Do not spawn cron (especially the alternate cron) while running the Customizer.
285
		remove_action( 'init', 'wp_cron' );
286
287
		// Do not run update checks when rendering the controls.
288
		remove_action( 'admin_init', '_maybe_update_core' );
289
		remove_action( 'admin_init', '_maybe_update_plugins' );
290
		remove_action( 'admin_init', '_maybe_update_themes' );
291
292
		add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
293
		add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
294
295
		add_action( 'customize_register',                 array( $this, 'register_controls' ) );
296
		add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
297
		add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
298
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
299
300
		// Render Panel, Section, and Control templates.
301
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
302
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
303
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
304
305
		// Export the settings to JS via the _wpCustomizeSettings variable.
306
		add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
307
	}
308
309
	/**
310
	 * Return true if it's an AJAX request.
311
	 *
312
	 * @since 3.4.0
313
	 * @since 4.2.0 Added `$action` param.
314
	 * @access public
315
	 *
316
	 * @param string|null $action Whether the supplied AJAX action is being run.
317
	 * @return bool True if it's an AJAX request, false otherwise.
318
	 */
319
	public function doing_ajax( $action = null ) {
320
		$doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
321
		if ( ! $doing_ajax ) {
322
			return false;
323
		}
324
325
		if ( ! $action ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
326
			return true;
327
		} else {
328
			/*
329
			 * Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need
330
			 * to check before admin-ajax.php gets to that point.
331
			 */
332
			return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
333
		}
334
	}
335
336
	/**
337
	 * Custom wp_die wrapper. Returns either the standard message for UI
338
	 * or the AJAX message.
339
	 *
340
	 * @since 3.4.0
341
	 *
342
	 * @param mixed $ajax_message AJAX return
343
	 * @param mixed $message UI message
344
	 */
345
	protected function wp_die( $ajax_message, $message = null ) {
346
		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
347
			wp_die( $ajax_message );
348
		}
349
350
		if ( ! $message ) {
351
			$message = __( 'Cheatin&#8217; uh?' );
352
		}
353
354
		wp_die( $message );
355
	}
356
357
	/**
358
	 * Return the AJAX wp_die() handler if it's a customized request.
359
	 *
360
	 * @since 3.4.0
361
	 *
362
	 * @return string
363
	 */
364
	public function wp_die_handler() {
365
		if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
366
			return '_ajax_wp_die_handler';
367
		}
368
369
		return '_default_wp_die_handler';
370
	}
371
372
	/**
373
	 * Start preview and customize theme.
374
	 *
375
	 * Check if customize query variable exist. Init filters to filter the current theme.
376
	 *
377
	 * @since 3.4.0
378
	 */
379
	public function setup_theme() {
380
		send_origin_headers();
381
382
		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
383
		if ( is_admin() && ! $doing_ajax_or_is_customized ) {
384
			auth_redirect();
385
		} elseif ( $doing_ajax_or_is_customized && ! is_user_logged_in() ) {
386
			$this->wp_die( 0, __( 'You must be logged in to complete this action.' ) );
387
		}
388
389
		show_admin_bar( false );
390
391
		if ( ! current_user_can( 'customize' ) ) {
392
			$this->wp_die( -1, __( 'You are not allowed to customize the appearance of this site.' ) );
393
		}
394
395
		$this->original_stylesheet = get_stylesheet();
396
397
		$this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
398
399
		if ( $this->is_theme_active() ) {
400
			// Once the theme is loaded, we'll validate it.
401
			add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
402
		} else {
403
			// If the requested theme is not the active theme and the user doesn't have the
404
			// switch_themes cap, bail.
405
			if ( ! current_user_can( 'switch_themes' ) ) {
406
				$this->wp_die( -1, __( 'You are not allowed to edit theme options on this site.' ) );
407
			}
408
409
			// If the theme has errors while loading, bail.
410
			if ( $this->theme()->errors() ) {
411
				$this->wp_die( -1, $this->theme()->errors()->get_error_message() );
412
			}
413
414
			// If the theme isn't allowed per multisite settings, bail.
415
			if ( ! $this->theme()->is_allowed() ) {
416
				$this->wp_die( -1, __( 'The requested theme does not exist.' ) );
417
			}
418
		}
419
420
		$this->start_previewing_theme();
421
	}
422
423
	/**
424
	 * Callback to validate a theme once it is loaded
425
	 *
426
	 * @since 3.4.0
427
	 */
428
	public function after_setup_theme() {
429
		$doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_SERVER['customized'] ) );
430
		if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
431
			wp_redirect( 'themes.php?broken=true' );
432
			exit;
433
		}
434
	}
435
436
	/**
437
	 * If the theme to be previewed isn't the active theme, add filter callbacks
438
	 * to swap it out at runtime.
439
	 *
440
	 * @since 3.4.0
441
	 */
442 View Code Duplication
	public function start_previewing_theme() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
443
		// Bail if we're already previewing.
444
		if ( $this->is_preview() ) {
445
			return;
446
		}
447
448
		$this->previewing = true;
449
450
		if ( ! $this->is_theme_active() ) {
451
			add_filter( 'template', array( $this, 'get_template' ) );
452
			add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
453
			add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
454
455
			// @link: https://core.trac.wordpress.org/ticket/20027
456
			add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
457
			add_filter( 'pre_option_template', array( $this, 'get_template' ) );
458
459
			// Handle custom theme roots.
460
			add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
461
			add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
462
		}
463
464
		/**
465
		 * Fires once the Customizer theme preview has started.
466
		 *
467
		 * @since 3.4.0
468
		 *
469
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
470
		 */
471
		do_action( 'start_previewing_theme', $this );
472
	}
473
474
	/**
475
	 * Stop previewing the selected theme.
476
	 *
477
	 * Removes filters to change the current theme.
478
	 *
479
	 * @since 3.4.0
480
	 */
481 View Code Duplication
	public function stop_previewing_theme() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
482
		if ( ! $this->is_preview() ) {
483
			return;
484
		}
485
486
		$this->previewing = false;
487
488
		if ( ! $this->is_theme_active() ) {
489
			remove_filter( 'template', array( $this, 'get_template' ) );
490
			remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
491
			remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
492
493
			// @link: https://core.trac.wordpress.org/ticket/20027
494
			remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
495
			remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
496
497
			// Handle custom theme roots.
498
			remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
499
			remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
500
		}
501
502
		/**
503
		 * Fires once the Customizer theme preview has stopped.
504
		 *
505
		 * @since 3.4.0
506
		 *
507
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
508
		 */
509
		do_action( 'stop_previewing_theme', $this );
510
	}
511
512
	/**
513
	 * Get the theme being customized.
514
	 *
515
	 * @since 3.4.0
516
	 *
517
	 * @return WP_Theme
518
	 */
519
	public function theme() {
520
		if ( ! $this->theme ) {
521
			$this->theme = wp_get_theme();
522
		}
523
		return $this->theme;
524
	}
525
526
	/**
527
	 * Get the registered settings.
528
	 *
529
	 * @since 3.4.0
530
	 *
531
	 * @return array
532
	 */
533
	public function settings() {
534
		return $this->settings;
535
	}
536
537
	/**
538
	 * Get the registered controls.
539
	 *
540
	 * @since 3.4.0
541
	 *
542
	 * @return array
543
	 */
544
	public function controls() {
545
		return $this->controls;
546
	}
547
548
	/**
549
	 * Get the registered containers.
550
	 *
551
	 * @since 4.0.0
552
	 *
553
	 * @return array
554
	 */
555
	public function containers() {
556
		return $this->containers;
557
	}
558
559
	/**
560
	 * Get the registered sections.
561
	 *
562
	 * @since 3.4.0
563
	 *
564
	 * @return array
565
	 */
566
	public function sections() {
567
		return $this->sections;
568
	}
569
570
	/**
571
	 * Get the registered panels.
572
	 *
573
	 * @since 4.0.0
574
	 * @access public
575
	 *
576
	 * @return array Panels.
577
	 */
578
	public function panels() {
579
		return $this->panels;
580
	}
581
582
	/**
583
	 * Checks if the current theme is active.
584
	 *
585
	 * @since 3.4.0
586
	 *
587
	 * @return bool
588
	 */
589
	public function is_theme_active() {
590
		return $this->get_stylesheet() == $this->original_stylesheet;
591
	}
592
593
	/**
594
	 * Register styles/scripts and initialize the preview of each setting
595
	 *
596
	 * @since 3.4.0
597
	 */
598
	public function wp_loaded() {
599
600
		/**
601
		 * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
602
		 *
603
		 * @since 3.4.0
604
		 *
605
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
606
		 */
607
		do_action( 'customize_register', $this );
608
609
		if ( $this->is_preview() && ! is_admin() )
610
			$this->customize_preview_init();
611
	}
612
613
	/**
614
	 * Prevents AJAX requests from following redirects when previewing a theme
615
	 * by issuing a 200 response instead of a 30x.
616
	 *
617
	 * Instead, the JS will sniff out the location header.
618
	 *
619
	 * @since 3.4.0
620
	 *
621
	 * @param $status
622
	 * @return int
623
	 */
624
	public function wp_redirect_status( $status ) {
625
		if ( $this->is_preview() && ! is_admin() )
626
			return 200;
627
628
		return $status;
629
	}
630
631
	/**
632
	 * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
633
	 * settings for subsequent post_value() lookups.
634
	 *
635
	 * @since 4.1.1
636
	 *
637
	 * @return array
638
	 */
639
	public function unsanitized_post_values() {
640
		if ( ! isset( $this->_post_values ) ) {
641
			if ( isset( $_POST['customized'] ) ) {
642
				$this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['customized']) targeting wp_unslash() can also be of type array; however, json_decode() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
643
			}
644
			if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
645
				$this->_post_values = array();
646
			}
647
		}
648
		if ( empty( $this->_post_values ) ) {
649
			return array();
650
		} else {
651
			return $this->_post_values;
652
		}
653
	}
654
655
	/**
656
	 * Return the sanitized value for a given setting from the request's POST data.
657
	 *
658
	 * @since 3.4.0
659
	 * @since 4.1.1 Introduced 'default' parameter.
660
	 *
661
	 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
662
	 * @param mixed $default value returned $setting has no post value (added in 4.2.0).
663
	 * @return string|mixed $post_value Sanitized value or the $default provided
664
	 */
665
	public function post_value( $setting, $default = null ) {
666
		$post_values = $this->unsanitized_post_values();
667
		if ( array_key_exists( $setting->id, $post_values ) ) {
668
			return $setting->sanitize( $post_values[ $setting->id ] );
669
		} else {
670
			return $default;
671
		}
672
	}
673
674
	/**
675
	 * Override a setting's (unsanitized) value as found in any incoming $_POST['customized'].
676
	 *
677
	 * @since 4.2.0
678
	 * @access public
679
	 *
680
	 * @param string $setting_id ID for the WP_Customize_Setting instance.
681
	 * @param mixed  $value      Post value.
682
	 */
683
	public function set_post_value( $setting_id, $value ) {
684
		$this->unsanitized_post_values();
685
		$this->_post_values[ $setting_id ] = $value;
686
687
		/**
688
		 * Announce when a specific setting's unsanitized post value has been set.
689
		 *
690
		 * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called.
691
		 *
692
		 * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID.
693
		 *
694
		 * @since 4.4.0
695
		 *
696
		 * @param mixed                $value Unsanitized setting post value.
697
		 * @param WP_Customize_Manager $this  WP_Customize_Manager instance.
698
		 */
699
		do_action( "customize_post_value_set_{$setting_id}", $value, $this );
700
701
		/**
702
		 * Announce when any setting's unsanitized post value has been set.
703
		 *
704
		 * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called.
705
		 *
706
		 * This is useful for `WP_Customize_Setting` instances to watch
707
		 * in order to update a cached previewed value.
708
		 *
709
		 * @since 4.4.0
710
		 *
711
		 * @param string               $setting_id Setting ID.
712
		 * @param mixed                $value      Unsanitized setting post value.
713
		 * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
714
		 */
715
		do_action( 'customize_post_value_set', $setting_id, $value, $this );
716
	}
717
718
	/**
719
	 * Print JavaScript settings.
720
	 *
721
	 * @since 3.4.0
722
	 */
723
	public function customize_preview_init() {
724
		$this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
725
726
		$this->prepare_controls();
727
728
		wp_enqueue_script( 'customize-preview' );
729
		add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
730
		add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
731
		add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
732
		add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
733
		add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
734
		add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
735
		add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
736
737
		foreach ( $this->settings as $setting ) {
738
			$setting->preview();
739
		}
740
741
		/**
742
		 * Fires once the Customizer preview has initialized and JavaScript
743
		 * settings have been printed.
744
		 *
745
		 * @since 3.4.0
746
		 *
747
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
748
		 */
749
		do_action( 'customize_preview_init', $this );
750
	}
751
752
	/**
753
	 * Prevent sending a 404 status when returning the response for the customize
754
	 * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
755
	 *
756
	 * @since 4.0.0
757
	 * @access public
758
	 */
759
	public function customize_preview_override_404_status() {
760
		if ( is_404() ) {
761
			status_header( 200 );
762
		}
763
	}
764
765
	/**
766
	 * Print base element for preview frame.
767
	 *
768
	 * @since 3.4.0
769
	 */
770
	public function customize_preview_base() {
771
		?><base href="<?php echo home_url( '/' ); ?>" /><?php
772
	}
773
774
	/**
775
	 * Print a workaround to handle HTML5 tags in IE < 9.
776
	 *
777
	 * @since 3.4.0
778
	 */
779
	public function customize_preview_html5() { ?>
780
		<!--[if lt IE 9]>
781
		<script type="text/javascript">
782
			var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
783
				'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
784
				'output', 'progress', 'section', 'time', 'video' ];
785
			for ( var i = 0; i < e.length; i++ ) {
786
				document.createElement( e[i] );
787
			}
788
		</script>
789
		<![endif]--><?php
790
	}
791
792
	/**
793
	 * Print CSS for loading indicators for the Customizer preview.
794
	 *
795
	 * @since 4.2.0
796
	 * @access public
797
	 */
798
	public function customize_preview_loading_style() {
799
		?><style>
800
			body.wp-customizer-unloading {
801
				opacity: 0.25;
802
				cursor: progress !important;
803
				-webkit-transition: opacity 0.5s;
804
				transition: opacity 0.5s;
805
			}
806
			body.wp-customizer-unloading * {
807
				pointer-events: none !important;
808
			}
809
		</style><?php
810
	}
811
812
	/**
813
	 * Print JavaScript settings for preview frame.
814
	 *
815
	 * @since 3.4.0
816
	 */
817
	public function customize_preview_settings() {
818
		$settings = array(
819
			'theme' => array(
820
				'stylesheet' => $this->get_stylesheet(),
821
				'active'     => $this->is_theme_active(),
822
			),
823
			'url' => array(
824
				'self' => empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ),
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_SERVER['REQUEST_URI']) targeting wp_unslash() can also be of type array; however, esc_url_raw() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
825
			),
826
			'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
827
			'activePanels' => array(),
828
			'activeSections' => array(),
829
			'activeControls' => array(),
830
			'nonce' => $this->get_nonces(),
831
			'l10n' => array(
832
				'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
833
			),
834
			'_dirty' => array_keys( $this->unsanitized_post_values() ),
835
		);
836
837 View Code Duplication
		foreach ( $this->panels as $panel_id => $panel ) {
838
			if ( $panel->check_capabilities() ) {
839
				$settings['activePanels'][ $panel_id ] = $panel->active();
840
				foreach ( $panel->sections as $section_id => $section ) {
841
					if ( $section->check_capabilities() ) {
842
						$settings['activeSections'][ $section_id ] = $section->active();
843
					}
844
				}
845
			}
846
		}
847
		foreach ( $this->sections as $id => $section ) {
848
			if ( $section->check_capabilities() ) {
849
				$settings['activeSections'][ $id ] = $section->active();
850
			}
851
		}
852
		foreach ( $this->controls as $id => $control ) {
853
			if ( $control->check_capabilities() ) {
854
				$settings['activeControls'][ $id ] = $control->active();
855
			}
856
		}
857
858
		?>
859
		<script type="text/javascript">
860
			var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
861
			_wpCustomizeSettings.values = {};
862
			(function( v ) {
863
				<?php
864
				/*
865
				 * Serialize settings separately from the initial _wpCustomizeSettings
866
				 * serialization in order to avoid a peak memory usage spike.
867
				 * @todo We may not even need to export the values at all since the pane syncs them anyway.
868
				 */
869
				foreach ( $this->settings as $id => $setting ) {
870
					if ( $setting->check_capabilities() ) {
871
						printf(
872
							"v[%s] = %s;\n",
873
							wp_json_encode( $id ),
874
							wp_json_encode( $setting->js_value() )
875
						);
876
					}
877
				}
878
				?>
879
			})( _wpCustomizeSettings.values );
880
		</script>
881
		<?php
882
	}
883
884
	/**
885
	 * Prints a signature so we can ensure the Customizer was properly executed.
886
	 *
887
	 * @since 3.4.0
888
	 */
889
	public function customize_preview_signature() {
890
		echo 'WP_CUSTOMIZER_SIGNATURE';
891
	}
892
893
	/**
894
	 * Removes the signature in case we experience a case where the Customizer was not properly executed.
895
	 *
896
	 * @since 3.4.0
897
	 *
898
	 * @param mixed $return Value passed through for wp_die_handler filter.
899
	 * @return mixed Value passed through for wp_die_handler filter.
900
	 */
901
	public function remove_preview_signature( $return = null ) {
902
		remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
903
904
		return $return;
905
	}
906
907
	/**
908
	 * Is it a theme preview?
909
	 *
910
	 * @since 3.4.0
911
	 *
912
	 * @return bool True if it's a preview, false if not.
913
	 */
914
	public function is_preview() {
915
		return (bool) $this->previewing;
916
	}
917
918
	/**
919
	 * Retrieve the template name of the previewed theme.
920
	 *
921
	 * @since 3.4.0
922
	 *
923
	 * @return string Template name.
924
	 */
925
	public function get_template() {
926
		return $this->theme()->get_template();
927
	}
928
929
	/**
930
	 * Retrieve the stylesheet name of the previewed theme.
931
	 *
932
	 * @since 3.4.0
933
	 *
934
	 * @return string Stylesheet name.
935
	 */
936
	public function get_stylesheet() {
937
		return $this->theme()->get_stylesheet();
938
	}
939
940
	/**
941
	 * Retrieve the template root of the previewed theme.
942
	 *
943
	 * @since 3.4.0
944
	 *
945
	 * @return string Theme root.
946
	 */
947
	public function get_template_root() {
948
		return get_raw_theme_root( $this->get_template(), true );
949
	}
950
951
	/**
952
	 * Retrieve the stylesheet root of the previewed theme.
953
	 *
954
	 * @since 3.4.0
955
	 *
956
	 * @return string Theme root.
957
	 */
958
	public function get_stylesheet_root() {
959
		return get_raw_theme_root( $this->get_stylesheet(), true );
960
	}
961
962
	/**
963
	 * Filter the current theme and return the name of the previewed theme.
964
	 *
965
	 * @since 3.4.0
966
	 *
967
	 * @param $current_theme {@internal Parameter is not used}
968
	 * @return string Theme name.
969
	 */
970
	public function current_theme( $current_theme ) {
0 ignored issues
show
Unused Code introduced by
The parameter $current_theme is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
971
		return $this->theme()->display('Name');
972
	}
973
974
	/**
975
	 * Switch the theme and trigger the save() method on each setting.
976
	 *
977
	 * @since 3.4.0
978
	 */
979
	public function save() {
980
		if ( ! $this->is_preview() ) {
981
			wp_send_json_error( 'not_preview' );
982
		}
983
984
		$action = 'save-customize_' . $this->get_stylesheet();
985
		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
986
			wp_send_json_error( 'invalid_nonce' );
987
		}
988
989
		// Do we have to switch themes?
990
		if ( ! $this->is_theme_active() ) {
991
			// Temporarily stop previewing the theme to allow switch_themes()
992
			// to operate properly.
993
			$this->stop_previewing_theme();
994
			switch_theme( $this->get_stylesheet() );
995
			update_option( 'theme_switched_via_customizer', true );
996
			$this->start_previewing_theme();
997
		}
998
999
		/**
1000
		 * Fires once the theme has switched in the Customizer, but before settings
1001
		 * have been saved.
1002
		 *
1003
		 * @since 3.4.0
1004
		 *
1005
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1006
		 */
1007
		do_action( 'customize_save', $this );
1008
1009
		foreach ( $this->settings as $setting ) {
1010
			$setting->save();
1011
		}
1012
1013
		/**
1014
		 * Fires after Customize settings have been saved.
1015
		 *
1016
		 * @since 3.6.0
1017
		 *
1018
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1019
		 */
1020
		do_action( 'customize_save_after', $this );
1021
1022
		/**
1023
		 * Filter response data for a successful customize_save AJAX request.
1024
		 *
1025
		 * This filter does not apply if there was a nonce or authentication failure.
1026
		 *
1027
		 * @since 4.2.0
1028
		 *
1029
		 * @param array                $data Additional information passed back to the 'saved'
1030
		 *                                   event on `wp.customize`.
1031
		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1032
		 */
1033
		$response = apply_filters( 'customize_save_response', array(), $this );
1034
		wp_send_json_success( $response );
1035
	}
1036
1037
	/**
1038
	 * Refresh nonces for the current preview.
1039
	 *
1040
	 * @since 4.2.0
1041
	 */
1042
	public function refresh_nonces() {
1043
		if ( ! $this->is_preview() ) {
1044
			wp_send_json_error( 'not_preview' );
1045
		}
1046
1047
		wp_send_json_success( $this->get_nonces() );
1048
	}
1049
1050
	/**
1051
	 * Add a customize setting.
1052
	 *
1053
	 * @since 3.4.0
1054
	 * @since 4.5.0 Return added WP_Customize_Setting instance.
1055
	 * @access public
1056
	 *
1057
	 * @param WP_Customize_Setting|string $id   Customize Setting object, or ID.
1058
	 * @param array                       $args Setting arguments; passed to WP_Customize_Setting
1059
	 *                                          constructor.
1060
	 * @return WP_Customize_Setting             The instance of the setting that was added.
1061
	 */
1062
	public function add_setting( $id, $args = array() ) {
1063
		if ( $id instanceof WP_Customize_Setting ) {
1064
			$setting = $id;
1065
		} else {
1066
			$class = 'WP_Customize_Setting';
1067
1068
			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
1069
			$args = apply_filters( 'customize_dynamic_setting_args', $args, $id );
1070
1071
			/** This filter is documented in wp-includes/class-wp-customize-manager.php */
1072
			$class = apply_filters( 'customize_dynamic_setting_class', $class, $id, $args );
1073
1074
			$setting = new $class( $this, $id, $args );
1075
		}
1076
1077
		$this->settings[ $setting->id ] = $setting;
1078
		return $setting;
1079
	}
1080
1081
	/**
1082
	 * Register any dynamically-created settings, such as those from $_POST['customized']
1083
	 * that have no corresponding setting created.
1084
	 *
1085
	 * This is a mechanism to "wake up" settings that have been dynamically created
1086
	 * on the front end and have been sent to WordPress in `$_POST['customized']`. When WP
1087
	 * loads, the dynamically-created settings then will get created and previewed
1088
	 * even though they are not directly created statically with code.
1089
	 *
1090
	 * @since 4.2.0
1091
	 * @access public
1092
	 *
1093
	 * @param array $setting_ids The setting IDs to add.
1094
	 * @return array The WP_Customize_Setting objects added.
1095
	 */
1096
	public function add_dynamic_settings( $setting_ids ) {
1097
		$new_settings = array();
1098
		foreach ( $setting_ids as $setting_id ) {
1099
			// Skip settings already created
1100
			if ( $this->get_setting( $setting_id ) ) {
1101
				continue;
1102
			}
1103
1104
			$setting_args = false;
1105
			$setting_class = 'WP_Customize_Setting';
1106
1107
			/**
1108
			 * Filter a dynamic setting's constructor args.
1109
			 *
1110
			 * For a dynamic setting to be registered, this filter must be employed
1111
			 * to override the default false value with an array of args to pass to
1112
			 * the WP_Customize_Setting constructor.
1113
			 *
1114
			 * @since 4.2.0
1115
			 *
1116
			 * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
1117
			 * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
1118
			 */
1119
			$setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
1120
			if ( false === $setting_args ) {
1121
				continue;
1122
			}
1123
1124
			/**
1125
			 * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
1126
			 *
1127
			 * @since 4.2.0
1128
			 *
1129
			 * @param string $setting_class WP_Customize_Setting or a subclass.
1130
			 * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
1131
			 * @param array  $setting_args  WP_Customize_Setting or a subclass.
1132
			 */
1133
			$setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
1134
1135
			$setting = new $setting_class( $this, $setting_id, $setting_args );
1136
1137
			$this->add_setting( $setting );
1138
			$new_settings[] = $setting;
1139
		}
1140
		return $new_settings;
1141
	}
1142
1143
	/**
1144
	 * Retrieve a customize setting.
1145
	 *
1146
	 * @since 3.4.0
1147
	 *
1148
	 * @param string $id Customize Setting ID.
1149
	 * @return WP_Customize_Setting|void The setting, if set.
1150
	 */
1151
	public function get_setting( $id ) {
1152
		if ( isset( $this->settings[ $id ] ) ) {
1153
			return $this->settings[ $id ];
1154
		}
1155
	}
1156
1157
	/**
1158
	 * Remove a customize setting.
1159
	 *
1160
	 * @since 3.4.0
1161
	 *
1162
	 * @param string $id Customize Setting ID.
1163
	 */
1164
	public function remove_setting( $id ) {
1165
		unset( $this->settings[ $id ] );
1166
	}
1167
1168
	/**
1169
	 * Add a customize panel.
1170
	 *
1171
	 * @since 4.0.0
1172
	 * @since 4.5.0 Return added WP_Customize_Panel instance.
1173
	 * @access public
1174
	 *
1175
	 * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
1176
	 * @param array                     $args Optional. Panel arguments. Default empty array.
1177
	 *
1178
	 * @return WP_Customize_Panel             The instance of the panel that was added.
1179
	 */
1180
	public function add_panel( $id, $args = array() ) {
1181
		if ( $id instanceof WP_Customize_Panel ) {
1182
			$panel = $id;
1183
		} else {
1184
			$panel = new WP_Customize_Panel( $this, $id, $args );
1185
		}
1186
1187
		$this->panels[ $panel->id ] = $panel;
1188
		return $panel;
1189
	}
1190
1191
	/**
1192
	 * Retrieve a customize panel.
1193
	 *
1194
	 * @since 4.0.0
1195
	 * @access public
1196
	 *
1197
	 * @param string $id Panel ID to get.
1198
	 * @return WP_Customize_Panel|void Requested panel instance, if set.
1199
	 */
1200
	public function get_panel( $id ) {
1201
		if ( isset( $this->panels[ $id ] ) ) {
1202
			return $this->panels[ $id ];
1203
		}
1204
	}
1205
1206
	/**
1207
	 * Remove a customize panel.
1208
	 *
1209
	 * @since 4.0.0
1210
	 * @access public
1211
	 *
1212
	 * @param string $id Panel ID to remove.
1213
	 */
1214
	public function remove_panel( $id ) {
1215
		// Removing core components this way is _doing_it_wrong().
1216
		if ( in_array( $id, $this->components, true ) ) {
1217
			/* translators: 1: panel id, 2: link to 'customize_loaded_components' filter reference */
1218
			$message = sprintf( __( 'Removing %1$s manually will cause PHP warnings. Use the %2$s filter instead.' ),
1219
				$id,
1220
				'<a href="' . esc_url( 'https://developer.wordpress.org/reference/hooks/customize_loaded_components/' ) . '"><code>customize_loaded_components</code></a>'
1221
			);
1222
1223
			_doing_it_wrong( __METHOD__, $message, '4.5' );
1224
		}
1225
		unset( $this->panels[ $id ] );
1226
	}
1227
1228
	/**
1229
	 * Register a customize panel type.
1230
	 *
1231
	 * Registered types are eligible to be rendered via JS and created dynamically.
1232
	 *
1233
	 * @since 4.3.0
1234
	 * @access public
1235
	 *
1236
	 * @see WP_Customize_Panel
1237
	 *
1238
	 * @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
1239
	 */
1240
	public function register_panel_type( $panel ) {
1241
		$this->registered_panel_types[] = $panel;
1242
	}
1243
1244
	/**
1245
	 * Render JS templates for all registered panel types.
1246
	 *
1247
	 * @since 4.3.0
1248
	 * @access public
1249
	 */
1250
	public function render_panel_templates() {
1251
		foreach ( $this->registered_panel_types as $panel_type ) {
1252
			$panel = new $panel_type( $this, 'temp', array() );
1253
			$panel->print_template();
1254
		}
1255
	}
1256
1257
	/**
1258
	 * Add a customize section.
1259
	 *
1260
	 * @since 3.4.0
1261
	 * @since 4.5.0 Return added WP_Customize_Section instance.
1262
	 * @access public
1263
	 *
1264
	 * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
1265
	 * @param array                       $args Section arguments.
1266
	 *
1267
	 * @return WP_Customize_Section             The instance of the section that was added.
1268
	 */
1269
	public function add_section( $id, $args = array() ) {
1270
		if ( $id instanceof WP_Customize_Section ) {
1271
			$section = $id;
1272
		} else {
1273
			$section = new WP_Customize_Section( $this, $id, $args );
1274
		}
1275
1276
		$this->sections[ $section->id ] = $section;
1277
		return $section;
1278
	}
1279
1280
	/**
1281
	 * Retrieve a customize section.
1282
	 *
1283
	 * @since 3.4.0
1284
	 *
1285
	 * @param string $id Section ID.
1286
	 * @return WP_Customize_Section|void The section, if set.
1287
	 */
1288
	public function get_section( $id ) {
1289
		if ( isset( $this->sections[ $id ] ) )
1290
			return $this->sections[ $id ];
1291
	}
1292
1293
	/**
1294
	 * Remove a customize section.
1295
	 *
1296
	 * @since 3.4.0
1297
	 *
1298
	 * @param string $id Section ID.
1299
	 */
1300
	public function remove_section( $id ) {
1301
		unset( $this->sections[ $id ] );
1302
	}
1303
1304
	/**
1305
	 * Register a customize section type.
1306
	 *
1307
	 * Registered types are eligible to be rendered via JS and created dynamically.
1308
	 *
1309
	 * @since 4.3.0
1310
	 * @access public
1311
	 *
1312
	 * @see WP_Customize_Section
1313
	 *
1314
	 * @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
1315
	 */
1316
	public function register_section_type( $section ) {
1317
		$this->registered_section_types[] = $section;
1318
	}
1319
1320
	/**
1321
	 * Render JS templates for all registered section types.
1322
	 *
1323
	 * @since 4.3.0
1324
	 * @access public
1325
	 */
1326
	public function render_section_templates() {
1327
		foreach ( $this->registered_section_types as $section_type ) {
1328
			$section = new $section_type( $this, 'temp', array() );
1329
			$section->print_template();
1330
		}
1331
	}
1332
1333
	/**
1334
	 * Add a customize control.
1335
	 *
1336
	 * @since 3.4.0
1337
	 * @since 4.5.0 Return added WP_Customize_Control instance.
1338
	 * @access public
1339
	 *
1340
	 * @param WP_Customize_Control|string $id   Customize Control object, or ID.
1341
	 * @param array                       $args Control arguments; passed to WP_Customize_Control
1342
	 *                                          constructor.
1343
	 * @return WP_Customize_Control             The instance of the control that was added.
1344
	 */
1345
	public function add_control( $id, $args = array() ) {
1346
		if ( $id instanceof WP_Customize_Control ) {
1347
			$control = $id;
1348
		} else {
1349
			$control = new WP_Customize_Control( $this, $id, $args );
1350
		}
1351
1352
		$this->controls[ $control->id ] = $control;
1353
		return $control;
1354
	}
1355
1356
	/**
1357
	 * Retrieve a customize control.
1358
	 *
1359
	 * @since 3.4.0
1360
	 *
1361
	 * @param string $id ID of the control.
1362
	 * @return WP_Customize_Control|void The control object, if set.
1363
	 */
1364
	public function get_control( $id ) {
1365
		if ( isset( $this->controls[ $id ] ) )
1366
			return $this->controls[ $id ];
1367
	}
1368
1369
	/**
1370
	 * Remove a customize control.
1371
	 *
1372
	 * @since 3.4.0
1373
	 *
1374
	 * @param string $id ID of the control.
1375
	 */
1376
	public function remove_control( $id ) {
1377
		unset( $this->controls[ $id ] );
1378
	}
1379
1380
	/**
1381
	 * Register a customize control type.
1382
	 *
1383
	 * Registered types are eligible to be rendered via JS and created dynamically.
1384
	 *
1385
	 * @since 4.1.0
1386
	 * @access public
1387
	 *
1388
	 * @param string $control Name of a custom control which is a subclass of
1389
	 *                        {@see WP_Customize_Control}.
1390
	 */
1391
	public function register_control_type( $control ) {
1392
		$this->registered_control_types[] = $control;
1393
	}
1394
1395
	/**
1396
	 * Render JS templates for all registered control types.
1397
	 *
1398
	 * @since 4.1.0
1399
	 * @access public
1400
	 */
1401
	public function render_control_templates() {
1402
		foreach ( $this->registered_control_types as $control_type ) {
1403
			$control = new $control_type( $this, 'temp', array(
1404
				'settings' => array(),
1405
			) );
1406
			$control->print_template();
1407
		}
1408
	}
1409
1410
	/**
1411
	 * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
1412
	 *
1413
	 * @since 3.4.0
1414
	 *
1415
	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
1416
	 * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
1417
	 * @return int
1418
	 */
1419
	protected function _cmp_priority( $a, $b ) {
1420
		if ( $a->priority === $b->priority ) {
1421
			return $a->instance_number - $b->instance_number;
1422
		} else {
1423
			return $a->priority - $b->priority;
1424
		}
1425
	}
1426
1427
	/**
1428
	 * Prepare panels, sections, and controls.
1429
	 *
1430
	 * For each, check if required related components exist,
1431
	 * whether the user has the necessary capabilities,
1432
	 * and sort by priority.
1433
	 *
1434
	 * @since 3.4.0
1435
	 */
1436
	public function prepare_controls() {
1437
1438
		$controls = array();
1439
		uasort( $this->controls, array( $this, '_cmp_priority' ) );
1440
1441
		foreach ( $this->controls as $id => $control ) {
1442
			if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
1443
				continue;
1444
			}
1445
1446
			$this->sections[ $control->section ]->controls[] = $control;
1447
			$controls[ $id ] = $control;
1448
		}
1449
		$this->controls = $controls;
1450
1451
		// Prepare sections.
1452
		uasort( $this->sections, array( $this, '_cmp_priority' ) );
1453
		$sections = array();
1454
1455
		foreach ( $this->sections as $section ) {
1456
			if ( ! $section->check_capabilities() ) {
1457
				continue;
1458
			}
1459
1460
			usort( $section->controls, array( $this, '_cmp_priority' ) );
1461
1462
			if ( ! $section->panel ) {
1463
				// Top-level section.
1464
				$sections[ $section->id ] = $section;
1465
			} else {
1466
				// This section belongs to a panel.
1467
				if ( isset( $this->panels [ $section->panel ] ) ) {
1468
					$this->panels[ $section->panel ]->sections[ $section->id ] = $section;
1469
				}
1470
			}
1471
		}
1472
		$this->sections = $sections;
1473
1474
		// Prepare panels.
1475
		uasort( $this->panels, array( $this, '_cmp_priority' ) );
1476
		$panels = array();
1477
1478
		foreach ( $this->panels as $panel ) {
1479
			if ( ! $panel->check_capabilities() ) {
1480
				continue;
1481
			}
1482
1483
			uasort( $panel->sections, array( $this, '_cmp_priority' ) );
1484
			$panels[ $panel->id ] = $panel;
1485
		}
1486
		$this->panels = $panels;
1487
1488
		// Sort panels and top-level sections together.
1489
		$this->containers = array_merge( $this->panels, $this->sections );
1490
		uasort( $this->containers, array( $this, '_cmp_priority' ) );
1491
	}
1492
1493
	/**
1494
	 * Enqueue scripts for customize controls.
1495
	 *
1496
	 * @since 3.4.0
1497
	 */
1498
	public function enqueue_control_scripts() {
1499
		foreach ( $this->controls as $control ) {
1500
			$control->enqueue();
1501
		}
1502
	}
1503
1504
	/**
1505
	 * Determine whether the user agent is iOS.
1506
	 *
1507
	 * @since 4.4.0
1508
	 * @access public
1509
	 *
1510
	 * @return bool Whether the user agent is iOS.
1511
	 */
1512
	public function is_ios() {
1513
		return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
1514
	}
1515
1516
	/**
1517
	 * Get the template string for the Customizer pane document title.
1518
	 *
1519
	 * @since 4.4.0
1520
	 * @access public
1521
	 *
1522
	 * @return string The template string for the document title.
1523
	 */
1524
	public function get_document_title_template() {
1525
		if ( $this->is_theme_active() ) {
1526
			/* translators: %s: document title from the preview */
1527
			$document_title_tmpl = __( 'Customize: %s' );
1528
		} else {
1529
			/* translators: %s: document title from the preview */
1530
			$document_title_tmpl = __( 'Live Preview: %s' );
1531
		}
1532
		$document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
1533
		return $document_title_tmpl;
1534
	}
1535
1536
	/**
1537
	 * Set the initial URL to be previewed.
1538
	 *
1539
	 * URL is validated.
1540
	 *
1541
	 * @since 4.4.0
1542
	 * @access public
1543
	 *
1544
	 * @param string $preview_url URL to be previewed.
1545
	 */
1546
	public function set_preview_url( $preview_url ) {
1547
		$this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
1548
	}
1549
1550
	/**
1551
	 * Get the initial URL to be previewed.
1552
	 *
1553
	 * @since 4.4.0
1554
	 * @access public
1555
	 *
1556
	 * @return string URL being previewed.
1557
	 */
1558
	public function get_preview_url() {
1559
		if ( empty( $this->preview_url ) ) {
1560
			$preview_url = home_url( '/' );
1561
		} else {
1562
			$preview_url = $this->preview_url;
1563
		}
1564
		return $preview_url;
1565
	}
1566
1567
	/**
1568
	 * Set URL to link the user to when closing the Customizer.
1569
	 *
1570
	 * URL is validated.
1571
	 *
1572
	 * @since 4.4.0
1573
	 * @access public
1574
	 *
1575
	 * @param string $return_url URL for return link.
1576
	 */
1577
	public function set_return_url( $return_url ) {
1578
		$return_url = remove_query_arg( wp_removable_query_args(), $return_url );
1579
		$return_url = wp_validate_redirect( $return_url );
0 ignored issues
show
Bug introduced by
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...
1580
		$this->return_url = $return_url;
1581
	}
1582
1583
	/**
1584
	 * Get URL to link the user to when closing the Customizer.
1585
	 *
1586
	 * @since 4.4.0
1587
	 * @access public
1588
	 *
1589
	 * @return string URL for link to close Customizer.
1590
	 */
1591
	public function get_return_url() {
1592
		$referer = wp_get_referer();
1593
		$excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
1594
1595
		if ( $this->return_url ) {
1596
			$return_url = $this->return_url;
1597
		} else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $referer of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

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