PluginProbe
WPGet API – Connect to any external REST API / 2.2.3
WPGet API – Connect to any external REST API v2.2.3
1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.1.0 2.1.1 2.1.3 2.1.4 2.1.5 2.2.0 2.2.1 2.2.10 2.2.2 2.2.3 All 97 releases
wpgetapi / lib / cmb2 / includes / CMB2_Sanitize.php

CMB2_Sanitize.php in WPGet API – Connect to any external REST API 2.2.3, at lib/cmb2/includes/CMB2_Sanitize.php

666 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CMB2 field sanitization
4 *
5 * @since 0.0.4
6 *
7 * @category WordPress_Plugin
8 * @package CMB2
9 * @author CMB2 team
10 * @license GPL-2.0+
11 * @link https://cmb2.io
12 *
13 * @method string _id()
14 */
15 class CMB2_Sanitize {
16
17 /**
18 * A CMB field object
19 *
20 * @var CMB2_Field object
21 */
22 public $field;
23
24 /**
25 * Field's value
26 *
27 * @var mixed
28 */
29 public $value;
30
31 /**
32 * Setup our class vars
33 *
34 * @since 1.1.0
35 * @param CMB2_Field $field A CMB2 field object.
36 * @param mixed $value Field value.
37 */
38 public function __construct( CMB2_Field $field, $value ) {
39 $this->field = $field;
40 $this->value = $value;
41 }
42
43 /**
44 * Catchall method if field's 'sanitization_cb' is NOT defined,
45 * or field type does not have a corresponding validation method.
46 *
47 * @since 1.0.0
48 *
49 * @param string $name Non-existent method name.
50 * @param array $arguments All arguments passed to the method.
51 * @return mixed
52 */
53 public function __call( $name, $arguments ) {
54 return $this->default_sanitization();
55 }
56
57 /**
58 * Default fallback sanitization method. Applies filters.
59 *
60 * @since 1.0.2
61 */
62 public function default_sanitization() {
63 $field_type = $this->field->type();
64
65 /**
66 * This exists for back-compatibility, but validation
67 * is not what happens here.
68 *
69 * @deprecated See documentation for "cmb2_sanitize_{$field_type}".
70 */
71 if ( function_exists( 'apply_filters_deprecated' ) ) {
72 $override_value = apply_filters_deprecated( "cmb2_validate_{$field_type}", array( null, $this->value, $this->field->object_id, $this->field->args(), $this ), '2.0.0', "cmb2_sanitize_{$field_type}" );
73 } else {
74 $override_value = apply_filters( "cmb2_validate_{$field_type}", null, $this->value, $this->field->object_id, $this->field->args(), $this );
75 }
76
77 if ( null !== $override_value ) {
78 return $override_value;
79 }
80
81 $sanitized_value = '';
82 switch ( $field_type ) {
83 case 'wysiwyg':
84 case 'textarea_small':
85 case 'oembed':
86 $sanitized_value = $this->textarea();
87 break;
88 case 'taxonomy_select':
89 case 'taxonomy_select_hierarchical':
90 case 'taxonomy_radio':
91 case 'taxonomy_radio_inline':
92 case 'taxonomy_radio_hierarchical':
93 case 'taxonomy_multicheck':
94 case 'taxonomy_multicheck_hierarchical':
95 case 'taxonomy_multicheck_inline':
96 $sanitized_value = $this->taxonomy();
97 break;
98 case 'multicheck':
99 case 'multicheck_inline':
100 case 'file_list':
101 case 'group':
102 // no filtering
103 $sanitized_value = $this->value;
104 break;
105 default:
106 // Handle repeatable fields array
107 // We'll fallback to 'sanitize_text_field'
108 $sanitized_value = $this->_default_sanitization();
109 break;
110 }
111
112 return $this->_is_empty_array( $sanitized_value ) ? '' : $sanitized_value;
113 }
114
115 /**
116 * Default sanitization method, sanitize_text_field. Checks if value is array.
117 *
118 * @since 2.2.4
119 * @return mixed Sanitized value.
120 */
121 protected function _default_sanitization() {
122 // Handle repeatable fields array.
123 return is_array( $this->value ) ? array_map( 'sanitize_text_field', $this->value ) : sanitize_text_field( $this->value );
124 }
125
126 /**
127 * Sets the object terms to the object (if not options-page) and optionally returns the sanitized term values.
128 *
129 * @since 2.2.4
130 * @return mixed Blank value, or sanitized term values if "cmb2_return_taxonomy_values_{$cmb_id}" is true.
131 */
132 public function taxonomy() {
133 $sanitized_value = '';
134
135 if ( ! $this->field->args( 'taxonomy' ) ) {
136 CMB2_Utils::log_if_debug( __METHOD__, __LINE__, "{$this->field->type()} {$this->field->_id( '', false )} is missing the 'taxonomy' parameter." );
137 } else {
138
139 if ( in_array( $this->field->object_type, array( 'options-page', 'term' ), true ) ) {
140 $return_values = true;
141 } else {
142 wp_set_object_terms( $this->field->object_id, $this->value, $this->field->args( 'taxonomy' ) );
143 $return_values = false;
144 }
145
146 $cmb_id = $this->field->cmb_id;
147
148 /**
149 * Filter whether 'taxonomy_*' fields should return their value when being sanitized.
150 *
151 * By default, these fields do not return a value as we do not want them stored to meta
152 * (as they are stored as terms). This allows overriding that and is used by CMB2::get_sanitized_values().
153 *
154 * The dynamic portion of the hook, $cmb_id, refers to the this field's CMB2 box id.
155 *
156 * @since 2.2.4
157 *
158 * @param bool $return_values By default, this is only true for 'options-page' boxes. To enable:
159 * `add_filter( "cmb2_return_taxonomy_values_{$cmb_id}", '__return_true' );`
160 * @param CMB2_Sanitize $sanitizer This object.
161 */
162 if ( apply_filters( "cmb2_return_taxonomy_values_{$cmb_id}", $return_values, $this ) ) {
163 $sanitized_value = $this->_default_sanitization();
164 }
165 }
166
167 return $sanitized_value;
168 }
169
170 /**
171 * Simple checkbox validation
172 *
173 * @since 1.0.1
174 * @return string|false 'on' or false
175 */
176 public function checkbox() {
177 return $this->value === 'on' ? 'on' : false;
178 }
179
180 /**
181 * Validate url in a meta value.
182 *
183 * @since 1.0.1
184 * @return string Empty string or escaped url
185 */
186 public function text_url() {
187 $protocols = $this->field->args( 'protocols' );
188 $default = $this->field->get_default();
189
190 // for repeatable.
191 if ( is_array( $this->value ) ) {
192 foreach ( $this->value as $key => $val ) {
193 $this->value[ $key ] = self::sanitize_and_secure_url( $val, $protocols, $default );
194 }
195 } else {
196 $this->value = self::sanitize_and_secure_url( $this->value, $protocols, $default );
197 }
198
199 return $this->value;
200 }
201
202 public function colorpicker() {
203 // for repeatable.
204 if ( is_array( $this->value ) ) {
205 $check = $this->value;
206 $this->value = array();
207 foreach ( $check as $key => $val ) {
208 if ( $val && '#' != $val ) {
209 $this->value[ $key ] = esc_attr( $val );
210 }
211 }
212 } else {
213 $this->value = ! $this->value || '#' == $this->value ? '' : esc_attr( $this->value );
214 }
215 return $this->value;
216 }
217
218 /**
219 * Validate email in a meta value
220 *
221 * @since 1.0.1
222 * @return string Empty string or sanitized email
223 */
224 public function text_email() {
225 // for repeatable.
226 if ( is_array( $this->value ) ) {
227 foreach ( $this->value as $key => $val ) {
228 $val = trim( $val );
229 $this->value[ $key ] = is_email( $val ) ? $val : '';
230 }
231 } else {
232 $this->value = trim( $this->value );
233 $this->value = is_email( $this->value ) ? $this->value : '';
234 }
235
236 return $this->value;
237 }
238
239 /**
240 * Validate money in a meta value
241 *
242 * @since 1.0.1
243 * @return string Empty string or sanitized money value
244 */
245 public function text_money() {
246 if ( ! $this->value ) {
247 return '';
248 }
249
250 global $wp_locale;
251
252 $search = array( $wp_locale->number_format['thousands_sep'], $wp_locale->number_format['decimal_point'] );
253 $replace = array( '', '.' );
254
255 // Strip slashes. Example: 2\'180.00.
256 // See https://github.com/CMB2/CMB2/issues/1014.
257 $this->value = wp_unslash( $this->value );
258
259 // for repeatable.
260 if ( is_array( $this->value ) ) {
261 foreach ( $this->value as $key => $val ) {
262 if ( $val ) {
263 $this->value[ $key ] = number_format_i18n( (float) str_ireplace( $search, $replace, $val ), 2 );
264 }
265 }
266 } else {
267 $this->value = number_format_i18n( (float) str_ireplace( $search, $replace, $this->value ), 2 );
268 }
269
270 return $this->value;
271 }
272
273 /**
274 * Converts text date to timestamp
275 *
276 * @since 1.0.2
277 * @return string Timestring
278 */
279 public function text_date_timestamp() {
280 // date_create_from_format if there is a slash in the value.
281 $this->value = wp_unslash( $this->value );
282
283 return is_array( $this->value )
284 ? array_map( array( $this->field, 'get_timestamp_from_value' ), $this->value )
285 : $this->field->get_timestamp_from_value( $this->value );
286 }
287
288 /**
289 * Datetime to timestamp
290 *
291 * @since 1.0.1
292 *
293 * @param bool $repeat Whether or not to repeat.
294 * @return string|array Timestring
295 */
296 public function text_datetime_timestamp( $repeat = false ) {
297 // date_create_from_format if there is a slash in the value.
298 $this->value = wp_unslash( $this->value );
299
300 if ( $this->is_empty_value() ) {
301 return '';
302 }
303
304 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
305 if ( false !== $repeat_value ) {
306 return $repeat_value;
307 }
308
309 // Account for timestamp values passed through REST API.
310 if ( $this->is_valid_date_value() ) {
311
312 $this->value = CMB2_Utils::make_valid_time_stamp( $this->value );
313
314 } elseif ( isset( $this->value['date'], $this->value['time'] ) ) {
315 $this->value = $this->field->get_timestamp_from_value( $this->value['date'] . ' ' . $this->value['time'] );
316 }
317
318 if ( $tz_offset = $this->field->field_timezone_offset() ) {
319 $this->value += (int) $tz_offset;
320 }
321
322 return $this->value;
323 }
324
325 /**
326 * Datetime to timestamp with timezone
327 *
328 * @since 1.0.1
329 *
330 * @param bool $repeat Whether or not to repeat.
331 * @return string Timestring
332 */
333 public function text_datetime_timestamp_timezone( $repeat = false ) {
334 static $utc_values = array();
335
336 if ( $this->is_empty_value() ) {
337 return '';
338 }
339
340 // date_create_from_format if there is a slash in the value.
341 $this->value = wp_unslash( $this->value );
342
343 $utc_key = $this->field->_id( '', false ) . '_utc';
344
345 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
346 if ( false !== $repeat_value ) {
347 if ( ! empty( $utc_values[ $utc_key ] ) ) {
348 $this->_save_utc_value( $utc_key, $utc_values[ $utc_key ] );
349 unset( $utc_values[ $utc_key ] );
350 }
351
352 return $repeat_value;
353 }
354
355 $tzstring = null;
356
357 if ( is_array( $this->value ) && array_key_exists( 'timezone', $this->value ) ) {
358 $tzstring = $this->value['timezone'];
359 }
360
361 if ( empty( $tzstring ) ) {
362 $tzstring = CMB2_Utils::timezone_string();
363 }
364
365 $offset = CMB2_Utils::timezone_offset( $tzstring );
366
367 if ( 'UTC' === substr( $tzstring, 0, 3 ) ) {
368 $tzstring = timezone_name_from_abbr( '', $offset, 0 );
369 /**
370 * The timezone_name_from_abbr() returns false if not found based on offset.
371 * Since there are currently some invalid timezones in wp_timezone_dropdown(),
372 * fallback to an offset of 0 (UTC+0)
373 * https://core.trac.wordpress.org/ticket/29205
374 */
375 $tzstring = false !== $tzstring ? $tzstring : timezone_name_from_abbr( '', 0, 0 );
376 }
377
378 $full_format = $this->field->args['date_format'] . ' ' . $this->field->args['time_format'];
379
380 try {
381 $datetime = null;
382
383 if ( is_array( $this->value ) ) {
384
385 $full_date = $this->value['date'] . ' ' . $this->value['time'];
386 $datetime = date_create_from_format( $full_format, $full_date );
387
388 } elseif ( $this->is_valid_date_value() ) {
389
390 $timestamp = CMB2_Utils::make_valid_time_stamp( $this->value );
391 if ( $timestamp ) {
392 $datetime = new DateTime();
393 $datetime->setTimestamp( $timestamp );
394 }
395 }
396
397 if ( ! is_object( $datetime ) ) {
398 $this->value = $utc_stamp = '';
399 } else {
400 $datetime->setTimezone( new DateTimeZone( $tzstring ) );
401 $utc_stamp = date_timestamp_get( $datetime ) - $offset;
402 $this->value = serialize( $datetime );
403 }
404
405 if ( $this->field->group ) {
406 $this->value = array(
407 'supporting_field_value' => $utc_stamp,
408 'supporting_field_id' => $utc_key,
409 'value' => $this->value,
410 );
411 } else {
412 // Save the utc timestamp supporting field.
413 if ( $repeat ) {
414 $utc_values[ $utc_key ][] = $utc_stamp;
415 } else {
416 $this->_save_utc_value( $utc_key, $utc_stamp );
417 }
418 }
419 } catch ( Exception $e ) {
420 $this->value = '';
421 CMB2_Utils::log_if_debug( __METHOD__, __LINE__, $e->getMessage() );
422 }
423
424 return $this->value;
425 }
426
427 /**
428 * Sanitize textareas and wysiwyg fields
429 *
430 * @since 1.0.1
431 * @return string Sanitized data
432 */
433 public function textarea() {
434 return is_array( $this->value ) ? array_map( 'wp_kses_post', $this->value ) : wp_kses_post( $this->value );
435 }
436
437 /**
438 * Sanitize code textareas
439 *
440 * @since 1.0.2
441 *
442 * @param bool $repeat Whether or not to repeat.
443 * @return string Sanitized data
444 */
445 public function textarea_code( $repeat = false ) {
446 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
447 if ( false !== $repeat_value ) {
448 return $repeat_value;
449 }
450
451 return htmlspecialchars_decode( stripslashes( $this->value ) );
452 }
453
454 /**
455 * Handles saving of attachment post ID and sanitizing file url
456 *
457 * @since 1.1.0
458 * @return string Sanitized url
459 */
460 public function file() {
461 $file_id_key = $this->field->_id( '', false ) . '_id';
462
463 if ( $this->field->group ) {
464 // Return an array with url/id if saving a group field.
465 $this->value = $this->_get_group_file_value_array( $file_id_key );
466 } else {
467 $this->_save_file_id_value( $file_id_key );
468 $this->text_url();
469 }
470
471 return $this->value;
472 }
473
474 /**
475 * Gets the values for the `file` field type from the data being saved.
476 *
477 * @since 2.2.0
478 *
479 * @param mixed $id_key ID key to use.
480 * @return array
481 */
482 public function _get_group_file_value_array( $id_key ) {
483 $alldata = $this->field->group->data_to_save;
484 $base_id = $this->field->group->_id( '', false );
485 $i = $this->field->group->index;
486
487 // Check group $alldata data.
488 $id_val = isset( $alldata[ $base_id ][ $i ][ $id_key ] )
489 ? absint( $alldata[ $base_id ][ $i ][ $id_key ] )
490 : '';
491
492 // We don't want to save 0 to the DB for file fields.
493 if ( 0 === $id_val ) {
494 $id_val = '';
495 }
496
497 return array(
498 'value' => $this->text_url(),
499 'supporting_field_value' => $id_val,
500 'supporting_field_id' => $id_key,
501 );
502 }
503
504 /**
505 * Peforms saving of `file` attachement's ID
506 *
507 * @since 1.1.0
508 *
509 * @param mixed $file_id_key ID key to use.
510 * @return mixed
511 */
512 public function _save_file_id_value( $file_id_key ) {
513 $id_field = $this->_new_supporting_field( $file_id_key );
514
515 // Check standard data_to_save data.
516 $id_val = isset( $this->field->data_to_save[ $file_id_key ] )
517 ? $this->field->data_to_save[ $file_id_key ]
518 : null;
519
520 // If there is no ID saved yet, try to get it from the url.
521 if ( $this->value && ! $id_val ) {
522 $id_val = CMB2_Utils::image_id_from_url( $this->value );
523
524 // If there is an ID but user emptied the input value, remove the ID.
525 } elseif ( ! $this->value && $id_val ) {
526 $id_val = null;
527 }
528
529 return $id_field->save_field( $id_val );
530 }
531
532 /**
533 * Peforms saving of `text_datetime_timestamp_timezone` utc timestamp
534 *
535 * @since 2.2.0
536 *
537 * @param mixed $utc_key UTC key.
538 * @param mixed $utc_stamp UTC timestamp.
539 * @return mixed
540 */
541 public function _save_utc_value( $utc_key, $utc_stamp ) {
542 return $this->_new_supporting_field( $utc_key )->save_field( $utc_stamp );
543 }
544
545 /**
546 * Returns a new, supporting, CMB2_Field object based on a new field id.
547 *
548 * @since 2.2.0
549 *
550 * @param mixed $new_field_id New field ID.
551 * @return CMB2_Field
552 */
553 public function _new_supporting_field( $new_field_id ) {
554 return $this->field->get_field_clone( array(
555 'id' => $new_field_id,
556 'sanitization_cb' => false,
557 ) );
558 }
559
560 /**
561 * If repeating, loop through and re-apply sanitization method
562 *
563 * @since 1.1.0
564 * @param string $method Class method.
565 * @param bool $repeat Whether repeating or not.
566 * @return mixed Sanitized value
567 */
568 public function _check_repeat( $method, $repeat ) {
569 if ( $repeat || ! $this->field->args( 'repeatable' ) ) {
570 return false;
571 }
572
573 $values_array = $this->value;
574
575 $new_value = array();
576 foreach ( $values_array as $iterator => $this->value ) {
577 if ( $this->value ) {
578 $val = $this->$method( true );
579 if ( ! empty( $val ) ) {
580 $new_value[] = $val;
581 }
582 }
583 }
584
585 $this->value = $new_value;
586
587 return empty( $this->value ) ? null : $this->value;
588 }
589
590 /**
591 * Determine if passed value is an empty array
592 *
593 * @since 2.0.6
594 * @param mixed $to_check Value to check.
595 * @return boolean Whether value is an array that's empty
596 */
597 public function _is_empty_array( $to_check ) {
598 if ( is_array( $to_check ) ) {
599 $cleaned_up = array_filter( $to_check );
600 return empty( $cleaned_up );
601 }
602 return false;
603 }
604
605 /**
606 * Sanitize a URL. Make the default scheme HTTPS.
607 *
608 * @since 2.10.0
609 * @param string $value Unescaped URL.
610 * @param array $protocols Allowed protocols for URL.
611 * @param string $default Default value if no URL found.
612 * @return string escaped URL.
613 */
614 public static function sanitize_and_secure_url( $url, $protocols = null, $default = null ) {
615 if ( empty( $url ) ) {
616 return $default;
617 }
618
619 $orig_scheme = parse_url( $url, PHP_URL_SCHEME );
620 $url = esc_url_raw( $url, $protocols );
621
622 // If original url has no scheme...
623 if ( null === $orig_scheme ) {
624
625 // Let's make sure the added scheme is https.
626 $url = set_url_scheme( $url, 'https' );
627 }
628
629 return $url;
630 }
631
632 /**
633 * Check if the current field's value is empty.
634 *
635 * @since 2.9.1
636 *
637 * @return boolean Wether value is empty.
638 */
639 public function is_empty_value() {
640 if ( empty( $this->value ) ) {
641 return true;
642 }
643
644 if ( is_array( $this->value ) ) {
645 $test = array_filter( $this->value );
646 if ( empty( $test ) ) {
647 return true;
648 }
649 }
650
651 return false;
652 }
653
654 /**
655 * Check if the current field's value is a valid date value.
656 *
657 * @since 2.9.1
658 *
659 * @return boolean Wether value is a valid date value.
660 */
661 public function is_valid_date_value() {
662 return is_scalar( $this->value ) && CMB2_Utils::is_valid_date( $this->value );
663 }
664
665 }
666