Completed
Push — trunk ( 7dd985...f02feb )
by Justin
29:04 queued 15:34
created

CMB2_Field::data_args()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1.008

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 10
ccs 4
cts 5
cp 0.8
crap 1.008
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
	 * @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 109
	 */
88
	public static $callable_fields = array(
89 109
		'default',
90 3
		'row_classes',
91 3
		'options_cb',
92 3
		'label_cb',
93 3
		'render_row_cb',
94 3
		'before_group',
95 109
		'before_group_row',
96 109
		'before_row',
97
		'before',
98 109
		'before_field',
99 40
		'after_field',
100 40
		'after',
101
		'after_row',
102
		'after_group_row',
103 109
		'after_group',
104
	);
105 109
106 103
	/**
107 103
	 * Constructs our field object
108 109
	 * @since 1.1.0
109
	 * @param array $args Field arguments
110
	 */
111
	public function __construct( $args ) {
112
113
		if ( ! empty( $args['group_field'] ) ) {
114
			$this->group       = $args['group_field'];
115
			$this->object_id   = $this->group->object_id;
116
			$this->object_type = $this->group->object_type;
117 97
			$this->cmb_id      = $this->group->cmb_id;
118 97
		} else {
119 97
			$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
				$this->cmb_id = $args['cmb_id'];
124
			}
125
		}
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 107
129 107
		if ( $this->object_id ) {
130 107
			$this->value = $this->get_data();
131
		}
132
	}
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 110
	 */
141 110
	public function __call( $name, $arguments ) {
142
		$key = isset( $arguments[0] ) ? $arguments[0] : false;
143 110
		return $this->args( $name, $key );
144
	}
145 1
146
	/**
147 110
	 * 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 110
	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
	 * @param  string $key  Argument to check
161
	 * @param  string $_key Sub argument to check
162 110
	 * @return mixed        Argument value or false if non-existent
163 110
	 */
164 110
	public function args( $key = '', $_key = '' ) {
165 110
		$arg = $this->_data( 'args', $key );
166
167 59
		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
		}
175
176 46
		return $arg;
177 46
	}
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
	public function _data( $var, $key = '' ) {
187 105
		$vars = $this->{$var};
188 105
		if ( $key ) {
189
			return array_key_exists( $key, $vars ) ? $vars[ $key ] : false;
190 105
		}
191
		return $vars;
192
	}
193
194 105
	/**
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 105
		 * 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 105
		 *
233
		 * @param array $args {
234
		 *     An array of arguments for retrieving data
235 105
		 *
236 102
		 *     @type string $type     The current object type
237 102
		 *     @type int    $id       The current object ID
238 102
		 *     @type string $field_id The ID of the field being requested
239 102
		 *     @type bool   $repeat   Whether current field is repeatable
240
		 *     @type bool   $single   Whether current field is a single database row
241 105
		 * }
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 105
		 * 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 11
258 11
		// If no override, get value normally
259
		if ( 'cmb2_field_no_override_val' === $data ) {
260 11
			$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 11
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 11
		 *
297
		 *     @type string $value    The value to set
298
		 *     @type string $type     The current object type
299 11
		 *     @type int    $id       The current object ID
300 2
		 *     @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 10
		 *
305 3
		 * @param array $field_args All field arguments
306
		 *
307
		 * @param CMB2_Field object $field This field object
308
		 */
309 7
		$override = apply_filters( 'cmb2_override_meta_save', null, $a, $this->args(), $this );
310 1
311
		/**
312
		 * Filter and parameters are documented for 'cmb2_override_meta_save' filter (above).
313
		 *
314 6
		 * 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 6
		 */
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 3
		// Options page handling (or temp data store)
328 3
		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 3
	 * Removes/updates metadata/option data
348
	 * @since  1.0.1
349
	 * @param  string $old Old value
350
	 */
351
	public function remove_data( $old = '' ) {
352
		$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 3
		 * @param CMB2_Field object $field This field object
370
		 */
371
		$override = apply_filters( 'cmb2_override_meta_remove', null, $a, $this->args(), $this );
372 3
373
		/**
374
		 * Filter whether to override removing of meta value.
375
		 *
376 3
		 * The dynamic portion of the hook, $a['field_id'], refers to the current
377 1
		 * field id paramater. Returning a non-null value
378
		 * will effectively short-circuit the function.
379
		 *
380
		 * @since 2.0.0
381 2
		 *
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 105
		 * @param array $field_args All field arguments
391 105
		 * @param CMB2_Field object $field This field object
392 105
		 */
393 105
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this );
394 105
395 105
		// If no override, remove as usual
