Completed
Pull Request — trunk (#541)
by Justin
06:39
created

CMB2_Field::id()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 2
1
<?php
2
/**
3
 * CMB2 field objects
4
 *
5
 * @since  1.1.0
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
 * @method string type()
15
 * @method mixed fields()
16
 */
17
class CMB2_Field {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
18
19
	/**
20
	 * Metabox object id
21
	 * @var   mixed
22
	 * @since 1.1.0
23
	 */
24
	public $object_id = null;
25
26
	/**
27
	 * Metabox object type
28
	 * @var   string
29
	 * @since 1.1.0
30
	 */
31
	public $object_type = '';
32
33
	/**
34
	 * Field arguments
35
	 * @var   mixed
36
	 * @since 1.1.0
37
	 */
38
	public $args = array();
39
40
	/**
41
	 * Field group object or false (if no group)
42
	 * @var   mixed
43
	 * @since 1.1.0
44
	 */
45
	public $group = false;
46
47
	/**
48
	 * Field meta value
49
	 * @var   mixed
50
	 * @since 1.1.0
51
	 */
52
	public $value = null;
53
54
	/**
55
	 * Field meta value
56
	 * @var   mixed
57
	 * @since 1.1.0
58
	 */
59
	public $escaped_value = null;
60
61
	/**
62
	 * Grouped Field's current numeric index during the save process
63
	 * @var   mixed
64
	 * @since 2.0.0
65
	 */
66
	public $index = 0;
67
68
	/**
69
	 * Array of field options
70
	 * @var   array
71
	 * @since 2.0.0
72
	 */
73
	protected $field_options = array();
74
75
	/**
76
	 * Array of provided field text strings
77
	 * @var   array
78
	 * @since 2.0.0
79
	 */
80
	protected $strings;
81
82
	/**
83
	 * Array of field param callback results
84
	 * @var   array
85
	 * @since 2.0.0
86
	 */
87
	protected $callback_results = array();
88
89
	/**
90
	 * Array of key => value data for saving. Likely $_POST data.
91
	 * @var   array
92
	 * @since 2.0.0
93
	 */
94
	public $data_to_save = array();
95
96
	/**
97
	 * Current field's CMB2 instance ID
98
	 * @var   string
99
	 * @since 2.2.2
100
	 */
101
	public $cmb_id = '';
102
103
	/**
104
	 * All CMB2_Field callable field arguments.
105
	 * Can be used to determine if a field argument is callable.
106
	 *
107
	 * @var array
108
	 */
109
	public static $callable_fields = array(
110
		'default',
111
		'row_classes',
112
		'options_cb',
113
		'label_cb',
114
		'render_row_cb',
115
		'before_group',
116
		'before_group_row',
117
		'before_row',
118
		'before',
119
		'before_field',
120
		'after_field',
121
		'after',
122
		'after_row',
123
		'after_group_row',
124
		'after_group',
125
	);
126
127
	/**
128
	 * Constructs our field object
129
	 * @since 1.1.0
130
	 * @param array $args Field arguments
131
	 */
132 74
	public function __construct( $args ) {
133
134 74
		if ( ! empty( $args['group_field'] ) ) {
135 3
			$this->group       = $args['group_field'];
136 3
			$this->object_id   = $this->group->object_id;
137 3
			$this->object_type = $this->group->object_type;
138 3
			$this->cmb_id      = $this->group->cmb_id;
139 3
		} else {
140 74
			$this->object_id   = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0;
141 74
			$this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post';
142
143 74
			if ( isset( $args['cmb_id'] ) ) {
144 36
				$this->cmb_id = $args['cmb_id'];
145 36
			}
146
		}
147
148 74
		$this->args = $this->_set_field_defaults( $args['field_args'] );
149
150 74
		if ( $this->object_id ) {
151 68
			$this->value = $this->get_data();
152 68
		}
153 74
	}
154
155
	/**
156
	 * Non-existent methods fallback to checking for field arguments of the same name
157
	 * @since  1.1.0
158
	 * @param  string $name     Method name
159
	 * @param  array  $arguments Array of passed-in arguments
160
	 * @return mixed             Value of field argument
161
	 */
162 62
	public function __call( $name, $arguments ) {
163 62
		$key = isset( $arguments[0] ) ? $arguments[0] : false;
164 62
		return $this->args( $name, $key );
165
	}
166
167
	/**
168
	 * Retrieves the field id
169
	 * @since  1.1.0
170
	 * @param  boolean $raw Whether to retrieve pre-modidifed id
171
	 * @return string       Field id
172
	 */
173 72
	public function id( $raw = false ) {
174 72
		$id = $raw ? '_id' : 'id';
175 72
		return $this->args( $id );
176
	}
177
178
	/**
179
	 * Get a field argument
180
	 * @since  1.1.0
181
	 * @param  string $key  Argument to check
182
	 * @param  string $_key Sub argument to check
183
	 * @return mixed        Argument value or false if non-existent
184
	 */
185 75
	public function args( $key = '', $_key = '' ) {
186 75
		$arg = $this->_data( 'args', $key );
187
188 75
		if ( in_array( $key, array( 'default', 'default_cb' ), true ) ) {
189
190 1
			$arg = $this->get_default();
191
192 75
		} elseif ( $_key ) {
193
194
			$arg = isset( $arg[ $_key ] ) ? $arg[ $_key ] : false;
195
		}
196
197 75
		return $arg;
198
	}
199
200
	/**
201
	 * Retrieve a portion of a field property
202
	 * @since  1.1.0
203
	 * @param  string  $var Field property to check
204
	 * @param  string  $key Field property array key to check
205
	 * @return mixed        Queried property value or false
206
	 */
207 75
	public function _data( $var, $key = '' ) {
208 75
		$vars = $this->$var;
209 75
		if ( $key ) {
210 75
			return array_key_exists( $key, $vars ) ? $vars[ $key ] : false;
211
		}
212 63
		return $vars;
213
	}
214
215
	/**
216
	 * Get Field's value
217
	 * @since  1.1.0
218
	 * @param  string $key If value is an array, is used to get array key->value
219
	 * @return mixed       Field value or false if non-existent
220
	 */
221 45
	public function value( $key = '' ) {
222 45
		return $this->_data( 'value', $key );
223
	}
224
225
	/**
226
	 * Retrieves metadata/option data
227
	 * @since  1.0.1
228
	 * @param  string $field_id Meta key/Option array key
229
	 * @param  array  $args     Override arguments
230
	 * @return mixed            Meta/Option value
231
	 */
232 70
	public function get_data( $field_id = '', $args = array() ) {
233 70
		if ( $field_id ) {
234
			$args['field_id'] = $field_id;
235 70
		} else if ( $this->group ) {
236
			$args['field_id'] = $this->group->id();
237
		}
238
239 70
		$a = $this->data_args( $args );
240
241
		/**
242
		 * Filter whether to override getting of meta value.
243
		 * Returning a non 'cmb2_field_no_override_val' value
244
		 * will effectively short-circuit the value retrieval.
245
		 *
246
		 * @since 2.0.0
247
		 *
248
		 * @param mixed $value     The value get_metadata() should
249
		 *                         return - a single metadata value,
250
		 *                         or an array of values.
251
		 *
252
		 * @param int   $object_id Object ID.
253
		 *
254
		 * @param array $args {
255
		 *     An array of arguments for retrieving data
256
		 *
257
		 *     @type string $type     The current object type
258
		 *     @type int    $id       The current object ID
259
		 *     @type string $field_id The ID of the field being requested
260
		 *     @type bool   $repeat   Whether current field is repeatable
261
		 *     @type bool   $single   Whether current field is a single database row
262
		 * }
263
		 *
264
		 * @param CMB2_Field object $field This field object
265
		 */
266 70
		$data = apply_filters( 'cmb2_override_meta_value', 'cmb2_field_no_override_val', $this->object_id, $a, $this );
267
268
		/**
269
		 * Filter and parameters are documented for 'cmb2_override_meta_value' filter (above).
270
		 *
271
		 * The dynamic portion of the hook, $field_id, refers to the current
272
		 * field id paramater. Returning a non 'cmb2_field_no_override_val' value
273
		 * will effectively short-circuit the value retrieval.
274
		 *
275
		 * @since 2.0.0
276
		 */
277 70
		$data = apply_filters( "cmb2_override_{$a['field_id']}_meta_value", $data, $this->object_id, $a, $this );
278
279
		// If no override, get value normally
280 70
		if ( 'cmb2_field_no_override_val' === $data ) {
281 69
			$data = 'options-page' === $a['type']
282 69
				? cmb2_options( $a['id'] )->get( $a['field_id'] )
283 69
				: get_metadata( $a['type'], $a['id'], $a['field_id'], ( $a['single'] || $a['repeat'] ) );
284 69
		}
285
286 70
		if ( $this->group ) {
287
288
			$data = is_array( $data ) && isset( $data[ $this->group->index ][ $this->args( '_id' ) ] )
289
				? $data[ $this->group->index ][ $this->args( '_id' ) ]
290
				: false;
291
		}
292
293 70
		return $data;
294
	}
295
296
	/**
297
	 * Updates metadata/option data
298
	 * @since  1.0.1
299
	 * @param  mixed $new_value Value to update data with
300
	 * @param  bool  $single    Whether data is an array (add_metadata)
301
	 */
302 9
	public function update_data( $new_value, $single = true ) {
303 9
		$a = $this->data_args( array( 'single' => $single ) );
304
305 9
		$a['value'] = $a['repeat'] ? array_values( $new_value ) : $new_value;
306
307
		/**
308
		 * Filter whether to override saving of meta value.
309
		 * Returning a non-null value will effectively short-circuit the function.
310
		 *
311
		 * @since 2.0.0
312
		 *
313
		 * @param null|bool $check  Whether to allow updating metadata for the given type.
314
		 *
315
		 * @param array $args {
316
		 *     Array of data about current field including:
317
		 *
318
		 *     @type string $value    The value to set
319
		 *     @type string $type     The current object type
320
		 *     @type int    $id       The current object ID
321
		 *     @type string $field_id The ID of the field being updated
322
		 *     @type bool   $repeat   Whether current field is repeatable
323
		 *     @type bool   $single   Whether current field is a single database row
324
		 * }
325
		 *
326
		 * @param array $field_args All field arguments
327
		 *
328
		 * @param CMB2_Field object $field This field object
329
		 */
330 9
		$override = apply_filters( 'cmb2_override_meta_save', null, $a, $this->args(), $this );
331
332
		/**
333
		 * Filter and parameters are documented for 'cmb2_override_meta_save' filter (above).
334
		 *
335
		 * The dynamic portion of the hook, $a['field_id'], refers to the current
336
		 * field id paramater. Returning a non-null value
337
		 * will effectively short-circuit the function.
338
		 *
339
		 * @since 2.0.0
340
		 */
341 9
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_save", $override, $a, $this->args(), $this );
342
343
		// If override, return that
344 9
		if ( null !== $override ) {
345 1
			return $override;
346
		}
347
348
		// Options page handling (or temp data store)
349 9
		if ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
350 3
			return cmb2_options( $a['id'] )->update( $a['field_id'], $a['value'], false, $a['single'] );
351
		}
352
353
		// Add metadata if not single
354 6
		if ( ! $a['single'] ) {
355 1
			return add_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'], false );
356
		}
357
358
		// Delete meta if we have an empty array
359 5
		if ( is_array( $a['value'] ) && empty( $a['value'] ) ) {
360
			return delete_metadata( $a['type'], $a['id'], $a['field_id'], $this->value );
361
		}
362
363
		// Update metadata
364 5
		return update_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'] );
365
	}
366
367
	/**
368
	 * Removes/updates metadata/option data
369
	 * @since  1.0.1
370
	 * @param  string $old Old value
371
	 */
372 3
	public function remove_data( $old = '' ) {
373 3
		$a = $this->data_args( array( 'old' => $old ) );
374
375
		/**
376
		 * Filter whether to override removing of meta value.
377
		 * Returning a non-null value will effectively short-circuit the function.
378
		 *
379
		 * @since 2.0.0
380
		 *
381
		 * @param null|bool $delete Whether to allow metadata deletion of the given type.
382
		 * @param array $args       Array of data about current field including:
383
		 *                              'type'     : Current object type
384
		 *                              'id'       : Current object ID
385
		 *                              'field_id' : Current Field ID
386
		 *                              'repeat'   : Whether current field is repeatable
387
		 *                              'single'   : Whether to save as a
388
		 *                              					single meta value
389
		 * @param array $field_args All field arguments
390
		 * @param CMB2_Field object $field This field object
391
		 */
392 3
		$override = apply_filters( 'cmb2_override_meta_remove', null, $a, $this->args(), $this );
393
394
		/**
395
		 * Filter whether to override removing of meta value.
396
		 *
397
		 * The dynamic portion of the hook, $a['field_id'], refers to the current
398
		 * field id paramater. Returning a non-null value
399
		 * will effectively short-circuit the function.
400
		 *
401
		 * @since 2.0.0
402
		 *
403
		 * @param null|bool $delete Whether to allow metadata deletion of the given type.
404
		 * @param array $args       Array of data about current field including:
405
		 *                              'type'     : Current object type
406
		 *                              'id'       : Current object ID
407
		 *                              'field_id' : Current Field ID
408
		 *                              'repeat'   : Whether current field is repeatable
409
		 *                              'single'   : Whether to save as a
410
		 *                              					single meta value
411
		 * @param array $field_args All field arguments
412
		 * @param CMB2_Field object $field This field object
413
		 */
414 3
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this );
415
416
		// If no override, remove as usual
417 3
		if ( null !== $override ) {
418
			return $override;
419
		}
420
		// Option page handling
421 3
		elseif ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
422 1
			return cmb2_options( $a['id'] )->remove( $a['field_id'] );
423
		}
