Completed
Pull Request — trunk (#541)
by Justin
04:53
created

CMB2_Field::save_field_from_data()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
cc 2
eloc 6
nc 2
nop 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 field param callback results
77
	 * @var   array
78
	 * @since 2.0.0
79
	 */
80
	protected $callback_results = array();
81
82
	/**
83
	 * Array of key => value data for saving. Likely $_POST data.
84
	 * @var   array
85
	 * @since 2.0.0
86
	 */
87
	public $data_to_save = array();
88
89
	/**
90
	 * Current field's CMB2 instance ID
91
	 * @var   string
92
	 * @since 2.2.2
93
	 */
94
	public $cmb_id = '';
95
96
	/**
97
	 * All CMB2_Field callable field arguments.
98
	 * Can be used to determine if a field argument is callable.
99
	 *
100
	 * @var array
101
	 */
102
	public static $callable_fields = array(
103
		'default',
104
		'row_classes',
105
		'options_cb',
106
		'label_cb',
107
		'render_row_cb',
108
		'before_group',
109
		'before_group_row',
110
		'before_row',
111
		'before',
112
		'before_field',
113
		'after_field',
114
		'after',
115
		'after_row',
116
		'after_group_row',
117
		'after_group',
118
	);
119
120
	/**
121
	 * Constructs our field object
122
	 * @since 1.1.0
123
	 * @param array $args Field arguments
124
	 */
125 72
	public function __construct( $args ) {
126
127 72
		if ( ! empty( $args['group_field'] ) ) {
128 3
			$this->group       = $args['group_field'];
129 3
			$this->object_id   = $this->group->object_id;
130 3
			$this->object_type = $this->group->object_type;
131 3
			$this->cmb_id      = $this->group->cmb_id;
132 3
		} else {
133 72
			$this->object_id   = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0;
134 72
			$this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post';
135
136 72
			if ( isset( $args['cmb_id'] ) ) {
137 35
				$this->cmb_id = $args['cmb_id'];
138 35
			}
139
		}
140
141 72
		$this->args = $this->_set_field_defaults( $args['field_args'] );
142
143 72
		if ( $this->object_id ) {
144 66
			$this->value = $this->get_data();
145 66
		}
146 72
	}
147
148
	/**
149
	 * Non-existent methods fallback to checking for field arguments of the same name
150
	 * @since  1.1.0
151
	 * @param  string $name     Method name
152
	 * @param  array  $arguments Array of passed-in arguments
153
	 * @return mixed             Value of field argument
154
	 */
155 60
	public function __call( $name, $arguments ) {
156 60
		$key = isset( $arguments[0] ) ? $arguments[0] : false;
157 60
		return $this->args( $name, $key );
158
	}
159
160
	/**
161
	 * Retrieves the field id
162
	 * @since  1.1.0
163
	 * @param  boolean $raw Whether to retrieve pre-modidifed id
164
	 * @return string       Field id
165
	 */
166 70
	public function id( $raw = false ) {
167 70
		$id = $raw ? '_id' : 'id';
168 70
		return $this->args( $id );
169
	}
170
171
	/**
172
	 * Get a field argument
173
	 * @since  1.1.0
174
	 * @param  string $key  Argument to check
175
	 * @param  string $_key Sub argument to check
176
	 * @return mixed        Argument value or false if non-existent
177
	 */
178 73
	public function args( $key = '', $_key = '' ) {
179 73
		$arg = $this->_data( 'args', $key );
180
181 73
		if ( in_array( $key, array( 'default', 'default_cb' ), true ) ) {
182
183 1
			$arg = $this->get_default();
184
185 73
		} elseif ( $_key ) {
186
187
			$arg = isset( $arg[ $_key ] ) ? $arg[ $_key ] : false;
188
		}
189
190 73
		return $arg;
191
	}
192
193
	/**
194
	 * Retrieve a portion of a field property
195
	 * @since  1.1.0
196
	 * @param  string  $var Field property to check
197
	 * @param  string  $key Field property array key to check
198
	 * @return mixed        Queried property value or false
199
	 */
200 73
	public function _data( $var, $key = '' ) {
201 73
		$vars = $this->$var;
202 73
		if ( $key ) {
203 73
			return array_key_exists( $key, $vars ) ? $vars[ $key ] : false;
204
		}
205 61
		return $vars;
206
	}
207
208
	/**
209
	 * Get Field's value
210
	 * @since  1.1.0
211
	 * @param  string $key If value is an array, is used to get array key->value
212
	 * @return mixed       Field value or false if non-existent
213
	 */
214 44
	public function value( $key = '' ) {
215 44
		return $this->_data( 'value', $key );
216
	}
217
218
	/**
219
	 * Retrieves metadata/option data
220
	 * @since  1.0.1
221
	 * @param  string $field_id Meta key/Option array key
222
	 * @param  array  $args     Override arguments
223
	 * @return mixed            Meta/Option value
224
	 */
225 68
	public function get_data( $field_id = '', $args = array() ) {
226 68
		if ( $field_id ) {
227
			$args['field_id'] = $field_id;
228 68
		} else if ( $this->group ) {
229
			$args['field_id'] = $this->group->id();
230
		}
231
232 68
		$a = $this->data_args( $args );
233
234
		/**
235
		 * Filter whether to override getting of meta value.
236
		 * Returning a non 'cmb2_field_no_override_val' value
237
		 * will effectively short-circuit the value retrieval.
238
		 *
239
		 * @since 2.0.0
240
		 *
241
		 * @param mixed $value     The value get_metadata() should
242
		 *                         return - a single metadata value,
243
		 *                         or an array of values.
244
		 *
245
		 * @param int   $object_id Object ID.
246
		 *
247
		 * @param array $args {
248
		 *     An array of arguments for retrieving data
249
		 *
250
		 *     @type string $type     The current object type
251
		 *     @type int    $id       The current object ID
252
		 *     @type string $field_id The ID of the field being requested
253
		 *     @type bool   $repeat   Whether current field is repeatable
254
		 *     @type bool   $single   Whether current field is a single database row
255
		 * }
256
		 *
257
		 * @param CMB2_Field object $field This field object
258
		 */
259 68
		$data = apply_filters( 'cmb2_override_meta_value', 'cmb2_field_no_override_val', $this->object_id, $a, $this );
260
261
		/**
262
		 * Filter and parameters are documented for 'cmb2_override_meta_value' filter (above).
263
		 *
264
		 * The dynamic portion of the hook, $field_id, refers to the current
265
		 * field id paramater. Returning a non 'cmb2_field_no_override_val' value
266
		 * will effectively short-circuit the value retrieval.
267
		 *
268
		 * @since 2.0.0
269
		 */
270 68
		$data = apply_filters( "cmb2_override_{$a['field_id']}_meta_value", $data, $this->object_id, $a, $this );
271
272
		// If no override, get value normally
273 68
		if ( 'cmb2_field_no_override_val' === $data ) {
274 67
			$data = 'options-page' === $a['type']
275 67
				? cmb2_options( $a['id'] )->get( $a['field_id'] )
276 67
				: get_metadata( $a['type'], $a['id'], $a['field_id'], ( $a['single'] || $a['repeat'] ) );
277 67
		}
278
279 68
		if ( $this->group ) {
280
281
			$data = is_array( $data ) && isset( $data[ $this->group->index ][ $this->args( '_id' ) ] )
282
				? $data[ $this->group->index ][ $this->args( '_id' ) ]
283
				: false;
284
		}
285
286 68
		return $data;
287
	}
288
289
	/**
290
	 * Updates metadata/option data
291
	 * @since  1.0.1
292
	 * @param  mixed $new_value Value to update data with
293
	 * @param  bool  $single    Whether data is an array (add_metadata)
294
	 */
295 8
	public function update_data( $new_value, $single = true ) {
296 8
		$a = $this->data_args( array( 'single' => $single ) );
297
298 8
		$a['value'] = $a['repeat'] ? array_values( $new_value ) : $new_value;
299
300
		/**
301
		 * Filter whether to override saving of meta value.
302
		 * Returning a non-null value will effectively short-circuit the function.
303
		 *
304
		 * @since 2.0.0
305
		 *
306
		 * @param null|bool $check  Whether to allow updating metadata for the given type.
307
		 *
308
		 * @param array $args {
309
		 *     Array of data about current field including:
310
		 *
311
		 *     @type string $value    The value to set
312
		 *     @type string $type     The current object type
313
		 *     @type int    $id       The current object ID
314
		 *     @type string $field_id The ID of the field being updated
315
		 *     @type bool   $repeat   Whether current field is repeatable
316
		 *     @type bool   $single   Whether current field is a single database row
317
		 * }
318
		 *
319
		 * @param array $field_args All field arguments
320
		 *
321
		 * @param CMB2_Field object $field This field object
322
		 */
323 8
		$override = apply_filters( 'cmb2_override_meta_save', null, $a, $this->args(), $this );
324
325
		/**
326
		 * Filter and parameters are documented for 'cmb2_override_meta_save' filter (above).
327
		 *
328
		 * The dynamic portion of the hook, $a['field_id'], refers to the current
329
		 * field id paramater. Returning a non-null value
330
		 * will effectively short-circuit the function.
331
		 *
332
		 * @since 2.0.0
333
		 */
334 8
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_save", $override, $a, $this->args(), $this );
335
336
		// If override, return that
337 8
		if ( null !== $override ) {
338 1
			return $override;
339
		}
340
341
		// Options page handling (or temp data store)
342 8
		if ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
343 3
			return cmb2_options( $a['id'] )->update( $a['field_id'], $a['value'], false, $a['single'] );
344
		}
345
346
		// Add metadata if not single
347 5
		if ( ! $a['single'] ) {
348 1
			return add_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'], false );
349
		}
350
351
		// Delete meta if we have an empty array
352 4
		if ( is_array( $a['value'] ) && empty( $a['value'] ) ) {
353
			return delete_metadata( $a['type'], $a['id'], $a['field_id'], $this->value );
354
		}
355
356
		// Update metadata
357 4
		return update_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'] );
358
	}
