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