424
425
		// Remove metadata
426 2
		return delete_metadata( $a['type'], $a['id'], $a['field_id'], $old );
427
	}
428
429
	/**
430
	 * Data variables for get/set data methods
431
	 * @since  1.1.0
432
	 * @param  array $args Override arguments
433
	 * @return array       Updated arguments
434
	 */
435 70
	public function data_args( $args = array() ) {
436 70
		$args = wp_parse_args( $args, array(
437 70
			'type'     => $this->object_type,
438 70
			'id'       => $this->object_id,
439 70
			'field_id' => $this->id( true ),
440 70
			'repeat'   => $this->args( 'repeatable' ),
441 70
			'single'   => ! $this->args( 'multiple' ),
442 70
		) );
443 70
		return $args;
444
	}
445
446
	/**
447
	 * Checks if field has a registered sanitization callback
448
	 * @since  1.0.1
449
	 * @param  mixed $meta_value Meta value
450
	 * @return mixed             Possibly sanitized meta value
451
	 */
452 10
	public function sanitization_cb( $meta_value ) {
453
454 10
		if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) {
455
			// Remove empties
456 1
			$meta_value = array_filter( $meta_value );
457 1
		}
458
459
		// Check if the field has a registered validation callback
460 10
		$cb = $this->maybe_callback( 'sanitization_cb' );