359
360
	/**
361
	 * Removes/updates metadata/option data
362
	 * @since  1.0.1
363
	 * @param  string $old Old value
364
	 */
365 2
	public function remove_data( $old = '' ) {
366 2
		$a = $this->data_args( array( 'old' => $old ) );
367
368
		/**
369
		 * Filter whether to override removing of meta value.
370
		 * Returning a non-null value will effectively short-circuit the function.
371
		 *
372
		 * @since 2.0.0
373
		 *
374
		 * @param null|bool $delete Whether to allow metadata deletion of the given type.
375
		 * @param array $args       Array of data about current field including:
376
		 *                              'type'     : Current object type
377
		 *                              'id'       : Current object ID
378
		 *                              'field_id' : Current Field ID
379
		 *                              'repeat'   : Whether current field is repeatable
380
		 *                              'single'   : Whether to save as a
381
		 *                              					single meta value
382
		 * @param array $field_args All field arguments
383
		 * @param CMB2_Field object $field This field object
384
		 */
385 2
		$override = apply_filters( 'cmb2_override_meta_remove', null, $a, $this->args(), $this );
386
387
		/**
388
		 * Filter whether to override removing of meta value.
389
		 *
390
		 * The dynamic portion of the hook, $a['field_id'], refers to the current
391
		 * field id paramater. Returning a non-null value
392
		 * will effectively short-circuit the function.
393
		 *
394
		 * @since 2.0.0
395
		 *
396
		 * @param null|bool $delete Whether to allow metadata deletion of the given type.
397
		 * @param array $args       Array of data about current field including:
398
		 *                              'type'     : Current object type
399
		 *                              'id'       : Current object ID
400
		 *                              'field_id' : Current Field ID
401
		 *                              'repeat'   : Whether current field is repeatable
402
		 *                              'single'   : Whether to save as a
403
		 *                              					single meta value
404
		 * @param array $field_args All field arguments
405
		 * @param CMB2_Field object $field This field object
406
		 */
407 2
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this );
408
409
		// If no override, remove as usual
