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