461 10
		if ( false === $cb ) {
462
			// If requesting NO validation, return meta value
463
			return $meta_value;
464 10
		} elseif ( $cb ) {
465
			// Ok, callback is good, let's run it.
466
			return call_user_func( $cb, $meta_value, $this->args(), $this );
467
		}
468
469 10
		$sanitizer = new CMB2_Sanitize( $this, $meta_value );
470
471
		/**
472
		 * Filter the value before it is saved.
473
		 *
474
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
475
		 *
476
		 * Passing a non-null value to the filter will short-circuit saving
477
		 * the field value, saving the passed value instead.
478
		 *
479
		 * @param bool|mixed $override_value Sanitization/Validation override value to return.
480
		 *                                   Default false to skip it.
481
		 * @param mixed      $value      The value to be saved to this field.
482
		 * @param int        $object_id  The ID of the object where the value will be saved
483
		 * @param array      $field_args The current field's arguments
484
		 * @param object     $sanitizer  This `CMB2_Sanitize` object
485
		 */
486 10
		$override_value = apply_filters( "cmb2_sanitize_{$this->type()}", null, $sanitizer->value, $this->object_id, $this->args(), $sanitizer );
487
488 10
		if ( null !== $override_value ) {
489 1
			return $override_value;
490
		}
491
492
		// Sanitization via 'CMB2_Sanitize'