410 2
		if ( null !== $override ) {
411
			return $override;
412
		}
413
		// Option page handling
414 2
		elseif ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
415 1
			return cmb2_options( $a['id'] )->remove( $a['field_id'] );
416
		}
417
418
		// Remove metadata
419 1
		return delete_metadata( $a['type'], $a['id'], $a['field_id'], $old );
420
	}
421
422
	/**
423
	 * Data variables for get/set data methods
424
	 * @since  1.1.0
425
	 * @param  array $args Override arguments
426
	 * @return array       Updated arguments
427
	 */
428 68
	public function data_args( $args = array() ) {
429 68
		$args = wp_parse_args( $args, array(
430 68
			'type'     => $this->object_type,
431 68
			'id'       => $this->object_id,
432 68
			'field_id' => $this->id( true ),
433 68
			'repeat'   => $this->args( 'repeatable' ),
434 68
			'single'   => ! $this->args( 'multiple' ),
435 68
		) );
436 68
		return $args;
437
	}
438
439
	/**
440
	 * Checks if field has a registered sanitization callback
441
	 * @since  1.0.1
442
	 * @param  mixed $meta_value Meta value
443
	 * @return mixed             Possibly sanitized meta value
444
	 */
445 9
	public function sanitization_cb( $meta_value ) {
446
447 9
		if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) {
448
			// Remove empties
449 1
			$meta_value = array_filter( $meta_value );
450 1
		}
451
452
		// Check if the field has a registered validation callback
453 9
		$cb = $this->maybe_callback( 'sanitization_cb' );
454 9
		if ( false === $cb ) {
455
			// If requesting NO validation, return meta value
456
			return $meta_value;
457 9
		} elseif ( $cb ) {
458
			// Ok, callback is good, let's run it.
459
			return call_user_func( $cb, $meta_value, $this->args(), $this );
460
		}
461
462 9
		$sanitizer = new CMB2_Sanitize( $this, $meta_value );
463
464
		/**
465
		 * Filter the value before it is saved.
466
		 *
467
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
468
		 *
469
		 * Passing a non-null value to the filter will short-circuit saving
470
		 * the field value, saving the passed value instead.
471
		 *
472
		 * @param bool|mixed $override_value Sanitization/Validation override value to return.
473
		 *                                   Default false to skip it.
474
		 * @param mixed      $value      The value to be saved to this field.
475
		 * @param int        $object_id  The ID of the object where the value will be saved
476
		 * @param array      $field_args The current field's arguments
477
		 * @param object     $sanitizer  This `CMB2_Sanitize` object
478
		 */
