Issues (2010)

Security Analysis    not enabled

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

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

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

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
295
296
		// Since no post value was defined, check if we have an initial value set.
297
		if ( ! $needs_preview ) {
298
			if ( $this->is_multidimensional_aggregated ) {
299
				$root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
300
				$value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
301
			} else {
302
				$default = $this->default;
303
				$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...
304
				$value = $this->value();
305
				$this->default = $default;
306
			}
307
			$needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
308
		}
309
310
		// If the setting does not need previewing now, defer to when it has a value to preview.
311
		if ( ! $needs_preview ) {
312
			if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) {
313
				add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) );
314
			}
315
			return false;
316
		}
317
318
		switch ( $this->type ) {
319 View Code Duplication
			case 'theme_mod' :
320
				if ( ! $is_multidimensional ) {
321
					add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
322
				} else {
323
					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
324
						// Only add this filter once for this ID base.
325
						add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
326
					}
327
					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
328
				}
329
				break;
330 View Code Duplication
			case 'option' :
331
				if ( ! $is_multidimensional ) {
332
					add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
333
				} else {
334
					if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
335
						// Only add these filters once for this ID base.
336
						add_filter( "option_{$id_base}", $multidimensional_filter );
337
						add_filter( "default_option_{$id_base}", $multidimensional_filter );
338
					}
339
					self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
340
				}
341
				break;
342
			default :
343
344
				/**
345
				 * Fires when the WP_Customize_Setting::preview() method is called for settings
346
				 * not handled as theme_mods or options.
347
				 *
348
				 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
349
				 *
350
				 * @since 3.4.0
351
				 *
352
				 * @param WP_Customize_Setting $this WP_Customize_Setting instance.
353
				 */
354
				do_action( "customize_preview_{$this->id}", $this );
355
356
				/**
357
				 * Fires when the WP_Customize_Setting::preview() method is called for settings
358
				 * not handled as theme_mods or options.
359
				 *
360
				 * The dynamic portion of the hook name, `$this->type`, refers to the setting type.
361
				 *
362
				 * @since 4.1.0
363
				 *
364
				 * @param WP_Customize_Setting $this WP_Customize_Setting instance.
365
				 */
366
				do_action( "customize_preview_{$this->type}", $this );
367
		}
368
369
		$this->is_previewed = true;
370
371
		return true;
372
	}
373
374
	/**
375
	 * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated.
376
	 *
377
	 * This ensures that the new value will get sanitized and used the next time
378
	 * that `WP_Customize_Setting::_multidimensional_preview_filter()`
379
	 * is called for this setting.
380
	 *
381
	 * @since 4.4.0
382
	 * @access private
383
	 * @see WP_Customize_Manager::set_post_value()
384
	 * @see WP_Customize_Setting::_multidimensional_preview_filter()
385
	 */
386
	final public function _clear_aggregated_multidimensional_preview_applied_flag() {
387
		unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] );
388
	}
389
390
	/**
391
	 * Callback function to filter non-multidimensional theme mods and options.
392
	 *
393
	 * If switch_to_blog() was called after the preview() method, and the current
394
	 * site is now not the same site, then this method does a no-op and returns
395
	 * the original value.
396
	 *
397
	 * @since 3.4.0
398
	 *
399
	 * @param mixed $original Old value.
400
	 * @return mixed New or old value.
401
	 */
402
	public function _preview_filter( $original ) {
403
		if ( ! $this->is_current_blog_previewed() ) {
404
			return $original;
405
		}
406
407
		$undefined = new stdClass(); // Symbol hack.
408
		$post_value = $this->post_value( $undefined );
409
		if ( $undefined !== $post_value ) {
410
			$value = $post_value;
411
		} else {
412
			/*
413
			 * Note that we don't use $original here because preview() will
414
			 * not add the filter in the first place if it has an initial value
415
			 * and there is no post value.
416
			 */
417
			$value = $this->default;
418
		}
419
		return $value;
420
	}
421
422
	/**
423
	 * Callback function to filter multidimensional theme mods and options.
424
	 *
425
	 * For all multidimensional settings of a given type, the preview filter for
426
	 * the first setting previewed will be used to apply the values for the others.
427
	 *
428
	 * @since 4.4.0
429
	 * @access private
430
	 *
431
	 * @see WP_Customize_Setting::$aggregated_multidimensionals
432
	 * @param mixed $original Original root value.
433
	 * @return mixed New or old value.
434
	 */
435
	final public function _multidimensional_preview_filter( $original ) {
436
		if ( ! $this->is_current_blog_previewed() ) {
437
			return $original;
438
		}
439
440
		$id_base = $this->id_data['base'];
441
442
		// If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value.
443
		if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
444
			return $original;
445
		}
