Completed
Pull Request — trunk (#588)
by Juliette
06:07
created

CMB2_Field::label()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

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

Loading history...
904
		// Default to showing the field
905 10
		$show = true;
906
907
		// Use the callback to determine showing the field, if it exists
908 10
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
909 1
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
910 1
		}
911
912 10
		return $show;
913
	}
914
915
	/**
916
	 * Displays the results of the param callbacks.
917
	 *
918
	 * @since 2.0.0
919
	 * @param string $param Field parameter
920
	 */
921 46
	public function peform_param_callback( $param ) {
922 46
		echo $this->get_param_callback_result( $param );
923 46
	}
924
925
	/**
926
	 * Store results of the param callbacks for continual access
927
	 * @since  2.0.0
928
	 * @param  string $param Field parameter
929
	 * @param  bool   $echo  Whether field should be 'echoed'
930
	 * @return mixed         Results of param/param callback
931
	 */
932 51
	public function get_param_callback_result( $param, $echo = true ) {
933
934
		// If we've already retrieved this param's value,
935 51
		if ( array_key_exists( $param, $this->callback_results ) ) {
936
			// send it back
937 10
			return $this->callback_results[ $param ];
938
		}
939
940 51
		if ( $cb = $this->maybe_callback( $param ) ) {
941 15
			if ( $echo ) {
942
				// Ok, callback is good, let's run it and store the result
943 4
				ob_start();
944 4
				echo call_user_func( $cb, $this->args(), $this );
945
				// grab the result from the output buffer and store it
946 4
				$this->callback_results[ $param ] = ob_get_contents();
947 4
				ob_end_clean();
948 4
			} else {
949 12
				$this->callback_results[ $param ] = call_user_func( $cb, $this->args(), $this );
950
			}
951
952 15
			return $this->callback_results[ $param ];
953
		}
954
955
		// Otherwise just get whatever is there
956 47
		$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
957
958 47
		return $this->callback_results[ $param ];
959
	}
960
961
	/**
962
	 * Replaces a hash key - {#} - with the repeatable index
963
	 * @since  1.2.0
964
	 * @param  string $value Value to update
965
	 * @return string        Updated value
966
	 */
967 2
	public function replace_hash( $value ) {
968
		// Replace hash with 1 based count
969 2
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
970
	}
971
972
	/**
973
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
974
	 * For back-compatibility, falls back to checking the options array.
975
	 *
976
	 * @since  2.2.2
977
	 * @param  string  $text_key Key in field's text array
978
	 * @param  string  $fallback Fallback text
979
	 * @return string            Text
980
	 */
