Issues (4967)

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.

src/wp-includes/class-wp-customize-setting.php (7 issues)

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 Setting classes
4
 *
5
 * @package WordPress
6
 * @subpackage Customize
7
 * @since 3.4.0
8
 */
9
10
/**
11
 * Customize Setting class.
12
 *
13
 * Handles saving and sanitizing of settings.
14
 *
15
 * @since 3.4.0
16
 *
17
 * @see WP_Customize_Manager
18
 */
19
class WP_Customize_Setting {
20
	/**
21
	 * Customizer bootstrap instance.
22
	 *
23
	 * @since 3.4.0
24
	 * @access public
25
	 * @var WP_Customize_Manager
26
	 */
27
	public $manager;
28
29
	/**
30
	 * Unique string identifier for the setting.
31
	 *
32
	 * @since 3.4.0
33
	 * @access public
34
	 * @var string
35
	 */
36
	public $id;
37
38
	/**
39
	 * Type of customize settings.
40
	 *
41
	 * @since 3.4.0
42
	 * @access public
43
	 * @var string
44
	 */
45
	public $type = 'theme_mod';
46
47
	/**
48
	 * Capability required to edit this setting.
49
	 *
50
	 * @since 3.4.0
51
	 * @access public
52
	 * @var string|array
53
	 */
54
	public $capability = 'edit_theme_options';
55
56
	/**
57
	 * Feature a theme is required to support to enable this setting.
58
	 *
59
	 * @since 3.4.0
60
	 * @access public
61
	 * @var string
62
	 */
63
	public $theme_supports = '';
64
65
	/**
66
	 * The default value for the setting.
67
	 *
68
	 * @since 3.4.0
69
	 * @access public
70
	 * @var string
71
	 */
72
	public $default = '';
73
74
	/**
75
	 * Options for rendering the live preview of changes in Theme Customizer.
76
	 *
77
	 * Set this value to 'postMessage' to enable a custom Javascript handler to render changes to this setting
78
	 * as opposed to reloading the whole page.
79
	 *
80
	 * @link https://developer.wordpress.org/themes/customize-api
81
	 *
82
	 * @since 3.4.0
83
	 * @access public
84
	 * @var string
85
	 */
86
	public $transport = 'refresh';
87
88
	/**
89
	 * Server-side validation callback for the setting's value.
90
	 *
91
	 * @since 4.6.0
92
	 * @access public
93
	 * @var callable
94
	 */
95
	public $validate_callback = '';
96
97
	/**
98
	 * Callback to filter a Customize setting value in un-slashed form.
99
	 *
100
	 * @since 3.4.0
101
	 * @access public
102
	 * @var callable
103
	 */
104
	public $sanitize_callback = '';
105
106
	/**
107
	 * Callback to convert a Customize PHP setting value to a value that is JSON serializable.
108
	 *
109
	 * @since 3.4.0
110
	 * @access public
111
	 * @var string
112
	 */
113
	public $sanitize_js_callback = '';
114
115
	/**
116
	 * Whether or not the setting is initially dirty when created.
117
	 *
118
	 * This is used to ensure that a setting will be sent from the pane to the
119
	 * preview when loading the Customizer. Normally a setting only is synced to
120
	 * the preview if it has been changed. This allows the setting to be sent
121
	 * from the start.
122
	 *
123
	 * @since 4.2.0
124
	 * @access public
125
	 * @var bool
126
	 */
127
	public $dirty = false;
128
129
	/**
130
	 * ID Data.
131
	 *
132
	 * @since 3.4.0
133
	 * @access protected
134
	 * @var array
135
	 */
136
	protected $id_data = array();
137
138
	/**
139
	 * Whether or not preview() was called.
140
	 *
141
	 * @since 4.4.0
142
	 * @access protected
143
	 * @var bool
144
	 */
145
	protected $is_previewed = false;
146
147
	/**
148
	 * Cache of multidimensional values to improve performance.
149
	 *
150
	 * @since 4.4.0
151
	 * @static
152
	 * @access protected
153
	 * @var array
154
	 */
155
	protected static $aggregated_multidimensionals = array();
156
157
	/**
158
	 * Whether the multidimensional setting is aggregated.
159
	 *
160
	 * @since 4.4.0
161
	 * @access protected
162
	 * @var bool
163
	 */
164
	protected $is_multidimensional_aggregated = false;
165
166
	/**
167
	 * Constructor.
168
	 *
169
	 * Any supplied $args override class property defaults.
170
	 *
171
	 * @since 3.4.0
172
	 *
173
	 * @param WP_Customize_Manager $manager
174
	 * @param string               $id      An specific ID of the setting. Can be a
175
	 *                                      theme mod or option name.
176
	 * @param array                $args    Setting arguments.
177
	 */
178
	public function __construct( $manager, $id, $args = array() ) {
179
		$keys = array_keys( get_object_vars( $this ) );
180
		foreach ( $keys as $key ) {
181
			if ( isset( $args[ $key ] ) ) {
182
				$this->$key = $args[ $key ];
183
			}
184
		}
185
186
		$this->manager = $manager;
187
		$this->id = $id;
188
189
		// Parse the ID for array keys.
190
		$this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
191
		$this->id_data['base'] = array_shift( $this->id_data['keys'] );
192
193
		// Rebuild the ID.
194
		$this->id = $this->id_data[ 'base' ];
195
		if ( ! empty( $this->id_data[ 'keys' ] ) ) {
196
			$this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
197
		}
198
199
		if ( $this->validate_callback ) {
200
			add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 );
201
		}
202
		if ( $this->sanitize_callback ) {
203
			add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
204
		}
205
		if ( $this->sanitize_js_callback ) {
206
			add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
207
		}
208
209
		if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
210
			// Other setting types can opt-in to aggregate multidimensional explicitly.
211
			$this->aggregate_multidimensional();
212
213
			// Allow option settings to indicate whether they should be autoloaded.
214
			if ( 'option' === $this->type && isset( $args['autoload'] ) ) {
215
				self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] = $args['autoload'];
216
			}