493 9
		return $sanitizer->{$this->type()}();
494
	}
495
496
	/**
497
	 * Process $_POST data to save this field's value
498
	 * @since  2.0.3
499
	 * @param  array $data_to_save $_POST data to check
500
	 * @return bool                Result of save
501
	 */
502 2
	public function save_field_from_data( array $data_to_save ) {
503 2
		$this->data_to_save = $data_to_save;
504
505 2
		$meta_value = isset( $this->data_to_save[ $this->id( true ) ] )
506 2
			? $this->data_to_save[ $this->id( true ) ]
507 2
			: null;
508
509 2
		return $this->save_field( $meta_value );
510
	}
511
512
	/**
513
	 * Sanitize/store a value to this field
514
	 * @since  2.0.0
515
	 * @param  array $meta_value Desired value to sanitize/store
516
	 * @return bool              Result of save
517
	 */
518 9
	public function save_field( $meta_value ) {
519
520 9
		$new_value = $this->sanitization_cb( $meta_value );
521 9
		$old       = $this->get_data();
522 9
		$updated   = false;
523 9
		$action    = '';
524
525 9
		if ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) {
526
527 1
			$this->remove_data();
528 1
			$count = 0;
529
530 1
			if ( ! empty( $new_value ) ) {
531 1
				foreach ( $new_value as $add_new ) {
532 1
					if ( $this->update_data( $add_new, false ) ) {
533 1
						$count++;
534 1
					}
535 1
				}
536 1
			}
537
538 1
			$updated = $count ? $count : false;
539 1
			$action  = 'repeatable';
540
541 9
		} elseif ( ! cmb2_utils()->isempty( $new_value ) && $new_value !== $old ) {
542 7
			$updated = $this->update_data( $new_value );
543 7
			$action  = 'updated';
544 8
		} elseif ( cmb2_utils()->isempty( $new_value ) ) {
545 2
			$updated = $this->remove_data();
546 2
			$action  = 'removed';
547 2
		}
548
549 9
		if ( $updated ) {
550 9
			$this->value = $this->get_data();
551 9
			$this->escaped_value = null;
552 9
		}
553
554 9
		$field_id = $this->id( true );
555
556
		/**
557
		 * Hooks after save field action.
558
		 *
559
		 * @since 2.2.0
560
		 *
561
		 * @param string            $field_id the current field id paramater.
562
		 * @param bool              $updated  Whether the metadata update action occurred.
563
		 * @param string            $action   Action performed. Could be "repeatable", "updated", or "removed".
564
		 * @param CMB2_Field object $field    This field object
565
		 */
566 9
		do_action( 'cmb2_save_field', $field_id, $updated, $action, $this );
567
568
		/**
569
		 * Hooks after save field action.
570
		 *
571
		 * The dynamic portion of the hook, $field_id, refers to the
572
		 * current field id paramater.
573
		 *
574
		 * @since 2.2.0
575
		 *
576
		 * @param bool              $updated Whether the metadata update action occurred.
577
		 * @param string            $action  Action performed. Could be "repeatable", "updated", or "removed".
578
		 * @param CMB2_Field object $field   This field object
579
		 */
580 8
		do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this );
581
582 7
		return $updated;
583
	}
584
585
	/**
586
	 * Checks if field has a callback value
587
	 * @since  1.0.1
588
	 * @param  string $cb Callback string
589
	 * @return mixed      NULL, false for NO validation, or $cb string if it exists.
590
	 */
591 63
	public function maybe_callback( $cb ) {
592 63
		$field_args = $this->args();
593 63
		if ( ! isset( $field_args[ $cb ] ) ) {
594 56
			return;
595
		}
596
597
		// Check if metabox is requesting NO validation
598 17
		$cb = false !== $field_args[ $cb ] && 'false' !== $field_args[ $cb ] ? $field_args[ $cb ] : false;
599
600
		// If requesting NO validation, return false
601 17
		if ( ! $cb ) {
602 8
			return false;
603
		}
604
605 17
		if ( is_callable( $cb ) ) {
606 16
			return $cb;
607
		}
608 5
	}
609
610
	/**
611
	 * Determine if current type is exempt from escaping
612
	 * @since  1.1.0
613
	 * @return bool  True if exempt
614
	 */
615 45
	public function escaping_exception() {
616
		// These types cannot be escaped
617 45
		return in_array( $this->type(), array(
618 45
			'file_list',
619 45
			'multicheck',
620 45
			'text_datetime_timestamp_timezone',
621 45
		) );
622
	}
623
624
	/**
625
	 * Determine if current type cannot be repeatable
626
	 * @since  1.1.0
627
	 * @param  string $type Field type to check
628
	 * @return bool         True if type cannot be repeatable
629
	 */
630 4
	public function repeatable_exception( $type ) {
631
		// These types cannot be escaped
632
		$internal_fields = array(
633
			// Use file_list instead
634 4
			'file'                => 1,
635 4
			'radio'               => 1,
636 4
			'title'               => 1,
637
			// @todo Ajax load wp_editor: http://wordpress.stackexchange.com/questions/51776/how-to-load-wp-editor-through-ajax-jquery
638 4
			'wysiwyg'             => 1,
639 4
			'checkbox'            => 1,
640 4
			'radio_inline'        => 1,
641 4
			'taxonomy_radio'      => 1,
642 4
			'taxonomy_select'     => 1,
643 4
			'taxonomy_multicheck' => 1,
644 4
		);
645
646
		/**
647
		 * Filter field types that are non-repeatable.
648
		 *
649
		 * Note that this does *not* allow overriding the default non-repeatable types.
650
		 *
651
		 * @since 2.1.1
652
		 *
653
		 * @param array $fields Array of fields designated as non-repeatable. Note that the field names are *keys*,
654
		 *                      and not values. The value can be anything, because it is meaningless. Example:
655
		 *                      array( 'my_custom_field' => 1 )
656
		 */
657 4
		$all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields );
658 4
		return isset( $all_fields[ $type ] );
659
	}
660
661
	/**
662
	 * Escape the value before output. Defaults to 'esc_attr()'
663
	 * @since  1.0.1
664
	 * @param  callable $func       Escaping function (if not esc_attr())
665
	 * @param  mixed    $meta_value Meta value
666
	 * @return mixed                Final value
667
	 */
668 45
	public function escaped_value( $func = 'esc_attr', $meta_value = '' ) {
669
670 45
		if ( null !== $this->escaped_value ) {
671 23
			return $this->escaped_value;
672
		}
673
674 45
		$meta_value = $meta_value ? $meta_value : $this->value();
675
676
		// Check if the field has a registered escaping callback
677 45
		if ( $cb = $this->maybe_callback( 'escape_cb' ) ) {
678
			// Ok, callback is good, let's run it.
679
			return call_user_func( $cb, $meta_value, $this->args(), $this );
680
		}
681
682
		// Or custom escaping filter can be used
683 45
		$esc = apply_filters( "cmb2_types_esc_{$this->type()}", null, $meta_value, $this->args(), $this );
684 45
		if ( null !== $esc ) {
685
			return $esc;
686
		}
687
688 45
		if ( false === $cb || $this->escaping_exception() ) {
689
			// If requesting NO escaping, return meta value
690 5
			return $this->val_or_default( $meta_value );
691
		}
692
693
		// escaping function passed in?
694 40
		$func       = $func ? $func : 'esc_attr';
695 40
		$meta_value = $this->val_or_default( $meta_value );
696
697 40
		if ( is_array( $meta_value ) ) {
698
			foreach ( $meta_value as $key => $value ) {
699
				$meta_value[ $key ] = call_user_func( $func, $value );
700
			}
701
		} else {
702 40
			$meta_value = call_user_func( $func, $meta_value );
703
		}
704
705 40
		$this->escaped_value = $meta_value;
706 40
		return $this->escaped_value;
707
	}
708
709
	/**
710
	 * Return non-empty value or field default if value IS empty
711
	 * @since  2.0.0
712
	 * @param  mixed $meta_value Field value
713
	 * @return mixed             Field value, or default value
714
	 */
715 45
	public function val_or_default( $meta_value ) {
716 45
		return ! cmb2_utils()->isempty( $meta_value ) ? $meta_value : $this->get_default();
717
	}
718
719
	/**
720
	 * Offset a time value based on timezone
721
	 * @since  1.0.0
722
	 * @return string Offset time string
723
	 */
724
	public function field_timezone_offset() {
725
		return cmb2_utils()->timezone_offset( $this->field_timezone() );
726
	}
727
728
	/**
729
	 * Return timezone string
730
	 * @since  1.0.0
731
	 * @return string Timezone string
732
	 */
733
	public function field_timezone() {
734
		$value = '';
735
736
		// Is timezone arg set?
737
		if ( $this->args( 'timezone' ) ) {
738
			$value = $this->args( 'timezone' );
739
		}
740
		// Is there another meta key with a timezone stored as its value we should use?
741
		else if ( $this->args( 'timezone_meta_key' ) ) {
742
			$value = $this->get_data( $this->args( 'timezone_meta_key' ) );
743
		}
744
745
		return $value;
746
	}
747
748
	/**
749
	 * Format the timestamp field value based on the field date/time format arg
750
	 * @since  2.0.0
751
	 * @param  int    $meta_value Timestamp
752
	 * @param  string $format     Either date_format or time_format
753
	 * @return string             Formatted date
754
	 */
755 5
	public function format_timestamp( $meta_value, $format = 'date_format' ) {
756 5
		return date( stripslashes( $this->args( $format ) ), $meta_value );
757
	}
