Completed
Push — trunk ( ddc286...f54c26 )
by Justin
07:14
created

CMB2_Field::display_value_callback()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 43
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3.0593

Importance

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

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

namespace YourVendor;

class YourClass { }

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

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

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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