Completed
Push — trunk ( 04102a...493a29 )
by Justin
05:45
created

CMB2_Field::save_field_from_data()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

namespace YourVendor;

class YourClass { }

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

Loading history...
18
19
	/**
20
	 * Metabox object id
21
	 * @var   mixed
22
	 * @since 1.1.0
23
	 */
24
	public $object_id = null;
25
26
	/**
27
	 * Metabox object type
28
	 * @var   string
29
	 * @since 1.1.0
30
	 */
31
	public $object_type = '';
32
33
	/**
34
	 * Field arguments
35
	 * @var   mixed
36
	 * @since 1.1.0
37
	 */
38
	public $args = array();
39
40
	/**
41
	 * Field group object or false (if no group)
42
	 * @var   mixed
43
	 * @since 1.1.0
44
	 */
45
	public $group = false;
46
47
	/**
48
	 * Field meta value
49
	 * @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 10
		$this->peform_param_callback( 'render_row_cb' );
770
		// For chaining
771 10
		return $this;
772
	}
773
774
	/**
775
	 * Default field render callback
776
	 * @since 2.1.1
777
	 */
778 9
	public function render_field_callback() {
779
780
		// If field is requesting to not be shown on the front-end
781 9
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
782
			return;
783
		}
784
785
		// If field is requesting to be conditionally shown
786 9
		if ( ! $this->should_show() ) {
787
			return;
788
		}
789
790 9
		$this->peform_param_callback( 'before_row' );
791
792 9
		printf( "<div class=\"cmb-row %s\" data-fieldtype=\"%s\">\n", $this->row_classes(), $this->type() );
793
794 9
		if ( ! $this->args( 'show_names' ) ) {
795
			echo "\n\t<div class=\"cmb-td\">\n";
796
797
			$this->peform_param_callback( 'label_cb' );
798
799
		} else {
800
801 9
			if ( $this->get_param_callback_result( 'label_cb' ) ) {
802 9
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
803 9
			}
804
805 9
			echo "\n\t<div class=\"cmb-td\">\n";
806
		}
807
808 9
		$this->peform_param_callback( 'before' );
809
810 9
		$field_type = new CMB2_Types( $this );
811 9
		$field_type->render();
812
813 9
		$this->peform_param_callback( 'after' );
814
815 9
		echo "\n\t</div>\n</div>";
816
817 9
		$this->peform_param_callback( 'after_row' );
818
819
		// For chaining
820 9
		return $this;
821
	}
822
823
	/**
824
	 * The default label_cb callback (if not a title field)
825
	 *
826
	 * @since  2.1.1
827
	 * @return string Label html markup
828
	 */
829 9
	public function label() {
830 9
		if ( ! $this->args( 'name' ) ) {
831
			return '';
832
		}
833
834 9
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
835
836 9
		return sprintf( "\n" . '<label%1$s for="%2$s">%3$s</label>' . "\n", $style, $this->id(), $this->args( 'name' ) );
837
	}
838
839
	/**
840
	 * Defines the classes for the current CMB2 field row
841
	 *
842
	 * @since  2.0.0
843
	 * @return string Space concatenated list of classes
844
	 */
845 12
	public function row_classes() {
846
847 12
		$classes = array();
848
849
		/**
850
		 * By default, 'text_url' and 'text' fields get table-like styling
851
		 *
852
		 * @since 2.0.0
853
		 *
854
		 * @param array $field_types The types of fields which should get the 'table-layout' class
855
		 */
856 12
		$repeat_table_rows_types = apply_filters( 'cmb2_repeat_table_row_types', array(
857 12
			'text_url', 'text',
858 12
		) );
859
860
		$conditional_classes = array(
861 12
			'cmb-type-' . str_replace( '_', '-', sanitize_html_class( $this->type() ) ) => true,
862 12
			'cmb2-id-' . str_replace( '_', '-', sanitize_html_class( $this->id() ) )    => true,
863 12
			'cmb-repeat'             => $this->args( 'repeatable' ),
864 12
			'cmb-repeat-group-field' => $this->group,
865 12
			'cmb-inline'             => $this->args( 'inline' ),
866 12
			'table-layout'           => in_array( $this->type(), $repeat_table_rows_types ),
867 12
		);
868
869 12
		foreach ( $conditional_classes as $class => $condition ) {
870 12
			if ( $condition ) {
871 12
				$classes[] = $class;
872 12
			}
873 12
		}
874
875 12
		if ( $added_classes = $this->get_param_callback_result( 'row_classes' ) ) {
876 3
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
877 3
		}
878
879 12
		if ( $added_classes ) {
880 3
			$classes[] = esc_attr( $added_classes );
881 3
		}
882
883
		/**
884
		 * Globally filter row classes
885
		 *
886
		 * @since 2.0.0
887
		 *
888
		 * @param string            $classes Space-separated list of row classes
889
		 * @param CMB2_Field object $field   This field object
890
		 */
891 12
		return apply_filters( 'cmb2_row_classes', implode( ' ', $classes ), $this );
892
	}
