Completed
Pull Request — trunk (#541)
by Justin
05:46
created

CMB2_Field::field_timezone()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

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

Loading history...
910
		// Default to showing the field
911 9
		$show = true;
912
913
		// Use the callback to determine showing the field, if it exists
914 9
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
915 1
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
916 1
		}
917
918 9
		return $show;
919
	}
920
921
	/**
922
	 * Displays the results of the param callbacks.
923
	 *
924
	 * @since 2.0.0
925
	 * @param string $param Field parameter
926
	 */
927 45
	public function peform_param_callback( $param ) {
928 45
		echo $this->get_param_callback_result( $param );
929 45
	}
930
931
	/**
932
	 * Store results of the param callbacks for continual access
933
	 * @since  2.0.0
934
	 * @param  string $param Field parameter
935
	 * @param  bool   $echo  Whether field should be 'echoed'
936
	 * @return mixed         Results of param/param callback
937
	 */
938 50
	public function get_param_callback_result( $param, $echo = true ) {
939
940
		// If we've already retrieved this param's value,
941 50
		if ( array_key_exists( $param, $this->callback_results ) ) {
942
			// send it back
943 9
			return $this->callback_results[ $param ];
944
		}
945
946 50
		if ( $cb = $this->maybe_callback( $param ) ) {
947 14
			if ( $echo ) {
948
				// Ok, callback is good, let's run it and store the result
949 4
				ob_start();
950 4
				echo call_user_func( $cb, $this->args(), $this );
951
				// grab the result from the output buffer and store it
952 4
				$this->callback_results[ $param ] = ob_get_contents();
953 4
				ob_end_clean();
954 4
			} else {
955 11
				$this->callback_results[ $param ] = call_user_func( $cb, $this->args(), $this );
956
			}
957
958 14
			return $this->callback_results[ $param ];
959
		}
960
961
		// Otherwise just get whatever is there
962 46
		$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
963
964 46
		return $this->callback_results[ $param ];
965
	}
966
967
	/**
968
	 * Replaces a hash key - {#} - with the repeatable index
969
	 * @since  1.2.0
970
	 * @param  string $value Value to update
971
	 * @return string        Updated value
972
	 */
973 2
	public function replace_hash( $value ) {
974
		// Replace hash with 1 based count
975 2
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
976
	}
977
978
	/**
979
	 * Retrieve options args. Calls options_cb if it exists.
980
	 * @since  2.0.0
981
	 * @param  string  $key Specific option to retrieve
982
	 * @return array        Array of options
983
	 */
984 23
	public function options( $key = '' ) {
985 23
		if ( ! empty( $this->field_options ) ) {
986 4
			if ( $key ) {
987 4
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
988
			}
989
990
			return $this->field_options;
991
		}
992
993 23
		$this->field_options = (array) $this->args['options'];
994
995 23
		if ( is_callable( $this->args['options_cb'] ) ) {
996 1
			$options = call_user_func( $this->args['options_cb'], $this );
997
998 1
			if ( $options && is_array( $options ) ) {
999 1
				$this->field_options += $options;
1000 1
			}
1001 1
		}
1002
1003 23
		if ( $key ) {
1004 8
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1005
		}
1006
1007 17
		return $this->field_options;
1008
	}
1009
1010
	/**
1011
	 * Store JS dependencies as part of the field args.
1012
	 * @since 2.2.0
1013
	 * @param array $dependencies Dependies to register for this field.
1014
	 */
1015 12
	public function add_js_dependencies( $dependencies = array() ) {
1016 12
		foreach ( (array) $dependencies as $dependency ) {
1017 12
			$this->args['js_dependencies'][ $dependency ] = $dependency;
1018 12
		}
1019
1020 12
		CMB2_JS::add_dependencies( $dependencies );
1021 12
	}
1022
1023
	/**
1024
	 * Get CMB2_Field default value, either from default param or default_cb param.
1025
	 *
1026
	 * @since  0.2.2
1027
	 *
1028
	 * @return mixed  Default field value
1029
	 */
1030 32
	public function get_default() {
1031 32
		if ( null !== $this->args['default'] ) {
1032 14
			return $this->args['default'];
1033
		}
1034
1035 31
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1036 31
		$default = $this->get_param_callback_result( $param, false );
1037
1038
		// Allow a filter override of the default value
1039 31
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1040
1041 31
		return $this->args['default'];
1042
	}
1043
1044
	/**
1045
	 * Fills in empty field parameters with defaults
1046
	 * @since 1.1.0
1047
	 * @param array $args Metabox field config array
1048
	 */
1049 70
	public function _set_field_defaults( $args ) {
1050
1051
		// Set up blank or default values for empty ones
1052 70
		$args = wp_parse_args( $args, array(
1053 70
			'type'              => '',
1054 70
			'name'              => '',
1055 70
			'desc'              => '',
1056 70
			'before'            => '',
1057 70
			'after'             => '',
1058 70
			'options_cb'        => '',
1059 70
			'options'           => array(),
1060 70
			'attributes'        => array(),
1061 70
			'protocols'         => null,
1062 70
			'default'           => null,
1063 70
			'default_cb'        => '',
1064 70
			'select_all_button' => true,
1065 70
			'multiple'          => false,
1066 70
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1067 70
			'inline'            => false,
1068 70
			'on_front'          => true,
1069 70
			'show_names'        => true,
1070 70
			'date_format'       => 'm\/d\/Y',
1071 70
			'time_format'       => 'h:i A',
1072 70
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1073 70
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1074 70
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1075 70
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1076 70
			'show_in_rest'      => null,
1077 70
			'js_dependencies'   => array(),
1078 70
		) );
1079
1080
		// default param can be passed a callback as well
1081 70
		if ( is_callable( $args['default'] ) ) {
1082 16
			$args['default_cb'] = $args['default'];
1083 16
			$args['default'] = null;
1084 16
		}
1085
1086 70
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1087 70
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1088
1089
		// options param can be passed a callback as well
1090 70
		if ( is_callable( $args['options'] ) ) {
1091
			$args['options_cb'] = $args['options'];
1092
			$args['options'] = array();
1093
		}
1094
1095 70
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1096 3
			'add_button'    => __( 'Add Group', 'cmb2' ),
1097 3
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1098 70
		) ) : $args['options'];
1099
1100 70
		$args['_id']        = $args['id'];
1101 70
		$args['_name']      = $args['id'];
1102
1103 70
		if ( $this->group ) {
1104
1105 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1106 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1107 3
		}
1108
1109 70
		if ( 'wysiwyg' == $args['type'] ) {
1110 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1111 1
			$args['options']['textarea_name'] = $args['_name'];
1112 1
		}
1113
1114 70
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1115
1116 70
		if ( in_array( $args['type'], $option_types, true ) ) {
1117
1118 9
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1119 9
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1120
1121 9
			if ( null === $args['show_option_none'] ) {
1122 9
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1123 9
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1124 9
			}
1125
1126 9
		}
1127
1128 70
		$args['has_supporting_data'] = in_array(
1129 70
			$args['type'],
1130
			array(
1131
				// 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...
1132 70
				'file',
1133
				// 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...
1134 70
				'text_datetime_timestamp_timezone',
1135 70
			),
1136
			true
1137 70
		);
1138
1139 70
		return $args;
1140
	}
1141
1142
}
1143