446
447
		foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) {
448
			// Skip applying previewed value for any settings that have already been applied.
449
			if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) {
450
				continue;
451
			}
452
453
			// Do the replacements of the posted/default sub value into the root value.
454
			$value = $previewed_setting->post_value( $previewed_setting->default );
455
			$root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
456
			$root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
457
			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
458
459
			// Mark this setting having been applied so that it will be skipped when the filter is called again.
460
			self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
461
		}
462
463
		return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
464
	}
465
466
	/**
467
	 * Checks user capabilities and theme supports, and then saves
468
	 * the value of the setting.
469
	 *
470
	 * @since 3.4.0
471
	 *
472
	 * @access public
473
	 *
474
	 * @return false|void False if cap check fails or value isn't set or is invalid.
475
	 */
476
	final public function save() {
477
		$value = $this->post_value();
478
479
		if ( ! $this->check_capabilities() || ! isset( $value ) ) {
480
			return false;
481
		}
482
483
		/**
484
		 * Fires when the WP_Customize_Setting::save() method is called.
485
		 *
486
		 * The dynamic portion of the hook name, `$this->id_data['base']` refers to
487
		 * the base slug of the setting name.
488
		 *
489
		 * @since 3.4.0
490
		 *
491
		 * @param WP_Customize_Setting $this WP_Customize_Setting instance.
492
		 */
493
		do_action( 'customize_save_' . $this->id_data['base'], $this );
494
495
		$this->update( $value );
496
	}
497
498
	/**
499
	 * Fetch and sanitize the $_POST value for the setting.
500
	 *
501
	 * @since 3.4.0
502
	 *
503
	 * @param mixed $default A default value which is used as a fallback. Default is null.
504
	 * @return mixed The default value on failure, otherwise the sanitized and validated value.
505
	 */
506
	final public function post_value( $default = null ) {
507
		return $this->manager->post_value( $this, $default );
508
	}
509
510
	/**
511
	 * Sanitize an input.
512
	 *
513
	 * @since 3.4.0
514
	 *
515
	 * @param string|array $value    The value to sanitize.
516
	 * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid.
517
	 */
518
	public function sanitize( $value ) {
519
520
		/**
521
		 * Filters a Customize setting value in un-slashed form.
522
		 *
523
		 * @since 3.4.0
524
		 *
525
		 * @param mixed                $value Value of the setting.
526
		 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
527
		 */
528
		return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
529
	}
530
531
	/**
532
	 * Validates an input.
533
	 *
534
	 * @since 4.6.0
535
	 * @access public
536
	 *
537
	 * @see WP_REST_Request::has_valid_params()
538
	 *
539
	 * @param mixed $value Value to validate.
540
	 * @return true|WP_Error True if the input was validated, otherwise WP_Error.
541
	 */
542
	public function validate( $value ) {
543
		if ( is_wp_error( $value ) ) {
544
			return $value;
545
		}
546
		if ( is_null( $value ) ) {
547
			return new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
548
		}
549
550
		$validity = new WP_Error();
551
552
		/**
553
		 * Validates a Customize setting value.
554
		 *
555
		 * Plugins should amend the `$validity` object via its `WP_Error::add()` method.
556
		 *
557
		 * The dynamic portion of the hook name, `$this->ID`, refers to the setting ID.
558
		 *
559
		 * @since 4.6.0
560
		 *
561
		 * @param WP_Error             $validity Filtered from `true` to `WP_Error` when invalid.
562
		 * @param mixed                $value    Value of the setting.
563
		 * @param WP_Customize_Setting $this     WP_Customize_Setting instance.
564
		 */
565
		$validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this );
566
567
		if ( is_wp_error( $validity ) && empty( $validity->errors ) ) {
568
			$validity = true;
569
		}
570
		return $validity;
571
	}
572
573
	/**
574
	 * Get the root value for a setting, especially for multidimensional ones.
575
	 *
576
	 * @since 4.4.0
577
	 * @access protected
578
	 *
579
	 * @param mixed $default Value to return if root does not exist.
580
	 * @return mixed
581
	 */
582
	protected function get_root_value( $default = null ) {
583
		$id_base = $this->id_data['base'];
584
		if ( 'option' === $this->type ) {
585
			return get_option( $id_base, $default );
586
		} else if ( 'theme_mod' ) {
587
			return get_theme_mod( $id_base, $default );
588
		} else {
589
			/*
590
			 * Any WP_Customize_Setting subclass implementing aggregate multidimensional
591
			 * will need to override this method to obtain the data from the appropriate
592
			 * location.
593
			 */
594
			return $default;
595
		}
596
	}