217
		}
218
	}
219
220
	/**
221
	 * Get parsed ID data for multidimensional setting.
222
	 *
223
	 * @since 4.4.0
224
	 *
225
	 * @return array {
226
	 *     ID data for multidimensional setting.
227
	 *
228
	 *     @type string $base ID base
229
	 *     @type array  $keys Keys for multidimensional array.
230
	 * }
231
	 */
232
	final public function id_data() {
233
		return $this->id_data;
234
	}
235
236
	/**
237
	 * Set up the setting for aggregated multidimensional values.
238
	 *
239
	 * When a multidimensional setting gets aggregated, all of its preview and update
240
	 * calls get combined into one call, greatly improving performance.
241
	 *
242
	 * @since 4.4.0
243
	 */
244
	protected function aggregate_multidimensional() {
245
		$id_base = $this->id_data['base'];
246
		if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) {
247
			self::$aggregated_multidimensionals[ $this->type ] = array();
248
		}
249
		if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) {
250
			self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array(
251
				'previewed_instances'       => array(), // Calling preview() will add the $setting to the array.
252
				'preview_applied_instances' => array(), // Flags for which settings have had their values applied.
253
				'root_value'                => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls.
254
			);
255
		}
256
257
		if ( ! empty( $this->id_data['keys'] ) ) {
258
			// Note the preview-applied flag is cleared at priority 9 to ensure it is cleared before a deferred-preview runs.
259
			add_action( "customize_post_value_set_{$this->id}", array( $this, '_clear_aggregated_multidimensional_preview_applied_flag' ), 9 );
260
			$this->is_multidimensional_aggregated = true;
261
		}
262
	}
263
264
	/**
265
	 * Reset `$aggregated_multidimensionals` static variable.
266
	 *
267
	 * This is intended only for use by unit tests.
268
	 *
269
	 * @since 4.5.0
270
	 * @ignore
271
	 */
272
	static public function reset_aggregated_multidimensionals() {
273
		self::$aggregated_multidimensionals = array();
274
	}
275
276
	/**
277
	 * The ID for the current site when the preview() method was called.
278
	 *
279
	 * @since 4.2.0
280
	 * @access protected
281
	 * @var int
282
	 */
283
	protected $_previewed_blog_id;
284
285
	/**
286
	 * Return true if the current site is not the same as the previewed site.
287
	 *
288
	 * @since 4.2.0
289
	 *
290
	 * @return bool If preview() has been called.
291
	 */
