PluginProbe
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI / 7.9.8
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI v7.9.8
8.0.4 8.0.3 8.0.1 8.0.2 8.0.0 7.9.9.7 7.9.9.6 7.9.9.5 7.9.9.4 7.9.9.3 7.9.9.2 7.9.9.1 7.9.9 7.9.8 7.9.7 7.9.6 7.9.5 7.9.4 7.9.3 7.9.2 7.9.1 7.9.0 7.8.9 7.8.8 7.8.7 All 46 releases
gamipress / libraries / cmb2 / includes / CMB2_Sanitize.php

CMB2_Sanitize.php in GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI 7.9.8, at libraries/cmb2/includes/CMB2_Sanitize.php

669 lines 17.8 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 if ( $this->value === null )
435 $this->value = '';
436
437 return is_array( $this->value ) ? array_map( 'wp_kses_post', $this->value ) : wp_kses_post( $this->value );
438 }
439
440 /**
441 * Sanitize code textareas
442 *
443 * @since 1.0.2
444 *
445 * @param bool $repeat Whether or not to repeat.
446 * @return string Sanitized data
447 */
448 public function textarea_code( $repeat = false ) {
449 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
450 if ( false !== $repeat_value ) {
451 return $repeat_value;
452 }
453
454 return htmlspecialchars_decode( stripslashes( $this->value ) );
455 }
456
457 /**
458 * Handles saving of attachment post ID and sanitizing file url
459 *
460 * @since 1.1.0
461 * @return string Sanitized url
462 */
463 public function file() {
464 $file_id_key = $this->field->_id( '', false ) . '_id';
465
466 if ( $this->field->group ) {
467 // Return an array with url/id if saving a group field.
468 $this->value = $this->_get_group_file_value_array( $file_id_key );
469 } else {
470 $this->_save_file_id_value( $file_id_key );
471 $this->text_url();
472 }
473
474 return $this->value;
475 }
476
477 /**
478 * Gets the values for the `file` field type from the data being saved.
479 *
480 * @since 2.2.0
481 *
482 * @param mixed $id_key ID key to use.
483 * @return array
484 */
485 public function _get_group_file_value_array( $id_key ) {
486 $alldata = $this->field->group->data_to_save;
487 $base_id = $this->field->group->_id( '', false );
488 $i = $this->field->group->index;
489
490 // Check group $alldata data.
491 $id_val = isset( $alldata[ $base_id ][ $i ][ $id_key ] )
492 ? absint( $alldata[ $base_id ][ $i ][ $id_key ] )
493 : '';
494
495 // We don't want to save 0 to the DB for file fields.
496 if ( 0 === $id_val ) {
497 $id_val = '';
498 }
499
500 return array(
501 'value' => $this->text_url(),
502 'supporting_field_value' => $id_val,
503 'supporting_field_id' => $id_key,
504 );
505 }
506
507 /**
508 * Peforms saving of `file` attachement's ID
509 *
510 * @since 1.1.0
511 *
512 * @param mixed $file_id_key ID key to use.
513 * @return mixed
514 */
515 public function _save_file_id_value( $file_id_key ) {
516 $id_field = $this->_new_supporting_field( $file_id_key );
517
518 // Check standard data_to_save data.
519 $id_val = isset( $this->field->data_to_save[ $file_id_key ] )
520 ? $this->field->data_to_save[ $file_id_key ]
521 : null;
522
523 // If there is no ID saved yet, try to get it from the url.
524 if ( $this->value && ! $id_val ) {
525 $id_val = CMB2_Utils::image_id_from_url( $this->value );
526
527 // If there is an ID but user emptied the input value, remove the ID.
528 } elseif ( ! $this->value && $id_val ) {
529 $id_val = null;
530 }
531
532 return $id_field->save_field( $id_val );
533 }
534
535 /**
536 * Peforms saving of `text_datetime_timestamp_timezone` utc timestamp
537 *
538 * @since 2.2.0
539 *
540 * @param mixed $utc_key UTC key.
541 * @param mixed $utc_stamp UTC timestamp.
542 * @return mixed
543 */
544 public function _save_utc_value( $utc_key, $utc_stamp ) {
545 return $this->_new_supporting_field( $utc_key )->save_field( $utc_stamp );
546 }
547
548 /**
549 * Returns a new, supporting, CMB2_Field object based on a new field id.
550 *
551 * @since 2.2.0
552 *
553 * @param mixed $new_field_id New field ID.
554 * @return CMB2_Field
555 */
556 public function _new_supporting_field( $new_field_id ) {
557 return $this->field->get_field_clone( array(
558 'id' => $new_field_id,
559 'sanitization_cb' => false,
560 ) );
561 }
562
563 /**
564 * If repeating, loop through and re-apply sanitization method
565 *
566 * @since 1.1.0
567 * @param string $method Class method.
568 * @param bool $repeat Whether repeating or not.
569 * @return mixed Sanitized value
570 */
571 public function _check_repeat( $method, $repeat ) {
572 if ( $repeat || ! $this->field->args( 'repeatable' ) ) {
573 return false;
574 }
575
576 $values_array = $this->value;
577
578 $new_value = array();
579 foreach ( $values_array as $iterator => $this->value ) {
580 if ( $this->value ) {
581 $val = $this->$method( true );
582 if ( ! empty( $val ) ) {
583 $new_value[] = $val;
584 }
585 }
586 }
587
588 $this->value = $new_value;
589
590 return empty( $this->value ) ? null : $this->value;
591 }
592
593 /**
594 * Determine if passed value is an empty array
595 *
596 * @since 2.0.6
597 * @param mixed $to_check Value to check.
598 * @return boolean Whether value is an array that's empty
599 */
600 public function _is_empty_array( $to_check ) {
601 if ( is_array( $to_check ) ) {
602 $cleaned_up = array_filter( $to_check );
603 return empty( $cleaned_up );
604 }
605 return false;
606 }
607
608 /**
609 * Sanitize a URL. Make the default scheme HTTPS.
610 *
611 * @since 2.10.0
612 * @param string $value Unescaped URL.
613 * @param array $protocols Allowed protocols for URL.
614 * @param string $default Default value if no URL found.
615 * @return string escaped URL.
616 */
617 public static function sanitize_and_secure_url( $url, $protocols = null, $default = null ) {
618 if ( empty( $url ) ) {
619 return $default;
620 }
621
622 $orig_scheme = parse_url( $url, PHP_URL_SCHEME );
623 $url = esc_url_raw( $url, $protocols );
624
625 // If original url has no scheme...
626 if ( null === $orig_scheme ) {
627
628 // Let's make sure the added scheme is https.
629 $url = set_url_scheme( $url, 'https' );
630 }
631
632 return $url;
633 }
634
635 /**
636 * Check if the current field's value is empty.
637 *
638 * @since 2.9.1
639 *
640 * @return boolean Wether value is empty.
641 */
642 public function is_empty_value() {
643 if ( empty( $this->value ) ) {
644 return true;
645 }
646
647 if ( is_array( $this->value ) ) {
648 $test = array_filter( $this->value );
649 if ( empty( $test ) ) {
650 return true;
651 }
652 }
653
654 return false;
655 }
656
657 /**
658 * Check if the current field's value is a valid date value.
659 *
660 * @since 2.9.1
661 *
662 * @return boolean Wether value is a valid date value.
663 */
664 public function is_valid_date_value() {
665 return is_scalar( $this->value ) && CMB2_Utils::is_valid_date( $this->value );
666 }
667
668 }
669