597
598
	/**
599
	 * Set the root value for a setting, especially for multidimensional ones.
600
	 *
601
	 * @since 4.4.0
602
	 * @access protected
603
	 *
604
	 * @param mixed $value Value to set as root of multidimensional setting.
605
	 * @return bool Whether the multidimensional root was updated successfully.
606
	 */
607
	protected function set_root_value( $value ) {
608
		$id_base = $this->id_data['base'];
609
		if ( 'option' === $this->type ) {
610
			$autoload = true;
611
			if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) {
612
				$autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'];
613
			}
614
			return update_option( $id_base, $value, $autoload );
615
		} else if ( 'theme_mod' ) {
616
			set_theme_mod( $id_base, $value );
617
			return true;
618
		} else {
619
			/*
620
			 * Any WP_Customize_Setting subclass implementing aggregate multidimensional
621
			 * will need to override this method to obtain the data from the appropriate
622
			 * location.
623
			 */
624
			return false;
625
		}
626
	}
627
628
	/**
629
	 * Save the value of the setting, using the related API.
630
	 *
631
	 * @since 3.4.0
632
	 *
633
	 * @param mixed $value The value to update.
634
	 * @return bool The result of saving the value.
635
	 */
636
	protected function update( $value ) {
637
		$id_base = $this->id_data['base'];
638
		if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
639
			if ( ! $this->is_multidimensional_aggregated ) {
640
				return $this->set_root_value( $value );
641
			} else {
642
				$root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
643
				$root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value );
644
				self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root;
645
				return $this->set_root_value( $root );
646
			}
647
		} else {
648
			/**
649
			 * Fires when the WP_Customize_Setting::update() method is called for settings
650
			 * not handled as theme_mods or options.
651
			 *
652
			 * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
653
			 *
654
			 * @since 3.4.0
655
			 *
656
			 * @param mixed                $value Value of the setting.
657
			 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
658
			 */
659
			do_action( "customize_update_{$this->type}", $value, $this );
660
661
			return has_action( "customize_update_{$this->type}" );
662
		}
663
	}
664
665
	/**
666
	 * Deprecated method.
667
	 *
668
	 * @since 3.4.0
669
	 * @deprecated 4.4.0 Deprecated in favor of update() method.
670
	 */
671
	protected function _update_theme_mod() {
672
		_deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
673
	}
674
675
	/**
676
	 * Deprecated method.
677
	 *
678
	 * @since 3.4.0
679
	 * @deprecated 4.4.0 Deprecated in favor of update() method.
680
	 */
681
	protected function _update_option() {
682
		_deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
683
	}
684
685
	/**
686
	 * Fetch the value of the setting.
687
	 *
688
	 * @since 3.4.0
689
	 *
690
	 * @return mixed The value.
691
	 */
692
	public function value() {
693
		$id_base = $this->id_data['base'];
694
		$is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
695
696
		if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
697
			$value = $this->get_root_value( $this->default );
698
699
			/**
700
			 * Filters a Customize setting value not handled as a theme_mod or option.
701
			 *
702
			 * The dynamic portion of the hook name, `$id_base`, refers to
703
			 * the base slug of the setting name, initialized from `$this->id_data['base']`.
704
			 *
705
			 * For settings handled as theme_mods or options, see those corresponding
706
			 * functions for available hooks.
707
			 *
708
			 * @since 3.4.0
709
			 * @since 4.6.0 Added the `$this` setting instance as the second parameter.
710
			 *
711
			 * @param mixed                $default The setting default value. Default empty.
712
			 * @param WP_Customize_Setting $this    The setting instance.
713
			 */
714
			$value = apply_filters( "customize_value_{$id_base}", $value, $this );
715
		} elseif ( $this->is_multidimensional_aggregated ) {
716
			$root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
717
			$value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
718
719
			// Ensure that the post value is used if the setting is previewed, since preview filters aren't applying on cached $root_value.
720
			if ( $this->is_previewed ) {
721
				$value = $this->post_value( $value );
722
			}
723
		} else {
724
			$value = $this->get_root_value( $this->default );
725
		}
726
		return $value;
727
	}
728
729
	/**
730
	 * Sanitize the setting's value for use in JavaScript.
731
	 *
732
	 * @since 3.4.0
733
	 *
734
	 * @return mixed The requested escaped value.
735
	 */