479 9
		$override_value = apply_filters( "cmb2_sanitize_{$this->type()}", null, $sanitizer->value, $this->object_id, $this->args(), $sanitizer );
480
481 9
		if ( null !== $override_value ) {
482 1
			return $override_value;
483
		}
484
485
		// Sanitization via 'CMB2_Sanitize'
486 8
		return $sanitizer->{$this->type()}();
487
	}
488
489
	/**
490
	 * Process $_POST data to save this field's value
491
	 * @since  2.0.3
492
	 * @param  array $data_to_save $_POST data to check
493
	 * @return bool                Result of save
494
	 */
495 2
	public function save_field_from_data( array $data_to_save ) {
496 2
		$this->data_to_save = $data_to_save;
497
498 2
		$meta_value = isset( $this->data_to_save[ $this->id( true ) ] )
499 2
			? $this->data_to_save[ $this->id( true ) ]
500 2
			: null;
501
502 2
		return $this->save_field( $meta_value );
503
	}
504
505
	/**
506
	 * Sanitize/store a value to this field
507
	 * @since  2.0.0
508
	 * @param  array $meta_value Desired value to sanitize/store
509
	 * @return bool              Result of save
510
	 */
511 8
	public function save_field( $meta_value ) {
512
513 8
		$new_value = $this->sanitization_cb( $meta_value );
514 8
		$old       = $this->get_data();
515 8
		$updated   = false;
516 8
		$action    = '';
517
518 8
		if ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) {
519
520 1
			$this->remove_data();
521 1
			$count = 0;
522
523 1
			if ( ! empty( $new_value ) ) {
524 1
				foreach ( $new_value as $add_new ) {
525 1
					if ( $this->update_data( $add_new, false ) ) {
526 1
						$count++;
527 1
					}
528 1
				}
529 1
			}
530
531 1
			$updated = $count ? $count : false;
532 1
			$action  = 'repeatable';
533
534 8
		} elseif ( ! cmb2_utils()->isempty( $new_value ) && $new_value !== $old ) {
535 6
			$updated = $this->update_data( $new_value );
536 6
			$action  = 'updated';
537 7
		} elseif ( cmb2_utils()->isempty( $new_value ) ) {
538 1
			$updated = $this->remove_data();
539 1
			$action  = 'removed';
540 1
		}
541
542 8
		if ( $updated ) {
543 8
			$this->value = $this->get_data();
544 8
			$this->escaped_value = null;
545 8
		}
546
547 8
		$field_id = $this->id( true );
548
549
		/**
550
		 * Hooks after save field action.
551
		 *
552
		 * @since 2.2.0
553
		 *
554
		 * @param string            $field_id the current field id paramater.
555
		 * @param bool              $updated  Whether the metadata update action occurred.
556
		 * @param string            $action   Action performed. Could be "repeatable", "updated", or "removed".
557
		 * @param CMB2_Field object $field    This field object
558
		 */
559 8
		do_action( 'cmb2_save_field', $field_id, $updated, $action, $this );
560
561
		/**
562
		 * Hooks after save field action.
563
		 *
564
		 * The dynamic portion of the hook, $field_id, refers to the
565
		 * current field id paramater.
566
		 *
567
		 * @since 2.2.0
568
		 *
569
		 * @param bool              $updated Whether the metadata update action occurred.
570
		 * @param string            $action  Action performed. Could be "repeatable", "updated", or "removed".
571
		 * @param CMB2_Field object $field   This field object
572
		 */
573 7
		do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this );
574
575 6
		return $updated;
576
	}
577
578
	/**
579
	 * Checks if field has a callback value
580
	 * @since  1.0.1
581
	 * @param  string $cb Callback string
582
	 * @return mixed      NULL, false for NO validation, or $cb string if it exists.
583
	 */
584 61
	public function maybe_callback( $cb ) {
585 61
		$field_args = $this->args();
586 61
		if ( ! isset( $field_args[ $cb ] ) ) {
587 54
			return;
588
		}
589
590
		// Check if metabox is requesting NO validation
591 16
		$cb = false !== $field_args[ $cb ] && 'false' !== $field_args[ $cb ] ? $field_args[ $cb ] : false;
592
593
		// If requesting NO validation, return false
594 16
		if ( ! $cb ) {
595 7
			return false;
596
		}
597
598 16
		if ( is_callable( $cb ) ) {
599 15
			return $cb;
600
		}
601 5
	}
602
603
	/**
604
	 * Determine if current type is excempt from escaping
605
	 * @since  1.1.0
606
	 * @return bool  True if exempt
607
	 */
608 44
	public function escaping_exception() {
609
		// These types cannot be escaped
610 44
		return in_array( $this->type(), array(
611 44
			'file_list',
612 44
			'multicheck',
613 44
			'text_datetime_timestamp_timezone',
614 44
		) );
615
	}
