WordPress 4.5
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-setting.php
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 $sanitize_callback    = '';
63         public $sanitize_js_callback = '';
64
65         /**
66          * Whether or not the setting is initially dirty when created.
67          *
68          * This is used to ensure that a setting will be sent from the pane to the
69          * preview when loading the Customizer. Normally a setting only is synced to
70          * the preview if it has been changed. This allows the setting to be sent
71          * from the start.
72          *
73          * @since 4.2.0
74          * @access public
75          * @var bool
76          */
77         public $dirty = false;
78
79         /**
80          * @var array
81          */
82         protected $id_data = array();
83
84         /**
85          * Whether or not preview() was called.
86          *
87          * @since 4.4.0
88          * @access protected
89          * @var bool
90          */
91         protected $is_previewed = false;
92
93         /**
94          * Cache of multidimensional values to improve performance.
95          *
96          * @since 4.4.0
97          * @access protected
98          * @var array
99          * @static
100          */
101         protected static $aggregated_multidimensionals = array();
102
103         /**
104          * Whether the multidimensional setting is aggregated.
105          *
106          * @since 4.4.0
107          * @access protected
108          * @var bool
109          */
110         protected $is_multidimensional_aggregated = false;
111
112         /**
113          * Constructor.
114          *
115          * Any supplied $args override class property defaults.
116          *
117          * @since 3.4.0
118          *
119          * @param WP_Customize_Manager $manager
120          * @param string               $id      An specific ID of the setting. Can be a
121          *                                      theme mod or option name.
122          * @param array                $args    Setting arguments.
123          */
124         public function __construct( $manager, $id, $args = array() ) {
125                 $keys = array_keys( get_object_vars( $this ) );
126                 foreach ( $keys as $key ) {
127                         if ( isset( $args[ $key ] ) ) {
128                                 $this->$key = $args[ $key ];
129                         }
130                 }
131
132                 $this->manager = $manager;
133                 $this->id = $id;
134
135                 // Parse the ID for array keys.
136                 $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
137                 $this->id_data['base'] = array_shift( $this->id_data['keys'] );
138
139                 // Rebuild the ID.
140                 $this->id = $this->id_data[ 'base' ];
141                 if ( ! empty( $this->id_data[ 'keys' ] ) ) {
142                         $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
143                 }
144
145                 if ( $this->sanitize_callback ) {
146                         add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
147                 }
148                 if ( $this->sanitize_js_callback ) {
149                         add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
150                 }
151
152                 if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
153                         // Other setting types can opt-in to aggregate multidimensional explicitly.
154                         $this->aggregate_multidimensional();
155
156                         // Allow option settings to indicate whether they should be autoloaded.
157                         if ( 'option' === $this->type && isset( $args['autoload'] ) ) {
158                                 self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] = $args['autoload'];
159                         }
160                 }
161         }
162
163         /**
164          * Get parsed ID data for multidimensional setting.
165          *
166          * @since 4.4.0
167          * @access public
168          *
169          * @return array {
170          *     ID data for multidimensional setting.
171          *
172          *     @type string $base ID base
173          *     @type array  $keys Keys for multidimensional array.
174          * }
175          */
176         final public function id_data() {
177                 return $this->id_data;
178         }
179
180         /**
181          * Set up the setting for aggregated multidimensional values.
182          *
183          * When a multidimensional setting gets aggregated, all of its preview and update
184          * calls get combined into one call, greatly improving performance.
185          *
186          * @since 4.4.0
187          * @access protected
188          */
189         protected function aggregate_multidimensional() {
190                 $id_base = $this->id_data['base'];
191                 if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) {
192                         self::$aggregated_multidimensionals[ $this->type ] = array();
193                 }
194                 if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) {
195                         self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array(
196                                 'previewed_instances'       => array(), // Calling preview() will add the $setting to the array.
197                                 'preview_applied_instances' => array(), // Flags for which settings have had their values applied.
198                                 'root_value'                => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls.
199                         );
200                 }
201
202                 if ( ! empty( $this->id_data['keys'] ) ) {
203                         // Note the preview-applied flag is cleared at priority 9 to ensure it is cleared before a deferred-preview runs.
204                         add_action( "customize_post_value_set_{$this->id}", array( $this, '_clear_aggregated_multidimensional_preview_applied_flag' ), 9 );
205                         $this->is_multidimensional_aggregated = true;
206                 }
207         }
208
209         /**
210          * Reset `$aggregated_multidimensionals` static variable.
211          *
212          * This is intended only for use by unit tests.
213          *
214          * @since 4.5.0
215          * @access public
216          * @ignore
217          */
218         static public function reset_aggregated_multidimensionals() {
219                 self::$aggregated_multidimensionals = array();
220         }
221
222         /**
223          * The ID for the current site when the preview() method was called.
224          *
225          * @since 4.2.0
226          * @access protected
227          * @var int
228          */
229         protected $_previewed_blog_id;
230
231         /**
232          * Return true if the current site is not the same as the previewed site.
233          *
234          * @since 4.2.0
235          * @access public
236          *
237          * @return bool If preview() has been called.
238          */
239         public function is_current_blog_previewed() {
240                 if ( ! isset( $this->_previewed_blog_id ) ) {
241                         return false;
242                 }
243                 return ( get_current_blog_id() === $this->_previewed_blog_id );
244         }
245
246         /**
247          * Original non-previewed value stored by the preview method.
248          *
249          * @see WP_Customize_Setting::preview()
250          * @since 4.1.1
251          * @var mixed
252          */
253         protected $_original_value;
254
255         /**
256          * Add filters to supply the setting's value when accessed.
257          *
258          * If the setting already has a pre-existing value and there is no incoming
259          * post value for the setting, then this method will short-circuit since
260          * there is no change to preview.
261          *
262          * @since 3.4.0
263          * @since 4.4.0 Added boolean return value.
264          * @access public
265          *
266          * @return bool False when preview short-circuits due no change needing to be previewed.
267          */
268         public function preview() {
269                 if ( ! isset( $this->_previewed_blog_id ) ) {
270                         $this->_previewed_blog_id = get_current_blog_id();
271                 }
272
273                 // Prevent re-previewing an already-previewed setting.
274                 if ( $this->is_previewed ) {
275                         return true;
276                 }
277
278                 $id_base = $this->id_data['base'];
279                 $is_multidimensional = ! empty( $this->id_data['keys'] );
280                 $multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
281
282                 /*
283                  * Check if the setting has a pre-existing value (an isset check),
284                  * and if doesn't have any incoming post value. If both checks are true,
285                  * then the preview short-circuits because there is nothing that needs
286                  * to be previewed.
287                  */
288                 $undefined = new stdClass();
289                 $needs_preview = ( $undefined !== $this->post_value( $undefined ) );
290                 $value = null;
291
292                 // Since no post value was defined, check if we have an initial value set.
293                 if ( ! $needs_preview ) {
294                         if ( $this->is_multidimensional_aggregated ) {
295                                 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
296                                 $value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
297                         } else {
298                                 $default = $this->default;
299                                 $this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
300                                 $value = $this->value();
301                                 $this->default = $default;
302                         }
303                         $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
304                 }
305
306                 // If the setting does not need previewing now, defer to when it has a value to preview.
307                 if ( ! $needs_preview ) {
308                         if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) {
309                                 add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) );
310                         }
311                         return false;
312                 }
313
314                 switch ( $this->type ) {
315                         case 'theme_mod' :
316                                 if ( ! $is_multidimensional ) {
317                                         add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
318                                 } else {
319                                         if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
320                                                 // Only add this filter once for this ID base.
321                                                 add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
322                                         }
323                                         self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
324                                 }
325                                 break;
326                         case 'option' :
327                                 if ( ! $is_multidimensional ) {
328                                         add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
329                                 } else {
330                                         if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
331                                                 // Only add these filters once for this ID base.
332                                                 add_filter( "option_{$id_base}", $multidimensional_filter );
333                                                 add_filter( "default_option_{$id_base}", $multidimensional_filter );
334                                         }
335                                         self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
336                                 }
337                                 break;
338                         default :
339
340                                 /**
341                                  * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings
342                                  * not handled as theme_mods or options.
343                                  *
344                                  * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
345                                  *
346                                  * @since 3.4.0
347                                  *
348                                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
349                                  */
350                                 do_action( "customize_preview_{$this->id}", $this );
351
352                                 /**
353                                  * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings
354                                  * not handled as theme_mods or options.
355                                  *
356                                  * The dynamic portion of the hook name, `$this->type`, refers to the setting type.
357                                  *
358                                  * @since 4.1.0
359                                  *
360                                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
361                                  */
362                                 do_action( "customize_preview_{$this->type}", $this );
363                 }
364
365                 $this->is_previewed = true;
366
367                 return true;
368         }
369
370         /**
371          * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated.
372          *
373          * This ensures that the new value will get sanitized and used the next time
374          * that `WP_Customize_Setting::_multidimensional_preview_filter()`
375          * is called for this setting.
376          *
377          * @since 4.4.0
378          * @access private
379          * @see WP_Customize_Manager::set_post_value()
380          * @see WP_Customize_Setting::_multidimensional_preview_filter()
381          */
382         final public function _clear_aggregated_multidimensional_preview_applied_flag() {
383                 unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] );
384         }
385
386         /**
387          * Callback function to filter non-multidimensional theme mods and options.
388          *
389          * If switch_to_blog() was called after the preview() method, and the current
390          * site is now not the same site, then this method does a no-op and returns
391          * the original value.
392          *
393          * @since 3.4.0
394          *
395          * @param mixed $original Old value.
396          * @return mixed New or old value.
397          */
398         public function _preview_filter( $original ) {
399                 if ( ! $this->is_current_blog_previewed() ) {
400                         return $original;
401                 }
402
403                 $undefined = new stdClass(); // Symbol hack.
404                 $post_value = $this->post_value( $undefined );
405                 if ( $undefined !== $post_value ) {
406                         $value = $post_value;
407                 } else {
408                         /*
409                          * Note that we don't use $original here because preview() will
410                          * not add the filter in the first place if it has an initial value
411                          * and there is no post value.
412                          */
413                         $value = $this->default;
414                 }
415                 return $value;
416         }
417
418         /**
419          * Callback function to filter multidimensional theme mods and options.
420          *
421          * For all multidimensional settings of a given type, the preview filter for
422          * the first setting previewed will be used to apply the values for the others.
423          *
424          * @since 4.4.0
425          * @access private
426          *
427          * @see WP_Customize_Setting::$aggregated_multidimensionals
428          * @param mixed $original Original root value.
429          * @return mixed New or old value.
430          */
431         final public function _multidimensional_preview_filter( $original ) {
432                 if ( ! $this->is_current_blog_previewed() ) {
433                         return $original;
434                 }
435
436                 $id_base = $this->id_data['base'];
437
438                 // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value.
439                 if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
440                         return $original;
441                 }
442
443                 foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) {
444                         // Skip applying previewed value for any settings that have already been applied.
445                         if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) {
446                                 continue;
447                         }
448
449                         // Do the replacements of the posted/default sub value into the root value.
450                         $value = $previewed_setting->post_value( $previewed_setting->default );
451                         $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
452                         $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
453                         self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
454
455                         // Mark this setting having been applied so that it will be skipped when the filter is called again.
456                         self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
457                 }
458
459                 return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
460         }
461
462         /**
463          * Check user capabilities and theme supports, and then save
464          * the value of the setting.
465          *
466          * @since 3.4.0
467          *
468          * @return false|void False if cap check fails or value isn't set.
469          */
470         final public function save() {
471                 $value = $this->post_value();
472
473                 if ( ! $this->check_capabilities() || ! isset( $value ) )
474                         return false;
475
476                 /**
477                  * Fires when the WP_Customize_Setting::save() method is called.
478                  *
479                  * The dynamic portion of the hook name, `$this->id_data['base']` refers to
480                  * the base slug of the setting name.
481                  *
482                  * @since 3.4.0
483                  *
484                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
485                  */
486                 do_action( 'customize_save_' . $this->id_data[ 'base' ], $this );
487
488                 $this->update( $value );
489         }
490
491         /**
492          * Fetch and sanitize the $_POST value for the setting.
493          *
494          * @since 3.4.0
495          *
496          * @param mixed $default A default value which is used as a fallback. Default is null.
497          * @return mixed The default value on failure, otherwise the sanitized value.
498          */
499         final public function post_value( $default = null ) {
500                 return $this->manager->post_value( $this, $default );
501         }
502
503         /**
504          * Sanitize an input.
505          *
506          * @since 3.4.0
507          *
508          * @param string|array $value The value to sanitize.
509          * @return string|array|null Null if an input isn't valid, otherwise the sanitized value.
510          */
511         public function sanitize( $value ) {
512
513                 /**
514                  * Filter a Customize setting value in un-slashed form.
515                  *
516                  * @since 3.4.0
517                  *
518                  * @param mixed                $value Value of the setting.
519                  * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
520                  */
521                 return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
522         }
523
524         /**
525          * Get the root value for a setting, especially for multidimensional ones.
526          *
527          * @since 4.4.0
528          * @access protected
529          *
530          * @param mixed $default Value to return if root does not exist.
531          * @return mixed
532          */
533         protected function get_root_value( $default = null ) {
534                 $id_base = $this->id_data['base'];
535                 if ( 'option' === $this->type ) {
536                         return get_option( $id_base, $default );
537                 } else if ( 'theme_mod' ) {
538                         return get_theme_mod( $id_base, $default );
539                 } else {
540                         /*
541                          * Any WP_Customize_Setting subclass implementing aggregate multidimensional
542                          * will need to override this method to obtain the data from the appropriate
543                          * location.
544                          */
545                         return $default;
546                 }
547         }
548
549         /**
550          * Set the root value for a setting, especially for multidimensional ones.
551          *
552          * @since 4.4.0
553          * @access protected
554          *
555          * @param mixed $value Value to set as root of multidimensional setting.
556          * @return bool Whether the multidimensional root was updated successfully.
557          */
558         protected function set_root_value( $value ) {
559                 $id_base = $this->id_data['base'];
560                 if ( 'option' === $this->type ) {
561                         $autoload = true;
562                         if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) {
563                                 $autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'];
564                         }
565                         return update_option( $id_base, $value, $autoload );
566                 } else if ( 'theme_mod' ) {
567                         set_theme_mod( $id_base, $value );
568                         return true;
569                 } else {
570                         /*
571                          * Any WP_Customize_Setting subclass implementing aggregate multidimensional
572                          * will need to override this method to obtain the data from the appropriate
573                          * location.
574                          */
575                         return false;
576                 }
577         }
578
579         /**
580          * Save the value of the setting, using the related API.
581          *
582          * @since 3.4.0
583          *
584          * @param mixed $value The value to update.
585          * @return bool The result of saving the value.
586          */
587         protected function update( $value ) {
588                 $id_base = $this->id_data['base'];
589                 if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
590                         if ( ! $this->is_multidimensional_aggregated ) {
591                                 return $this->set_root_value( $value );
592                         } else {
593                                 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
594                                 $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value );
595                                 self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root;
596                                 return $this->set_root_value( $root );
597                         }
598                 } else {
599                         /**
600                          * Fires when the {@see WP_Customize_Setting::update()} method is called for settings
601                          * not handled as theme_mods or options.
602                          *
603                          * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
604                          *
605                          * @since 3.4.0
606                          *
607                          * @param mixed                $value Value of the setting.
608                          * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
609                          */
610                         do_action( "customize_update_{$this->type}", $value, $this );
611
612                         return has_action( "customize_update_{$this->type}" );
613                 }
614         }
615
616         /**
617          * Deprecated method.
618          *
619          * @since 3.4.0
620          * @deprecated 4.4.0 Deprecated in favor of update() method.
621          */
622         protected function _update_theme_mod() {
623                 _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
624         }
625
626         /**
627          * Deprecated method.
628          *
629          * @since 3.4.0
630          * @deprecated 4.4.0 Deprecated in favor of update() method.
631          */
632         protected function _update_option() {
633                 _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
634         }
635
636         /**
637          * Fetch the value of the setting.
638          *
639          * @since 3.4.0
640          *
641          * @return mixed The value.
642          */
643         public function value() {
644                 $id_base = $this->id_data['base'];
645                 $is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
646
647                 if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
648                         $value = $this->get_root_value( $this->default );
649
650                         /**
651                          * Filter a Customize setting value not handled as a theme_mod or option.
652                          *
653                          * The dynamic portion of the hook name, `$this->id_date['base']`, refers to
654                          * the base slug of the setting name.
655                          *
656                          * For settings handled as theme_mods or options, see those corresponding
657                          * functions for available hooks.
658                          *
659                          * @since 3.4.0
660                          *
661                          * @param mixed $default The setting default value. Default empty.
662                          */
663                         $value = apply_filters( "customize_value_{$id_base}", $value );
664                 } else if ( $this->is_multidimensional_aggregated ) {
665                         $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
666                         $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
667                 } else {
668                         $value = $this->get_root_value( $this->default );
669                 }
670                 return $value;
671         }
672
673         /**
674          * Sanitize the setting's value for use in JavaScript.
675          *
676          * @since 3.4.0
677          *
678          * @return mixed The requested escaped value.
679          */
680         public function js_value() {
681
682                 /**
683                  * Filter a Customize setting value for use in JavaScript.
684                  *
685                  * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
686                  *
687                  * @since 3.4.0
688                  *
689                  * @param mixed                $value The setting value.
690                  * @param WP_Customize_Setting $this  {@see WP_Customize_Setting} instance.
691                  */
692                 $value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
693
694                 if ( is_string( $value ) )
695                         return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
696
697                 return $value;
698         }
699
700         /**
701          * Validate user capabilities whether the theme supports the setting.
702          *
703          * @since 3.4.0
704          *
705          * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
706          */
707         final public function check_capabilities() {
708                 if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
709                         return false;
710
711                 if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
712                         return false;
713
714                 return true;
715         }
716
717         /**
718          * Multidimensional helper function.
719          *
720          * @since 3.4.0
721          *
722          * @param $root
723          * @param $keys
724          * @param bool $create Default is false.
725          * @return array|void Keys are 'root', 'node', and 'key'.
726          */
727         final protected function multidimensional( &$root, $keys, $create = false ) {
728                 if ( $create && empty( $root ) )
729                         $root = array();
730
731                 if ( ! isset( $root ) || empty( $keys ) )
732                         return;
733
734                 $last = array_pop( $keys );
735                 $node = &$root;
736
737                 foreach ( $keys as $key ) {
738                         if ( $create && ! isset( $node[ $key ] ) )
739                                 $node[ $key ] = array();
740
741                         if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
742                                 return;
743
744                         $node = &$node[ $key ];
745                 }
746
747                 if ( $create ) {
748                         if ( ! is_array( $node ) ) {
749                                 // account for an array overriding a string or object value
750                                 $node = array();
751                         }
752                         if ( ! isset( $node[ $last ] ) ) {
753                                 $node[ $last ] = array();
754                         }
755                 }
756
757                 if ( ! isset( $node[ $last ] ) )
758                         return;
759
760                 return array(
761                         'root' => &$root,
762                         'node' => &$node,
763                         'key'  => $last,
764                 );
765         }
766
767         /**
768          * Will attempt to replace a specific value in a multidimensional array.
769          *
770          * @since 3.4.0
771          *
772          * @param $root
773          * @param $keys
774          * @param mixed $value The value to update.
775          * @return mixed
776          */
777         final protected function multidimensional_replace( $root, $keys, $value ) {
778                 if ( ! isset( $value ) )
779                         return $root;
780                 elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
781                         return $value;
782
783                 $result = $this->multidimensional( $root, $keys, true );
784
785                 if ( isset( $result ) )
786                         $result['node'][ $result['key'] ] = $value;
787
788                 return $root;
789         }
790
791         /**
792          * Will attempt to fetch a specific value from a multidimensional array.
793          *
794          * @since 3.4.0
795          *
796          * @param $root
797          * @param $keys
798          * @param mixed $default A default value which is used as a fallback. Default is null.
799          * @return mixed The requested value or the default value.
800          */
801         final protected function multidimensional_get( $root, $keys, $default = null ) {
802                 if ( empty( $keys ) ) // If there are no keys, test the root.
803                         return isset( $root ) ? $root : $default;
804
805                 $result = $this->multidimensional( $root, $keys );
806                 return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
807         }
808
809         /**
810          * Will attempt to check if a specific value in a multidimensional array is set.
811          *
812          * @since 3.4.0
813          *
814          * @param $root
815          * @param $keys
816          * @return bool True if value is set, false if not.
817          */
818         final protected function multidimensional_isset( $root, $keys ) {
819                 $result = $this->multidimensional_get( $root, $keys );
820                 return isset( $result );
821         }
822 }
823
824 /** WP_Customize_Filter_Setting class */
825 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
826
827 /** WP_Customize_Header_Image_Setting class */
828 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
829
830 /** WP_Customize_Background_Image_Setting class */
831 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
832
833 /** WP_Customize_Nav_Menu_Item_Setting class */
834 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
835
836 /** WP_Customize_Nav_Menu_Setting class */
837 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );