Completed
Push — trunk ( df6be1...704260 )
by Justin
05:04
created

CMB2_Field::_data()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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