893
894
	/**
895
	 * Determine whether this field should show, based on the 'show_on_cb' callback.
896
	 *
897
	 * @since 2.0.9
898
	 *
899
	 * @return bool Whether the field should be shown.
900
	 */
901 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...
902
		// Default to showing the field
903 10
		$show = true;
904
905
		// Use the callback to determine showing the field, if it exists
906 10
		if ( is_callable( $this->args( 'show_on_cb' ) ) ) {
907 1
			$show = call_user_func( $this->args( 'show_on_cb' ), $this );
908 1
		}
909
910 10
		return $show;
911
	}
912
913
	/**
914
	 * Displays the results of the param callbacks.
915
	 *
916
	 * @since 2.0.0
917
	 * @param string $param Field parameter
918
	 */
919 47
	public function peform_param_callback( $param ) {
920 47
		echo $this->get_param_callback_result( $param );
921 47
	}
922
923
	/**
924
	 * Store results of the param callbacks for continual access
925
	 * @since  2.0.0
926
	 * @param  string $param Field parameter
927
	 * @param  bool   $echo  Whether field should be 'echoed'
0 ignored issues
show
Bug introduced by
There is no parameter named $echo. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
928
	 * @return mixed         Results of param/param callback
929
	 */
930 52
	public function get_param_callback_result( $param ) {
931
932
		// If we've already retrieved this param's value,
933 52
		if ( array_key_exists( $param, $this->callback_results ) ) {
934
935
			// send it back
936 10
			return $this->callback_results[ $param ];
937
		}
938
939
		// Check if parameter has registered a callback.
940 52
		if ( $cb = $this->maybe_callback( $param ) ) {
941
942
			// Ok, callback is good, let's run it and store the result.
943 16
			ob_start();
944 16
			$returned = call_user_func( $cb, $this->args(), $this );
945
946
			// Grab the result from the output buffer and store it.
947 16
			$echoed = ob_get_clean();
948
949
			// This checks if the user returned or echoed their callback.
950
			// Defaults to using the echoed value.
951 16
			$this->callback_results[ $param ] = $echoed ? $echoed : $returned;
952
953 16
		} else {
954
955
			// Otherwise just get whatever is there.
956 47
			$this->callback_results[ $param ] = isset( $this->args[ $param ] ) ? $this->args[ $param ] : false;
957
		}
958
959 52
		return $this->callback_results[ $param ];
960
	}
961
962
	/**
963
	 * Replaces a hash key - {#} - with the repeatable index
964
	 * @since  1.2.0
965
	 * @param  string $value Value to update
966
	 * @return string        Updated value
967
	 */
968 2
	public function replace_hash( $value ) {
969
		// Replace hash with 1 based count
970 2
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
971
	}
972
973
	/**
974
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
975
	 * For back-compatibility, falls back to checking the options array.
976
	 *
977
	 * @since  2.2.2
978
	 * @param  string  $text_key Key in field's text array
979
	 * @param  string  $fallback Fallback text
980
	 * @return string            Text
981
	 */
982 6
	public function string( $text_key, $fallback ) {
983
		// If null, populate with our field strings values.
984 6
		if ( null === $this->strings ) {
985 6
			$this->strings = (array) $this->args['text'];
986
987 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...
988
				$strings = call_user_func( $this->args['text_cb'], $this );
989
990
				if ( $strings && is_array( $strings ) ) {
991
					$this->strings += $strings;
992
				}
993
			}
994 6
		}
995
996
		// If we have that string value, send it back.
997 6
		if ( isset( $this->strings[ $text_key ] ) ) {
998 1
			return $this->strings[ $text_key ];
999
		}
1000
1001
		// Check options for back-compat.
1002 6
		$string = $this->options( $text_key );
1003
1004 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 1004 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
1005
	}
1006
1007
	/**
1008
	 * Retrieve options args. Calls options_cb if it exists.
1009
	 * @since  2.0.0
1010
	 * @param  string  $key Specific option to retrieve
1011
	 * @return array        Array of options
1012
	 */
1013 24
	public function options( $key = '' ) {
1014 24
		if ( ! empty( $this->field_options ) ) {
1015 5
			if ( $key ) {
1016 5
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1017
			}
1018
1019 1
			return $this->field_options;
1020
		}
1021
1022 24
		$this->field_options = (array) $this->args['options'];
1023
1024 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...
1025 1
			$options = call_user_func( $this->args['options_cb'], $this );
1026
1027 1
			if ( $options && is_array( $options ) ) {
1028 1
				$this->field_options += $options;
1029 1
			}
1030 1
		}
1031
1032 24
		if ( $key ) {
1033 8
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
1034
		}
1035
1036 18
		return $this->field_options;
1037
	}