616
617
	/**
618
	 * Determine if current type cannot be repeatable
619
	 * @since  1.1.0
620
	 * @param  string $type Field type to check
621
	 * @return bool         True if type cannot be repeatable
622
	 */
623 3
	public function repeatable_exception( $type ) {
624
		// These types cannot be escaped
625
		$internal_fields = array(
626
			// Use file_list instead
627 3
			'file'                => 1,
628 3
			'radio'               => 1,
629 3
			'title'               => 1,
630
			// @todo Ajax load wp_editor: http://wordpress.stackexchange.com/questions/51776/how-to-load-wp-editor-through-ajax-jquery
631 3
			'wysiwyg'             => 1,
632 3
			'checkbox'            => 1,
633 3
			'radio_inline'        => 1,
634 3
			'taxonomy_radio'      => 1,
635 3
			'taxonomy_select'     => 1,
636 3
			'taxonomy_multicheck' => 1,
637 3
		);
638
639
		/**
640
		 * Filter field types that are non-repeatable.
641
		 *
642
		 * Note that this does *not* allow overriding the default non-repeatable types.
643
		 *
644
		 * @since 2.1.1
645
		 *
646
		 * @param array $fields Array of fields designated as non-repeatable. Note that the field names are *keys*,
647
		 *                      and not values. The value can be anything, because it is meaningless. Example:
648
		 *                      array( 'my_custom_field' => 1 )
649
		 */
650 3
		$all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields );
651 3
		return isset( $all_fields[ $type ] );
652
	}
653
654
	/**
655
	 * Escape the value before output. Defaults to 'esc_attr()'
656
	 * @since  1.0.1
657
	 * @param  callable $func       Escaping function (if not esc_attr())
658
	 * @param  mixed    $meta_value Meta value
659
	 * @return mixed                Final value
660
	 */
661 44
	public function escaped_value( $func = 'esc_attr', $meta_value = '' ) {
662
663 44
		if ( null !== $this->escaped_value ) {
664 22
			return $this->escaped_value;
665
		}
666
667 44
		$meta_value = $meta_value ? $meta_value : $this->value();
668
669
		// Check if the field has a registered escaping callback
670 44
		if ( $cb = $this->maybe_callback( 'escape_cb' ) ) {
671
			// Ok, callback is good, let's run it.
672
			return call_user_func( $cb, $meta_value, $this->args(), $this );
673
		}
674
675
		// Or custom escaping filter can be used
676 44
		$esc = apply_filters( "cmb2_types_esc_{$this->type()}", null, $meta_value, $this->args(), $this );
677 44
		if ( null !== $esc ) {
678
			return $esc;
679
		}
680
681 44
		if ( false === $cb || $this->escaping_exception() ) {
682
			// If requesting NO escaping, return meta value
683 5
			return $this->val_or_default( $meta_value );
684
		}
685
686
		// escaping function passed in?
687 39
		$func       = $func ? $func : 'esc_attr';
688 39
		$meta_value = $this->val_or_default( $meta_value );
689
690 39
		if ( is_array( $meta_value ) ) {
691
			foreach ( $meta_value as $key => $value ) {
692
				$meta_value[ $key ] = call_user_func( $func, $value );
693
			}
694
		} else {
695 39
			$meta_value = call_user_func( $func, $meta_value );
696
		}
697
698 39
		$this->escaped_value = $meta_value;
699 39
		return $this->escaped_value;
700
	}
701
702
	/**
703
	 * Return non-empty value or field default if value IS empty
704
	 * @since  2.0.0
705
	 * @param  mixed $meta_value Field value
706
	 * @return mixed             Field value, or default value
707
	 */
708 44
	public function val_or_default( $meta_value ) {
709 44
		return ! cmb2_utils()->isempty( $meta_value ) ? $meta_value : $this->get_default();
710
	}
711
712
	/**
713
	 * Offset a time value based on timezone
714
	 * @since  1.0.0
715
	 * @return string Offset time string
716
	 */
717
	public function field_timezone_offset() {
718
		return cmb2_utils()->timezone_offset( $this->field_timezone() );
719
	}
720
721
	/**
722
	 * Return timezone string
723
	 * @since  1.0.0
724
	 * @return string Timezone string
725
	 */
726
	public function field_timezone() {
727
		$value = '';
728
729
		// Is timezone arg set?
730
		if ( $this->args( 'timezone' ) ) {
731
			$value = $this->args( 'timezone' );
732
		}
733
		// Is there another meta key with a timezone stored as its value we should use?
734
		else if ( $this->args( 'timezone_meta_key' ) ) {
735
			$value = $this->get_data( $this->args( 'timezone_meta_key' ) );
736
		}
737
738
		return $value;
739
	}
740
741
	/**
742
	 * Format the timestamp field value based on the field date/time format arg
743
	 * @since  2.0.0
744
	 * @param  int    $meta_value Timestamp
745
	 * @param  string $format     Either date_format or time_format
746
	 * @return string             Formatted date
747
	 */