292
	public function is_current_blog_previewed() {
293
		if ( ! isset( $this->_previewed_blog_id ) ) {
294
			return false;
295
		}
296
		return ( get_current_blog_id() === $this->_previewed_blog_id );
297
	}
298
299
	/**
300
	 * Original non-previewed value stored by the preview method.
301
	 *
302
	 * @see WP_Customize_Setting::preview()
303
	 * @since 4.1.1
304
	 * @access protected
305
	 * @var mixed
306
	 */
307
	protected $_original_value;
308
309
	/**
310
	 * Add filters to supply the setting's value when accessed.
311
	 *
312
	 * If the setting already has a pre-existing value and there is no incoming
313
	 * post value for the setting, then this method will short-circuit since
314
	 * there is no change to preview.
315
	 *
316
	 * @since 3.4.0
317
	 * @since 4.4.0 Added boolean return value.
318
	 *
319
	 * @return bool False when preview short-circuits due no change needing to be previewed.
320
	 */
321
	public function preview() {
322
		if ( ! isset( $this->_previewed_blog_id ) ) {
323
			$this->_previewed_blog_id = get_current_blog_id();
0 ignored issues
show
Documentation Bug introduced by
It seems like get_current_blog_id() can also be of type double. However, the property $_previewed_blog_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
324
		}
325
326
		// Prevent re-previewing an already-previewed setting.
327
		if ( $this->is_previewed ) {
328
			return true;
329
		}
330
331
		$id_base = $this->id_data['base'];
332
		$is_multidimensional = ! empty( $this->id_data['keys'] );
333
		$multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
334
335
		/*
336
		 * Check if the setting has a pre-existing value (an isset check),
337
		 * and if doesn't have any incoming post value. If both checks are true,
338
		 * then the preview short-circuits because there is nothing that needs
339
		 * to be previewed.
340
		 */
341
		$undefined = new stdClass();
342
		$needs_preview = ( $undefined !== $this->post_value( $undefined ) );
343
		$value = null;
344
345
		// Since no post value was defined, check if we have an initial value set.
346
		if ( ! $needs_preview ) {
347
			if ( $this->is_multidimensional_aggregated ) {
348
				$root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
349
				$value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
350
			} else {
351
				$default = $this->default;
352
				$this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
0 ignored issues
show
Documentation Bug introduced by
It seems like $undefined of type object<stdClass> is incompatible with the declared type string of property $default.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
353
				$value = $this->value();
354
				$this->default = $default;
355
			}
356
			$needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
357
		}
358
359
		// If the setting does not need previewing now, defer to when it has a value to preview.
360
		if ( ! $needs_preview ) {
361
			if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) {
362
				add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) );
363
			}
364
			return false;
365
		}
366
367
		switch ( $this->type ) {
368 View Code Duplication
			case 'theme_mod' :
369
				if ( ! $is_multidimensional ) {
370
					add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
371
				} else {
372
					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
373
						// Only add this filter once for this ID base.
374
						add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
375
					}
376
					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
377
				}
378
				break;
379 View Code Duplication
			case 'option' :
380
				if ( ! $is_multidimensional ) {
381
					add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
382
				} else {
383
					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
384
						// Only add these filters once for this ID base.
385
						add_filter( "option_{$id_base}", $multidimensional_filter );
386
						add_filter( "default_option_{$id_base}", $multidimensional_filter );
387
					}
388
					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
389
				}
390
				break;
391
			default :
392
393
				/**
394
				 * Fires when the WP_Customize_Setting::preview() method is called for settings
395
				 * not handled as theme_mods or options.
396
				 *
397
				 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
398
				 *
399
				 * @since 3.4.0
400
				 *
401
				 * @param WP_Customize_Setting $this WP_Customize_Setting instance.
402
				 */
403
				do_action( "customize_preview_{$this->id}", $this );
404
405
				/**
406
				 * Fires when the WP_Customize_Setting::preview() method is called for settings
407
				 * not handled as theme_mods or options.
408
				 *
409
				 * The dynamic portion of the hook name, `$this->type`, refers to the setting type.
410
				 *
411
				 * @since 4.1.0
412
				 *
413
				 * @param WP_Customize_Setting $this WP_Customize_Setting instance.
414
				 */