981 6
	public function string( $text_key, $fallback ) {
982
		// If null, populate with our field strings values.
983 6
		if ( null === $this->strings ) {
984 6
			$this->strings = (array) $this->args['text'];
985
986 6 View Code Duplication
			if ( is_callable( $this->args['text_cb'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
987
				$strings = call_user_func( $this->args['text_cb'], $this );
988
989
				if ( $strings && is_array( $strings ) ) {
990
					$this->strings += $strings;
991
				}
992
			}
993 6
		}
994
995
		// If we have that string value, send it back.
996 6
		if ( isset( $this->strings[ $text_key ] ) ) {
997 1
			return $this->strings[ $text_key ];
998
		}
999
1000
		// Check options for back-compat.
1001 6
		$string = $this->options( $text_key );
1002
1003 6
		return $string ? $string : $fallback;
0 ignored issues
show
Bug Compatibility introduced by
The expression $string ? $string : $fallback; of type array|string adds the type array to the return on line 1003 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
1004
	}
1005
1006
	/**
1007
	 * Retrieve options args. Calls options_cb if it exists.
1008
	 * @since  2.0.0
1009
	 * @param  string  $key Specific option to retrieve
1010
	 * @return array        Array of options
1011
	 */
1012 24
	public function options( $key = '' ) {
1013 24
		if ( ! empty( $this->field_options ) ) {
1014 5
			if ( $key ) {
1015 5
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1016
			}
1017
1018 1
			return $this->field_options;
1019
		}
1020
1021 24
		$this->field_options = (array) $this->args['options'];
1022
1023 24 View Code Duplication
		if ( is_callable( $this->args['options_cb'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1024 1
			$options = call_user_func( $this->args['options_cb'], $this );
1025
1026 1
			if ( $options && is_array( $options ) ) {
1027 1
				$this->field_options += $options;
1028 1
			}
1029 1
		}
1030
1031 24
		if ( $key ) {
1032 8
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1033
		}
1034
1035 18
		return $this->field_options;
1036
	}
1037
1038
	/**
1039
	 * Get CMB2_Field default value, either from default param or default_cb param.
1040
	 *
1041
	 * @since  0.2.2
1042
	 *
1043
	 * @return mixed  Default field value
1044
	 */
1045 33
	public function get_default() {
1046 33
		if ( null !== $this->args['default'] ) {
1047 15
			return $this->args['default'];
1048
		}
1049
1050 32
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1051 32
		$default = $this->get_param_callback_result( $param, false );
1052
1053
		// Allow a filter override of the default value
1054 32
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1055
1056 32
		return $this->args['default'];
1057
	}
1058
1059
	/**
1060
	 * Fills in empty field parameters with defaults
1061
	 * @since 1.1.0
1062
	 * @param array $args Metabox field config array
1063
	 */
1064 73
	public function _set_field_defaults( $args ) {
1065
1066
		/**
1067
		 * Filter the CMB2 Field defaults.
1068
		 *
1069
		 * @since 2.2.2
1070
		 * @param array             $defaults Metabox field config array defaults.
1071
		 * @param string			$id       Field id for the current field to allow for selective filtering.
1072
		 * @param string            $type     Field type for the current field to allow for selective filtering.
1073
		 * @param CMB2_Field object $field    This field object.
1074
		 */
1075 73
		$defaults = apply_filters( 'cmb2_field_defaults', array(
1076 73
			'type'              => '',
1077 73
			'name'              => '',
1078 73
			'desc'              => '',
1079 73
			'before'            => '',
1080 73
			'after'             => '',
1081 73
			'options'           => array(),
1082 73
			'options_cb'        => '',
1083 73
			'text'              => array(),
1084 73
			'text_cb'           => '',
1085 73
			'attributes'        => array(),
1086 73
			'protocols'         => null,
1087 73
			'default'           => null,
1088 73
			'default_cb'        => '',
1089 73
			'select_all_button' => true,
1090 73
			'multiple'          => false,
1091 73
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1092 73
			'inline'            => false,
1093 73
			'on_front'          => true,
1094 73
			'show_names'        => true,
1095 73
			'date_format'       => 'm\/d\/Y',
1096 73
			'time_format'       => 'h:i A',
1097 73
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1098 73
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1099 73
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1100 73
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1101 73
		), $args['id'], $args['type'], $this );
1102
		 
1103
		// Set up blank or default values for empty ones
1104 73
		$args = wp_parse_args( $args, $defaults );
1105
1106
		/**
1107
		 * Filtering the CMB2 Field arguments once merged with the defaults, but before further processing.
1108
		 *
1109
		 * @since 2.2.2
1110
		 * @param array             $args  Metabox field config array defaults.
1111
		 * @param CMB2_Field object $field This field object.
1112
		 */
1113 73
		$args = apply_filters( 'cmb2_field_arguments_raw', $args, $this );
1114
1115
		// default param can be passed a callback as well
1116 73
		if ( is_callable( $args['default'] ) ) {
1117 19
			$args['default_cb'] = $args['default'];
1118 19
			$args['default'] = null;
1119 19
		}
1120
1121 73
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1122 73
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1123
1124
		// options param can be passed a callback as well
1125 73
		if ( is_callable( $args['options'] ) ) {
1126
			$args['options_cb'] = $args['options'];
1127
			$args['options'] = array();
1128
		}
1129
1130 73
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1131 3
			'add_button'    => __( 'Add Group', 'cmb2' ),
1132 3
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1133 73
		) ) : $args['options'];
1134
1135 73
		$args['_id']        = $args['id'];
1136 73
		$args['_name']      = $args['id'];
1137
1138 73
		if ( $this->group ) {
1139
1140 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1141 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1142 3
		}
1143
1144 73
		if ( 'wysiwyg' == $args['type'] ) {
1145 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1146 1
			$args['options']['textarea_name'] = $args['_name'];
1147 1
		}
1148
1149 73
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1150
1151 73
		if ( in_array( $args['type'], $option_types, true ) ) {
1152
1153 10
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1154 10
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1155
1156 10
			if ( null === $args['show_option_none'] ) {
1157 9
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1158 9
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1159 9
			}
1160
1161 10
		}
1162
1163 73
		$args['has_supporting_data'] = in_array(
1164 73
			$args['type'],
1165
			array(
1166
				// 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...
1167 73
				'file',
1168
				// 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...
1169 73
				'text_datetime_timestamp_timezone',
1170 73
			),
1171
			true
1172 73
		);
1173
1174
		/**
1175
		 * Filter the CMB2 Field arguments after processing.
1176
		 *
1177
		 * @since 2.2.2
1178
		 * @param array             $args  Metabox field config array after processing.
1179
		 * @param CMB2_Field object $field This field object.
1180
		 */
1181 73
		return apply_filters( 'cmb2_field_arguments', $args, $this );
1182
	}
1183
1184
	/**
1185
	 * Returns a cloned version of this field object with, but with
1186
	 * modified/overridden field arguments.
1187
	 *
1188
	 * @since  2.2.2
1189
	 * @param  array  $field_args Array of field arguments, or entire array of
1190
	 *                            arguments for CMB2_Field
1191
	 *
1192
	 * @return CMB2_Field         The new CMB2_Field instance.
1193
	 */
1194 3
	public function get_field_clone( $field_args ) {
1195
		$args = array(
1196 3
			'field_args'  => array(),
1197 3
			'group_field' => $this->group,
1198 3
			'object_id'   => $this->object_id,
1199 3
			'object_type' => $this->object_type,
1200 3
			'cmb_id'      => $this->cmb_id,
1201 3
		);
1202
1203 3
		if ( isset( $field_args['field_args'] ) ) {
1204
			$args = wp_parse_args( $field_args, $args );
1205
		} else {
1206 3
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1207
		}
1208
1209 3
		return new CMB2_Field( $args );
1210
	}
1211
1212
	/**
1213
	 * Returns the CMB2 instance this field is registered to.
1214
	 *
1215
	 * @since  2.2.2
1216
	 *
1217
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1218
	 */
1219 1
	public function get_cmb() {
1220 1
		if ( ! $this->cmb_id ) {
1221
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1222
		}
1223
1224 1
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1225
	}
1226
1227
}
1228