748 5
	public function format_timestamp( $meta_value, $format = 'date_format' ) {
749 5
		return date( stripslashes( $this->args( $format ) ), $meta_value );
750
	}
751
752
	/**
753
	 * Return a formatted timestamp for a field
754
	 * @since  2.0.0
755
	 * @param  string $format     Either date_format or time_format
756
	 * @param  string $meta_value Optional meta value to check
757
	 * @return string             Formatted date
758
	 */
759 5
	public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) {
760 5
		$meta_value = $meta_value ? $meta_value : $this->escaped_value();
761 5
		$meta_value = cmb2_utils()->make_valid_time_stamp( $meta_value );
762
763 5
		if ( empty( $meta_value ) ) {
764
			return '';
765
		}
766
767 5
		return is_array( $meta_value )
768 5
			? array_map( array( $this, 'format_timestamp' ), $meta_value, $format )
769 5
			: $this->format_timestamp( $meta_value, $format );
770
	}
771
772
	/**
773
	 * Get timestamp from text date
774
	 * @since  2.2.0
775
	 * @param  string $value Date value
776
	 * @return mixed         Unix timestamp representing the date.
777
	 */
778
	public function get_timestamp_from_value( $value ) {
779
		return cmb2_utils()->get_timestamp_from_value( $value, $this->args( 'date_format' ) );
780
	}
781
782
	/**
783
	 * Get field render callback and Render the field row
784
	 * @since 1.0.0
785
	 */
786 9
	public function render_field() {
787
		// Check if the field has a registered render_field callback
788 9
		if ( $cb = $this->maybe_callback( 'render_row_cb' ) ) {
789
			// Ok, callback is good, let's run it.
790 9
			return call_user_func( $cb, $this->args(), $this );
791
		}
792
	}
793
794
	/**
795
	 * Default field render callback
796
	 * @since 2.1.1
797
	 */
798 8
	public function render_field_callback() {
799
800
		// If field is requesting to not be shown on the front-end
801 8
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
802
			return;
803
		}
804
805
		// If field is requesting to be conditionally shown
806 8
		if ( ! $this->should_show() ) {
807
			return;
808
		}
809
810 8
		$this->peform_param_callback( 'before_row' );
811
812 8
		printf( "<div class=\"cmb-row %s\" data-fieldtype=\"%s\">\n", $this->row_classes(), $this->type() );
813
814 8
		if ( ! $this->args( 'show_names' ) ) {
815
			echo "\n\t<div class=\"cmb-td\">\n";
816
817
			$this->peform_param_callback( 'label_cb' );
818
819
		} else {
820
821 8
			if ( $this->get_param_callback_result( 'label_cb', false ) ) {
822 8
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
823 8
			}
824
825 8
			echo "\n\t<div class=\"cmb-td\">\n";
826
		}
827
828 8
		$this->peform_param_callback( 'before' );
829
830 8
		$field_type = new CMB2_Types( $this );
831 8
		$field_type->render();
832
833 8
		$this->peform_param_callback( 'after' );
834
835 8
		echo "\n\t</div>\n</div>";
836
837 8
		$this->peform_param_callback( 'after_row' );
838
839
		// For chaining
840 8
		return $this;
841
	}
842
843
	/**
844
	 * The default label_cb callback (if not a title field)
845
	 *
846
	 * @since  2.1.1
847
	 * @return string Label html markup
848
	 */
849 8
	public function label() {
850 8
		if ( ! $this->args( 'name' ) ) {
851
			return '';
852
		}
853
854 8
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
855
856 8
		return sprintf( "\n" . '<label%1$s for="%2$s">%3$s</label>' . "\n", $style, $this->id(), $this->args( 'name' ) );
857
	}
858
859
	/**
860
	 * Defines the classes for the current CMB2 field row
861
	 *
862
	 * @since  2.0.0
863
	 * @return string Space concatenated list of classes
864
	 */
865 11
	public function row_classes() {
866
867 11
		$classes = array();
868
869
		/**
870
		 * By default, 'text_url' and 'text' fields get table-like styling
871
		 *
872
		 * @since 2.0.0
873
		 *
874
		 * @param array $field_types The types of fields which should get the 'table-layout' class
875
		 */
876 11
		$repeat_table_rows_types = apply_filters( 'cmb2_repeat_table_row_types', array(
877 11
			'text_url', 'text',
878 11
		) );
879
880
		$conditional_classes = array(
881 11
			'cmb-type-' . str_replace( '_', '-', sanitize_html_class( $this->type() ) ) => true,
882 11
			'cmb2-id-' . str_replace( '_', '-', sanitize_html_class( $this->id() ) )    => true,
883 11
			'cmb-repeat'             => $this->args( 'repeatable' ),
884 11
			'cmb-repeat-group-field' => $this->group,
885 11
			'cmb-inline'             => $this->args( 'inline' ),
886 11
			'table-layout'           => in_array( $this->type(), $repeat_table_rows_types ),
887 11
		);
888
889 11
		foreach ( $conditional_classes as $class => $condition ) {
890 11
			if ( $condition ) {
891 11
				$classes[] = $class;
892 11
			}
893 11
		}
894
895 11
		if ( $added_classes = $this->get_param_callback_result( 'row_classes', false ) ) {
896 3
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
897 3
		}
898
899 11
		if ( $added_classes ) {
900 3
			$classes[] = esc_attr( $added_classes );
901 3
		}
902
903
		/**
904
		 * Globally filter row classes
905
		 *
906
		 * @since 2.0.0
907
		 *
908
		 * @param string            $classes Space-separated list of row classes
909
		 * @param CMB2_Field object $field   This field object
910
		 */
911 11
		return apply_filters( 'cmb2_row_classes', implode( ' ', $classes ), $this );
912
	}