396 105
		if ( null !== $override ) {
397 105
			return $override;
398 105
		}
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 13
408
	/**
409 13
	 * Data variables for get/set data methods
410
	 * @since  1.1.0
411 1
	 * @param  array $args Override arguments
412 1
	 * @return array       Updated arguments
413
	 */
414
	public function data_args( $args = array() ) {
415 13
		$args = wp_parse_args( $args, array(
416 13
			'type'     => $this->object_type,
417
			'id'       => $this->object_id,
418 1
			'field_id' => $this->id( true ),
419 12
			'repeat'   => $this->args( 'repeatable' ),
420
			'single'   => ! $this->args( 'multiple' ),
421
		) );
422
		return $args;
423
	}
424 12
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
	public function sanitization_cb( $meta_value ) {
432
433
		if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) {
434
			// Remove empties
435
			$meta_value = array_filter( $meta_value );
436
		}
437
438
		// Check if the field has a registered validation callback
439
		$cb = $this->maybe_callback( 'sanitization_cb' );
440
		if ( false === $cb ) {
441 12
			// If requesting NO validation, return meta value
442
			return $meta_value;
443 12
		} elseif ( $cb ) {
444 1
			// Ok, callback is good, let's run it.
445
			return call_user_func( $cb, $meta_value, $this->args(), $this );
446
		}
447
448 11
		$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 2
		 *
458 2
		 * @param bool|mixed $override_value Sanitization/Validation override value to return.
459
		 *                                   Default false to skip it.
460 2
		 * @param mixed      $value      The value to be saved to this field.
461 2
		 * @param int        $object_id  The ID of the object where the value will be saved
462 2
		 * @param array      $field_args The current field's arguments
463
		 * @param object     $sanitizer  This `CMB2_Sanitize` object
464 2
		 */
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
			return $override_value;
469
		}
470
471
		// Sanitization via 'CMB2_Sanitize'
472
		return $sanitizer->{$this->type()}();
473 12
	}
474
475 12
	/**
476 12
	 * Process $_POST data to save this field's value
477 12
	 * @since  2.0.3
478
	 * @param  array $data_to_save $_POST data to check
479 12
	 * @return array|int|bool                Result of save, false on failure
480
	 */
481
	public function save_field_from_data( array $data_to_save ) {
482 1
		$this->data_to_save = $data_to_save;
483
484 12
		$meta_value = isset( $this->data_to_save[ $this->id( true ) ] )
485
			? $this->data_to_save[ $this->id( true ) ]
486 1
			: null;
487 1
488
		return $this->save_field( $meta_value );
489 1
	}
490 1
491 1
	/**
492 1
	 * Sanitize/store a value to this field
493 1
	 * @since  2.0.0
494 1
	 * @param  array $meta_value Desired value to sanitize/store
495 1
	 * @return array|int|bool              Result of save. false on failure
496
	 */
497 1
	public function save_field( $meta_value ) {
498 1
499
		$updated   = false;
500 11
		$action    = '';
501 9
		$new_value = $this->sanitization_cb( $meta_value );
502 9
503 10
		if ( ! $this->args( 'save_field' ) ) {
504 2
505 2
			// Nothing to see here.
506 2
			$action = 'disabled';
507
508 12
		} elseif ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) {
509 11
510 11
			$this->remove_data();
511
			$count = 0;
512 12
513
			if ( ! empty( $new_value ) ) {
514
				foreach ( $new_value as $add_new ) {
515
					if ( $this->update_data( $add_new, false ) ) {
516
						$count++;
517
					}
518
				}
519
			}
520
521
			$updated = $count ? $count : false;
522
			$action  = 'repeatable';
523
524 12
		} elseif ( ! cmb2_utils()->isempty( $new_value ) && $new_value !== $this->get_data() ) {
525
			$updated = $this->update_data( $new_value );
526
			$action  = 'updated';
527
		} elseif ( cmb2_utils()->isempty( $new_value ) ) {
528
			$updated = $this->remove_data();
529
			$action  = 'removed';
530
		}