415
				do_action( "customize_preview_{$this->type}", $this );
416
		}
417
418
		$this->is_previewed = true;
419
420
		return true;
421
	}
422
423
	/**
424
	 * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated.
425
	 *
426
	 * This ensures that the new value will get sanitized and used the next time
427
	 * that `WP_Customize_Setting::_multidimensional_preview_filter()`
428
	 * is called for this setting.
429
	 *
430
	 * @since 4.4.0
431
	 *
432
	 * @see WP_Customize_Manager::set_post_value()
433
	 * @see WP_Customize_Setting::_multidimensional_preview_filter()
434
	 */
435
	final public function _clear_aggregated_multidimensional_preview_applied_flag() {
436
		unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] );
437
	}
438
439
	/**
440
	 * Callback function to filter non-multidimensional theme mods and options.
441
	 *
442
	 * If switch_to_blog() was called after the preview() method, and the current
443
	 * site is now not the same site, then this method does a no-op and returns
444
	 * the original value.
445
	 *
446
	 * @since 3.4.0
447
	 *
448
	 * @param mixed $original Old value.
449
	 * @return mixed New or old value.
450
	 */
451
	public function _preview_filter( $original ) {
452
		if ( ! $this->is_current_blog_previewed() ) {
453
			return $original;
454
		}
455
456
		$undefined = new stdClass(); // Symbol hack.
457
		$post_value = $this->post_value( $undefined );
458
		if ( $undefined !== $post_value ) {
459
			$value = $post_value;
460
		} else {
461
			/*
462
			 * Note that we don't use $original here because preview() will
463
			 * not add the filter in the first place if it has an initial value
464
			 * and there is no post value.
465
			 */
466
			$value = $this->default;
467
		}
468
		return $value;
469
	}
470
471
	/**
472
	 * Callback function to filter multidimensional theme mods and options.
473
	 *
474
	 * For all multidimensional settings of a given type, the preview filter for
475
	 * the first setting previewed will be used to apply the values for the others.
476
	 *
477
	 * @since 4.4.0
478
	 *
479
	 * @see WP_Customize_Setting::$aggregated_multidimensionals
480
	 * @param mixed $original Original root value.
481
	 * @return mixed New or old value.
482
	 */
483
	final public function _multidimensional_preview_filter( $original ) {
484
		if ( ! $this->is_current_blog_previewed() ) {
485
			return $original;
486
		}
487
488
		$id_base = $this->id_data['base'];
489
490
		// If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value.
491
		if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
492
			return $original;
493
		}
494
495
		foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) {
496
			// Skip applying previewed value for any settings that have already been applied.
497
			if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) {
498
				continue;
499
			}
500
501
			// Do the replacements of the posted/default sub value into the root value.
502
			$value = $previewed_setting->post_value( $previewed_setting->default );
503
			$root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
504
			$root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
505
			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
506
507
			// Mark this setting having been applied so that it will be skipped when the filter is called again.
508
			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
509
		}
510
511
		return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
512
	}
513
514
	/**
515
	 * Checks user capabilities and theme supports, and then saves
516
	 * the value of the setting.
517
	 *
518
	 * @since 3.4.0
519
	 *
520
	 * @return false|void False if cap check fails or value isn't set or is invalid.
521
	 */
522
	final public function save() {
523
		$value = $this->post_value();
524
525
		if ( ! $this->check_capabilities() || ! isset( $value ) ) {
526
			return false;
527
		}
528
529
		$id_base = $this->id_data['base'];
530
531
		/**
532
		 * Fires when the WP_Customize_Setting::save() method is called.
533
		 *
534
		 * The dynamic portion of the hook name, `$id_base` refers to
535
		 * the base slug of the setting name.
536
		 *
537
		 * @since 3.4.0
538
		 *
539
		 * @param WP_Customize_Setting $this WP_Customize_Setting instance.
540
		 */
541
		do_action( "customize_save_{$id_base}", $this );
542
543
		$this->update( $value );
544
	}