913
914
	/**
915
	 * Determine whether this field should show, based on the 'show_on_cb' callback.
916
	 *
917
	 * @since 2.0.9
918
	 *
919
	 * @return bool Whether the field should be shown.
920
	 */
921 9 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...
922
		// Default to showing the field
923 9
		$show = true;
924
925
		// Use the callback to determine showing the field, if it exists
926 9
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
927 1
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
928 1
		}
929
930 9
		return $show;
931
	}
932
933
	/**
934
	 * Displays the results of the param callbacks.
935
	 *
936
	 * @since 2.0.0
937
	 * @param string $param Field parameter
938
	 */
939 45
	public function peform_param_callback( $param ) {
940 45
		echo $this->get_param_callback_result( $param );
941 45
	}
942
943
	/**
944
	 * Store results of the param callbacks for continual access
945
	 * @since  2.0.0
946
	 * @param  string $param Field parameter
947
	 * @param  bool   $echo  Whether field should be 'echoed'
948
	 * @return mixed         Results of param/param callback
949
	 */
950 50
	public function get_param_callback_result( $param, $echo = true ) {
951
952
		// If we've already retrieved this param's value,
953 50
		if ( array_key_exists( $param, $this->callback_results ) ) {
954
			// send it back
955 9
			return $this->callback_results[ $param ];
956
		}
957
958 50
		if ( $cb = $this->maybe_callback( $param ) ) {
959 14
			if ( $echo ) {
960
				// Ok, callback is good, let's run it and store the result
961 4
				ob_start();
962 4
				echo call_user_func( $cb, $this->args(), $this );
963
				// grab the result from the output buffer and store it
964 4
				$this->callback_results[ $param ] = ob_get_contents();
965 4
				ob_end_clean();
966 4
			} else {
967 11
				$this->callback_results[ $param ] = call_user_func( $cb, $this->args(), $this );
968
			}
969
970 14
			return $this->callback_results[ $param ];
971
		}
972
973
		// Otherwise just get whatever is there
974 46
		$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
975
976 46
		return $this->callback_results[ $param ];
977
	}
978
979
	/**
980
	 * Replaces a hash key - {#} - with the repeatable index
981
	 * @since  1.2.0
982
	 * @param  string $value Value to update
983
	 * @return string        Updated value
984
	 */
985 2
	public function replace_hash( $value ) {
986
		// Replace hash with 1 based count
987 2
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
988
	}
989
990
	/**
991
	 * Retrieve options args. Calls options_cb if it exists.
992
	 * @since  2.0.0
993
	 * @param  string  $key Specific option to retrieve
994
	 * @return array        Array of options
995
	 */
996 23
	public function options( $key = '' ) {
997 23
		if ( ! empty( $this->field_options ) ) {
998 4
			if ( $key ) {
999 4
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1000
			}
1001
1002
			return $this->field_options;
1003
		}
1004
1005 23
		$this->field_options = (array) $this->args['options'];
1006
1007 23
		if ( is_callable( $this->args['options_cb'] ) ) {
1008 1
			$options = call_user_func( $this->args['options_cb'], $this );
1009
1010 1
			if ( $options && is_array( $options ) ) {
1011 1
				$this->field_options += $options;
1012 1
			}
1013 1
		}
1014
1015 23
		if ( $key ) {
1016 8
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1017
		}
1018
1019 17
		return $this->field_options;
1020
	}
1021
1022
	/**
1023
	 * Store JS dependencies as part of the field args.
1024
	 * @since 2.2.0
1025
	 * @param array $dependencies Dependies to register for this field.
1026
	 */
1027 12
	public function add_js_dependencies( $dependencies = array() ) {
1028 12
		foreach ( (array) $dependencies as $dependency ) {
1029 12
			$this->args['js_dependencies'][ $dependency ] = $dependency;
1030 12
		}
1031
1032 12
		CMB2_JS::add_dependencies( $dependencies );
1033 12
	}
