Completed
Pull Request — trunk (#578)
by Juliette
14:17 queued 06:50
created

CMB2_Field::repeatable_exception()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

namespace YourVendor;

class YourClass { }

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

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