531
532
		if ( $updated ) {
533
			$this->value = $this->get_data();
534
			$this->escaped_value = null;
535
		}
536
537
		$field_id = $this->id( true );
538 11
539
		/**
540 10
		 * 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 46
		 */
549
		do_action( 'cmb2_save_field', $field_id, $updated, $action, $this );
550 46
551 46
		/**
552 46
		 * Hooks after save field action.
553 46
		 *
554 46
		 * 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 4
		do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this );
564
565
		return $updated;
566
	}
567 4
568 4
	/**
569 4
	 * Determine if current type is exempt from escaping
570
	 * @since  1.1.0
571 4
	 * @return bool  True if exempt
572 4
	 */
573 4
	public function escaping_exception() {
574 4
		// These types cannot be escaped
575 4
		return in_array( $this->type(), array(
576 4
			'file_list',
577 4
			'multicheck',
578
			'text_datetime_timestamp_timezone',
579
		) );
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
	public function repeatable_exception( $type ) {
589
		// These types cannot be escaped
590 4
		$internal_fields = array(
591 4
			// Use file_list instead
592
			'file'                => 1,
593
			'radio'               => 1,
594
			'title'               => 1,
595
			// @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
			'radio_inline'        => 1,
599
			'taxonomy_radio'      => 1,
600
			'taxonomy_select'     => 1,
601 46
			'taxonomy_multicheck' => 1,
602
		);
603 46
604 27
		/**
605
		 * Filter field types that are non-repeatable.
606
		 *
607 46
		 * Note that this does *not* allow overriding the default non-repeatable types.
608
		 *
609
		 * @since 2.1.1
610 46
		 *
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
		$all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields );
616 46
		return isset( $all_fields[ $type ] );
617 46
	}
618
619
	/**
620
	 * Escape the value before output. Defaults to 'esc_attr()'
621 46
	 * @since  1.0.1
622
	 * @param  callable $func       Escaping function (if not esc_attr())
623 5
	 * @param  mixed    $meta_value Meta value
624
	 * @return mixed                Final value
625
	 */
626
	public function escaped_value( $func = 'esc_attr', $meta_value = '' ) {
627 41
628 41
		if ( null !== $this->escaped_value ) {
629
			return $this->escaped_value;
630 41
		}
631
632
		$meta_value = $meta_value ? $meta_value : $this->value();
633
634
		// Check if the field has a registered escaping callback
635 41
		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 41
		}
639 41
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
646
		if ( false === $cb || $this->escaping_exception() ) {
647
			// If requesting NO escaping, return meta value
648 46
			return $this->val_or_default( $meta_value );
649 46
		}
650
651
		// escaping function passed in?
652
		$func       = $func ? $func : 'esc_attr';
653
		$meta_value = $this->val_or_default( $meta_value );
654
655
		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
			$meta_value = call_user_func( $func, $meta_value );
661
		}
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
	 * @return mixed             Field value, or default value
672
	 */
673
	public function val_or_default( $meta_value ) {
674
		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 10
	 * @since  1.0.0
689 10
	 * @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 10
		else if ( $this->args( 'timezone_meta_key' ) ) {
700 10
			$value = $this->get_data( $this->args( 'timezone_meta_key' ) );
701 10
		}
702
703 10
		return $value;
704
	}
705
706
	/**
707 10
	 * Format the timestamp field value based on the field date/time format arg
708 10
	 * @since  2.0.0
709 10
	 * @param  int    $meta_value Timestamp
710
	 * @param  string $format     Either date_format or time_format
711
	 * @return string             Formatted date
712
	 */
713
	public function format_timestamp( $meta_value, $format = 'date_format' ) {
714
		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
	public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) {
725
		$meta_value = $meta_value ? $meta_value : $this->escaped_value();
726 10
		$meta_value = cmb2_utils()->make_valid_time_stamp( $meta_value );
727 10
728
		if ( empty( $meta_value ) ) {
729 10
			return '';
730
		}
731
732 10
		return is_array( $meta_value )
733
			? array_map( array( $this, 'format_timestamp' ), $meta_value, $format )
734
			: $this->format_timestamp( $meta_value, $format );
735
	}
736
737
	/**
738
	 * Get timestamp from text date
739 9
	 * @since  2.2.0
740
	 * @param  string $value Date value
741
	 * @return mixed         Unix timestamp representing the date.
742 9
	 */
743
	public function get_timestamp_from_value( $value ) {
744
		return cmb2_utils()->get_timestamp_from_value( $value, $this->args( 'date_format' ) );
745
	}
746
747 9
	/**
748
	 * Get field render callback and Render the field row
749
	 * @since 1.0.0
750
	 */
751 9
	public function render_field() {
752
		$this->render_context = 'edit';
753 9
754
		$this->peform_param_callback( 'render_row_cb' );
755 9
756
		// For chaining
757
		return $this;
758
	}
759
760
	/**
761
	 * Default field render callback
762 9
	 * @since 2.1.1
763 9
	 */
764 9
	public function render_field_callback() {
765
766 9
		// If field is requesting to not be shown on the front-end
767
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
768
			return;
769 9
		}
770
771 9
		// If field is requesting to be conditionally shown
772 9
		if ( ! $this->should_show() ) {
773
			return;
774 9
		}
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
		if ( ! $this->args( 'show_names' ) ) {
781 9
			echo "\n\t<div class=\"cmb-td\">\n";
782
783
			$this->peform_param_callback( 'label_cb' );
784
785
		} else {
786
787
			if ( $this->get_param_callback_result( 'label_cb' ) ) {
788
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
789
			}
790 9
791 9
			echo "\n\t<div class=\"cmb-td\">\n";
792
		}
793
794
		$this->peform_param_callback( 'before' );
795 9
796
		$field_type = new CMB2_Types( $this );
797 9
		$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
		// For chaining
806 45
		return $this;
807
	}