758
759
	/**
760
	 * Return a formatted timestamp for a field
761
	 * @since  2.0.0
762
	 * @param  string $format     Either date_format or time_format
763
	 * @param  string $meta_value Optional meta value to check
764
	 * @return string             Formatted date
765
	 */
766 5
	public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) {
767 5
		$meta_value = $meta_value ? $meta_value : $this->escaped_value();
768 5
		$meta_value = cmb2_utils()->make_valid_time_stamp( $meta_value );
769
770 5
		if ( empty( $meta_value ) ) {
771
			return '';
772
		}
773
774 5
		return is_array( $meta_value )
775 5
			? array_map( array( $this, 'format_timestamp' ), $meta_value, $format )
776 5
			: $this->format_timestamp( $meta_value, $format );
777
	}
778
779
	/**
780
	 * Get timestamp from text date
781
	 * @since  2.2.0
782
	 * @param  string $value Date value
783
	 * @return mixed         Unix timestamp representing the date.
784
	 */
785
	public function get_timestamp_from_value( $value ) {
786
		return cmb2_utils()->get_timestamp_from_value( $value, $this->args( 'date_format' ) );
787
	}
788
789
	/**
790
	 * Get field render callback and Render the field row
791
	 * @since 1.0.0
792
	 */
793 10
	public function render_field() {
794 10
		$this->peform_param_callback( 'render_row_cb' );
795
		// For chaining
796 10
		return $this;
797
	}
798
799
	/**
800
	 * Default field render callback
801
	 * @since 2.1.1
802
	 */
803 9
	public function render_field_callback() {
804
805
		// If field is requesting to not be shown on the front-end
806 9
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
807
			return;
808
		}
809
810
		// If field is requesting to be conditionally shown
811 9
		if ( ! $this->should_show() ) {
812
			return;
813
		}
814
815 9
		$this->peform_param_callback( 'before_row' );
816
817 9
		printf( "<div class=\"cmb-row %s\" data-fieldtype=\"%s\">\n", $this->row_classes(), $this->type() );
818
819 9
		if ( ! $this->args( 'show_names' ) ) {
820
			echo "\n\t<div class=\"cmb-td\">\n";
821
822
			$this->peform_param_callback( 'label_cb' );
823
824
		} else {
825
826 9
			if ( $this->get_param_callback_result( 'label_cb' ) ) {
827 9
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
828 9
			}
829
830 9
			echo "\n\t<div class=\"cmb-td\">\n";
831
		}
832
833 9
		$this->peform_param_callback( 'before' );
834
835 9
		$field_type = new CMB2_Types( $this );
836 9
		$field_type->render();
837
838 9
		$this->peform_param_callback( 'after' );
839
840 9
		echo "\n\t</div>\n</div>";
841
842 9
		$this->peform_param_callback( 'after_row' );
843
844
		// For chaining
845 9
		return $this;
846
	}
847
848
	/**
849
	 * The default label_cb callback (if not a title field)
850
	 *
851
	 * @since  2.1.1
852
	 * @return string Label html markup
853
	 */
854 9
	public function label() {
855 9
		if ( ! $this->args( 'name' ) ) {
856
			return '';
857
		}
858
859 9
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
860
861 9
		return sprintf( "\n" . '<label%1$s for="%2$s">%3$s</label>' . "\n", $style, $this->id(), $this->args( 'name' ) );
862
	}
863
864
	/**
865
	 * Defines the classes for the current CMB2 field row
866
	 *
867
	 * @since  2.0.0
868
	 * @return string Space concatenated list of classes
869
	 */
870 12
	public function row_classes() {
871
872 12
		$classes = array();
873
874
		/**
875
		 * By default, 'text_url' and 'text' fields get table-like styling
876
		 *
877
		 * @since 2.0.0
878
		 *
879
		 * @param array $field_types The types of fields which should get the 'table-layout' class
880
		 */
881 12
		$repeat_table_rows_types = apply_filters( 'cmb2_repeat_table_row_types', array(
882 12
			'text_url', 'text',
883 12
		) );
884
885
		$conditional_classes = array(
886 12
			'cmb-type-' . str_replace( '_', '-', sanitize_html_class( $this->type() ) ) => true,
887 12
			'cmb2-id-' . str_replace( '_', '-', sanitize_html_class( $this->id() ) )    => true,
888 12
			'cmb-repeat'             => $this->args( 'repeatable' ),
889 12
			'cmb-repeat-group-field' => $this->group,
890 12
			'cmb-inline'             => $this->args( 'inline' ),
891 12
			'table-layout'           => in_array( $this->type(), $repeat_table_rows_types ),
892 12
		);
893
894 12
		foreach ( $conditional_classes as $class => $condition ) {
895 12
			if ( $condition ) {
896 12
				$classes[] = $class;
897 12
			}
898 12
		}
899
900 12
		if ( $added_classes = $this->get_param_callback_result( 'row_classes' ) ) {
901 3
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
902 3
		}
903
904 12
		if ( $added_classes ) {
905 3
			$classes[] = esc_attr( $added_classes );
906 3
		}
907
908
		/**
909
		 * Globally filter row classes
910
		 *
911
		 * @since 2.0.0
912
		 *
913
		 * @param string            $classes Space-separated list of row classes
914
		 * @param CMB2_Field object $field   This field object
915
		 */
916 12
		return apply_filters( 'cmb2_row_classes', implode( ' ', $classes ), $this );
917
	}