545
546
	/**
547
	 * Fetch and sanitize the $_POST value for the setting.
548
	 *
549
	 * During a save request prior to save, post_value() provides the new value while value() does not.
550
	 *
551
	 * @since 3.4.0
552
	 *
553
	 * @param mixed $default A default value which is used as a fallback. Default is null.
554
	 * @return mixed The default value on failure, otherwise the sanitized and validated value.
555
	 */
556
	final public function post_value( $default = null ) {
557
		return $this->manager->post_value( $this, $default );
558
	}
559
560
	/**
561
	 * Sanitize an input.
562
	 *
563
	 * @since 3.4.0
564
	 *
565
	 * @param string|array $value    The value to sanitize.
566
	 * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid.
567
	 */
568
	public function sanitize( $value ) {
569
570
		/**
571
		 * Filters a Customize setting value in un-slashed form.
572
		 *
573
		 * @since 3.4.0
574
		 *
575
		 * @param mixed                $value Value of the setting.
576
		 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
577
		 */
578
		return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
579
	}
580
581
	/**
582
	 * Validates an input.
583
	 *
584
	 * @since 4.6.0
585
	 *
586
	 * @see WP_REST_Request::has_valid_params()
587
	 *
588
	 * @param mixed $value Value to validate.
589
	 * @return true|WP_Error True if the input was validated, otherwise WP_Error.
590
	 */
591
	public function validate( $value ) {
592
		if ( is_wp_error( $value ) ) {
593
			return $value;
594
		}
595
		if ( is_null( $value ) ) {
596
			return new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
597
		}
598
599
		$validity = new WP_Error();
600
601
		/**
602
		 * Validates a Customize setting value.
603
		 *
604
		 * Plugins should amend the `$validity` object via its `WP_Error::add()` method.
605
		 *
606
		 * The dynamic portion of the hook name, `$this->ID`, refers to the setting ID.
607
		 *
608
		 * @since 4.6.0
609
		 *
610
		 * @param WP_Error             $validity Filtered from `true` to `WP_Error` when invalid.
611
		 * @param mixed                $value    Value of the setting.
612
		 * @param WP_Customize_Setting $this     WP_Customize_Setting instance.
613
		 */
614
		$validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this );
615
616
		if ( is_wp_error( $validity ) && empty( $validity->errors ) ) {
617
			$validity = true;
618
		}
619
		return $validity;
620
	}
621
622
	/**
623
	 * Get the root value for a setting, especially for multidimensional ones.
624
	 *
625
	 * @since 4.4.0
626
	 *
627
	 * @param mixed $default Value to return if root does not exist.
628
	 * @return mixed
629
	 */
630
	protected function get_root_value( $default = null ) {
631
		$id_base = $this->id_data['base'];
632
		if ( 'option' === $this->type ) {
633
			return get_option( $id_base, $default );
634
		} elseif ( 'theme_mod' === $this->type ) {
635
			return get_theme_mod( $id_base, $default );
636
		} else {
637
			/*
638
			 * Any WP_Customize_Setting subclass implementing aggregate multidimensional
639
			 * will need to override this method to obtain the data from the appropriate
640
			 * location.
641
			 */
642
			return $default;
643
		}
644
	}
645
646
	/**
647
	 * Set the root value for a setting, especially for multidimensional ones.
648
	 *
649
	 * @since 4.4.0
650
	 *
651
	 * @param mixed $value Value to set as root of multidimensional setting.
652
	 * @return bool Whether the multidimensional root was updated successfully.
653
	 */
654
	protected function set_root_value( $value ) {
655
		$id_base = $this->id_data['base'];
656
		if ( 'option' === $this->type ) {
657
			$autoload = true;
658
			if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) {
659
				$autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'];
660
			}
661
			return update_option( $id_base, $value, $autoload );
662
		} elseif ( 'theme_mod' === $this->type ) {
663
			set_theme_mod( $id_base, $value );
664
			return true;
665
		} else {
666
			/*
667
			 * Any WP_Customize_Setting subclass implementing aggregate multidimensional
668
			 * will need to override this method to obtain the data from the appropriate
669
			 * location.
670
			 */
671
			return false;
672
		}
673
	}
674
675
	/**
676
	 * Save the value of the setting, using the related API.
677
	 *
678
	 * @since 3.4.0
679
	 *
680
	 * @param mixed $value The value to update.
681
	 * @return bool The result of saving the value.
0 ignored issues
show
Should the return type not be boolean|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
682
	 */
