Completed
Pull Request — trunk (#541)
by Justin
04:31
created

CMB2_Field::get_timestamp_from_value()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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 field param callback results
77
	 * @var   array
78 46
	 * @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 46
		'row_classes',
98
		'options_cb',
99
		'label_cb',
100 46
		'render_row_cb',
101 46
		'before_group',
102 46
		'before_group_row',
103 46
		'before_row',
104
		'before',
105
		'before_field',
106
		'after_field',
107
		'after',
108
		'after_row',
109
		'after_group_row',
110
		'after_group',
111
	);
112 38
113 38
	/**
114 38
	 * Constructs our field object
115
	 * @since 1.1.0
116
	 * @param array $args Field arguments
117
	 */
118
	public function __construct( $args ) {
119
120
		if ( ! empty( $args['group_field'] ) ) {
121
			$this->group       = $args['group_field'];
122
			$this->object_id   = $this->group->object_id;
123 46
			$this->object_type = $this->group->object_type;
124 46
		} else {
125 46
			$this->object_id   = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0;
126
			$this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post';
127
		}
128
129
		$this->args = $this->_set_field_defaults( $args['field_args'] );
130
131
		if ( $this->object_id ) {
132
			$this->value = $this->get_data();
133
		}
134
	}
135 46
136 46
	/**
137 46
	 * 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 46
	 * @param  array  $arguments Array of passed-in arguments
141
	 * @return mixed             Value of field argument
142
	 */
143
	public function __call( $name, $arguments ) {
144
		$key = isset( $arguments[0] ) ? $arguments[0] : false;
145
		return $this->args( $name, $key );
146
	}
147
148
	/**
149 37
	 * Retrieves the field id
150 37
	 * @since  1.1.0
151
	 * @param  boolean $raw Whether to retrieve pre-modidifed id
152
	 * @return string       Field id
153
	 */
154
	public function id( $raw = false ) {
155
		$id = $raw ? '_id' : 'id';
156
		return $this->args( $id );
157
	}
158
159
	/**
160 46
	 * Get a field argument
161 46
	 * @since  1.1.0
162 46
	 * @param  string $key  Argument to check
163 46
	 * @param  string $_key Sub argument to check
164
	 * @return mixed        Argument value or false if non-existent
165 46
	 */
166
	public function args( $key = '', $_key = '' ) {
167
		$arg = $this->_data( 'args', $key );
168
169
		if ( 'default' == $key ) {
170
171
			$arg = $this->get_param_callback_result( 'default', false );
172
173
		} elseif ( $_key ) {
174 46
175 46
			$arg = isset( $arg[ $_key ] ) ? $arg[ $_key ] : false;
176
		}
177 46
178
		return $arg;
179
	}
180
181 46
	/**
182
	 * Retrieve a portion of a field property
183 46
	 * @since  1.1.0
184 46
	 * @param  string  $var Field property to check
185 46
	 * @param  string  $key Field property array key to check
186
	 * @return mixed        Queried property value or false
187 46
	 */
