WordPress 4.1.1
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-setting.php
1 <?php
2 /**
3  * Customize Setting Class.
4  *
5  * Handles saving and sanitizing of settings.
6  *
7  * @package WordPress
8  * @subpackage Customize
9  * @since 3.4.0
10  */
11 class WP_Customize_Setting {
12         /**
13          * @access public
14          * @var WP_Customize_Manager
15          */
16         public $manager;
17
18         /**
19          * @access public
20          * @var string
21          */
22         public $id;
23
24         /**
25          * @access public
26          * @var string
27          */
28         public $type = 'theme_mod';
29
30         /**
31          * Capability required to edit this setting.
32          *
33          * @var string
34          */
35         public $capability = 'edit_theme_options';
36
37         /**
38          * Feature a theme is required to support to enable this setting.
39          *
40          * @access public
41          * @var string
42          */
43         public $theme_supports  = '';
44         public $default         = '';
45         public $transport       = 'refresh';
46
47         /**
48          * Server-side sanitization callback for the setting's value.
49          *
50          * @var callback
51          */
52         public $sanitize_callback    = '';
53         public $sanitize_js_callback = '';
54
55         protected $id_data = array();
56
57         /**
58          * Cached and sanitized $_POST value for the setting.
59          *
60          * @access private
61          * @var mixed
62          */
63         private $_post_value;
64
65         /**
66          * Constructor.
67          *
68          * Any supplied $args override class property defaults.
69          *
70          * @since 3.4.0
71          *
72          * @param WP_Customize_Manager $manager
73          * @param string               $id      An specific ID of the setting. Can be a
74          *                                      theme mod or option name.
75          * @param array                $args    Setting arguments.
76          * @return WP_Customize_Setting $setting
77          */
78         public function __construct( $manager, $id, $args = array() ) {
79                 $keys = array_keys( get_object_vars( $this ) );
80                 foreach ( $keys as $key ) {
81                         if ( isset( $args[ $key ] ) )
82                                 $this->$key = $args[ $key ];
83                 }
84
85                 $this->manager = $manager;
86                 $this->id = $id;
87
88                 // Parse the ID for array keys.
89                 $this->id_data[ 'keys' ] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
90                 $this->id_data[ 'base' ] = array_shift( $this->id_data[ 'keys' ] );
91
92                 // Rebuild the ID.
93                 $this->id = $this->id_data[ 'base' ];
94                 if ( ! empty( $this->id_data[ 'keys' ] ) )
95                         $this->id .= '[' . implode( '][', $this->id_data[ 'keys' ] ) . ']';
96
97                 if ( $this->sanitize_callback )
98                         add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
99
100                 if ( $this->sanitize_js_callback )
101                         add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
102
103                 return $this;
104         }
105
106         protected $_original_value;
107
108         /**
109          * Handle previewing the setting.
110          *
111          * @since 3.4.0
112          */
113         public function preview() {
114                 if ( ! isset( $this->_original_value ) ) {
115                         $this->_original_value = $this->value();
116                 }
117
118                 switch( $this->type ) {
119                         case 'theme_mod' :
120                                 add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
121                                 break;
122                         case 'option' :
123                                 if ( empty( $this->id_data[ 'keys' ] ) )
124                                         add_filter( 'pre_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
125                                 else {
126                                         add_filter( 'option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
127                                         add_filter( 'default_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
128                                 }
129                                 break;
130                         default :
131
132                                 /**
133                                  * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings
134                                  * not handled as theme_mods or options.
135                                  *
136                                  * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
137                                  *
138                                  * @since 3.4.0
139                                  *
140                                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
141                                  */
142                                 do_action( "customize_preview_{$this->id}", $this );
143
144                                 /**
145                                  * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings
146                                  * not handled as theme_mods or options.
147                                  *
148                                  * The dynamic portion of the hook name, `$this->type`, refers to the setting type.
149                                  *
150                                  * @since 4.1.0
151                                  *
152                                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
153                                  */
154                                 do_action( "customize_preview_{$this->type}", $this );
155                 }
156         }
157
158         /**
159          * Callback function to filter the theme mods and options.
160          *
161          * @since 3.4.0
162          * @uses WP_Customize_Setting::multidimensional_replace()
163          *
164          * @param mixed $original Old value.
165          * @return mixed New or old value.
166          */
167         public function _preview_filter( $original ) {
168                 $undefined = new stdClass(); // symbol hack
169                 $post_value = $this->manager->post_value( $this, $undefined );
170                 if ( $undefined === $post_value ) {
171                         $value = $this->_original_value;
172                 } else {
173                         $value = $post_value;
174                 }
175
176                 return $this->multidimensional_replace( $original, $this->id_data['keys'], $value );
177         }
178
179         /**
180          * Check user capabilities and theme supports, and then save
181          * the value of the setting.
182          *
183          * @since 3.4.0
184          *
185          * @return false|null False if cap check fails or value isn't set.
186          */
187         public final function save() {
188                 $value = $this->post_value();
189
190                 if ( ! $this->check_capabilities() || ! isset( $value ) )
191                         return false;
192
193                 /**
194                  * Fires when the WP_Customize_Setting::save() method is called.
195                  *
196                  * The dynamic portion of the hook name, `$this->id_data['base']` refers to
197                  * the base slug of the setting name.
198                  *
199                  * @since 3.4.0
200                  *
201                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
202                  */
203                 do_action( 'customize_save_' . $this->id_data[ 'base' ], $this );
204
205                 $this->update( $value );
206         }
207
208         /**
209          * Fetch and sanitize the $_POST value for the setting.
210          *
211          * @since 3.4.0
212          *
213          * @param mixed $default A default value which is used as a fallback. Default is null.
214          * @return mixed The default value on failure, otherwise the sanitized value.
215          */
216         public final function post_value( $default = null ) {
217                 // Check for a cached value
218                 if ( isset( $this->_post_value ) )
219                         return $this->_post_value;
220
221                 // Call the manager for the post value
222                 $result = $this->manager->post_value( $this );
223
224                 if ( isset( $result ) )
225                         return $this->_post_value = $result;
226                 else
227                         return $default;
228         }
229
230         /**
231          * Sanitize an input.
232          *
233          * @since 3.4.0
234          *
235          * @param mixed $value The value to sanitize.
236          * @return mixed Null if an input isn't valid, otherwise the sanitized value.
237          */
238         public function sanitize( $value ) {
239                 $value = wp_unslash( $value );
240
241                 /**
242                  * Filter a Customize setting value in un-slashed form.
243                  *
244                  * @since 3.4.0
245                  *
246                  * @param mixed                $value Value of the setting.
247                  * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
248                  */
249                 return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
250         }
251
252         /**
253          * Save the value of the setting, using the related API.
254          *
255          * @since 3.4.0
256          *
257          * @param mixed $value The value to update.
258          * @return mixed The result of saving the value.
259          */
260         protected function update( $value ) {
261                 switch( $this->type ) {
262                         case 'theme_mod' :
263                                 return $this->_update_theme_mod( $value );
264
265                         case 'option' :
266                                 return $this->_update_option( $value );
267
268                         default :
269
270                                 /**
271                                  * Fires when the {@see WP_Customize_Setting::update()} method is called for settings
272                                  * not handled as theme_mods or options.
273                                  *
274                                  * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
275                                  *
276                                  * @since 3.4.0
277                                  *
278                                  * @param mixed                $value Value of the setting.
279                                  * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
280                                  */
281                                 return do_action( 'customize_update_' . $this->type, $value, $this );
282                 }
283         }
284
285         /**
286          * Update the theme mod from the value of the parameter.
287          *
288          * @since 3.4.0
289          *
290          * @param mixed $value The value to update.
291          * @return mixed The result of saving the value.
292          */
293         protected function _update_theme_mod( $value ) {
294                 // Handle non-array theme mod.
295                 if ( empty( $this->id_data[ 'keys' ] ) )
296                         return set_theme_mod( $this->id_data[ 'base' ], $value );
297
298                 // Handle array-based theme mod.
299                 $mods = get_theme_mod( $this->id_data[ 'base' ] );
300                 $mods = $this->multidimensional_replace( $mods, $this->id_data[ 'keys' ], $value );
301                 if ( isset( $mods ) )
302                         return set_theme_mod( $this->id_data[ 'base' ], $mods );
303         }
304
305         /**
306          * Update the option from the value of the setting.
307          *
308          * @since 3.4.0
309          *
310          * @param mixed $value The value to update.
311          * @return bool|null The result of saving the value.
312          */
313         protected function _update_option( $value ) {
314                 // Handle non-array option.
315                 if ( empty( $this->id_data[ 'keys' ] ) )
316                         return update_option( $this->id_data[ 'base' ], $value );
317
318                 // Handle array-based options.
319                 $options = get_option( $this->id_data[ 'base' ] );
320                 $options = $this->multidimensional_replace( $options, $this->id_data[ 'keys' ], $value );
321                 if ( isset( $options ) )
322                         return update_option( $this->id_data[ 'base' ], $options );
323         }
324
325         /**
326          * Fetch the value of the setting.
327          *
328          * @since 3.4.0
329          *
330          * @return mixed The value.
331          */
332         public function value() {
333                 // Get the callback that corresponds to the setting type.
334                 switch( $this->type ) {
335                         case 'theme_mod' :
336                                 $function = 'get_theme_mod';
337                                 break;
338                         case 'option' :
339                                 $function = 'get_option';
340                                 break;
341                         default :
342
343                                 /**
344                                  * Filter a Customize setting value not handled as a theme_mod or option.
345                                  *
346                                  * The dynamic portion of the hook name, `$this->id_date['base']`, refers to
347                                  * the base slug of the setting name.
348                                  *
349                                  * For settings handled as theme_mods or options, see those corresponding
350                                  * functions for available hooks.
351                                  *
352                                  * @since 3.4.0
353                                  *
354                                  * @param mixed $default The setting default value. Default empty.
355                                  */
356                                 return apply_filters( 'customize_value_' . $this->id_data[ 'base' ], $this->default );
357                 }
358
359                 // Handle non-array value
360                 if ( empty( $this->id_data[ 'keys' ] ) )
361                         return $function( $this->id_data[ 'base' ], $this->default );
362
363                 // Handle array-based value
364                 $values = $function( $this->id_data[ 'base' ] );
365                 return $this->multidimensional_get( $values, $this->id_data[ 'keys' ], $this->default );
366         }
367
368         /**
369          * Sanitize the setting's value for use in JavaScript.
370          *
371          * @since 3.4.0
372          *
373          * @return mixed The requested escaped value.
374          */
375         public function js_value() {
376
377                 /**
378                  * Filter a Customize setting value for use in JavaScript.
379                  *
380                  * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
381                  *
382                  * @since 3.4.0
383                  *
384                  * @param mixed                $value The setting value.
385                  * @param WP_Customize_Setting $this  {@see WP_Customize_Setting} instance.
386                  */
387                 $value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
388
389                 if ( is_string( $value ) )
390                         return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
391
392                 return $value;
393         }
394
395         /**
396          * Validate user capabilities whether the theme supports the setting.
397          *
398          * @since 3.4.0
399          *
400          * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
401          */
402         public final function check_capabilities() {
403                 if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
404                         return false;
405
406                 if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
407                         return false;
408
409                 return true;
410         }
411
412         /**
413          * Multidimensional helper function.
414          *
415          * @since 3.4.0
416          *
417          * @param $root
418          * @param $keys
419          * @param bool $create Default is false.
420          * @return null|array Keys are 'root', 'node', and 'key'.
421          */
422         final protected function multidimensional( &$root, $keys, $create = false ) {
423                 if ( $create && empty( $root ) )
424                         $root = array();
425
426                 if ( ! isset( $root ) || empty( $keys ) )
427                         return;
428
429                 $last = array_pop( $keys );
430                 $node = &$root;
431
432                 foreach ( $keys as $key ) {
433                         if ( $create && ! isset( $node[ $key ] ) )
434                                 $node[ $key ] = array();
435
436                         if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
437                                 return;
438
439                         $node = &$node[ $key ];
440                 }
441
442                 if ( $create ) {
443                         if ( ! is_array( $node ) ) {
444                                 // account for an array overriding a string or object value
445                                 $node = array();
446                         }
447                         if ( ! isset( $node[ $last ] ) ) {
448                                 $node[ $last ] = array();
449                         }
450                 }
451
452                 if ( ! isset( $node[ $last ] ) )
453                         return;
454
455                 return array(
456                         'root' => &$root,
457                         'node' => &$node,
458                         'key'  => $last,
459                 );
460         }
461
462         /**
463          * Will attempt to replace a specific value in a multidimensional array.
464          *
465          * @since 3.4.0
466          *
467          * @param $root
468          * @param $keys
469          * @param mixed $value The value to update.
470          * @return
471          */
472         final protected function multidimensional_replace( $root, $keys, $value ) {
473                 if ( ! isset( $value ) )
474                         return $root;
475                 elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
476                         return $value;
477
478                 $result = $this->multidimensional( $root, $keys, true );
479
480                 if ( isset( $result ) )
481                         $result['node'][ $result['key'] ] = $value;
482
483                 return $root;
484         }
485
486         /**
487          * Will attempt to fetch a specific value from a multidimensional array.
488          *
489          * @since 3.4.0
490          *
491          * @param $root
492          * @param $keys
493          * @param mixed $default A default value which is used as a fallback. Default is null.
494          * @return mixed The requested value or the default value.
495          */
496         final protected function multidimensional_get( $root, $keys, $default = null ) {
497                 if ( empty( $keys ) ) // If there are no keys, test the root.
498                         return isset( $root ) ? $root : $default;
499
500                 $result = $this->multidimensional( $root, $keys );
501                 return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
502         }
503
504         /**
505          * Will attempt to check if a specific value in a multidimensional array is set.
506          *
507          * @since 3.4.0
508          *
509          * @param $root
510          * @param $keys
511          * @return bool True if value is set, false if not.
512          */
513         final protected function multidimensional_isset( $root, $keys ) {
514                 $result = $this->multidimensional_get( $root, $keys );
515                 return isset( $result );
516         }
517 }
518
519 /**
520  * A setting that is used to filter a value, but will not save the results.
521  *
522  * Results should be properly handled using another setting or callback.
523  *
524  * @package WordPress
525  * @subpackage Customize
526  * @since 3.4.0
527  */
528 class WP_Customize_Filter_Setting extends WP_Customize_Setting {
529
530         /**
531          * @since 3.4.0
532          */
533         public function update( $value ) {}
534 }
535
536 /**
537  * A setting that is used to filter a value, but will not save the results.
538  *
539  * Results should be properly handled using another setting or callback.
540  *
541  * @package WordPress
542  * @subpackage Customize
543  * @since 3.4.0
544  */
545 final class WP_Customize_Header_Image_Setting extends WP_Customize_Setting {
546         public $id = 'header_image_data';
547
548         /**
549          * @since 3.4.0
550          *
551          * @param $value
552          */
553         public function update( $value ) {
554                 global $custom_image_header;
555
556                 // If the value doesn't exist (removed or random),
557                 // use the header_image value.
558                 if ( ! $value )
559                         $value = $this->manager->get_setting('header_image')->post_value();
560
561                 if ( is_array( $value ) && isset( $value['choice'] ) )
562                         $custom_image_header->set_header_image( $value['choice'] );
563                 else
564                         $custom_image_header->set_header_image( $value );
565         }
566 }
567
568 /**
569  * Class WP_Customize_Background_Image_Setting
570  *
571  * @package WordPress
572  * @subpackage Customize
573  * @since 3.4.0
574  */
575 final class WP_Customize_Background_Image_Setting extends WP_Customize_Setting {
576         public $id = 'background_image_thumb';
577
578         /**
579          * @since 3.4.0
580          *
581          * @param $value
582          */
583         public function update( $value ) {
584                 remove_theme_mod( 'background_image_thumb' );
585         }
586 }