918
919
	/**
920
	 * Determine whether this field should show, based on the 'show_on_cb' callback.
921
	 *
922
	 * @since 2.0.9
923
	 *
924
	 * @return bool Whether the field should be shown.
925
	 */
926 10 View Code Duplication
	public function should_show() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
927
		// Default to showing the field
928 10
		$show = true;
929
930
		// Use the callback to determine showing the field, if it exists
931 10
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
932 1
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
933 1
		}
934
935 10
		return $show;
936
	}
937
938
	/**
939
	 * Displays the results of the param callbacks.
940
	 *
941
	 * @since 2.0.0
942
	 * @param string $param Field parameter
943
	 */
944 47
	public function peform_param_callback( $param ) {
945 47
		echo $this->get_param_callback_result( $param );
946 47
	}
947
948
	/**
949
	 * Store results of the param callbacks for continual access
950
	 * @since  2.0.0
951
	 * @param  string $param Field parameter
952
	 * @return mixed         Results of param/param callback
953
	 */
954 52
	public function get_param_callback_result( $param ) {
955
956
		// If we've already retrieved this param's value,
957 52
		if ( array_key_exists( $param, $this->callback_results ) ) {
958
959
			// send it back
960 10
			return $this->callback_results[ $param ];
961
		}
962
963
		// Check if parameter has registered a callback.
964 52
		if ( $cb = $this->maybe_callback( $param ) ) {
965
966
			// Ok, callback is good, let's run it and store the result.
967 16
			ob_start();
968 16
			$returned = call_user_func( $cb, $this->args(), $this );
969
970
			// Grab the result from the output buffer and store it.
971 16
			$echoed = ob_get_clean();
972
973
			// This checks if the user returned or echoed their callback.
974
			// Defaults to using the echoed value.
975 16
			$this->callback_results[ $param ] = $echoed ? $echoed : $returned;
976
977 16
		} else {
978
979
			// Otherwise just get whatever is there.
980 47
			$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
981
		}
982
983 52
		return $this->callback_results[ $param ];
984
	}
985
986
	/**
987
	 * Replaces a hash key - {#} - with the repeatable index
988
	 * @since  1.2.0
989
	 * @param  string $value Value to update
990
	 * @return string        Updated value
991
	 */
992 2
	public function replace_hash( $value ) {
993
		// Replace hash with 1 based count
994 2
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
995
	}
996
997
	/**
998
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
999
	 * For back-compatibility, falls back to checking the options array.
1000
	 *
1001
	 * @since  2.2.2
1002
	 * @param  string  $text_key Key in field's text array
1003
	 * @param  string  $fallback Fallback text
1004
	 * @return string            Text
1005
	 */