808 45
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
	public function label() {
816
		if ( ! $this->args( 'name' ) ) {
817 45
			return '';
818 45
		}
819 45
820
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
821
822 45
		return sprintf( "\n" . '<label%1$s for="%2$s">%3$s</label>' . "\n", $style, $this->id(), $this->args( 'name' ) );
823 45
	}
824 45
825 45
	/**
826 45
	 * Defines the classes for the current CMB2 field row
827 45
	 *
828 45
	 * @since  2.0.0
829
	 * @return string Space concatenated list of classes
830 45
	 */
831 45
	public function row_classes() {
832 45
833 45
		$classes = array();
834 45
835
		/**
836 45
		 * By default, 'text_url' and 'text' fields get table-like styling
837 1
		 *
838 45
		 * @since 2.0.0
839 2
		 *
840 2
		 * @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 45
			'text_url', 'text',
844 3
		) );
845 3
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 45
		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 33
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
865 33
		}
866
867 33
		if ( $added_classes ) {
868
			$classes[] = esc_attr( $added_classes );
869
		}
870 33
871
		/**
872
		 * Globally filter row classes
873
		 *
874
		 * @since 2.0.0
875
		 *
876
		 * @param string            $classes Space-separated list of row classes
877 33
		 * @param CMB2_Field object $field   This field object
878
		 */
879 33
		return apply_filters( 'cmb2_row_classes', implode( ' ', $classes ), $this );
880
	}
881
882
883 33
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 33
897
	/**
898 33
	 * 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 33
		if ( ! $this->should_show() ) {
904
			return;
905 33
		}
906
907 33
		$display = new CMB2_Field_Display( $this );
908
909 33
		/**
910
		 * A filter to bypass the default display.
911 33
		 *
912
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
913 33
		 *
914
		 * Passing a non-null value to the filter will short-circuit the default display.
915 33
		 *
916
		 * @param bool|mixed         $pre_output Default null value.
917
		 * @param CMB2_Field         $field      This field object.
918 33
		 * @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 2
		$this->peform_param_callback( 'before_display_wrap' );
928
929 2
		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 8
		// For chaining
942
		return $this;
943 8
	}
944 8
945
	/**
946 8
	 * 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 8
		return str_replace( '{#}', ( $this->index + 1 ), $value );
954
	}
955
956 8
	/**
957 1
	 * 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 8
	 * @param  string  $text_key Key in field's text array
962
	 * @param  string  $fallback Fallback text
963 8
	 * @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 31
973 31
				if ( $strings && is_array( $strings ) ) {
974 5
					$this->strings += $strings;
975 5
				}
976
			}
977
		}
978 1
979
		// If we have that string value, send it back.
980
		if ( isset( $this->strings[ $text_key ] ) ) {
981 31
			return $this->strings[ $text_key ];
982
		}
983 31
984 1
		// Check options for back-compat.
985
		$string = $this->options( $text_key );
986 1
987 1
		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 1
	}
989 1
990
	/**
991 31
	 * Retrieve options args. Calls options_cb if it exists.
992 10
	 * @since  2.0.0
993
	 * @param  string  $key Specific option to retrieve
994
	 * @return array        Array of options
995 23
	 */
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 33
		$this->field_options = (array) $this->args['options'];