683
	protected function update( $value ) {
684
		$id_base = $this->id_data['base'];
685
		if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
686
			if ( ! $this->is_multidimensional_aggregated ) {
687
				return $this->set_root_value( $value );
688
			} else {
689
				$root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
690
				$root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value );
691
				self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root;
692
				return $this->set_root_value( $root );
693
			}
694
		} else {
695
			/**
696
			 * Fires when the WP_Customize_Setting::update() method is called for settings
697
			 * not handled as theme_mods or options.
698
			 *
699
			 * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
700
			 *
701
			 * @since 3.4.0
702
			 *
703
			 * @param mixed                $value Value of the setting.
704
			 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
705
			 */
706
			do_action( "customize_update_{$this->type}", $value, $this );
707
708
			return has_action( "customize_update_{$this->type}" );
709
		}
710
	}
711
712
	/**
713
	 * Deprecated method.
714
	 *
715
	 * @since 3.4.0
716
	 * @deprecated 4.4.0 Deprecated in favor of update() method.
717
	 */
718
	protected function _update_theme_mod() {
719
		_deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
720
	}
721
722
	/**
723
	 * Deprecated method.
724
	 *
725
	 * @since 3.4.0
726
	 * @deprecated 4.4.0 Deprecated in favor of update() method.
727
	 */
728
	protected function _update_option() {
729
		_deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
730
	}
731
732
	/**
733
	 * Fetch the value of the setting.
734
	 *
735
	 * @since 3.4.0
736
	 *
737
	 * @return mixed The value.
738
	 */
739
	public function value() {
740
		$id_base = $this->id_data['base'];
741
		$is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
742
743
		if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
744
745
			// Use post value if previewed and a post value is present.
746 View Code Duplication
			if ( $this->is_previewed ) {
747
				$value = $this->post_value( null );
748
				if ( null !== $value ) {
749
					return $value;
750
				}
751
			}
752
753
			$value = $this->get_root_value( $this->default );
754
755
			/**
756
			 * Filters a Customize setting value not handled as a theme_mod or option.
757
			 *
758
			 * The dynamic portion of the hook name, `$id_base`, refers to
759
			 * the base slug of the setting name, initialized from `$this->id_data['base']`.
760
			 *
761
			 * For settings handled as theme_mods or options, see those corresponding
762
			 * functions for available hooks.
763
			 *
764
			 * @since 3.4.0
765
			 * @since 4.6.0 Added the `$this` setting instance as the second parameter.
766
			 *
767
			 * @param mixed                $default The setting default value. Default empty.
768
			 * @param WP_Customize_Setting $this    The setting instance.
769
			 */
770
			$value = apply_filters( "customize_value_{$id_base}", $value, $this );
771
		} elseif ( $this->is_multidimensional_aggregated ) {
772
			$root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
773
			$value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
774
775
			// Ensure that the post value is used if the setting is previewed, since preview filters aren't applying on cached $root_value.
776
			if ( $this->is_previewed ) {
777
				$value = $this->post_value( $value );
778
			}
779
		} else {
780
			$value = $this->get_root_value( $this->default );
781
		}
782
		return $value;
783
	}
784
785
	/**
786
	 * Sanitize the setting's value for use in JavaScript.
787
	 *
788
	 * @since 3.4.0
789
	 *
790
	 * @return mixed The requested escaped value.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use string|object|integer|double|null|array|boolean.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
791
	 */
792
	public function js_value() {
793
794
		/**
795
		 * Filters a Customize setting value for use in JavaScript.
796
		 *
797
		 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
798
		 *
799
		 * @since 3.4.0
800
		 *
801
		 * @param mixed                $value The setting value.
802
		 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
803
		 */
804
		$value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
805
806
		if ( is_string( $value ) )
807
			return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
808
809
		return $value;
810
	}
811
812
	/**
813
	 * Retrieves the data to export to the client via JSON.
814
	 *
815
	 * @since 4.6.0
816
	 *
817
	 * @return array Array of parameters passed to JavaScript.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<string,string|obje...ble|null|array|boolean>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
818
	 */
819
	public function json() {
820
		return array(
821
			'value'     => $this->js_value(),
822
			'transport' => $this->transport,
823
			'dirty'     => $this->dirty,
824
			'type'      => $this->type,
825
		);
826
	}
