Completed
Pull Request — trunk (#698)
by Justin
37:05 queued 28:16
created

CMB2_Field::get_default()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 3
eloc 7
c 3
b 1
f 0
nc 3
nop 0
dl 0
loc 13
ccs 0
cts 0
cp 0
crap 12
rs 9.4285
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 46
	 * @var   mixed
50
	 * @since 1.1.0
51 46
	 */
52
	public $escaped_value = null;
53
54
	/**
55
	 * Grouped Field's current numeric index during the save process
56 46
	 * @var   mixed
57 46
	 * @since 2.0.0
58 46
	 */
59
	public $index = 0;
60
61 46
	/**
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 46
	 * @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 46
		'before',
98
		'before_field',
99
		'after_field',
100 46
		'after',
101 46
		'after_row',
102 46
		'after_group_row',
103 46
		'after_group',
104
	);
105
106
	/**
107
	 * Constructs our field object
108
	 * @since 1.1.0
109
	 * @param array $args Field arguments
110
	 */
111
	public function __construct( $args ) {
112 38
113 38
		if ( ! empty( $args['group_field'] ) ) {
114 38
			$this->group       = $args['group_field'];
115
			$this->object_id   = $this->group->object_id;
116
			$this->object_type = $this->group->object_type;
117
			$this->cmb_id      = $this->group->cmb_id;
118
		} else {
119
			$this->object_id   = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0;
120
			$this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post';
121
122
			if ( isset( $args['cmb_id'] ) ) {
123 46
				$this->cmb_id = $args['cmb_id'];
124 46
			}
125 46
		}
126
127
		$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
		if ( $this->object_id ) {
130
			$this->value = $this->get_data();
131
		}
132
	}
133
134
	/**
135 46
	 * Non-existent methods fallback to checking for field arguments of the same name
136 46
	 * @since  1.1.0
137 46
	 * @param  string $name     Method name
138
	 * @param  array  $arguments Array of passed-in arguments
139
	 * @return mixed             Value of field argument
140 46
	 */
141
	public function __call( $name, $arguments ) {
142
		$key = isset( $arguments[0] ) ? $arguments[0] : false;
143
		return $this->args( $name, $key );
144
	}
145
146
	/**
147
	 * Retrieves the field id
148
	 * @since  1.1.0
149 37
	 * @param  boolean $raw Whether to retrieve pre-modidifed id
150 37
	 * @return string       Field id
151
	 */
152
	public function id( $raw = false ) {
153
		$id = $raw ? '_id' : 'id';
154
		return $this->args( $id );
155
	}
156
157
	/**
158
	 * Get a field argument
159
	 * @since  1.1.0
160 46
	 * @param  string $key  Argument to check
161 46
	 * @param  string $_key Sub argument to check
162 46
	 * @return mixed        Argument value or false if non-existent
163 46
	 */
164
	public function args( $key = '', $_key = '' ) {
165 46
		$arg = $this->_data( 'args', $key );
166
167
		if ( in_array( $key, array( 'default', 'default_cb' ), true ) ) {
168
169
			$arg = $this->get_default();
170
171
		} elseif ( $_key ) {
172
173
			$arg = isset( $arg[ $_key ] ) ? $arg[ $_key ] : false;
174 46
		}
175 46
176
		return $arg;
177 46
	}
178
179
	/**
180
	 * Retrieve a portion of a field property
181 46
	 * @since  1.1.0
182
	 * @param  string  $var Field property to check
183 46
	 * @param  string  $key Field property array key to check
184 46
	 * @return mixed        Queried property value or false
185 46
	 */
186
	public function _data( $var, $key = '' ) {
187 46
		$vars = $this->{$var};
188
		if ( $key ) {
189
			return array_key_exists( $key, $vars ) ? $vars[ $key ] : false;
190
		}
191
		return $vars;
192 46
	}
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
	public function value( $key = '' ) {
201
		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
	public function get_data( $field_id = '', $args = array() ) {
212
		if ( $field_id ) {
213
			$args['field_id'] = $field_id;
214
		} else if ( $this->group ) {
215
			$args['field_id'] = $this->group->id();
216
		}
217
218
		$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
		$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
		$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
		if ( 'cmb2_field_no_override_val' === $data ) {
260
			$data = 'options-page' === $a['type']
261
				? cmb2_options( $a['id'] )->get( $a['field_id'] )
262
				: get_metadata( $a['type'], $a['id'], $a['field_id'], ( $a['single'] || $a['repeat'] ) );
263
		}
264
265
		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
		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
	public function update_data( $new_value, $single = true ) {
282
		$a = $this->data_args( array( 'single' => $single ) );
283
284
		$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
		$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
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_save", $override, $a, $this->args(), $this );
321
322
		// If override, return that
323
		if ( null !== $override ) {
324
			return $override;
325
		}
326
327
		// Options page handling (or temp data store)
328
		if ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
329
			return cmb2_options( $a['id'] )->update( $a['field_id'], $a['value'], false, $a['single'] );
330
		}
331
332
		// Add metadata if not single
333
		if ( ! $a['single'] ) {
334
			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
		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
		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 46
	 */
351 46
	public function remove_data( $old = '' ) {
352 46
		$a = $this->data_args( array( 'old' => $old ) );
353 46
354 46
		/**
355 46
		 * Filter whether to override removing of meta value.
356 46
		 * Returning a non-null value will effectively short-circuit the function.
357 46
		 *
358 46
		 * @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
		$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
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this );
394
395
		// If no override, remove as usual
396
		if ( null !== $override ) {
397
			return $override;
398
		}
399
		// Option page handling
400
		elseif ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
401
			return cmb2_options( $a['id'] )->remove( $a['field_id'] );
402
		}
403
404
		// Remove metadata
405
		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
	public function data_args( $args = array() ) {
415
		$args = wp_parse_args( $args, array(
416
			'type'     => $this->object_type,
417
			'id'       => $this->object_id,
418
			'field_id' => $this->id( true ),
419
			'repeat'   => $this->args( 'repeatable' ),
420
			'single'   => ! $this->args( 'multiple' ),
421
		) );
422
		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 39
	 * @return mixed             Possibly sanitized meta value
430 39
	 */
431 39
	public function sanitization_cb( $meta_value ) {
432 38
433
		if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) {
434
			// Remove empties
435
			$meta_value = array_filter( $meta_value );
436 7
		}
437
438
		// Check if the field has a registered validation callback
439 7
		$cb = $this->maybe_callback( 'sanitization_cb' );
440 4
		if ( false === $cb ) {
441
			// If requesting NO validation, return meta value
442
			return $meta_value;
443 3
		} elseif ( $cb ) {
444 2
			// Ok, callback is good, let's run it.
445
			return call_user_func( $cb, $meta_value, $this->args(), $this );
446 3
		}
447
448
		$sanitizer = new CMB2_Sanitize( $this, $meta_value );
449
450
		/**
451
		 * Filter the value before it is saved.
452
		 *
453 37
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
454
		 *
455 37
		 * Passing a non-null value to the filter will short-circuit saving
456 37
		 * the field value, saving the passed value instead.
457 37
		 *
458 37
		 * @param bool|mixed $override_value Sanitization/Validation override value to return.
459 37
		 *                                   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
		$override_value = apply_filters( "cmb2_sanitize_{$this->type()}", null, $sanitizer->value, $this->object_id, $this->args(), $sanitizer );
466
467
		if ( null !== $override_value ) {
468 1
			return $override_value;
469
		}
470 1
471 1
		// Sanitization via 'CMB2_Sanitize'
472 1
		return $sanitizer->{$this->type()}();
473 1
	}
474 1
475
	/**
476 1
	 * Process $_POST data to save this field's value
477 1
	 * @since  2.0.3
478 1
	 * @param  array $data_to_save $_POST data to check
479 1
	 * @return array|int|bool                Result of save, false on failure
480 1
	 */
481 1
	public function save_field_from_data( array $data_to_save ) {
482 1
		$this->data_to_save = $data_to_save;
483
484
		$meta_value = isset( $this->data_to_save[ $this->id( true ) ] )
485
			? $this->data_to_save[ $this->id( true ) ]
486
			: null;
487
488
		return $this->save_field( $meta_value );
489
	}
490
491
	/**
492 37
	 * Sanitize/store a value to this field
493
	 * @since  2.0.0
494 37
	 * @param  array $meta_value Desired value to sanitize/store
495 16
	 * @return array|int|bool              Result of save. false on failure
496
	 */
497
	public function save_field( $meta_value ) {
498 37
499
		$updated   = false;
500
		$action    = '';
501 37
		$new_value = $this->sanitization_cb( $meta_value );
502
503
		if ( ! $this->args( 'save_field' ) ) {
504
505
			// Nothing to see here.
506
			$action = 'disabled';
507 37
508 37
		} elseif ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) {
509
510
			$this->remove_data();
511
			$count = 0;
512 37
513
			if ( ! empty( $new_value ) ) {
514 4
				foreach ( $new_value as $add_new ) {
515
					if ( $this->update_data( $add_new, false ) ) {
516
						$count++;
517
					}
518 33
				}
519 33
			}
520
521 33
			$updated = $count ? $count : false;
522
			$action  = 'repeatable';
523
524
		} elseif ( ! cmb2_utils()->isempty( $new_value ) && $new_value !== $this->get_data() ) {
525
			$updated = $this->update_data( $new_value );
526 33
			$action  = 'updated';
527
		} elseif ( cmb2_utils()->isempty( $new_value ) ) {
528
			$updated = $this->remove_data();
529 33
			$action  = 'removed';
530 33
		}
531
532
		if ( $updated ) {
533
			$this->value = $this->get_data();
534
			$this->escaped_value = null;
535
		}
536
537
		$field_id = $this->id( true );
538 1
539 1
		/**
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 1
		 * @param CMB2_Field object $field    This field object
548
		 */
549
		do_action( 'cmb2_save_field', $field_id, $updated, $action, $this );
550 1
551
		/**
552
		 * Hooks after save field action.
553
		 *
554 1
		 * The dynamic portion of the hook, $field_id, refers to the
555
		 * current field id paramater.
556
		 *
557
		 * @since 2.2.0
558 1
		 *
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
		do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this );
564
565
		return $updated;
566
	}
567
568 5
	/**
569 5
	 * Determine if current type is exempt from escaping
570
	 * @since  1.1.0
571
	 * @return bool  True if exempt
572
	 */
573
	public function escaping_exception() {
574
		// These types cannot be escaped
575
		return in_array( $this->type(), array(
576
			'file_list',
577
			'multicheck',
578 5
			'text_datetime_timestamp_timezone',
579 5
		) );
580 5
	}
581
582 5
	/**
583
	 * Determine if current type cannot be repeatable
584
	 * @since  1.1.0
585
	 * @param  string $type Field type to check
586 5
	 * @return bool         True if type cannot be repeatable
587 5
	 */
588 5
	public function repeatable_exception( $type ) {
589
		// These types cannot be escaped
590
		$internal_fields = array(
591
			// Use file_list instead
592
			'file'                => 1,
593
			'radio'               => 1,
594
			'title'               => 1,
595 5
			// @todo Ajax load wp_editor: http://wordpress.stackexchange.com/questions/51776/how-to-load-wp-editor-through-ajax-jquery
596
			'wysiwyg'             => 1,
597
			'checkbox'            => 1,
598 5
			'radio_inline'        => 1,
599
			'taxonomy_radio'      => 1,
600
			'taxonomy_select'     => 1,
601
			'taxonomy_multicheck' => 1,
602
		);
603 5
604
		/**
605
		 * Filter field types that are non-repeatable.
606
		 *
607 5
		 * Note that this does *not* allow overriding the default non-repeatable types.
608
		 *
609 5
		 * @since 2.1.1
610
		 *
611 5
		 * @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
		$all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields );
616
		return isset( $all_fields[ $type ] );
617
	}
618
619
	/**
620 5
	 * Escape the value before output. Defaults to 'esc_attr()'
621 5
	 * @since  1.0.1
622 5
	 * @param  callable $func       Escaping function (if not esc_attr())
623
	 * @param  mixed    $meta_value Meta value
624 5
	 * @return mixed                Final value
625
	 */
626
	public function escaped_value( $func = 'esc_attr', $meta_value = '' ) {
627 5
628
		if ( null !== $this->escaped_value ) {
629 5
			return $this->escaped_value;
630 5
		}
631
632 5
		$meta_value = $meta_value ? $meta_value : $this->value();
633
634 5
		// Check if the field has a registered escaping callback
635
		if ( $cb = $this->maybe_callback( 'escape_cb' ) ) {
636 5
			// Ok, callback is good, let's run it.
637 5
			return call_user_func( $cb, $meta_value, $this->args(), $this );
638
		}
639
640
		// Or custom escaping filter can be used
641
		$esc = apply_filters( "cmb2_types_esc_{$this->type()}", null, $meta_value, $this->args(), $this );
642
		if ( null !== $esc ) {
643
			return $esc;
644
		}
645 5
646 5
		if ( false === $cb || $this->escaping_exception() ) {
647 5
			// If requesting NO escaping, return meta value
648 5
			return $this->val_or_default( $meta_value );
649 5
		}
650
651 5
		// escaping function passed in?
652
		$func       = $func ? $func : 'esc_attr';
653 5
		$meta_value = $this->val_or_default( $meta_value );
654 5
655 5
		if ( is_array( $meta_value ) ) {
656
			foreach ( $meta_value as $key => $value ) {
657 5
				$meta_value[ $key ] = call_user_func( $func, $value );
658 3
			}
659 3
		} else {
660
			$meta_value = call_user_func( $func, $meta_value );
661 5
		}
662
663
		$this->escaped_value = $meta_value;
664
		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 38
	 * @return mixed             Field value, or default value
672 38
	 */
673
	public function val_or_default( $meta_value ) {
674 2
		return ! cmb2_utils()->isempty( $meta_value ) ? $meta_value : $this->get_default();
675 2
	}
676
677
	/**
678
	 * Offset a time value based on timezone
679 38
	 * @since  1.0.0
680 38
	 * @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 14
		else if ( $this->args( 'timezone_meta_key' ) ) {
700 14
			$value = $this->get_data( $this->args( 'timezone_meta_key' ) );
701 5
		}
702 4
703
		return $value;
704
	}
705 1
706
	/**
707
	 * Format the timestamp field value based on the field date/time format arg
708 14
	 * @since  2.0.0
709
	 * @param  int    $meta_value Timestamp
710 14
	 * @param  string $format     Either date_format or time_format
711 1
	 * @return string             Formatted date
712
	 */
713 1
	public function format_timestamp( $meta_value, $format = 'date_format' ) {
714 1
		return date( stripslashes( $this->args( $format ) ), $meta_value );
715 1
	}
716 1
717
	/**
718 14
	 * Return a formatted timestamp for a field
719 4
	 * @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 11
	 * @return string             Formatted date
723
	 */
724
	public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) {
725
		$meta_value = $meta_value ? $meta_value : $this->escaped_value();
726
		$meta_value = cmb2_utils()->make_valid_time_stamp( $meta_value );
727
728
		if ( empty( $meta_value ) ) {
729
			return '';
730 46
		}
731
732
		return is_array( $meta_value )
733 46
			? array_map( array( $this, 'format_timestamp' ), $meta_value, $format )
734 46
			: $this->format_timestamp( $meta_value, $format );
735 46
	}
736 46
737 46
	/**
738 46
	 * Get timestamp from text date
739 46
	 * @since  2.2.0
740 46
	 * @param  string $value Date value
741 46
	 * @return mixed         Unix timestamp representing the date.
742 46
	 */
743 46
	public function get_timestamp_from_value( $value ) {
744 46
		return cmb2_utils()->get_timestamp_from_value( $value, $this->args( 'date_format' ) );
745 46
	}
746 46
747 46
	/**
748 46
	 * Get field render callback and Render the field row
749 46
	 * @since 1.0.0
750 46
	 */
751 46
	public function render_field() {
752 46
		$this->render_context = 'edit';
753 46
754 46
		$this->peform_param_callback( 'render_row_cb' );
755
756
		// For chaining
757
		return $this;
758 46
	}
759
760 46
	/**
761 46
	 * Default field render callback
762
	 * @since 2.1.1
763
	 */
764 46
	public function render_field_callback() {
765
766
		// If field is requesting to not be shown on the front-end
767
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
768
			return;
769 46
		}
770
771
		// If field is requesting to be conditionally shown
772 46
		if ( ! $this->should_show() ) {
773
			return;
774 46
		}
775 46
776
		$this->peform_param_callback( 'before_row' );
777 46
778
		printf( "<div class=\"cmb-row %s\" data-fieldtype=\"%s\">\n", $this->row_classes(), $this->type() );
779
780
		if ( ! $this->args( 'show_names' ) ) {
781
			echo "\n\t<div class=\"cmb-td\">\n";
782
783 46
			$this->peform_param_callback( 'label_cb' );
784 1
785 1
		} else {
786 1
787
			if ( $this->get_param_callback_result( 'label_cb' ) ) {
788 46
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
789
			}
790 46
791
			echo "\n\t<div class=\"cmb-td\">\n";
792 2
		}
793 2
794
		$this->peform_param_callback( 'before' );
795 2
796
		$field_type = new CMB2_Types( $this );
797 46
		$field_type->render();
798
799
		$this->peform_param_callback( 'after' );
800
801
		echo "\n\t</div>\n</div>";
802
803
		$this->peform_param_callback( 'after_row' );
804
805 36
		// For chaining
806 36
		return $this;
807
	}
808
809
	/**
810 1
	 * The default label_cb callback (if not a title field)
811
	 *
812
	 * @since  2.1.1
813
	 * @return string Label html markup
814
	 */
815
	public function label() {
816
		if ( ! $this->args( 'name' ) ) {
817
			return '';
818
		}
819
820
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
821
822
		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
	public function row_classes() {
832
833
		$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
		$repeat_table_rows_types = apply_filters( 'cmb2_repeat_table_row_types', array(
843
			'text_url', 'text',
844
		) );
845
846
		$conditional_classes = array(
847
			'cmb-type-' . str_replace( '_', '-', sanitize_html_class( $this->type() ) ) => true,
848
			'cmb2-id-' . str_replace( '_', '-', sanitize_html_class( $this->id() ) )    => true,
849
			'cmb-repeat'             => $this->args( 'repeatable' ),
850
			'cmb-repeat-group-field' => $this->group,
851
			'cmb-inline'             => $this->args( 'inline' ),
852
			'table-layout'           => 'edit' === $this->render_context && in_array( $this->type(), $repeat_table_rows_types ),
853
		);
854
855
		foreach ( $conditional_classes as $class => $condition ) {
856
			if ( $condition ) {
857
				$classes[] = $class;
858
			}
859
		}
860
861
		if ( $added_classes = $this->args( 'classes' ) ) {
862
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
863
		} elseif ( $added_classes = $this->get_param_callback_result( 'classes_cb' ) ) {
864
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
865
		}
866
867
		if ( $added_classes ) {
868
			$classes[] = esc_attr( $added_classes );
869
		}
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
		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
	public function render_column() {
889
		$this->render_context = 'display';
890
891
		$this->peform_param_callback( 'display_cb' );
892
893
		// For chaining
894
		return $this;
895
	}
896
897
	/**
898
	 * Default callback to outputs field value in a display format.
899
	 * @since 2.2.2
900
	 */
901
	public function display_value_callback() {
902
		// If field is requesting to be conditionally shown
903
		if ( ! $this->should_show() ) {
904
			return;
905
		}
906
907
		$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
		$pre_output = apply_filters( "cmb2_pre_field_display_{$this->type()}", null, $this, $display );
921
922
		if ( null !== $pre_output ) {
923
			echo $pre_output;
924
			return;
925
		}
926
927
		$this->peform_param_callback( 'before_display_wrap' );
928
929
		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
		$this->peform_param_callback( 'before_display' );
932
933
		CMB2_Field_Display::get( $this )->display();
934
935
		$this->peform_param_callback( 'after_display' );
936
937
		echo "\n</div>";
938
939
		$this->peform_param_callback( 'after_display_wrap' );
940
941
		// For chaining
942
		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
	public function replace_hash( $value ) {
952
		// Replace hash with 1 based count
953
		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
	public function string( $text_key, $fallback ) {
966
		// If null, populate with our field strings values.
967
		if ( null === $this->strings ) {
968
			$this->strings = (array) $this->args['text'];
969
970 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
		}
978
979
		// If we have that string value, send it back.
980
		if ( isset( $this->strings[ $text_key ] ) ) {
981
			return $this->strings[ $text_key ];
982
		}
983
984
		// Check options for back-compat.
985
		$string = $this->options( $text_key );
986
987
		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
	public function options( $key = '' ) {
997
		if ( ! empty( $this->field_options ) ) {
998
			if ( $key ) {
999
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1000
			}
1001
1002
			return $this->field_options;
1003
		}
1004
1005
		$this->field_options = (array) $this->args['options'];
1006
1007 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
			$options = call_user_func( $this->args['options_cb'], $this );
1009
1010
			if ( $options && is_array( $options ) ) {
1011
				$this->field_options = $options + $this->field_options;
1012
			}
1013
		}
1014
1015
		if ( $key ) {
1016
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1017
		}
1018
1019
		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
	public function add_js_dependencies( $dependencies = array() ) {
1028
		foreach ( (array) $dependencies as $dependency ) {
1029
			$this->args['js_dependencies'][ $dependency ] = $dependency;
1030
		}
1031
1032
		CMB2_JS::add_dependencies( $dependencies );
1033
	}
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
	public function get_default() {
1043
		if ( null !== $this->args['default'] ) {
1044
			return $this->args['default'];
1045
		}
1046
1047
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1048
		$default = $this->get_param_callback_result( $param );
1049
1050
		// Allow a filter override of the default value
1051
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1052
1053
		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
	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
		$args = wp_parse_args( $args, array(
1074
			'type'              => '',
1075
			'name'              => '',
1076
			'desc'              => '',
1077
			'before'            => '',
1078
			'after'             => '',
1079
			'options'           => array(),
1080
			'options_cb'        => '',
1081
			'text'              => array(),
1082
			'text_cb'           => '',
1083
			'attributes'        => array(),
1084
			'protocols'         => null,
1085
			'default'           => null,
1086
			'default_cb'        => '',
1087
			'classes'           => null,
1088
			'classes_cb'        => '',
1089
			'select_all_button' => true,
1090
			'multiple'          => false,
1091
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1092
			'inline'            => false,
1093
			'on_front'          => true,
1094
			'show_names'        => true,
1095
			'save_field'        => true, // Will not save if false
1096
			'date_format'       => 'm\/d\/Y',
1097
			'time_format'       => 'h:i A',
1098
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1099
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1100
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1101
			'display_cb'        => array( $this, 'display_value_callback' ),
1102
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1103
			'column'            => false,
1104
			'js_dependencies'   => array(),
1105
		) );
1106
1107
		/*
1108
		 * Deprecated usage.
1109
		 */
1110
1111
		if ( isset( $args['row_classes'] ) ) {
1112
1113
			// row_classes param could be a callback
1114
			if ( is_callable( $args['row_classes'] ) ) {
1115
				$args['classes_cb'] = $args['row_classes'];
1116
				$args['classes'] = null;
1117
			} else {
1118
				$args['classes'] = $args['row_classes'];
1119
			}
1120
1121
			unset( $args['row_classes'] );
1122
		}
1123
1124
		// default param can be passed a callback as well
1125
		if ( is_callable( $args['default'] ) ) {
1126
			$args['default_cb'] = $args['default'];
1127
			$args['default'] = null;
1128
		}
1129
1130
		/*
1131
		 * END deprecated usage.
1132
		 */
1133
1134
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1135
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1136
1137
		// options param can be passed a callback as well
1138
		if ( is_callable( $args['options'] ) ) {
1139
			$args['options_cb'] = $args['options'];
1140
			$args['options'] = array();
1141
		}
1142
1143
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1144
			'add_button'    => __( 'Add Group', 'cmb2' ),
1145
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1146
		) ) : $args['options'];
1147
1148
		$args['_id']        = $args['id'];
1149
		$args['_name']      = $args['id'];
1150
1151
		if ( $this->group ) {
1152
1153
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1154
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1155
		}
1156
1157
		if ( 'wysiwyg' == $args['type'] ) {
1158
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1159
			$args['options']['textarea_name'] = $args['_name'];
1160
		}
1161
1162
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1163
1164
		if ( in_array( $args['type'], $option_types, true ) ) {
1165
1166
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1167
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1168
1169
			if ( null === $args['show_option_none'] ) {
1170
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1171
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1172
			}
1173
1174
		}
1175
1176
		$args['has_supporting_data'] = in_array(
1177
			$args['type'],
1178
			array(
1179
				// 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...
1180
				'file',
1181
				// 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...
1182
				'text_datetime_timestamp_timezone',
1183
			),
1184
			true
1185
		);
1186
1187
		return $args;
1188
	}
1189
1190
	/**
1191
	 * Get default field arguments specific to this CMB2 object.
1192
	 * @since  2.2.0
1193
	 * @param  array      $field_args  Metabox field config array.
1194
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1195
	 * @return array                   Array of field arguments.
1196
	 */
1197
	protected function get_default_args( $field_args, $field_group = null ) {
1198
		$args = parent::get_default_args( array(), $this->group );
1199
1200
		if ( isset( $field_args['field_args'] ) ) {
1201
			$args = wp_parse_args( $field_args, $args );
1202
		} else {
1203
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1204
		}
1205
1206
		return $args;
1207
	}
1208
1209
	/**
1210
	 * Returns a cloned version of this field object with, but with
1211
	 * modified/overridden field arguments.
1212
	 *
1213
	 * @since  2.2.2
1214
	 * @param  array  $field_args Array of field arguments, or entire array of
1215
	 *                            arguments for CMB2_Field
1216
	 *
1217
	 * @return CMB2_Field         The new CMB2_Field instance.
1218
	 */
1219
	public function get_field_clone( $field_args ) {
1220
		return $this->get_new_field( $field_args );
1221
	}
1222
1223
	/**
1224
	 * Returns the CMB2 instance this field is registered to.
1225
	 *
1226
	 * @since  2.2.2
1227
	 *
1228
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1229
	 */
1230
	public function get_cmb() {
1231
		if ( ! $this->cmb_id ) {
1232
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1233
		}
1234
1235
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1236
	}
1237
1238
}
1239