Completed
Pull Request — trunk (#541)
by Justin
12:21 queued 08:58
created

CMB2_Field::options()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 25
Code Lines 13

Duplication

Lines 7
Ratio 28 %

Code Coverage

Tests 15
CRAP Score 9

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 9
eloc 13
c 2
b 1
f 0
nc 12
nop 1
dl 7
loc 25
rs 4.909
ccs 15
cts 15
cp 1
crap 9
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 extends CMB2_Base {
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
	 * The object properties name.
21
	 * @var   string
22
	 * @since 2.2.3
23
	 */
24
	protected $properties_name = 'args';
25
26
	/**
27
	 * Field arguments
28
	 * @var   mixed
29
	 * @since 1.1.0
30
	 */
31
	public $args = array();
32
33
	/**
34
	 * Field group object or false (if no group)
35
	 * @var   mixed
36
	 * @since 1.1.0
37
	 */
38
	public $group = false;
39
40
	/**
41
	 * Field meta value
42
	 * @var   mixed
43
	 * @since 1.1.0
44
	 */
45
	public $value = null;
46
47
	/**
48
	 * Field meta value
49
	 * @var   mixed
50
	 * @since 1.1.0
51
	 */
52
	public $escaped_value = null;
53
54
	/**
55
	 * Grouped Field's current numeric index during the save process
56
	 * @var   mixed
57
	 * @since 2.0.0
58
	 */
59
	public $index = 0;
60
61
	/**
62
	 * Array of field options
63
	 * @var   array
64
	 * @since 2.0.0
65
	 */
66
	protected $field_options = array();
67
68
	/**
69
	 * Array of provided field text strings
70
	 * @var   array
71
	 * @since 2.0.0
72
	 */
73
	protected $strings;
74
75
	/**
76
	 * The field's render context. In most cases, 'edit', but can be 'display'.
77
	 * @var   string
78
	 * @since 2.2.2
79
	 */
80
	public $render_context = 'edit';
81
82
	/**
83
	 * All CMB2_Field callable field arguments.
84
	 * Can be used to determine if a field argument is callable.
85
	 *
86
	 * @var array
87
	 */
88
	public static $callable_fields = array(
89
		'default',
90
		'row_classes',
91
		'options_cb',
92
		'label_cb',
93
		'render_row_cb',
94
		'before_group',
95
		'before_group_row',
96
		'before_row',
97
		'before',
98
		'before_field',
99
		'after_field',
100
		'after',
101
		'after_row',
102
		'after_group_row',
103
		'after_group',
104
	);
105
106
	/**
107
	 * Constructs our field object
108
	 * @since 1.1.0
109
	 * @param array $args Field arguments
110
	 */
111 109
	public function __construct( $args ) {
112
113 109
		if ( ! empty( $args['group_field'] ) ) {
114 3
			$this->group       = $args['group_field'];
115 3
			$this->object_id   = $this->group->object_id;
116 3
			$this->object_type = $this->group->object_type;
117 3
			$this->cmb_id      = $this->group->cmb_id;
118 3
		} else {
119 109
			$this->object_id   = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0;
120 109
			$this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post';
121
122 109
			if ( isset( $args['cmb_id'] ) ) {
123 40
				$this->cmb_id = $args['cmb_id'];
124 40
			}
125
		}
126
127 109
		$this->args = $this->_set_field_defaults( $args['field_args'], $args );
0 ignored issues
show
Unused Code introduced by
The call to CMB2_Field::_set_field_defaults() has too many arguments starting with $args.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
930
931 33
		$this->peform_param_callback( 'before_display' );
932
933 33
		CMB2_Field_Display::get( $this )->display();
934
935 33
		$this->peform_param_callback( 'after_display' );
936
937 33
		echo "\n</div>";
938
939 33
		$this->peform_param_callback( 'after_display_wrap' );
940
941
		// For chaining
942 33
		return $this;
943
	}
944
945
	/**
946
	 * Replaces a hash key - {#} - with the repeatable index
947
	 * @since  1.2.0
948
	 * @param  string $value Value to update
949
	 * @return string        Updated value
950
	 */
951 2
	public function replace_hash( $value ) {
952
		// Replace hash with 1 based count
953 2
		return str_replace( '{#}', ( $this->index + 1 ), $value );
954
	}
955
956
	/**
957
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
958
	 * For back-compatibility, falls back to checking the options array.
959
	 *
960
	 * @since  2.2.2
961
	 * @param  string  $text_key Key in field's text array
962
	 * @param  string  $fallback Fallback text
963
	 * @return string            Text
964
	 */
965 8
	public function string( $text_key, $fallback ) {
966
		// If null, populate with our field strings values.
967 8
		if ( null === $this->strings ) {
968 8
			$this->strings = (array) $this->args['text'];
969
970 8 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...
971
				$strings = call_user_func( $this->args['text_cb'], $this );
972
973
				if ( $strings && is_array( $strings ) ) {
974
					$this->strings += $strings;
975
				}
976
			}
977 8
		}
978
979
		// If we have that string value, send it back.
980 8
		if ( isset( $this->strings[ $text_key ] ) ) {
981 1
			return $this->strings[ $text_key ];
982
		}
983
984
		// Check options for back-compat.
985 8
		$string = $this->options( $text_key );
986
987 8
		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 987 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
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 31
	public function options( $key = '' ) {
997 31
		if ( ! empty( $this->field_options ) ) {
998 5
			if ( $key ) {
999 5
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1000
			}
1001
1002 1
			return $this->field_options;
1003
		}
1004
1005 31
		$this->field_options = (array) $this->args['options'];
1006
1007 31 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...
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 + $this->field_options;
1012 1
			}
1013 1
		}
1014
1015 31
		if ( $key ) {
1016 10
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1017
		}
1018
1019 23
		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 33
	public function get_default() {
1043 33
		if ( null !== $this->args['default'] ) {
1044 6
			return $this->args['default'];
1045
		}
1046
1047 32
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1048 32
		$default = $this->get_param_callback_result( $param );
1049
1050
		// Allow a filter override of the default value
1051 32
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1052
1053 32
		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 109
	public function _set_field_defaults( $args ) {
1062
1063
		/*
1064
		 * Deprecated parameters:
1065
		 *
1066
		 * 'std' -- use 'default' (no longer works)
1067
		 * 'row_classes' -- use 'class', or 'class_cb'
1068
		 * 'default' -- as callback (use default_cb)
1069
		 */
1070
1071
1072
		// Set up blank or default values for empty ones
1073 109
		$args = wp_parse_args( $args, array(
1074 109
			'type'              => '',
1075 109
			'name'              => '',
1076 109
			'desc'              => '',
1077 109
			'before'            => '',
1078 109
			'after'             => '',
1079 109
			'options'           => array(),
1080 109
			'options_cb'        => '',
1081 109
			'text'              => array(),
1082 109
			'text_cb'           => '',
1083 109
			'attributes'        => array(),
1084 109
			'protocols'         => null,
1085 109
			'default'           => null,
1086 109
			'default_cb'        => '',
1087 109
			'classes'           => null,
1088 109
			'classes_cb'        => '',
1089 109
			'select_all_button' => true,
1090 109
			'multiple'          => false,
1091 109
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1092 109
			'inline'            => false,
1093 109
			'on_front'          => true,
1094 109
			'show_names'        => true,
1095 109
			'save_field'        => true, // Will not save if false
1096 109
			'date_format'       => 'm\/d\/Y',
1097 109
			'time_format'       => 'h:i A',
1098 109
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1099 109
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1100 109
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1101 109
			'display_cb'        => array( $this, 'display_value_callback' ),
1102 109
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1103 109
			'column'            => false,
1104 109
			'js_dependencies'   => array(),
1105 109
			'show_in_rest'      => null,
1106 109
		) );
1107
1108
		/*
1109
		 * Deprecated usage.
1110
		 */
1111
1112 109
		if ( isset( $args['row_classes'] ) ) {
1113
1114
			// row_classes param could be a callback
1115 2
			if ( is_callable( $args['row_classes'] ) ) {
1116 1
				$args['classes_cb'] = $args['row_classes'];
1117 1
				$args['classes'] = null;
1118 1
			} else {
1119 1
				$args['classes'] = $args['row_classes'];
1120
			}
1121
1122 2
			unset( $args['row_classes'] );
1123 2
		}
1124
1125
		// default param can be passed a callback as well
1126 109
		if ( is_callable( $args['default'] ) ) {
1127 21
			$args['default_cb'] = $args['default'];
1128 21
			$args['default'] = null;
1129 21
		}
1130
1131
		/*
1132
		 * END deprecated usage.
1133
		 */
1134
1135 109
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1136 109
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1137
1138
		// options param can be passed a callback as well
1139 109
		if ( is_callable( $args['options'] ) ) {
1140
			$args['options_cb'] = $args['options'];
1141
			$args['options'] = array();
1142
		}
1143
1144 109
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1145 3
			'add_button'    => esc_html__( 'Add Group', 'cmb2' ),
1146 3
			'remove_button' => esc_html__( 'Remove Group', 'cmb2' ),
1147 109
		) ) : $args['options'];
1148
1149 109
		$args['_id']        = $args['id'];
1150 109
		$args['_name']      = $args['id'];
1151
1152 109
		if ( $this->group ) {
1153
1154 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1155 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1156 3
		}
1157
1158 109
		if ( 'wysiwyg' == $args['type'] ) {
1159 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1160 1
			$args['options']['textarea_name'] = $args['_name'];
1161 1
		}
1162
1163 109
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1164
1165 109
		if ( in_array( $args['type'], $option_types, true ) ) {
1166
1167 16
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1168 16
			$args['show_option_none'] = true === $args['show_option_none'] ? esc_html__( 'None', 'cmb2' ) : $args['show_option_none'];
1169
1170 16
			if ( null === $args['show_option_none'] ) {
1171 15
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1172 15
				$args['show_option_none'] = $off_by_default ? false : esc_html__( 'None', 'cmb2' );
1173 15
			}
1174
1175 16
		}
1176
1177 109
		$args['has_supporting_data'] = in_array(
1178 109
			$args['type'],
1179
			array(
1180
				// 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...
1181 109
				'file',
1182
				// 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...
1183 109
				'text_datetime_timestamp_timezone',
1184 109
			),
1185
			true
1186 109
		);
1187
1188 109
		return $args;
1189
	}