827
828
	/**
829
	 * Validate user capabilities whether the theme supports the setting.
830
	 *
831
	 * @since 3.4.0
832
	 *
833
	 * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
834
	 */
835 View Code Duplication
	final public function check_capabilities() {
836
		if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
837
			return false;
838
839
		if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
0 ignored issues
show
This if statement, and the following return statement can be replaced with return !($this->theme_su...this->theme_supports));.
Loading history...
840
			return false;
841
842
		return true;
843
	}
844
845
	/**
846
	 * Multidimensional helper function.
847
	 *
848
	 * @since 3.4.0
849
	 *
850
	 * @param $root
851
	 * @param $keys
852
	 * @param bool $create Default is false.
853
	 * @return array|void Keys are 'root', 'node', and 'key'.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
854
	 */
855
	final protected function multidimensional( &$root, $keys, $create = false ) {
856
		if ( $create && empty( $root ) )
857
			$root = array();
858
859
		if ( ! isset( $root ) || empty( $keys ) )
860
			return;
861
862
		$last = array_pop( $keys );
863
		$node = &$root;
864
865
		foreach ( $keys as $key ) {
866
			if ( $create && ! isset( $node[ $key ] ) )
867
				$node[ $key ] = array();
868
869
			if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
870
				return;
871
872
			$node = &$node[ $key ];
873
		}
874
875 View Code Duplication
		if ( $create ) {
876
			if ( ! is_array( $node ) ) {
877
				// account for an array overriding a string or object value
878
				$node = array();
879
			}
880
			if ( ! isset( $node[ $last ] ) ) {
881
				$node[ $last ] = array();
882
			}
883
		}
884
885
		if ( ! isset( $node[ $last ] ) )
886
			return;
887
888
		return array(
889
			'root' => &$root,
890
			'node' => &$node,
891
			'key'  => $last,
892
		);
893
	}
894
895
	/**
896
	 * Will attempt to replace a specific value in a multidimensional array.
897
	 *
898
	 * @since 3.4.0
899
	 *
900
	 * @param $root
901
	 * @param $keys
902
	 * @param mixed $value The value to update.
903
	 * @return mixed
904
	 */
905
	final protected function multidimensional_replace( $root, $keys, $value ) {
906
		if ( ! isset( $value ) )
907
			return $root;
908
		elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
909
			return $value;
910
911
		$result = $this->multidimensional( $root, $keys, true );
912
913
		if ( isset( $result ) )
914
			$result['node'][ $result['key'] ] = $value;
915
916
		return $root;
917
	}
918
919
	/**
920
	 * Will attempt to fetch a specific value from a multidimensional array.
921
	 *
922
	 * @since 3.4.0
923
	 *
924
	 * @param $root
925
	 * @param $keys
926
	 * @param mixed $default A default value which is used as a fallback. Default is null.
927
	 * @return mixed The requested value or the default value.
928
	 */
929
	final protected function multidimensional_get( $root, $keys, $default = null ) {
930
		if ( empty( $keys ) ) // If there are no keys, test the root.
931
			return isset( $root ) ? $root : $default;
932
933
		$result = $this->multidimensional( $root, $keys );
934
		return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
935
	}
936
937
	/**
938
	 * Will attempt to check if a specific value in a multidimensional array is set.
939
	 *
940
	 * @since 3.4.0
941
	 *
942
	 * @param $root
943
	 * @param $keys
944
	 * @return bool True if value is set, false if not.
945
	 */
946
	final protected function multidimensional_isset( $root, $keys ) {
947
		$result = $this->multidimensional_get( $root, $keys );
948
		return isset( $result );
949
	}
950
}
951
952
/**
953
 * WP_Customize_Filter_Setting class.
954
 */
955
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
956
957
/**
958
 * WP_Customize_Header_Image_Setting class.
959
 */
960
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
961
962
/**
963
 * WP_Customize_Background_Image_Setting class.
964
 */
965
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
966
967
/**
968
 * WP_Customize_Nav_Menu_Item_Setting class.
969
 */
970
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
971
972
/**
973
 * WP_Customize_Nav_Menu_Setting class.
974
 */
975
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
976