Completed
Push — trunk ( 90dfb1...7ce277 )
by Justin
03:29
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 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 13
rs 9.4285
ccs 0
cts 0
cp 0
cc 3
eloc 7
nc 3
nop 0
crap 12
1
<?php
2
/**
3
 * CMB2 field objects
4
 *
5
 * @since  1.1.0
6
 *
7
 * @category  WordPress_Plugin
8
 * @package   CMB2
9
 * @author    WebDevStudios
10
 * @license   GPL-2.0+
11
 * @link      http://webdevstudios.com
12
 *
13
 * @method string _id()
14
 * @method string type()
15
 * @method mixed fields()
16
 */
17
class CMB2_Field {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

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

namespace YourVendor;

class YourClass { }

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

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

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

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

Loading history...
904
		// Default to showing the field
905
		$show = true;
906
907
		// Use the callback to determine showing the field, if it exists
908
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
909
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
910
		}
911
912
		return $show;
913
	}
914
915
	/**
916
	 * Displays the results of the param callbacks.
917
	 *
918
	 * @since 2.0.0
919
	 * @param string $param Field parameter
920
	 */
921
	public function peform_param_callback( $param ) {
922
		echo $this->get_param_callback_result( $param );
923
	}
924
925
	/**
926
	 * Store results of the param callbacks for continual access
927
	 * @since  2.0.0
928
	 * @param  string $param Field parameter
929
	 * @param  bool   $echo  Whether field should be 'echoed'
930
	 * @return mixed         Results of param/param callback
931
	 */
932
	public function get_param_callback_result( $param, $echo = true ) {
933
934
		// If we've already retrieved this param's value,
935
		if ( array_key_exists( $param, $this->callback_results ) ) {
936
			// send it back
937
			return $this->callback_results[ $param ];
938
		}
939
940
		if ( $cb = $this->maybe_callback( $param ) ) {
941
			if ( $echo ) {
942
				// Ok, callback is good, let's run it and store the result
943
				ob_start();
944
				echo call_user_func( $cb, $this->args(), $this );
945
				// grab the result from the output buffer and store it
946
				$this->callback_results[ $param ] = ob_get_contents();
947
				ob_end_clean();
948
			} else {
949
				$this->callback_results[ $param ] = call_user_func( $cb, $this->args(), $this );
950
			}
951
952
			return $this->callback_results[ $param ];
953
		}
954
955
		// Otherwise just get whatever is there
956
		$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
957
958
		return $this->callback_results[ $param ];
959
	}
960
961
	/**
962
	 * Replaces a hash key - {#} - with the repeatable index
963
	 * @since  1.2.0
964
	 * @param  string $value Value to update
965
	 * @return string        Updated value
966
	 */
967
	public function replace_hash( $value ) {
968
		// Replace hash with 1 based count
969
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
970
	}
971
972
	/**
973
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
974
	 * For back-compatibility, falls back to checking the options array.
975
	 *
976
	 * @since  2.2.2
977
	 * @param  string  $text_key Key in field's text array
978
	 * @param  string  $fallback Fallback text
979
	 * @return string            Text
980
	 */
981
	public function string( $text_key, $fallback ) {
982
		// If null, populate with our field strings values.
983
		if ( null === $this->strings ) {
984
			$this->strings = (array) $this->args['text'];
985
986 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...
987
				$strings = call_user_func( $this->args['text_cb'], $this );
988
989
				if ( $strings && is_array( $strings ) ) {
990
					$this->strings += $strings;
991
				}
992
			}
993
		}
994
995
		// If we have that string value, send it back.
996
		if ( isset( $this->strings[ $text_key ] ) ) {
997
			return $this->strings[ $text_key ];
998
		}
999
1000
		// Check options for back-compat.
1001
		$string = $this->options( $text_key );
1002
1003
		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 1003 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
1004
	}
1005
1006
	/**
1007
	 * Retrieve options args. Calls options_cb if it exists.
1008
	 * @since  2.0.0
1009
	 * @param  string  $key Specific option to retrieve
1010
	 * @return array        Array of options
1011
	 */
1012
	public function options( $key = '' ) {
1013
		if ( ! empty( $this->field_options ) ) {
1014
			if ( $key ) {
1015
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1016
			}
1017
1018
			return $this->field_options;
1019
		}
1020
1021
		$this->field_options = (array) $this->args['options'];
1022
1023 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...
1024
			$options = call_user_func( $this->args['options_cb'], $this );
1025
1026
			if ( $options && is_array( $options ) ) {
1027
				$this->field_options += $options;
1028
			}
1029
		}
1030
1031
		if ( $key ) {
1032
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1033
		}
