Completed
Push — trunk ( ddc286...f54c26 )
by Justin
07:14
created

includes/CMB2_Sanitize.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * CMB2 field sanitization
4
 *
5
 * @since  0.0.4
6
 *
7
 * @category  WordPress_Plugin
8
 * @package   CMB2
9
 * @author    WebDevStudios
10
 * @license   GPL-2.0+
11
 * @link      http://webdevstudios.com
12
 *
13
 * @method string _id()
14
 */
15
class CMB2_Sanitize {
16
17
	/**
18
	 * A CMB field object
19
	 * @var CMB2_Field object
20
	 */
21
	public $field;
22
23
	/**
24
	 * Field's value
25
	 * @var mixed
26
	 */
27
	public $value;
28
29
	/**
30
	 * Setup our class vars
31
	 * @since 1.1.0
32
	 * @param CMB2_Field $field A CMB2 field object
33
	 * @param mixed      $value Field value
34
	 */
35 11
	public function __construct( CMB2_Field $field, $value ) {
36 11
		$this->field = $field;
37 11
		$this->value = stripslashes_deep( $value ); // get rid of those evil magic quotes
38 11
	}
39
40
	/**
41
	 * Catchall method if field's 'sanitization_cb' is NOT defined, or field type does not have a corresponding validation method
42
	 * @since  1.0.0
43
	 * @param  string $name      Non-existent method name
44
	 * @param  array  $arguments All arguments passed to the method
45
	 */
46 8
	public function __call( $name, $arguments ) {
47 8
		return $this->default_sanitization();
48
	}
49
50
	/**
51
	 * Default fallback sanitization method. Applies filters.
52
	 * @since  1.0.2
53
	 */
54 8
	public function default_sanitization() {
55
56
		/**
57
		 * This exists for back-compatibility, but validation
58
		 * is not what happens here.
59
		 * @deprecated See documentation for "cmb2_sanitize_{$this->type()}".
60
		 */
61 8
		$override_value = apply_filters( "cmb2_validate_{$this->field->type()}", null, $this->value, $this->field->object_id, $this->field->args(), $this );
0 ignored issues
show
The property $object_id is declared protected in CMB2_Base. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
62
63 8
		if ( null !== $override_value ) {
64
			return $override_value;
65
		}
66
67 8
		$sanitized_value = '';
68 8
		switch ( $this->field->type() ) {
69 8
			case 'wysiwyg':
70 8
			case 'textarea_small':
71 8
			case 'oembed':
72 2
				$sanitized_value = $this->textarea();
73 2
				break;
74 8
			case 'taxonomy_select':
75 8
			case 'taxonomy_radio':
76 8
			case 'taxonomy_radio_inline':
77 8
			case 'taxonomy_multicheck':
78 8
			case 'taxonomy_multicheck_inline':
79
				if ( $this->field->args( 'taxonomy' ) ) {
80
					wp_set_object_terms( $this->field->object_id, $this->value, $this->field->args( 'taxonomy' ) );
0 ignored issues
show
The property $object_id is declared protected in CMB2_Base. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
81
				} else {
82
					cmb2_utils()->log_if_debug( __METHOD__, __LINE__, "{$this->field->type()} {$this->field->_id()} is missing the 'taxonomy' parameter." );
83
				}
84
				break;
85 8
			case 'multicheck':
86 8
			case 'multicheck_inline':
87 8
			case 'file_list':
88 8
			case 'group':
89
				// no filtering
90 2
				$sanitized_value = $this->value;
91 2
				break;
92 7
			default:
93
				// Handle repeatable fields array
94
				// We'll fallback to 'sanitize_text_field'
95 7
				$sanitized_value = is_array( $this->value ) ? array_map( 'sanitize_text_field', $this->value ) : sanitize_text_field( $this->value );
96 7
				break;
97 8
		}
98
99 8
		return $this->_is_empty_array( $sanitized_value ) ? '' : $sanitized_value;
100
	}
101
102
	/**
103
	 * Simple checkbox validation
104
	 * @since  1.0.1
105
	 * @return string|false 'on' or false
106
	 */
107
	public function checkbox() {
108
		return $this->value === 'on' ? 'on' : false;
109
	}
110
111
	/**
112
	 * Validate url in a meta value
113
	 * @since  1.0.1
114
	 * @return string        Empty string or escaped url
115
	 */
116 1
	public function text_url() {
117 1
		$protocols = $this->field->args( 'protocols' );
118
		// for repeatable
119 1
		if ( is_array( $this->value ) ) {
120
			foreach ( $this->value as $key => $val ) {
121
				$this->value[ $key ] = $val ? esc_url_raw( $val, $protocols ) : $this->field->get_default();
122
			}
123
		} else {
124 1
			$this->value = $this->value ? esc_url_raw( $this->value, $protocols ) : $this->field->get_default();
125
		}
126
127 1
		return $this->value;
128
	}
129
130
	public function colorpicker() {
131
		// for repeatable
132
		if ( is_array( $this->value ) ) {
133
			$check = $this->value;
134
			$this->value = array();
135
			foreach ( $check as $key => $val ) {
136
				if ( $val && '#' != $val ) {
137
					$this->value[ $key ] = esc_attr( $val );
138
				}
139
			}
140
		} else {
141
			$this->value = ! $this->value || '#' == $this->value ? '' : esc_attr( $this->value );
142
		}
143
		return $this->value;
144
	}
145
146
	/**
147
	 * Validate email in a meta value
148
	 * @since  1.0.1
149
	 * @return string       Empty string or sanitized email
150
	 */
151
	public function text_email() {
152
		// for repeatable
153
		if ( is_array( $this->value ) ) {
154
			foreach ( $this->value as $key => $val ) {
155
				$val = trim( $val );
156
				$this->value[ $key ] = is_email( $val ) ? $val : '';
157
			}
158
		} else {
159
			$this->value = trim( $this->value );
160
			$this->value = is_email( $this->value ) ? $this->value : '';
161
		}
162
163
		return $this->value;
164
	}
165
166
	/**
167
	 * Validate money in a meta value
168
	 * @since  1.0.1
169
	 * @return string Empty string or sanitized money value
170
	 */
171 1
	public function text_money() {
172 1
		if ( ! $this->value ) {
173 1
			return '';
174
		}
175
176 1
		global $wp_locale;
177
178 1
		$search = array( $wp_locale->number_format['thousands_sep'], $wp_locale->number_format['decimal_point'] );
179 1
		$replace = array( '', '.' );
180
181
		// for repeatable
182 1
		if ( is_array( $this->value ) ) {
183
			foreach ( $this->value as $key => $val ) {
184
				if ( $val ) {
185
					$this->value[ $key ] = number_format_i18n( (float) str_ireplace( $search, $replace, $val ), 2 );
186
				}
187
			}
188
		} else {
189 1
			$this->value = number_format_i18n( (float) str_ireplace( $search, $replace, $this->value ), 2 );
190
		}
191
192 1
		return $this->value;
193
	}
194
195
	/**
196
	 * Converts text date to timestamp
197
	 * @since  1.0.2
198
	 * @return string Timestring
199
	 */
200
	public function text_date_timestamp() {
201
		return is_array( $this->value )
202
			? array_map( array( $this->field, 'get_timestamp_from_value' ), $this->value )
203
			: $this->field->get_timestamp_from_value( $this->value );
204
	}
205
206
	/**
207
	 * Datetime to timestamp
208
	 * @since  1.0.1
209
	 * @return string Timestring
210
	 */
211
	public function text_datetime_timestamp( $repeat = false ) {
212
213
		$test = is_array( $this->value ) ? array_filter( $this->value ) : '';
214
		if ( empty( $test ) ) {
215
			return '';
216
		}
217
218
		$repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
219
		if ( false !== $repeat_value ) {
220
			return $repeat_value;
221
		}
222
223
		if ( isset( $this->value['date'], $this->value['time'] ) ) {
224
			$this->value = $this->field->get_timestamp_from_value( $this->value['date'] . ' ' . $this->value['time'] );
225
		}
226
227
		if ( $tz_offset = $this->field->field_timezone_offset() ) {
228
			$this->value += (int) $tz_offset;
229
		}
230
231
		return $this->value;
232
	}
233
234
	/**
235
	 * Datetime to timestamp with timezone
236
	 * @since  1.0.1
237
	 * @return string       Timestring
238
	 */
239 2
	public function text_datetime_timestamp_timezone( $repeat = false ) {
240 2
		static $utc_values = array();
241
242 2
		$test = is_array( $this->value ) ? array_filter( $this->value ) : '';
243 2
		if ( empty( $test ) ) {
244 1
			return '';
245
		}
246
247 2
		$utc_key = $this->field->_id() . '_utc';
248
249 2
		$repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
250 2
		if ( false !== $repeat_value ) {
251 1
			if ( ! empty( $utc_values[ $utc_key ] ) ) {
252
				$this->_save_utc_value( $utc_key, $utc_values[ $utc_key ] );
253
				unset( $utc_values[ $utc_key ] );
254
			}
255
256 1
			return $repeat_value;
257
		}
258
259 2
		$tzstring = null;
260
261 2
		if ( is_array( $this->value ) && array_key_exists( 'timezone', $this->value ) ) {
262 2
			$tzstring = $this->value['timezone'];
263 2
		}
264
265 2
		if ( empty( $tzstring ) ) {
266
			$tzstring = cmb2_utils()->timezone_string();
267
		}
268
269 2
		$offset = cmb2_utils()->timezone_offset( $tzstring );
270
271 2
		if ( 'UTC' === substr( $tzstring, 0, 3 ) ) {
272 1
			$tzstring = timezone_name_from_abbr( '', $offset, 0 );
273
			/*
274
			 * timezone_name_from_abbr() returns false if not found based on offset.
275
			 * Since there are currently some invalid timezones in wp_timezone_dropdown(),
276
			 * fallback to an offset of 0 (UTC+0)
277
			 * https://core.trac.wordpress.org/ticket/29205
278
			 */
279 1
			$tzstring = false !== $tzstring ? $tzstring : timezone_name_from_abbr( '', 0, 0 );
280 1
		}
281
282 2
		$full_format = $this->field->args['date_format'] . ' ' . $this->field->args['time_format'];
283 2
		$full_date   = $this->value['date'] . ' ' . $this->value['time'];
284
285
		try {
286
287 2
			$datetime = date_create_from_format( $full_format, $full_date );
288
289 2
			if ( ! is_object( $datetime ) ) {
290
				$this->value = $utc_stamp = '';
291
			} else {
292 2
				$timestamp   = $datetime->setTimezone( new DateTimeZone( $tzstring ) )->getTimestamp();
293 2
				$utc_stamp   = $timestamp - $offset;
294 2
				$this->value = serialize( $datetime );
295
			}
296
297 2
			if ( $this->field->group ) {
298 1
				$this->value = array(
299 1
					'supporting_field_value' => $utc_stamp,
300 1
					'supporting_field_id'    => $utc_key,
301 1
					'value'                  => $this->value,
302
				);
303 1
			} else {
304
				// Save the utc timestamp supporting field
305 1
				if ( $repeat ) {
306
					$utc_values[ $utc_key ][] = $utc_stamp;
307
				} else {
308 1
					$this->_save_utc_value( $utc_key, $utc_stamp );
309
				}
310
			}
311
312 2
		} catch ( Exception $e ) {
313
			$this->value = '';
314
			cmb2_utils()->log_if_debug( __METHOD__, __LINE__, $e->getMessage() );
315
		}
316
317 2
		return $this->value;
318
	}
319
320
	/**
321
	 * Sanitize textareas and wysiwyg fields
322
	 * @since  1.0.1
323
	 * @return string       Sanitized data
324
	 */
325 2
	public function textarea() {
326 2
		return is_array( $this->value ) ? array_map( 'wp_kses_post', $this->value ) : wp_kses_post( $this->value );
327
	}
328
329
	/**
330
	 * Sanitize code textareas
331
	 * @since  1.0.2
332
	 * @return string       Sanitized data
333
	 */
334
	public function textarea_code( $repeat = false ) {
335
		$repeat_value = $this->_check_repeat( __FUNCTION__, $repeat );
336
		if ( false !== $repeat_value ) {
337
			return $repeat_value;
338
		}
339
340
		return htmlspecialchars_decode( stripslashes( $this->value ) );
341
	}
342
343
	/**
344
	 * Handles saving of attachment post ID and sanitizing file url
345
	 * @since  1.1.0
346
	 * @return string        Sanitized url
347
	 */
348 1
	public function file() {
349 1
		$file_id_key = $this->field->_id() . '_id';
350
351 1
		if ( $this->field->group ) {
352
			// Return an array with url/id if saving a group field
353 1
			$this->value = $this->_get_group_file_value_array( $file_id_key );
354 1
		} else {
355
			$this->_save_file_id_value( $file_id_key );
356
			$this->text_url();
357
		}
358
359 1
		return $this->value;
360
	}
361
362
	/**
363
	 * Gets the values for the `file` field type from the data being saved.
364
	 * @since  2.2.0
365
	 */
366 1
	public function _get_group_file_value_array( $id_key ) {
367 1
		$alldata = $this->field->group->data_to_save;
368 1
		$base_id = $this->field->group->_id();
369 1
		$i       = $this->field->group->index;
370
371
		// Check group $alldata data
372 1
		$id_val  = isset( $alldata[ $base_id ][ $i ][ $id_key ] )
373 1
			? absint( $alldata[ $base_id ][ $i ][ $id_key ] )
374 1
			: 0;
375
376
		return array(
377 1
			'value' => $this->text_url(),
378 1
			'supporting_field_value' => $id_val,
379 1
			'supporting_field_id'    => $id_key,
380 1
		);
381
	}
382
383
	/**
384
	 * Peforms saving of `file` attachement's ID
385
	 * @since  1.1.0
386
	 */
387
	public function _save_file_id_value( $file_id_key ) {
388
		$id_field = $this->_new_supporting_field( $file_id_key );
389
390
		// Check standard data_to_save data
391
		$id_val = isset( $this->field->data_to_save[ $file_id_key ] )
392
			? $this->field->data_to_save[ $file_id_key ]
393
			: null;
394
395
		// If there is no ID saved yet, try to get it from the url
396
		if ( $this->value && ! $id_val ) {
397
			$id_val = cmb2_utils()->image_id_from_url( $this->value );
398
		}
399
400
		return $id_field->save_field( $id_val );
401
	}
402
403
	/**
404
	 * Peforms saving of `text_datetime_timestamp_timezone` utc timestamp
405
	 * @since  2.2.0
406
	 */
407 1
	public function _save_utc_value( $utc_key, $utc_stamp ) {
408 1
		return $this->_new_supporting_field( $utc_key )->save_field( $utc_stamp );
409
	}
410
411
	/**
412
	 * Returns a new, supporting, CMB2_Field object based on a new field id.
413
	 * @since  2.2.0
414
	 */
415 1
	public function _new_supporting_field( $new_field_id ) {
416 1
		return $this->field->get_field_clone( array(
417 1
			'id' => $new_field_id,
418 1
			'sanitization_cb' => false,
419 1
		) );
420
	}
421
422
	/**
423
	 * If repeating, loop through and re-apply sanitization method
424
	 * @since  1.1.0
425
	 * @param  string $method Class method
426
	 * @param  bool   $repeat Whether repeating or not
427
	 * @return mixed          Sanitized value
428
	 */
429 2
	public function _check_repeat( $method, $repeat ) {
430 2
		if ( $repeat || ! $this->field->args( 'repeatable' ) ) {
431 2
			return false;
432
		}
433
434 1
		$values_array = $this->value;
435
436 1
		$new_value = array();
437 1
		foreach ( $values_array as $iterator => $this->value ) {
438 1
			if ( $this->value ) {
439 1
				$val = $this->$method( true );
440 1
				if ( ! empty( $val ) ) {
441 1
					$new_value[] = $val;
442 1
				}
443 1
			}
444 1
		}
445
446 1
		$this->value = $new_value;
447
448 1
		return empty( $this->value ) ? null : $this->value;
449
	}
450
451
	/**
452
	 * Determine if passed value is an empty array
453
	 * @since  2.0.6
454
	 * @param  mixed  $to_check Value to check
455
	 * @return boolean          Whether value is an array that's empty
456
	 */
457 8
	public function _is_empty_array( $to_check ) {
458 8
		if ( is_array( $to_check ) ) {
459 3
			$cleaned_up = array_filter( $to_check );
460 3
			return empty( $cleaned_up );
461
		}
462 6
		return false;
463
	}
464
465
}
466