Completed
Pull Request — trunk (#674)
by
unknown
24:14 queued 10:31
created

CMB2_Field::_set_field_defaults()   F

Complexity

Conditions 18
Paths 3328

Size

Total Lines 101
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 69
CRAP Score 18.2531

Importance

Changes 8
Bugs 4 Features 1
Metric Value
cc 18
eloc 68
c 8
b 4
f 1
nc 3328
nop 2
dl 0
loc 101
rs 2
ccs 69
cts 76
cp 0.9079
crap 18.2531

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

Loading history...
903
904 33
		$this->peform_param_callback( 'before_display' );
905
906 33
		CMB2_Field_Display::get( $this )->display();
907
908 33
		$this->peform_param_callback( 'after_display' );
909
910 33
		echo "\n</div>";
911
912 33
		$this->peform_param_callback( 'after_display_wrap' );
913
914
		// For chaining
915 33
		return $this;
916
	}
917
918
	/**
919
	 * Replaces a hash key - {#} - with the repeatable index
920
	 * @since  1.2.0
921
	 * @param  string $value Value to update
922
	 * @return string        Updated value
923
	 */
924 2
	public function replace_hash( $value ) {
925
		// Replace hash with 1 based count
926 2
		return str_ireplace( '{#}', ( $this->index + 1 ), $value );
927
	}
928
929
	/**
930
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
931
	 * For back-compatibility, falls back to checking the options array.
932
	 *
933
	 * @since  2.2.2
934
	 * @param  string  $text_key Key in field's text array
935
	 * @param  string  $fallback Fallback text
936
	 * @return string            Text
937
	 */