1006 6
	public function string( $text_key, $fallback ) {
1007
		// If null, populate with our field strings values.
1008 6
		if ( null === $this->strings ) {
1009 6
			$this->strings = (array) $this->args['text'];
1010
1011 6 View Code Duplication
			if ( is_callable( $this->args['text_cb'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1012
				$strings = call_user_func( $this->args['text_cb'], $this );
1013
1014
				if ( $strings && is_array( $strings ) ) {
1015
					$this->strings += $strings;
1016
				}
1017
			}
1018 6
		}
1019
1020
		// If we have that string value, send it back.
1021 6
		if ( isset( $this->strings[ $text_key ] ) ) {
1022 1
			return $this->strings[ $text_key ];
1023
		}
1024
1025
		// Check options for back-compat.
1026 6
		$string = $this->options( $text_key );
1027
1028 6
		return $string ? $string : $fallback;
0 ignored issues
show
Bug Compatibility introduced by
The expression $string ? $string : $fallback; of type array|string adds the type array to the return on line 1028 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
1029
	}
1030
1031
	/**
1032
	 * Retrieve options args. Calls options_cb if it exists.
1033
	 * @since  2.0.0
1034
	 * @param  string  $key Specific option to retrieve
1035
	 * @return array        Array of options
1036
	 */
1037 24
	public function options( $key = '' ) {
1038 24
		if ( ! empty( $this->field_options ) ) {
1039 5
			if ( $key ) {
1040 5
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1041
			}
1042
1043 1
			return $this->field_options;
1044
		}
1045
1046 24
		$this->field_options = (array) $this->args['options'];
1047
1048 24 View Code Duplication
		if ( is_callable( $this->args['options_cb'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1049 1
			$options = call_user_func( $this->args['options_cb'], $this );
1050
1051 1
			if ( $options && is_array( $options ) ) {
1052 1
				$this->field_options = $options + $this->field_options;
1053 1
			}
1054 1
		}
1055
1056 24
		if ( $key ) {
1057 8
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1058
		}
1059
1060 18
		return $this->field_options;
1061
	}
1062
1063
	/**
1064
	 * Store JS dependencies as part of the field args.
1065
	 * @since 2.2.0
1066
	 * @param array $dependencies Dependies to register for this field.
1067
	 */
1068 12
	public function add_js_dependencies( $dependencies = array() ) {
1069 12
		foreach ( (array) $dependencies as $dependency ) {
1070 12
			$this->args['js_dependencies'][ $dependency ] = $dependency;
1071 12
		}
1072
1073 12
		CMB2_JS::add_dependencies( $dependencies );
1074 12
	}
1075
1076
	/**
1077
	 * Get CMB2_Field default value, either from default param or default_cb param.
1078
	 *
1079
	 * @since  0.2.2
1080
	 *
1081
	 * @return mixed  Default field value
1082
	 */
1083 33
	public function get_default() {
1084 33
		if ( null !== $this->args['default'] ) {
1085 15
			return $this->args['default'];
1086
		}
1087
1088 32
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1089 32
		$default = $this->get_param_callback_result( $param );
1090
1091
		// Allow a filter override of the default value
1092 32
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1093
1094 32
		return $this->args['default'];
1095
	}
1096
1097
	/**
1098
	 * Fills in empty field parameters with defaults
1099
	 * @since 1.1.0
1100
	 * @param array $args Metabox field config array
1101
	 */
1102 74
	public function _set_field_defaults( $args ) {
1103
1104
		// Set up blank or default values for empty ones
1105 74
		$args = wp_parse_args( $args, array(
1106 74
			'type'              => '',
1107 74
			'name'              => '',
1108 74
			'desc'              => '',
1109 74
			'before'            => '',
1110 74
			'after'             => '',
1111 74
			'options'           => array(),
1112 74
			'options_cb'        => '',
1113 74
			'text'              => array(),
1114 74
			'text_cb'           => '',
1115 74
			'attributes'        => array(),
1116 74
			'protocols'         => null,
1117 74
			'default'           => null,
1118 74
			'default_cb'        => '',
1119 74
			'select_all_button' => true,
1120 74
			'multiple'          => false,
1121 74
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1122 74
			'inline'            => false,
1123 74
			'on_front'          => true,
1124 74
			'show_names'        => true,
1125 74
			'date_format'       => 'm\/d\/Y',
1126 74
			'time_format'       => 'h:i A',
1127 74
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1128 74
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1129 74
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1130 74
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1131 74
			'show_in_rest'      => null,
1132 74
			'js_dependencies'   => array(),
1133 74
		) );
1134
1135
		// default param can be passed a callback as well
1136 74
		if ( is_callable( $args['default'] ) ) {
1137 19
			$args['default_cb'] = $args['default'];
1138 19
			$args['default'] = null;
1139 19
		}
1140
1141 74
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1142 74
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1143
1144
		// options param can be passed a callback as well
1145 74
		if ( is_callable( $args['options'] ) ) {
1146
			$args['options_cb'] = $args['options'];
1147
			$args['options'] = array();
1148
		}
1149
1150 74
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1151 3
			'add_button'    => __( 'Add Group', 'cmb2' ),
1152 3
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1153 74
		) ) : $args['options'];
1154
1155 74
		$args['_id']        = $args['id'];
1156 74
		$args['_name']      = $args['id'];
1157
1158 74
		if ( $this->group ) {
1159
1160 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1161 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1162 3
		}
1163
1164 74
		if ( 'wysiwyg' == $args['type'] ) {
1165 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1166 1
			$args['options']['textarea_name'] = $args['_name'];
1167 1
		}
1168
1169 74
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1170
1171 74
		if ( in_array( $args['type'], $option_types, true ) ) {
1172
1173 10
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1174 10
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1175
1176 10
			if ( null === $args['show_option_none'] ) {
1177 9
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1178 9
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1179 9
			}
1180
1181 10
		}
1182
1183 74
		$args['has_supporting_data'] = in_array(
1184 74
			$args['type'],
1185
			array(
1186
				// CMB2_Sanitize::_save_file_id_value()/CMB2_Sanitize::_get_group_file_value_array()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1187 74
				'file',
1188
				// See CMB2_Sanitize::_save_utc_value()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1189 74
				'text_datetime_timestamp_timezone',
1190 74
			),
1191
			true
1192 74
		);
1193
1194 74
		return $args;
1195
	}
1196
1197
	/**
1198
	 * Returns a cloned version of this field object with, but with
1199
	 * modified/overridden field arguments.
1200
	 *
1201
	 * @since  2.2.2
1202
	 * @param  array  $field_args Array of field arguments, or entire array of
1203
	 *                            arguments for CMB2_Field
1204
	 *
1205
	 * @return CMB2_Field         The new CMB2_Field instance.
1206
	 */
1207 3
	public function get_field_clone( $field_args ) {
1208
		$args = array(
1209 3
			'field_args'  => array(),
1210 3
			'group_field' => $this->group,
1211 3
			'object_id'   => $this->object_id,
1212 3
			'object_type' => $this->object_type,
1213 3
			'cmb_id'      => $this->cmb_id,
1214 3
		);
1215
1216 3
		if ( isset( $field_args['field_args'] ) ) {
1217
			$args = wp_parse_args( $field_args, $args );
1218
		} else {
1219 3
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1220
		}
1221
1222 3
		return new CMB2_Field( $args );
1223
	}
1224
1225
	/**
1226
	 * Returns the CMB2 instance this field is registered to.
1227
	 *
1228
	 * @since  2.2.2
1229
	 *
1230
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1231
	 */
1232 1
	public function get_cmb() {
1233 1
		if ( ! $this->cmb_id ) {
1234
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1235
		}
1236
1237 1
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1238
	}
1239
1240
}
1241