736
	public function js_value() {
737
738
		/**
739
		 * Filters a Customize setting value for use in JavaScript.
740
		 *
741
		 * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
742
		 *
743
		 * @since 3.4.0
744
		 *
745
		 * @param mixed                $value The setting value.
746
		 * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
747
		 */
748
		$value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
749
750
		if ( is_string( $value ) )
751
			return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
752
753
		return $value;
754
	}
755
756
	/**
757
	 * Retrieves the data to export to the client via JSON.
758
	 *
759
	 * @since 4.6.0
760
	 * @access public
761
	 *
762
	 * @return array Array of parameters passed to JavaScript.
763
	 */
764
	public function json() {
765
		return array(
766
			'value'     => $this->js_value(),
767
			'transport' => $this->transport,
768
			'dirty'     => $this->dirty,
769
			'type'      => $this->type,
770
		);
771
	}
772
773
	/**
774
	 * Validate user capabilities whether the theme supports the setting.
775
	 *
776
	 * @since 3.4.0
777
	 *
778
	 * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
779
	 */
780 View Code Duplication
	final public function check_capabilities() {
781
		if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
782
			return false;
783
784
		if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
785
			return false;
786
787
		return true;
788
	}
789
790
	/**
791
	 * Multidimensional helper function.
792
	 *
793
	 * @since 3.4.0
794
	 *
795
	 * @param $root
796
	 * @param $keys
797
	 * @param bool $create Default is false.
798
	 * @return array|void Keys are 'root', 'node', and 'key'.
799
	 */
800
	final protected function multidimensional( &$root, $keys, $create = false ) {
801
		if ( $create && empty( $root ) )
802
			$root = array();
803
804
		if ( ! isset( $root ) || empty( $keys ) )
805
			return;
806
807
		$last = array_pop( $keys );
808
		$node = &$root;
809
810
		foreach ( $keys as $key ) {
811
			if ( $create && ! isset( $node[ $key ] ) )
812
				$node[ $key ] = array();
813
814
			if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
815
				return;
816
817
			$node = &$node[ $key ];
818
		}
819
820 View Code Duplication
		if ( $create ) {
821
			if ( ! is_array( $node ) ) {
822
				// account for an array overriding a string or object value
823
				$node = array();
824
			}
825
			if ( ! isset( $node[ $last ] ) ) {
826
				$node[ $last ] = array();
827
			}
828
		}
829
830
		if ( ! isset( $node[ $last ] ) )
831
			return;
832
833
		return array(
834
			'root' => &$root,
835
			'node' => &$node,
836
			'key'  => $last,
837
		);
838
	}
839
840
	/**
841
	 * Will attempt to replace a specific value in a multidimensional array.
842
	 *
843
	 * @since 3.4.0
844
	 *
845
	 * @param $root
846
	 * @param $keys
847
	 * @param mixed $value The value to update.
848
	 * @return mixed
849
	 */
850
	final protected function multidimensional_replace( $root, $keys, $value ) {
851
		if ( ! isset( $value ) )
852
			return $root;
853
		elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
854
			return $value;
855
856
		$result = $this->multidimensional( $root, $keys, true );
857
858
		if ( isset( $result ) )
859
			$result['node'][ $result['key'] ] = $value;
860
861
		return $root;
862
	}
863
864
	/**
865
	 * Will attempt to fetch a specific value from a multidimensional array.
866
	 *
867
	 * @since 3.4.0
868
	 *
869
	 * @param $root
870
	 * @param $keys
871
	 * @param mixed $default A default value which is used as a fallback. Default is null.
872
	 * @return mixed The requested value or the default value.
873
	 */
874
	final protected function multidimensional_get( $root, $keys, $default = null ) {
875
		if ( empty( $keys ) ) // If there are no keys, test the root.
876
			return isset( $root ) ? $root : $default;
877
878
		$result = $this->multidimensional( $root, $keys );
879
		return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
880
	}
881
882
	/**
883
	 * Will attempt to check if a specific value in a multidimensional array is set.
884
	 *
885
	 * @since 3.4.0
886
	 *
887
	 * @param $root
888
	 * @param $keys
889
	 * @return bool True if value is set, false if not.
890
	 */
891
	final protected function multidimensional_isset( $root, $keys ) {
892
		$result = $this->multidimensional_get( $root, $keys );
893
		return isset( $result );
894
	}
895
}
896
897
/** WP_Customize_Filter_Setting class */
898
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
899
900
/** WP_Customize_Header_Image_Setting class */
901
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
902
903
/** WP_Customize_Background_Image_Setting class */
904
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
905
906
/** WP_Customize_Nav_Menu_Item_Setting class */
907
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
908
909
/** WP_Customize_Nav_Menu_Setting class */
910
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
911