1034
1035
		return $this->field_options;
1036
	}
1037
1038
	/**
1039
	 * Get CMB2_Field default value, either from default param or default_cb param.
1040
	 *
1041
	 * @since  0.2.2
1042
	 *
1043
	 * @return mixed  Default field value
1044
	 */
1045
	public function get_default() {
1046
		if ( null !== $this->args['default'] ) {
1047
			return $this->args['default'];
1048
		}
1049
1050
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1051
		$default = $this->get_param_callback_result( $param, false );
1052
1053
		// Allow a filter override of the default value
1054
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1055
1056
		return $this->args['default'];
1057
	}
1058
1059
	/**
1060
	 * Fills in empty field parameters with defaults
1061
	 * @since 1.1.0
1062
	 * @param array $args Metabox field config array
1063
	 */
1064
	public function _set_field_defaults( $args ) {
1065
1066
		// Set up blank or default values for empty ones
1067
		$args = wp_parse_args( $args, array(
1068
			'type'              => '',
1069
			'name'              => '',
1070
			'desc'              => '',
1071
			'before'            => '',
1072
			'after'             => '',
1073
			'options'           => array(),
1074
			'options_cb'        => '',
1075
			'text'              => array(),
1076
			'text_cb'           => '',
1077
			'attributes'        => array(),
1078
			'protocols'         => null,
1079
			'default'           => null,
1080
			'default_cb'        => '',
1081
			'select_all_button' => true,
1082
			'multiple'          => false,
1083
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1084
			'inline'            => false,
1085
			'on_front'          => true,
1086
			'show_names'        => true,
1087
			'date_format'       => 'm\/d\/Y',
1088
			'time_format'       => 'h:i A',
1089
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1090
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1091
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1092
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1093
		) );
1094
1095
		// default param can be passed a callback as well
1096
		if ( is_callable( $args['default'] ) ) {
1097
			$args['default_cb'] = $args['default'];
1098
			$args['default'] = null;
1099
		}
1100
1101
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1102
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1103
1104
		// options param can be passed a callback as well
1105
		if ( is_callable( $args['options'] ) ) {
1106
			$args['options_cb'] = $args['options'];
1107
			$args['options'] = array();
1108
		}
1109
1110
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1111
			'add_button'    => __( 'Add Group', 'cmb2' ),
1112
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1113
		) ) : $args['options'];
1114
1115
		$args['_id']        = $args['id'];
1116
		$args['_name']      = $args['id'];
1117
1118
		if ( $this->group ) {
1119
1120
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1121
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1122
		}
1123
1124
		if ( 'wysiwyg' == $args['type'] ) {
1125
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1126
			$args['options']['textarea_name'] = $args['_name'];
1127
		}
1128
1129
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1130
1131
		if ( in_array( $args['type'], $option_types, true ) ) {
1132
1133
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1134
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1135
1136
			if ( null === $args['show_option_none'] ) {
1137
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1138
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1139
			}
1140
1141
		}
1142
1143
		$args['has_supporting_data'] = in_array(
1144
			$args['type'],
1145
			array(
1146
				// 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...
1147
				'file',
1148
				// 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...
1149
				'text_datetime_timestamp_timezone',
1150
			),
1151
			true
1152
		);
1153
1154
		return $args;
1155
	}
1156
1157
	/**
1158
	 * Returns a cloned version of this field object with, but with
1159
	 * modified/overridden field arguments.
1160
	 *
1161
	 * @since  2.2.2
1162
	 * @param  array  $field_args Array of field arguments, or entire array of
1163
	 *                            arguments for CMB2_Field
1164
	 *
1165
	 * @return CMB2_Field         The new CMB2_Field instance.
1166
	 */
1167
	public function get_field_clone( $field_args ) {
1168
		$args = array(
1169
			'field_args'  => array(),
1170
			'group_field' => $this->group,
1171
			'object_id'   => $this->object_id,
1172
			'object_type' => $this->object_type,
1173
			'cmb_id'      => $this->cmb_id,
1174
		);
1175
1176
		if ( isset( $field_args['field_args'] ) ) {
1177
			$args = wp_parse_args( $field_args, $args );
1178
		} else {
1179
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1180
		}
1181
1182
		return new CMB2_Field( $args );
1183
	}
1184
1185
	/**
1186
	 * Returns the CMB2 instance this field is registered to.
1187
	 *
1188
	 * @since  2.2.2
1189
	 *
1190
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1191
	 */
1192
	public function get_cmb() {
1193
		if ( ! $this->cmb_id ) {
1194
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1195
		}
1196
1197
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1198
	}
1199
1200
}
1201