1034
1035
	/**
1036
	 * Get CMB2_Field default value, either from default param or default_cb param.
1037
	 *
1038
	 * @since  0.2.2
1039
	 *
1040
	 * @return mixed  Default field value
1041
	 */
1042 32
	public function get_default() {
1043 32
		if ( null !== $this->args['default'] ) {
1044 14
			return $this->args['default'];
1045
		}
1046
1047 31
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1048 31
		$default = $this->get_param_callback_result( $param, false );
1049
1050
		// Allow a filter override of the default value
1051 31
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1052
1053 31
		return $this->args['default'];
1054
	}
1055
1056
	/**
1057
	 * Fills in empty field parameters with defaults
1058
	 * @since 1.1.0
1059
	 * @param array $args Metabox field config array
1060
	 */
1061 72
	public function _set_field_defaults( $args ) {
1062
1063
		// Set up blank or default values for empty ones
1064 72
		$args = wp_parse_args( $args, array(
1065 72
			'type'              => '',
1066 72
			'name'              => '',
1067 72
			'desc'              => '',
1068 72
			'before'            => '',
1069 72
			'after'             => '',
1070 72
			'options_cb'        => '',
1071 72
			'options'           => array(),
1072 72
			'attributes'        => array(),
1073 72
			'protocols'         => null,
1074 72
			'default'           => null,
1075 72
			'default_cb'        => '',
1076 72
			'select_all_button' => true,
1077 72
			'multiple'          => false,
1078 72
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1079 72
			'inline'            => false,
1080 72
			'on_front'          => true,
1081 72
			'show_names'        => true,
1082 72
			'date_format'       => 'm\/d\/Y',
1083 72
			'time_format'       => 'h:i A',
1084 72
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1085 72
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1086 72
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1087 72
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1088 72
			'show_in_rest'      => null,
1089 72
			'js_dependencies'   => array(),
1090 72
		) );
1091
1092
		// default param can be passed a callback as well
1093 72
		if ( is_callable( $args['default'] ) ) {
1094 18
			$args['default_cb'] = $args['default'];
1095 18
			$args['default'] = null;
1096 18
		}
1097
1098 72
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1099 72
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1100
1101
		// options param can be passed a callback as well
1102 72
		if ( is_callable( $args['options'] ) ) {
1103
			$args['options_cb'] = $args['options'];
1104
			$args['options'] = array();
1105
		}
1106
1107 72
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1108 3
			'add_button'    => __( 'Add Group', 'cmb2' ),
1109 3
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1110 72
		) ) : $args['options'];
1111
1112 72
		$args['_id']        = $args['id'];
1113 72
		$args['_name']      = $args['id'];
1114
1115 72
		if ( $this->group ) {
1116
1117 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1118 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1119 3
		}
1120
1121 72
		if ( 'wysiwyg' == $args['type'] ) {
1122 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1123 1
			$args['options']['textarea_name'] = $args['_name'];
1124 1
		}
1125
1126 72
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1127
1128 72
		if ( in_array( $args['type'], $option_types, true ) ) {
1129
1130 9
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1131 9
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1132
1133 9
			if ( null === $args['show_option_none'] ) {
1134 9
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1135 9
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1136 9
			}
1137
1138 9
		}
1139
1140 72
		$args['has_supporting_data'] = in_array(
1141 72
			$args['type'],
1142
			array(
1143
				// 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...
1144 72
				'file',
1145
				// 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...
1146 72
				'text_datetime_timestamp_timezone',
1147 72
			),
1148
			true
1149 72
		);
1150
1151 72
		return $args;
1152
	}
1153
1154
	/**
1155
	 * Returns a cloned version of this field object with, but with
1156
	 * modified/overridden field arguments.
1157
	 *
1158
	 * @since  2.2.2
1159
	 * @param  array  $field_args Array of field arguments, or entire array of
1160
	 *                            arguments for CMB2_Field
1161
	 *
1162
	 * @return CMB2_Field         The new CMB2_Field instance.
1163
	 */
1164 3
	public function get_field_clone( $field_args ) {
1165
		$args = array(
1166 3
			'field_args'  => array(),
1167 3
			'group_field' => $this->group,
1168 3
			'object_id'   => $this->object_id,
1169 3
			'object_type' => $this->object_type,
1170 3
			'cmb_id'      => $this->cmb_id,
1171 3
		);
1172
1173 3
		if ( isset( $field_args['field_args'] ) ) {
1174
			$args = wp_parse_args( $field_args, $args );
1175
		} else {
1176 3
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1177
		}
1178
1179 3
		return new CMB2_Field( $args );
1180
	}
1181
1182
	/**
1183
	 * Returns the CMB2 instance this field is registered to.
1184
	 *
1185
	 * @since  2.2.2
1186
	 *
1187
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1188
	 */
1189 1
	public function get_cmb() {
1190 1
		if ( ! $this->cmb_id ) {
1191
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1192
		}
1193
1194 1
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1195
	}
1196
1197
}
1198