PluginProbe
Virtue/Ascend/Pinnacle Toolkit / 4.9.12.2
Virtue/Ascend/Pinnacle Toolkit v4.9.12.2
4.9.12.2 trunk 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 3.1 3.2 3.3 3.4 3.7 All 48 releases
virtue-toolkit / cmb / includes / CMB2_Sanitize.php

CMB2_Sanitize.php in Virtue/Ascend/Pinnacle Toolkit 4.9.12.2, at cmb/includes/CMB2_Sanitize.php

589 lines 15.4 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 // for repeatable.
189 if ( is_array( $this->value ) ) {
190 foreach ( $this->value as $key => $val ) {
191 $this->value[ $key ] = $val ? esc_url_raw( $val, $protocols ) : $this->field->get_default();
192 }
193 } else {
194 $this->value = $this->value ? esc_url_raw( $this->value, $protocols ) : $this->field->get_default();
195 }
196
197 return $this->value;
198 }
199
200 public function colorpicker() {
201 // for repeatable.
202 if ( is_array( $this->value ) ) {
203 $check = $this->value;
204 $this->value = array();
205 foreach ( $check as $key => $val ) {
206 if ( $val && '#' != $val ) {
207 $this->value[ $key ] = esc_attr( $val );
208 }
209 }
210 } else {
211 $this->value = ! $this->value || '#' == $this->value ? '' : esc_attr( $this->value );
212 }
213 return $this->value;
214 }
215
216 /**
217 * Validate email in a meta value
218 *
219 * @since 1.0.1
220 * @return string Empty string or sanitized email
221 */
222 public function text_email() {
223 // for repeatable.
224 if ( is_array( $this->value ) ) {
225 foreach ( $this->value as $key => $val ) {
226 $val = trim( $val );
227 $this->value[ $key ] = is_email( $val ) ? $val : '';
228 }
229 } else {
230 $this->value = trim( $this->value );
231 $this->value = is_email( $this->value ) ? $this->value : '';
232 }
233
234 return $this->value;
235 }
236
237 /**
238 * Validate money in a meta value
239 *
240 * @since 1.0.1
241 * @return string Empty string or sanitized money value
242 */
243 public function text_money() {
244 if ( ! $this->value ) {
245 return '';
246 }
247
248 global $wp_locale;
249
250 $search = array( $wp_locale->number_format['thousands_sep'], $wp_locale->number_format['decimal_point'] );
251 $replace = array( '', '.' );
252
253 // Strip slashes. Example: 2\'180.00.
254 // See https://github.com/CMB2/CMB2/issues/1014.
255 $this->value = wp_unslash( $this->value );
256
257 // for repeatable.
258 if ( is_array( $this->value ) ) {
259 foreach ( $this->value as $key => $val ) {
260 if ( $val ) {
261 $this->value[ $key ] = number_format_i18n( (float) str_ireplace( $search, $replace, $val ), 2 );
262 }
263 }
264 } else {
265 $this->value = number_format_i18n( (float) str_ireplace( $search, $replace, $this->value ), 2 );
266 }
267
268 return $this->value;
269 }
270
271 /**
272 * Converts text date to timestamp
273 *
274 * @since 1.0.2
275 * @return string Timestring
276 */
277 public function text_date_timestamp() {
278 // date_create_from_format if there is a slash in the value.
279 $this->value = wp_unslash( $this->value );
280
281 return is_array( $this->value )
282 ? array_map( array( $this->field, 'get_timestamp_from_value' ), $this->value )
283 : $this->field->get_timestamp_from_value( $this->value );
284 }
285
286 /**
287 * Datetime to timestamp
288 *
289 * @since 1.0.1
290 *
291 * @param bool $repeat Whether or not to repeat.
292 * @return string|array Timestring
293 */
294 public function text_datetime_timestamp( $repeat = false ) {
295 // date_create_from_format if there is a slash in the value.
296 $this->value = wp_unslash( $this->value );
297
298 $test = is_array( $this->value ) ? array_filter( $this->value ) : '';
299 if ( empty( $test ) ) {
300 return '';
301 }
302
303 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
304 if ( false !== $repeat_value ) {
305 return $repeat_value;
306 }
307
308 if ( isset( $this->value['date'], $this->value['time'] ) ) {
309 $this->value = $this->field->get_timestamp_from_value( $this->value['date'] . ' ' . $this->value['time'] );
310 }
311
312 if ( $tz_offset = $this->field->field_timezone_offset() ) {
313 $this->value += (int) $tz_offset;
314 }
315
316 return $this->value;
317 }
318
319 /**
320 * Datetime to timestamp with timezone
321 *
322 * @since 1.0.1
323 *
324 * @param bool $repeat Whether or not to repeat.
325 * @return string Timestring
326 */
327 public function text_datetime_timestamp_timezone( $repeat = false ) {
328 static $utc_values = array();
329
330 $test = is_array( $this->value ) ? array_filter( $this->value ) : '';
331 if ( empty( $test ) ) {
332 return '';
333 }
334
335 // date_create_from_format if there is a slash in the value.
336 $this->value = wp_unslash( $this->value );
337
338 $utc_key = $this->field->_id( '', false ) . '_utc';
339
340 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
341 if ( false !== $repeat_value ) {
342 if ( ! empty( $utc_values[ $utc_key ] ) ) {
343 $this->_save_utc_value( $utc_key, $utc_values[ $utc_key ] );
344 unset( $utc_values[ $utc_key ] );
345 }
346
347 return $repeat_value;
348 }
349
350 $tzstring = null;
351
352 if ( is_array( $this->value ) && array_key_exists( 'timezone', $this->value ) ) {
353 $tzstring = $this->value['timezone'];
354 }
355
356 if ( empty( $tzstring ) ) {
357 $tzstring = CMB2_Utils::timezone_string();
358 }
359
360 $offset = CMB2_Utils::timezone_offset( $tzstring );
361
362 if ( 'UTC' === substr( $tzstring, 0, 3 ) ) {
363 $tzstring = timezone_name_from_abbr( '', $offset, 0 );
364 /**
365 * The timezone_name_from_abbr() returns false if not found based on offset.
366 * Since there are currently some invalid timezones in wp_timezone_dropdown(),
367 * fallback to an offset of 0 (UTC+0)
368 * https://core.trac.wordpress.org/ticket/29205
369 */
370 $tzstring = false !== $tzstring ? $tzstring : timezone_name_from_abbr( '', 0, 0 );
371 }
372
373 $full_format = $this->field->args['date_format'] . ' ' . $this->field->args['time_format'];
374 $full_date = $this->value['date'] . ' ' . $this->value['time'];
375
376 try {
377
378 $datetime = date_create_from_format( $full_format, $full_date );
379
380 if ( ! is_object( $datetime ) ) {
381 $this->value = $utc_stamp = '';
382 } else {
383 $datetime->setTimezone( new DateTimeZone( $tzstring ) );
384 $utc_stamp = date_timestamp_get( $datetime ) - $offset;
385 $this->value = serialize( $datetime );
386 }
387
388 if ( $this->field->group ) {
389 $this->value = array(
390 'supporting_field_value' => $utc_stamp,
391 'supporting_field_id' => $utc_key,
392 'value' => $this->value,
393 );
394 } else {
395 // Save the utc timestamp supporting field.
396 if ( $repeat ) {
397 $utc_values[ $utc_key ][] = $utc_stamp;
398 } else {
399 $this->_save_utc_value( $utc_key, $utc_stamp );
400 }
401 }
402 } catch ( Exception $e ) {
403 $this->value = '';
404 CMB2_Utils::log_if_debug( __METHOD__, __LINE__, $e->getMessage() );
405 }
406
407 return $this->value;
408 }
409
410 /**
411 * Sanitize textareas and wysiwyg fields
412 *
413 * @since 1.0.1
414 * @return string Sanitized data
415 */
416 public function textarea() {
417 return is_array( $this->value ) ? array_map( 'wp_kses_post', $this->value ) : wp_kses_post( $this->value );
418 }
419
420 /**
421 * Sanitize code textareas
422 *
423 * @since 1.0.2
424 *
425 * @param bool $repeat Whether or not to repeat.
426 * @return string Sanitized data
427 */
428 public function textarea_code( $repeat = false ) {
429 $repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
430 if ( false !== $repeat_value ) {
431 return $repeat_value;
432 }
433
434 return htmlspecialchars_decode( stripslashes( $this->value ) );
435 }
436
437 /**
438 * Handles saving of attachment post ID and sanitizing file url
439 *
440 * @since 1.1.0
441 * @return string Sanitized url
442 */
443 public function file() {
444 $file_id_key = $this->field->_id( '', false ) . '_id';
445
446 if ( $this->field->group ) {
447 // Return an array with url/id if saving a group field.
448 $this->value = $this->_get_group_file_value_array( $file_id_key );
449 } else {
450 $this->_save_file_id_value( $file_id_key );
451 $this->text_url();
452 }
453
454 return $this->value;
455 }
456
457 /**
458 * Gets the values for the `file` field type from the data being saved.
459 *
460 * @since 2.2.0
461 *
462 * @param mixed $id_key ID key to use.
463 * @return array
464 */
465 public function _get_group_file_value_array( $id_key ) {
466 $alldata = $this->field->group->data_to_save;
467 $base_id = $this->field->group->_id( '', false );
468 $i = $this->field->group->index;
469
470 // Check group $alldata data.
471 $id_val = isset( $alldata[ $base_id ][ $i ][ $id_key ] )
472 ? absint( $alldata[ $base_id ][ $i ][ $id_key ] )
473 : '';
474
475 // We don't want to save 0 to the DB for file fields.
476 if ( 0 === $id_val ) {
477 $id_val = '';
478 }
479
480 return array(
481 'value' => $this->text_url(),
482 'supporting_field_value' => $id_val,
483 'supporting_field_id' => $id_key,
484 );
485 }
486
487 /**
488 * Peforms saving of `file` attachement's ID
489 *
490 * @since 1.1.0
491 *
492 * @param mixed $file_id_key ID key to use.
493 * @return mixed
494 */
495 public function _save_file_id_value( $file_id_key ) {
496 $id_field = $this->_new_supporting_field( $file_id_key );
497
498 // Check standard data_to_save data.
499 $id_val = isset( $this->field->data_to_save[ $file_id_key ] )
500 ? $this->field->data_to_save[ $file_id_key ]
501 : null;
502
503 // If there is no ID saved yet, try to get it from the url.
504 if ( $this->value && ! $id_val ) {
505 $id_val = CMB2_Utils::image_id_from_url( $this->value );
506
507 // If there is an ID but user emptied the input value, remove the ID.
508 } elseif ( ! $this->value && $id_val ) {
509 $id_val = null;
510 }
511
512 return $id_field->save_field( $id_val );
513 }
514
515 /**
516 * Peforms saving of `text_datetime_timestamp_timezone` utc timestamp
517 *
518 * @since 2.2.0
519 *
520 * @param mixed $utc_key UTC key.
521 * @param mixed $utc_stamp UTC timestamp.
522 * @return mixed
523 */
524 public function _save_utc_value( $utc_key, $utc_stamp ) {
525 return $this->_new_supporting_field( $utc_key )->save_field( $utc_stamp );
526 }
527
528 /**
529 * Returns a new, supporting, CMB2_Field object based on a new field id.
530 *
531 * @since 2.2.0
532 *
533 * @param mixed $new_field_id New field ID.
534 * @return CMB2_Field
535 */
536 public function _new_supporting_field( $new_field_id ) {
537 return $this->field->get_field_clone( array(
538 'id' => $new_field_id,
539 'sanitization_cb' => false,
540 ) );
541 }
542
543 /**
544 * If repeating, loop through and re-apply sanitization method
545 *
546 * @since 1.1.0
547 * @param string $method Class method.
548 * @param bool $repeat Whether repeating or not.
549 * @return mixed Sanitized value
550 */
551 public function _check_repeat( $method, $repeat ) {
552 if ( $repeat || ! $this->field->args( 'repeatable' ) ) {
553 return false;
554 }
555
556 $values_array = $this->value;
557
558 $new_value = array();
559 foreach ( $values_array as $iterator => $this->value ) {
560 if ( $this->value ) {
561 $val = $this->$method( true );
562 if ( ! empty( $val ) ) {
563 $new_value[] = $val;
564 }
565 }
566 }
567
568 $this->value = $new_value;
569
570 return empty( $this->value ) ? null : $this->value;
571 }
572
573 /**
574 * Determine if passed value is an empty array
575 *
576 * @since 2.0.6
577 * @param mixed $to_check Value to check.
578 * @return boolean Whether value is an array that's empty
579 */
580 public function _is_empty_array( $to_check ) {
581 if ( is_array( $to_check ) ) {
582 $cleaned_up = array_filter( $to_check );
583 return empty( $cleaned_up );
584 }
585 return false;
586 }
587
588 }
589