1038
1039
	/**
1040
	 * Get CMB2_Field default value, either from default param or default_cb param.
1041
	 *
1042
	 * @since  0.2.2
1043
	 *
1044
	 * @return mixed  Default field value
1045
	 */
1046 33
	public function get_default() {
1047 33
		if ( null !== $this->args['default'] ) {
1048 15
			return $this->args['default'];
1049
		}
1050
1051 32
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1052 32
		$default = $this->get_param_callback_result( $param );
1053
1054
		// Allow a filter override of the default value
1055 32
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1056
1057 32
		return $this->args['default'];
1058
	}
1059
1060
	/**
1061
	 * Fills in empty field parameters with defaults
1062
	 * @since 1.1.0
1063
	 * @param array $args Metabox field config array
1064
	 */
1065 73
	public function _set_field_defaults( $args ) {
1066
1067
		// Set up blank or default values for empty ones
1068 73
		$args = wp_parse_args( $args, array(
1069 73
			'type'              => '',
1070 73
			'name'              => '',
1071 73
			'desc'              => '',
1072 73
			'before'            => '',
1073 73
			'after'             => '',
1074 73
			'options'           => array(),
1075 73
			'options_cb'        => '',
1076 73
			'text'              => array(),
1077 73
			'text_cb'           => '',
1078 73
			'attributes'        => array(),
1079 73
			'protocols'         => null,
1080 73
			'default'           => null,
1081 73
			'default_cb'        => '',
1082 73
			'select_all_button' => true,
1083 73
			'multiple'          => false,
1084 73
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1085 73
			'inline'            => false,
1086 73
			'on_front'          => true,
1087 73
			'show_names'        => true,
1088 73
			'date_format'       => 'm\/d\/Y',
1089 73
			'time_format'       => 'h:i A',
1090 73
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1091 73
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1092 73
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1093 73
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1094 73
		) );
1095
1096
		// default param can be passed a callback as well
1097 73
		if ( is_callable( $args['default'] ) ) {
1098 19
			$args['default_cb'] = $args['default'];
1099 19
			$args['default'] = null;
1100 19
		}
1101
1102 73
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1103 73
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1104
1105
		// options param can be passed a callback as well
1106 73
		if ( is_callable( $args['options'] ) ) {
1107
			$args['options_cb'] = $args['options'];
1108
			$args['options'] = array();
1109
		}
1110
1111 73
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1112 3
			'add_button'    => __( 'Add Group', 'cmb2' ),
1113 3
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1114 73
		) ) : $args['options'];
1115
1116 73
		$args['_id']        = $args['id'];
1117 73
		$args['_name']      = $args['id'];
1118
1119 73
		if ( $this->group ) {
1120
1121 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1122 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1123 3
		}
1124
1125 73
		if ( 'wysiwyg' == $args['type'] ) {
1126 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1127 1
			$args['options']['textarea_name'] = $args['_name'];
1128 1
		}
1129
1130 73
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1131
1132 73
		if ( in_array( $args['type'], $option_types, true ) ) {
1133
1134 10
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1135 10
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1136
1137 10
			if ( null === $args['show_option_none'] ) {
1138 9
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1139 9
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1140 9
			}
1141
1142 10
		}
1143
1144 73
		$args['has_supporting_data'] = in_array(
1145 73
			$args['type'],
1146
			array(
1147
				// 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...
1148 73
				'file',
1149
				// 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...
1150 73
				'text_datetime_timestamp_timezone',
1151 73
			),
1152
			true
1153 73
		);
1154
1155 73
		return $args;
1156
	}
1157
1158
	/**
1159
	 * Returns a cloned version of this field object with, but with
1160
	 * modified/overridden field arguments.
1161
	 *
1162
	 * @since  2.2.2
1163
	 * @param  array  $field_args Array of field arguments, or entire array of
1164
	 *                            arguments for CMB2_Field
1165
	 *
1166
	 * @return CMB2_Field         The new CMB2_Field instance.
1167
	 */
1168 3
	public function get_field_clone( $field_args ) {
1169
		$args = array(
1170 3
			'field_args'  => array(),
1171 3
			'group_field' => $this->group,
1172 3
			'object_id'   => $this->object_id,
1173 3
			'object_type' => $this->object_type,
1174 3
			'cmb_id'      => $this->cmb_id,
1175 3
		);
1176
1177 3
		if ( isset( $field_args['field_args'] ) ) {
1178
			$args = wp_parse_args( $field_args, $args );
1179
		} else {
1180 3
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1181
		}
1182
1183 3
		return new CMB2_Field( $args );
1184
	}
1185
1186
	/**
1187
	 * Returns the CMB2 instance this field is registered to.
1188
	 *
1189
	 * @since  2.2.2
1190
	 *
1191
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1192
	 */
1193 1
	public function get_cmb() {
1194 1
		if ( ! $this->cmb_id ) {
1195
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1196
		}
1197
1198 1
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1199
	}
1200
1201
}
1202