938 8
	public function string( $text_key, $fallback ) {
939
		// If null, populate with our field strings values.
940 8
		if ( null === $this->strings ) {
941 8
			$this->strings = (array) $this->args['text'];
942
943 8 View Code Duplication
			if ( is_callable( $this->args['text_cb'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
944
				$strings = call_user_func( $this->args['text_cb'], $this );
945
946
				if ( $strings && is_array( $strings ) ) {
947
					$this->strings += $strings;
948
				}
949
			}
950 8
		}
951
952
		// If we have that string value, send it back.
953 8
		if ( isset( $this->strings[ $text_key ] ) ) {
954 1
			return $this->strings[ $text_key ];
955
		}
956
957
		// Check options for back-compat.
958 8
		$string = $this->options( $text_key );
959
960 8
		return $string ? $string : $fallback;
0 ignored issues
show
Bug Compatibility introduced by
The expression $string ? $string : $fallback; of type array|string adds the type array to the return on line 960 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
961
	}
962
963
	/**
964
	 * Retrieve options args. Calls options_cb if it exists.
965
	 * @since  2.0.0
966
	 * @param  string  $key Specific option to retrieve
967
	 * @return array        Array of options
968
	 */
969 31
	public function options( $key = '' ) {
970 31
		if ( ! empty( $this->field_options ) ) {
971 5
			if ( $key ) {
972 5
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
973
			}
974
975 1
			return $this->field_options;
976
		}
977
978 31
		$this->field_options = (array) $this->args['options'];
979
980 31 View Code Duplication
		if ( is_callable( $this->args['options_cb'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
981 1
			$options = call_user_func( $this->args['options_cb'], $this );
982
983 1
			if ( $options && is_array( $options ) ) {
984 1
				$this->field_options = $options + $this->field_options;
985 1
			}
986 1
		}
987
988 31
		if ( $key ) {
989 10
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
990
		}
991
992 23
		return $this->field_options;
993
	}
994
995
	/**
996
	 * Get CMB2_Field default value, either from default param or default_cb param.
997
	 *
998
	 * @since  0.2.2
999
	 *
1000
	 * @return mixed  Default field value
1001
	 */
1002 33
	public function get_default() {
1003 33
		if ( null !== $this->args['default'] ) {
1004 15
			return $this->args['default'];
1005
		}
1006
1007 32
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1008 32
		$default = $this->get_param_callback_result( $param );
1009
1010
		// Allow a filter override of the default value
1011 32
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1012
1013 32
		return $this->args['default'];
1014
	}
1015
1016
	/**
1017
	 * Fills in empty field parameters with defaults
1018
	 * @since 1.1.0
1019
	 * @param array $args Metabox field config array
1020
	 */
1021 109
	public function _set_field_defaults( $args, $blah ) {
1022
1023 109
		if ( ! isset( $args['type'] ) ) {
1024
			$this;
1025
			$trace = wp_debug_backtrace_summary( null, 0, false );
1026
			// echo '<xmp>: '. print_r( $this, true ) .'</xmp>';
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1027
			wp_die( '<xmp>'. __LINE__ .') '. print_r( get_defined_vars(), true ) .'</xmp>' );
1028
		}
1029
		// Set up blank or default values for empty ones
1030 109
		$args = wp_parse_args( $args, array(
1031 109
			'type'              => '',
1032 109
			'name'              => '',
1033 109
			'desc'              => '',
1034 109
			'before'            => '',
1035 109
			'after'             => '',
1036 109
			'options'           => array(),
1037 109
			'options_cb'        => '',
1038 109
			'text'              => array(),
1039 109
			'text_cb'           => '',
1040 109
			'attributes'        => array(),
1041 109
			'protocols'         => null,
1042 109
			'default'           => null,
1043 109
			'default_cb'        => '',
1044 109
			'select_all_button' => true,
1045 109
			'multiple'          => false,
1046 109
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1047 109
			'inline'            => false,
1048 109
			'on_front'          => true,
1049 109
			'show_names'        => true,
1050 109
			'save_field'        => true, // Will not save if false
1051 109
			'date_format'       => 'm\/d\/Y',
1052 109
			'time_format'       => 'h:i A',
1053 109
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1054 109
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1055 109
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1056 109
			'display_cb'        => array( $this, 'display_value_callback' ),
1057 109
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1058 109
			'column'            => false,
1059 109
		) );
1060
1061
		// default param can be passed a callback as well
1062 109
		if ( is_callable( $args['default'] ) ) {
1063 21
			$args['default_cb'] = $args['default'];
1064 21
			$args['default'] = null;
1065 21
		}
1066
1067 109
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1068 109
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1069
1070
		// options param can be passed a callback as well
1071 109
		if ( is_callable( $args['options'] ) ) {
1072
			$args['options_cb'] = $args['options'];
1073
			$args['options'] = array();
1074
		}
1075
1076 109
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1077 3
			'add_button'    => __( 'Add Group', 'cmb2' ),
1078 3
			'remove_button' => __( 'Remove Group', 'cmb2' ),
1079 109
		) ) : $args['options'];
1080
1081 109
		$args['_id']        = $args['id'];
1082 109
		$args['_name']      = $args['id'];
1083
1084 109
		if ( $this->group ) {
1085
1086 3
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1087 3
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1088 3
		}
1089
1090 109
		if ( 'wysiwyg' == $args['type'] ) {
1091 1
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1092 1
			$args['options']['textarea_name'] = $args['_name'];
1093 1
		}
1094
1095 109
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1096
1097 109
		if ( in_array( $args['type'], $option_types, true ) ) {
1098
1099 16
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1100 16
			$args['show_option_none'] = true === $args['show_option_none'] ? __( 'None', 'cmb2' ) : $args['show_option_none'];
1101
1102 16
			if ( null === $args['show_option_none'] ) {
1103 15
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1104 15
				$args['show_option_none'] = $off_by_default ? false : __( 'None', 'cmb2' );
1105 15
			}
1106
1107 16
		}
1108
1109 109
		$args['has_supporting_data'] = in_array(
1110 109
			$args['type'],
1111
			array(
1112
				// CMB2_Sanitize::_save_file_id_value()/CMB2_Sanitize::_get_group_file_value_array()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1113 109
				'file',
1114
				// See CMB2_Sanitize::_save_utc_value()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1115 109
				'text_datetime_timestamp_timezone',
1116 109
			),
1117
			true
1118 109
		);
1119
1120 109
		return $args;
1121
	}
1122
1123
	/**
1124
	 * Get default field arguments specific to this CMB2 object.
1125
	 * @since  2.2.0
1126
	 * @param  array      $field_args  Metabox field config array.
1127
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1128
	 * @return array                   Array of field arguments.
1129
	 */
1130 5
	protected function get_default_args( $field_args, $field_group = null ) {
1131 5
		$args = parent::get_default_args( array(), $this->group );
1132
1133 5
		if ( isset( $field_args['field_args'] ) ) {
1134
			$args = wp_parse_args( $field_args, $args );
1135
		} else {
1136 5
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1137
		}
1138
1139 5
		return $args;
1140
	}
1141
1142
	/**
1143
	 * Returns a cloned version of this field object with, but with
1144
	 * modified/overridden field arguments.
1145
	 *
1146
	 * @since  2.2.2
1147
	 * @param  array  $field_args Array of field arguments, or entire array of
1148
	 *                            arguments for CMB2_Field
1149
	 *
1150
	 * @return CMB2_Field         The new CMB2_Field instance.
1151
	 */
1152 5
	public function get_field_clone( $field_args ) {
1153 5
		return $this->get_new_field( $field_args );
1154
	}
1155
1156
	/**
1157
	 * Returns the CMB2 instance this field is registered to.
1158
	 *
1159
	 * @since  2.2.2
1160
	 *
1161
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1162
	 */
1163 1
	public function get_cmb() {
1164 1
		if ( ! $this->cmb_id ) {
1165
			return new WP_Error( 'no_cmb_id', __( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1166
		}
1167
1168 1
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1169
	}
1170
1171
}
1172