188
	public function _data( $var, $key = '' ) {
189
		$vars = $this->$var;
190
		if ( $key ) {
191
			return array_key_exists( $key, $vars ) ? $vars[ $key ] : false;
192 46
		}
193
		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
	public function value( $key = '' ) {
203
		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
	public function get_data( $field_id = '', $args = array() ) {
214
		if ( $field_id ) {
215
			$args['field_id'] = $field_id;
216
		} else if ( $this->group ) {
217
			$args['field_id'] = $this->group->id();
218
		}
219
220
		$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
		$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
		$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
		if ( 'cmb2_field_no_override_val' === $data ) {
262
			$data = 'options-page' === $a['type']
263
				? cmb2_options( $a['id'] )->get( $a['field_id'] )
264
				: get_metadata( $a['type'], $a['id'], $a['field_id'], ( $a['single'] || $a['repeat'] ) );
265
		}
266
267
		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
		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
	public function update_data( $new_value, $single = true ) {
284
		$a = $this->data_args( array( 'single' => $single ) );
285
286
		$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
		$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
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_save", $override, $a, $this->args(), $this );
323
324
		// If override, return that
325
		if ( null !== $override ) {
326
			return $override;
327
		}
328
329
		// Options page handling (or temp data store)
330
		if ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
331
			return cmb2_options( $a['id'] )->update( $a['field_id'], $a['value'], false, $a['single'] );
332
		}
333
334
		// Add metadata if not single
335
		if ( ! $a['single'] ) {
336
			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
		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
		return update_metadata( $a['type'], $a['id'], $a['field_id'], $a['value'] );
346
	}
347
348
	/**
349
	 * Removes/updates metadata/option data
350 46
	 * @since  1.0.1
351 46
	 * @param  string $old Old value
352 46
	 */
353 46
	public function remove_data( $old = '' ) {
354 46
		$a = $this->data_args( array( 'old' => $old ) );
355 46
356 46
		/**
357 46
		 * Filter whether to override removing of meta value.
358 46
		 * 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
		$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
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this );
396
397
		// If no override, remove as usual
398
		if ( null !== $override ) {
399
			return $override;
400
		}
401
		// Option page handling
402
		elseif ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
403
			return cmb2_options( $a['id'] )->remove( $a['field_id'] );
404
		}
405
406
		// Remove metadata
407
		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
	public function data_args( $args = array() ) {
417
		$args = wp_parse_args( $args, array(
418
			'type'     => $this->object_type,
419
			'id'       => $this->object_id,
420
			'field_id' => $this->id( true ),
421
			'repeat'   => $this->args( 'repeatable' ),
422
			'single'   => ! $this->args( 'multiple' ),
423
		) );
424
		return $args;
425
	}
426
427
	/**
428
	 * Checks if field has a registered sanitization callback
429 39
	 * @since  1.0.1
430 39
	 * @param  mixed $meta_value Meta value
431 39
	 * @return mixed             Possibly sanitized meta value
432 38
	 */
433
	public function sanitization_cb( $meta_value ) {
434
435
		if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) {
436 7
			// Remove empties
437
			$meta_value = array_filter( $meta_value );
438
		}
439 7
440 4
		// Check if the field has a registered validation callback
441
		$cb = $this->maybe_callback( 'sanitization_cb' );
442
		if ( false === $cb ) {
443 3
			// If requesting NO validation, return meta value
444 2
			return $meta_value;
445
		} elseif ( $cb ) {
446 3
			// Ok, callback is good, let's run it.
447
			return call_user_func( $cb, $meta_value, $this->args(), $this );
448
		}
449
450
		$sanitizer = new CMB2_Sanitize( $this, $meta_value );
451
452
		/**
453 37
		 * Filter the value before it is saved.
454
		 *
455 37
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
456 37
		 *
457 37
		 * Passing a non-null value to the filter will short-circuit saving
458 37
		 * the field value, saving the passed value instead.
459 37
		 *
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
		$override_value = apply_filters( "cmb2_sanitize_{$this->type()}", null, $sanitizer->value, $this->object_id, $this->args(), $sanitizer );
468 1
469
		if ( null !== $override_value ) {
470 1
			return $override_value;
471 1
		}
472 1
473 1
		// Sanitization via 'CMB2_Sanitize'
474 1
		return $sanitizer->{$this->type()}();
475
	}
476 1
477 1
	/**
478 1
	 * Process $_POST data to save this field's value
479 1
	 * @since  2.0.3
480 1
	 * @param  array $data_to_save $_POST data to check
481 1
	 * @return bool                Result of save
482 1
	 */
483
	public function save_field_from_data( array $data_to_save ) {
484
		$this->data_to_save = $data_to_save;
485
486
		$meta_value = isset( $this->data_to_save[ $this->id( true ) ] )
487
			? $this->data_to_save[ $this->id( true ) ]
488
			: null;
489
490
		return $this->save_field( $meta_value );
491
	}
492 37
493
	/**
494 37
	 * Sanitize/store a value to this field
495 16
	 * @since  2.0.0
496
	 * @param  array $meta_value Desired value to sanitize/store
497
	 * @return bool              Result of save
498 37
	 */
499
	public function save_field( $meta_value ) {
500
501 37
		$new_value = $this->sanitization_cb( $meta_value );
502
		$old       = $this->get_data();
503
		$updated   = false;
504
		$action    = '';
505
506
		if ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) {
507 37
508 37
			$this->remove_data();
509
			$count = 0;
510
511
			if ( ! empty( $new_value ) ) {
512 37
				foreach ( $new_value as $add_new ) {
513
					if ( $this->update_data( $add_new, false ) ) {
514 4
						$count++;
515
					}
516
				}
517
			}
518 33
519 33
			$updated = $count ? $count : false;
520
			$action  = 'repeatable';
521 33
522
		} elseif ( ! cmb2_utils()->isempty( $new_value ) && $new_value !== $old ) {
523
			$updated = $this->update_data( $new_value );
524
			$action  = 'updated';
525
		} elseif ( cmb2_utils()->isempty( $new_value ) ) {
526 33
			$updated = $this->remove_data();
527
			$action  = 'removed';
528
		}
529 33
530 33
		if ( $updated ) {
531
			$this->value = $this->get_data();
532
			$this->escaped_value = null;
533
		}
534
535
		$field_id = $this->id( true );
536
537
		/**
538 1
		 * Hooks after save field action.
539 1
		 *
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 1
		do_action( 'cmb2_save_field', $field_id, $updated, $action, $this );
548
549
		/**
550 1
		 * Hooks after save field action.
551
		 *
552
		 * The dynamic portion of the hook, $field_id, refers to the
553
		 * current field id paramater.
554 1
		 *
555
		 * @since 2.2.0
556
		 *
557
		 * @param bool              $updated Whether the metadata update action occurred.
558 1
		 * @param string            $action  Action performed. Could be "repeatable", "updated", or "removed".
559
		 * @param CMB2_Field object $field   This field object
560
		 */
561
		do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this );
562
563
		return $updated;
564
	}
565
566
	/**
567
	 * Checks if field has a callback value
568 5
	 * @since  1.0.1
569 5
	 * @param  string $cb Callback string
570
	 * @return mixed      NULL, false for NO validation, or $cb string if it exists.
571
	 */
572
	public function maybe_callback( $cb ) {
573
		$field_args = $this->args();
574
		if ( ! isset( $field_args[ $cb ] ) ) {
575
			return;
576
		}
577
578 5
		// Check if metabox is requesting NO validation
579 5
		$cb = false !== $field_args[ $cb ] && 'false' !== $field_args[ $cb ] ? $field_args[ $cb ] : false;
580 5
581
		// If requesting NO validation, return false
582 5
		if ( ! $cb ) {
583
			return false;
584
		}
585
586 5
		if ( is_callable( $cb ) ) {
587 5
			return $cb;
588 5
		}
589
	}
590
591
	/**
592
	 * Determine if current type is excempt from escaping
593
	 * @since  1.1.0
594
	 * @return bool  True if exempt
595 5
	 */
596
	public function escaping_exception() {
597
		// These types cannot be escaped
598 5
		return in_array( $this->type(), array(
599
			'file_list',
600
			'multicheck',
601
			'text_datetime_timestamp_timezone',
602
		) );
603 5
	}
604
605
	/**
606
	 * Determine if current type cannot be repeatable
607 5
	 * @since  1.1.0
608
	 * @param  string $type Field type to check
609 5
	 * @return bool         True if type cannot be repeatable
610
	 */
611 5
	public function repeatable_exception( $type ) {
612
		// These types cannot be escaped
613
		$internal_fields = array(
614
			// Use file_list instead
615
			'file'                => 1,
616
			'radio'               => 1,
617
			'title'               => 1,
618
			// @todo Ajax load wp_editor: http://wordpress.stackexchange.com/questions/51776/how-to-load-wp-editor-through-ajax-jquery
619
			'wysiwyg'             => 1,
620 5
			'checkbox'            => 1,
621 5
			'radio_inline'        => 1,
622 5
			'taxonomy_radio'      => 1,
623
			'taxonomy_select'     => 1,
624 5
			'taxonomy_multicheck' => 1,
625
		);
626
627 5
		/**
628
		 * Filter field types that are non-repeatable.
629 5
		 *
630 5
		 * Note that this does *not* allow overriding the default non-repeatable types.
631
		 *
632 5
		 * @since 2.1.1
633
		 *
634 5
		 * @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 5
		 *                      array( 'my_custom_field' => 1 )
637 5
		 */
638
		$all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields );
639
		return isset( $all_fields[ $type ] );
640
	}
641
642
	/**
643
	 * Escape the value before output. Defaults to 'esc_attr()'
644
	 * @since  1.0.1
645 5
	 * @param  callable $func       Escaping function (if not esc_attr())
646 5
	 * @param  mixed    $meta_value Meta value
647 5
	 * @return mixed                Final value
648 5
	 */
649 5
	public function escaped_value( $func = 'esc_attr', $meta_value = '' ) {
650
651 5
		if ( null !== $this->escaped_value ) {
652
			return $this->escaped_value;
653 5
		}
654 5
655 5
		$meta_value = $meta_value ? $meta_value : $this->value();
656
657 5
		// Check if the field has a registered escaping callback
658 3
		if ( $cb = $this->maybe_callback( 'escape_cb' ) ) {
659 3
			// Ok, callback is good, let's run it.
660
			return call_user_func( $cb, $meta_value, $this->args(), $this );
661 5
		}
662
663
		// Or custom escaping filter can be used
664
		$esc = apply_filters( "cmb2_types_esc_{$this->type()}", null, $meta_value, $this->args(), $this );
665
		if ( null !== $esc ) {
666
			return $esc;
667
		}
668
669
		if ( false === $cb || $this->escaping_exception() ) {
670
			// If requesting NO escaping, return meta value
671 38
			return $this->val_or_default( $meta_value );
672 38
		}
673
674 2
		// escaping function passed in?
675 2
		$func       = $func ? $func : 'esc_attr';
676
		$meta_value = $this->val_or_default( $meta_value );
677
678
		if ( is_array( $meta_value ) ) {
679 38
			foreach ( $meta_value as $key => $value ) {
680 38
				$meta_value[ $key ] = call_user_func( $func, $value );
681
			}
682
		} else {
683
			$meta_value = call_user_func( $func, $meta_value );
684
		}
685
686
		$this->escaped_value = $meta_value;
687
		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
	public function val_or_default( $meta_value ) {
697
		return ! cmb2_utils()->isempty( $meta_value ) ? $meta_value : $this->get_param_callback_result( 'default', false );
698
	}
699 14
700 14
	/**
701 5
	 * Offset a time value based on timezone
702 4
	 * @since  1.0.0
703
	 * @return string Offset time string
704
	 */
705 1
	public function field_timezone_offset() {
706
		return cmb2_utils()->timezone_offset( $this->field_timezone() );
707
	}
708 14
709
	/**
710 14
	 * Return timezone string
711 1
	 * @since  1.0.0
712
	 * @return string Timezone string
713 1
	 */
714 1
	public function field_timezone() {
715 1
		$value = '';
716 1
717
		// Is timezone arg set?
718 14
		if ( $this->args( 'timezone' ) ) {
719 4
			$value = $this->args( 'timezone' );
720
		}
721
		// Is there another meta key with a timezone stored as its value we should use?
722 11
		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 46
	 * 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 46
	 * @param  string $format     Either date_format or time_format
734 46
	 * @return string             Formatted date
735 46
	 */
736 46
	public function format_timestamp( $meta_value, $format = 'date_format' ) {
737 46
		return date( stripslashes( $this->args( $format ) ), $meta_value );
738 46
	}
739 46
740 46
	/**
741 46
	 * Return a formatted timestamp for a field
742 46
	 * @since  2.0.0
743 46
	 * @param  string $format     Either date_format or time_format
744 46
	 * @param  string $meta_value Optional meta value to check
745 46
	 * @return string             Formatted date
746 46
	 */
747 46
	public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) {
748 46
		$meta_value = $meta_value ? $meta_value : $this->escaped_value();
749 46
		$meta_value = cmb2_utils()->make_valid_time_stamp( $meta_value );
750 46
751 46
		if ( empty( $meta_value ) ) {
752 46
			return '';
753 46
		}
754 46
755
		return is_array( $meta_value )
756
			? array_map( array( $this, 'format_timestamp' ), $meta_value, $format )
757
			: $this->format_timestamp( $meta_value, $format );
758 46
	}
759
760 46
	/**
761 46
	 * Get timestamp from text date
762
	 * @since  2.2.0
763
	 * @param  string $value Date value
764 46
	 * @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 46
770
	/**
771
	 * Get field render callback and Render the field row
772 46
	 * @since 1.0.0
773
	 */
774 46
	public function render_field() {
775 46
		// Check if the field has a registered render_field callback
776
		if ( $cb = $this->maybe_callback( 'render_row_cb' ) ) {
777 46
			// Ok, callback is good, let's run it.
778
			return call_user_func( $cb, $this->args(), $this );
779
		}
780
	}
781
782
	/**
783 46
	 * Default field render callback
784 1
	 * @since 2.1.1
785 1
	 */
786 1
	public function render_field_callback() {
787
788 46
		// If field is requesting to not be shown on the front-end
789
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
790 46
			return;
791
		}
792 2
793 2
		// If field is requesting to be conditionally shown
794
		if ( ! $this->should_show() ) {
795 2
			return;
796
		}
797 46
798
		$this->peform_param_callback( 'before_row' );
799
800
		printf( "<div class=\"cmb-row %s\" data-fieldtype=\"%s\">\n", $this->row_classes(), $this->type() );
801
802
		if ( ! $this->args( 'show_names' ) ) {
803
			echo "\n\t<div class=\"cmb-td\">\n";
804
805 36
			$this->peform_param_callback( 'label_cb' );
806 36
807
		} else {
808
809
			if ( $this->get_param_callback_result( 'label_cb', false ) ) {
810 1
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
811
			}
812
813
			echo "\n\t<div class=\"cmb-td\">\n";
814
		}
815
816
		$this->peform_param_callback( 'before' );
817
818
		$field_type = new CMB2_Types( $this );
819
		$field_type->render();
820
821
		$this->peform_param_callback( 'after' );
822
823
		echo "\n\t</div>\n</div>";
824
825
		$this->peform_param_callback( 'after_row' );
826
827
		// For chaining
828
		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
	public function label() {
838
		if ( ! $this->args( 'name' ) ) {
839
			return '';
840
		}
841
842
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
843
844
		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
	public function row_classes() {
854
855
		$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
		$repeat_table_rows_types = apply_filters( 'cmb2_repeat_table_row_types', array(
865
			'text_url', 'text',
866
		) );
867
868
		$conditional_classes = array(
869
			'cmb-type-' . str_replace( '_', '-', sanitize_html_class( $this->type() ) ) => true,
870
			'cmb2-id-' . str_replace( '_', '-', sanitize_html_class( $this->id() ) )    => true,
871
			'cmb-repeat'             => $this->args( 'repeatable' ),
872
			'cmb-repeat-group-field' => $this->group,
873
			'cmb-inline'             => $this->args( 'inline' ),
874
			'table-layout'           => in_array( $this->type(), $repeat_table_rows_types ),
875
		);
876
877
		foreach ( $conditional_classes as $class => $condition ) {
878
			if ( $condition ) {
879
				$classes[] = $class;
880
			}
881
		}
882
883
		if ( $added_classes = $this->get_param_callback_result( 'row_classes', false ) ) {
884
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
885
		}
886
887
		if ( $added_classes ) {
888
			$classes[] = esc_attr( $added_classes );
889
		}
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
		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 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
		$show = true;
912
913
		// Use the callback to determine showing the field, if it exists
914
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
915
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
916
		}
917
918
		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
	public function peform_param_callback( $param ) {
928
		echo $this->get_param_callback_result( $param );
929
	}
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
	public function get_param_callback_result( $param, $echo = true ) {
939
940
		// If we've already retrieved this param's value,
941
		if ( array_key_exists( $param, $this->callback_results ) ) {
942
			// send it back
943
			return $this->callback_results[ $param ];
944
		}
945
946
		if ( $cb = $this->maybe_callback( $param ) ) {
947
			if ( $echo ) {
948
				// Ok, callback is good, let's run it and store the result
949
				ob_start();
950
				echo call_user_func( $cb, $this->args(), $this );
951
				// grab the result from the output buffer and store it
952
				$this->callback_results[ $param ] = ob_get_contents();
953
				ob_end_clean();
954
			} else {
955
				$this->callback_results[ $param ] = call_user_func( $cb, $this->args(), $this );
956
			}
957
958
			return $this->callback_results[ $param ];
959
		}
960
961
		// Otherwise just get whatever is there
962
		$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
963
964
		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
	public function replace_hash( $value ) {
974
		// Replace hash with 1 based count
975
		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
	public function options( $key = '' ) {
985
		if ( ! empty( $this->field_options ) ) {
986
			if ( $key ) {
987
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
988
			}
989
990
			return $this->field_options;
991
		}
992
993
		$this->field_options = (array) $this->args['options'];
994
995
		if ( is_callable( $this->args['options_cb'] ) ) {
996
			$options = call_user_func( $this->args['options_cb'], $this );
997
998
			if ( $options && is_array( $options ) ) {
999
				$this->field_options += $options;
1000
			}
1001
		}
1002
1003
		if ( $key ) {
1004
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1005
		}
1006
1007
		return $this->field_options;
1008
	}
1009
1010
	/**
1011
	 * Fills in empty field parameters with defaults
1012
	 * @since 1.1.0
1013
	 * @param array $args Metabox field config array
1014
	 */
1015
	public function _set_field_defaults( $args ) {
1016
1017
		// Set up blank or default values for empty ones
1018
		$args = wp_parse_args( $args, array(
1019
			'type'              => '',
1020
			'name'              => '',
1021
			'desc'              => '',
1022
			'before'            => '',
1023
			'after'             => '',
1024
			'options_cb'        => '',
1025
			'options'           => array(),
1026
			'attributes'        => array(),
1027
			'protocols'         => null,
1028
			'default'           => null,
1029
			'select_all_button' => true,
1030
			'multiple'          => false,
1031
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1032
			'inline'            => false,
1033
			'on_front'          => true,
1034
			'show_names'        => true,
1035
			'date_format'       => 'm\/d\/Y',
1036
			'time_format'       => 'h:i A',
1037
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1038
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1039
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1040
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1041
			'show_in_rest'      => null,
1042
		) );
1043
1044
		// Allow a filter override of the default value
1045
		$args['default']    = apply_filters( 'cmb2_default_filter', $args['default'], $this );
1046
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1047
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1048
1049
		// options param can be passed a callback as well
1050
		if ( is_callable( $args['options'] ) ) {
1051
			$args['options_cb'] = $args['options'];
1052
			$args['options'] = array();
1053
		}
1054
1055
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1056
			'add_button'    => __( 'Add Group', 'cmb2' ),
1057
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1058
		) ) : $args['options'];
1059
1060
		$args['_id']        = $args['id'];
1061
		$args['_name']      = $args['id'];
1062
1063
		if ( $this->group ) {
1064
1065
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1066
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1067
		}
1068
1069
		if ( 'wysiwyg' == $args['type'] ) {
1070
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1071
			$args['options']['textarea_name'] = $args['_name'];
1072
		}
1073
1074
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1075
1076
		if ( in_array( $args['type'], $option_types, true ) ) {
1077
1078
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1079
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1080
1081
			if ( null === $args['show_option_none'] ) {
1082
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1083
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1084
			}
1085
1086
		}
1087
1088
		$args['has_supporting_data'] = in_array(
1089
			$args['type'],
1090
			array(
1091
				// 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...
1092
				'file',
1093
				// 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...
1094
				'text_datetime_timestamp_timezone',
1095
			),
1096
			true
1097
		);
1098
1099
		return $args;
1100
	}
1101
1102
	/**
1103
	 * Updates attributes array values unless they exist from the field config array
1104
	 * @since 1.1.0
1105
	 * @param array $attrs Array of attributes to update
1106
	 */
1107
	public function maybe_set_attributes( $attrs = array() ) {
1108
		return wp_parse_args( $this->args['attributes'], $attrs );
1109
	}
1110
1111
}
1112