1190
1191
	/**
1192
	 * Get default field arguments specific to this CMB2 object.
1193
	 * @since  2.2.0
1194
	 * @param  array      $field_args  Metabox field config array.
1195
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1196
	 * @return array                   Array of field arguments.
1197
	 */
1198 5
	protected function get_default_args( $field_args, $field_group = null ) {
1199 5
		$args = parent::get_default_args( array(), $this->group );
1200
1201 5
		if ( isset( $field_args['field_args'] ) ) {
1202
			$args = wp_parse_args( $field_args, $args );
1203
		} else {
1204 5
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1205
		}
1206
1207 5
		return $args;
1208
	}
1209
1210
	/**
1211
	 * Returns a cloned version of this field object with, but with
1212
	 * modified/overridden field arguments.
1213
	 *
1214
	 * @since  2.2.2
1215
	 * @param  array  $field_args Array of field arguments, or entire array of
1216
	 *                            arguments for CMB2_Field
1217
	 *
1218
	 * @return CMB2_Field         The new CMB2_Field instance.
1219
	 */
1220 5
	public function get_field_clone( $field_args ) {
1221 5
		return $this->get_new_field( $field_args );
1222
	}
1223
1224
	/**
1225
	 * Returns the CMB2 instance this field is registered to.
1226
	 *
1227
	 * @since  2.2.2
1228
	 *
1229
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1230
	 */
1231 1
	public function get_cmb() {
1232 1
		if ( ! $this->cmb_id ) {
1233
			return new WP_Error( 'no_cmb_id', esc_html__( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1234
		}
1235
1236 1
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1237
	}
1238
1239
}
1240