1006 33
1007 6 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 32
			if ( $options && is_array( $options ) ) {
1011 32
				$this->field_options = $options + $this->field_options;
1012
			}
1013
		}
1014 32
1015
		if ( $key ) {
1016 32
			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 109
	 * @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 109
	 * Get CMB2_Field default value, either from default param or default_cb param.
1037 109
	 *
1038 109
	 * @since  0.2.2
1039 109
	 *
1040 109
	 * @return mixed  Default field value
1041 109
	 */
1042 109
	public function get_default() {
1043 109
		if ( null !== $this->args['default'] ) {
1044 109
			return $this->args['default'];
1045 109
		}
1046 109
1047 109
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1048 109
		$default = $this->get_param_callback_result( $param );
1049 109
1050 109
		// Allow a filter override of the default value
1051 109
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1052 109
1053 109
		return $this->args['default'];
1054 109
	}
1055 109
1056 109
	/**
1057 109
	 * Fills in empty field parameters with defaults
1058 109
	 * @since 1.1.0
1059 109
	 * @param array $args Metabox field config array
1060 109
	 */
1061 109
	public function _set_field_defaults( $args ) {
1062 109
1063 109
		/*
1064 109
		 * Deprecated parameters:
1065 109
		 *
1066 109
		 * 'std' -- use 'default' (no longer works)
1067 109
		 * '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
			'type'              => '',
1075
			'name'              => '',
1076 2
			'desc'              => '',
1077 1
			'before'            => '',
1078 1
			'after'             => '',
1079 1
			'options'           => array(),
1080 1
			'options_cb'        => '',
1081
			'text'              => array(),
1082
			'text_cb'           => '',
1083 2
			'attributes'        => array(),
1084 2
			'protocols'         => null,
1085
			'default'           => null,
1086
			'default_cb'        => '',
1087 109
			'classes'           => null,
1088 21
			'classes_cb'        => '',
1089 21
			'select_all_button' => true,
1090 21
			'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 109
			'date_format'       => 'm\/d\/Y',
1097 109
			'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 109
			'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 109
		) );
1106 3
1107 3
		/*
1108 109
		 * Deprecated usage.
1109
		 */
1110 109
1111 109
		if ( isset( $args['row_classes'] ) ) {
1112
1113 109
			// row_classes param could be a callback
1114
			if ( is_callable( $args['row_classes'] ) ) {
1115 3
				$args['classes_cb'] = $args['row_classes'];
1116 3
				$args['classes'] = null;
1117 3
			} else {
1118
				$args['classes'] = $args['row_classes'];
1119 109
			}
1120 1
1121 1
			unset( $args['row_classes'] );
1122 1
		}
1123
1124 109
		// default param can be passed a callback as well
1125
		if ( is_callable( $args['default'] ) ) {
1126 109
			$args['default_cb'] = $args['default'];
1127
			$args['default'] = null;
1128 16
		}
1129 16
1130
		/*
1131 16
		 * END deprecated usage.
1132 15
		 */
1133 15
1134 15
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1135
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1136 16
1137
		// options param can be passed a callback as well
1138 109
		if ( is_callable( $args['options'] ) ) {
1139 109
			$args['options_cb'] = $args['options'];
1140
			$args['options'] = array();
1141
		}
1142 109
1143
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1144 109
			'add_button'    => __( 'Add Group', 'cmb2' ),
1145 109
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1146
		) ) : $args['options'];
1147 109
1148
		$args['_id']        = $args['id'];
1149 109
		$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 5
			$args['options']['textarea_name'] = $args['_name'];
1160 5
		}
1161
1162 5
		$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 5
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 5
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 5
				// 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 5
				'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 1
	 * @since  2.2.0
1193 1
	 * @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 1
	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