Completed
Pull Request — trunk (#696)
by
unknown
03:31
created

CMB2_Field::escaped_value()   D

Complexity

Conditions 10
Paths 15

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 13.2218

Importance

Changes 0
Metric Value
cc 10
eloc 20
nc 15
nop 2
dl 0
loc 40
ccs 15
cts 22
cp 0.6818
crap 13.2218
rs 4.8196
c 0
b 0
f 0

How to fix   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 46
	 * @var   mixed
50
	 * @since 1.1.0
51 46
	 */
52
	public $escaped_value = null;
53
54
	/**
55
	 * Grouped Field's current numeric index during the save process
56 46
	 * @var   mixed
57 46
	 * @since 2.0.0
58 46
	 */
59
	public $index = 0;
60
61 46
	/**
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 46
	 * @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
	public function __construct( $args ) {
88
89
		if ( ! empty( $args['group_field'] ) ) {
90
			$this->group       = $args['group_field'];
91
			$this->object_id   = $this->group->object_id;
92
			$this->object_type = $this->group->object_type;
93
			$this->cmb_id      = $this->group->cmb_id;
94
		} else {
95
			$this->object_id   = isset( $args['object_id'] ) && '_' !== $args['object_id'] ? $args['object_id'] : 0;
96
			$this->object_type = isset( $args['object_type'] ) ? $args['object_type'] : 'post';
97 46
98
			if ( isset( $args['cmb_id'] ) ) {
99
				$this->cmb_id = $args['cmb_id'];
100 46
			}
101 46
		}
102 46
103 46
		$this->args = $this->_set_field_defaults( $args['field_args'], $args );
104
105
		if ( $this->object_id ) {
106
			$this->value = $this->get_data();
107
		}
108
	}
109
110
	/**
111
	 * Non-existent methods fallback to checking for field arguments of the same name
112 38
	 * @since  1.1.0
113 38
	 * @param  string $name     Method name
114 38
	 * @param  array  $arguments Array of passed-in arguments
115
	 * @return mixed             Value of field argument
116
	 */
117
	public function __call( $name, $arguments ) {
118
		$key = isset( $arguments[0] ) ? $arguments[0] : false;
119
		return $this->args( $name, $key );
120
	}
121
122
	/**
123 46
	 * Retrieves the field id
124 46
	 * @since  1.1.0
125 46
	 * @param  boolean $raw Whether to retrieve pre-modidifed id
126
	 * @return string       Field id
127
	 */
128
	public function id( $raw = false ) {
129
		$id = $raw ? '_id' : 'id';
130
		return $this->args( $id );
131
	}
132
133
	/**
134
	 * Get a field argument
135 46
	 * @since  1.1.0
136 46
	 * @param  string $key  Argument to check
137 46
	 * @param  string $_key Sub argument to check
138
	 * @return mixed        Argument value or false if non-existent
139
	 */
140 46
	public function args( $key = '', $_key = '' ) {
141
		$arg = $this->_data( 'args', $key );
142
143
		if ( in_array( $key, array( 'default', 'default_cb' ), true ) ) {
144
145
			$arg = $this->get_default();
146
147
		} elseif ( $_key ) {
148
149 37
			$arg = isset( $arg[ $_key ] ) ? $arg[ $_key ] : false;
150 37
		}
151
152
		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 46
	 * @return mixed        Queried property value or false
161 46
	 */
162 46
	public function _data( $var, $key = '' ) {
163 46
		$vars = $this->{$var};
164
		if ( $key ) {
165 46
			return isset( $vars[ $key ] ) ? $vars[ $key ] : false;
166
		}
167
		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 46
	 * @return mixed       Field value or false if non-existent
175 46
	 */
176
	public function value( $key = '' ) {
177 46
		return $this->_data( 'value', $key );
178
	}
179
180
	/**
181 46
	 * Retrieves metadata/option data
182
	 * @since  1.0.1
183 46
	 * @param  string $field_id Meta key/Option array key
184 46
	 * @param  array  $args     Override arguments
185 46
	 * @return mixed            Meta/Option value
186
	 */
187 46
	public function get_data( $field_id = '', $args = array() ) {
188
		if ( $field_id ) {
189
			$args['field_id'] = $field_id;
190
		} else if ( $this->group ) {
191
			$args['field_id'] = $this->group->id();
192 46
		}
193
194
		$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
		$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
		$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
		if ( 'cmb2_field_no_override_val' === $data ) {
236
			$data = 'options-page' === $a['type']
237
				? cmb2_options( $a['id'] )->get( $a['field_id'] )
238
				: get_metadata( $a['type'], $a['id'], $a['field_id'], ( $a['single'] || $a['repeat'] ) );
239
		}
240
241
		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
		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
	public function update_data( $new_value, $single = true ) {
258
		$a = $this->data_args( array( 'single' => $single ) );
259
260
		$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
		$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
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_save", $override, $a, $this->args(), $this );
297
298
		// If override, return that
299
		if ( null !== $override ) {
300
			return $override;
301
		}
302
303
		// Options page handling (or temp data store)
304
		if ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
305
			return cmb2_options( $a['id'] )->update( $a['field_id'], $a['value'], false, $a['single'] );
306
		}
307
308
		// Add metadata if not single
309
		if ( ! $a['single'] ) {
310
			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
		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
		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
	public function remove_data( $old = '' ) {
328
		$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
		$override = apply_filters( 'cmb2_override_meta_remove', null, $a, $this->args(), $this );
348
349
		/**
350 46
		 * Filter whether to override removing of meta value.
351 46
		 *
352 46
		 * The dynamic portion of the hook, $a['field_id'], refers to the current
353 46
		 * field id paramater. Returning a non-null value
354 46
		 * will effectively short-circuit the function.
355 46
		 *
356 46
		 * @since 2.0.0
357 46
		 *
358 46
		 * @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
		$override = apply_filters( "cmb2_override_{$a['field_id']}_meta_remove", $override, $a, $this->args(), $this );
370
371
		// If no override, remove as usual
372
		if ( null !== $override ) {
373
			return $override;
374
		}
375
		// Option page handling
376
		elseif ( 'options-page' === $a['type'] || empty( $a['id'] ) ) {
377
			return cmb2_options( $a['id'] )->remove( $a['field_id'] );
378
		}
379
380
		// Remove metadata
381
		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
	public function data_args( $args = array() ) {
391
		$args = wp_parse_args( $args, array(
392
			'type'     => $this->object_type,
393
			'id'       => $this->object_id,
394
			'field_id' => $this->id( true ),
395
			'repeat'   => $this->args( 'repeatable' ),
396
			'single'   => ! $this->args( 'multiple' ),
397
		) );
398
		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
	public function sanitization_cb( $meta_value ) {
408
409
		if ( $this->args( 'repeatable' ) && is_array( $meta_value ) ) {
410
			// Remove empties
411
			$meta_value = array_filter( $meta_value );
412
		}
413
414
		// Check if the field has a registered validation callback
415
		$cb = $this->maybe_callback( 'sanitization_cb' );
416
		if ( false === $cb ) {
417
			// If requesting NO validation, return meta value
418
			return $meta_value;
419
		} 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
		$sanitizer = new CMB2_Sanitize( $this, $meta_value );
425
426
		/**
427
		 * Filter the value before it is saved.
428
		 *
429 39
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
430 39
		 *
431 39
		 * Passing a non-null value to the filter will short-circuit saving
432 38
		 * 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 7
		 * @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 7
		 * @param object     $sanitizer  This `CMB2_Sanitize` object
440 4
		 */
441
		$override_value = apply_filters( "cmb2_sanitize_{$this->type()}", null, $sanitizer->value, $this->object_id, $this->args(), $sanitizer );
442
443 3
		if ( null !== $override_value ) {
444 2
			return $override_value;
445
		}
446 3
447
		// Sanitization via 'CMB2_Sanitize'
448
		return $sanitizer->{$this->type()}();
449
	}
450
451
	/**
452
	 * Process $_POST data to save this field's value
453 37
	 * @since  2.0.3
454
	 * @param  array $data_to_save $_POST data to check
455 37
	 * @return array|int|bool                Result of save, false on failure
456 37
	 */
457 37
	public function save_field_from_data( array $data_to_save ) {
458 37
		$this->data_to_save = $data_to_save;
459 37
460
		$meta_value = isset( $this->data_to_save[ $this->id( true ) ] )
461
			? $this->data_to_save[ $this->id( true ) ]
462
			: null;
463
464
		return $this->save_field( $meta_value );
465
	}
466
467
	/**
468 1
	 * Sanitize/store a value to this field
469
	 * @since  2.0.0
470 1
	 * @param  array $meta_value Desired value to sanitize/store
471 1
	 * @return array|int|bool              Result of save. false on failure
472 1
	 */
473 1
	public function save_field( $meta_value ) {
474 1
475
		$updated   = false;
476 1
		$action    = '';
477 1
		$new_value = $this->sanitization_cb( $meta_value );
478 1
479 1
		if ( ! $this->args( 'save_field' ) ) {
480 1
481 1
			// Nothing to see here.
482 1
			$action = 'disabled';
483
484
		} elseif ( $this->args( 'multiple' ) && ! $this->args( 'repeatable' ) && ! $this->group ) {
485
486
			$this->remove_data();
487
			$count = 0;
488
489
			if ( ! empty( $new_value ) ) {
490
				foreach ( $new_value as $add_new ) {
491
					if ( $this->update_data( $add_new, false ) ) {
492 37
						$count++;
493
					}
494 37
				}
495 16
			}
496
497
			$updated = $count ? $count : false;
498 37
			$action  = 'repeatable';
499
500
		} elseif ( ! cmb2_utils()->isempty( $new_value ) && $new_value !== $this->get_data() ) {
501 37
			$updated = $this->update_data( $new_value );
502
			$action  = 'updated';
503
		} elseif ( cmb2_utils()->isempty( $new_value ) ) {
504
			$updated = $this->remove_data();
505
			$action  = 'removed';
506
		}
507 37
508 37
		if ( $updated ) {
509
			$this->value = $this->get_data();
510
		}
511
512 37
		$field_id = $this->id( true );
513
514 4
		/**
515
		 * Hooks after save field action.
516
		 *
517
		 * @since 2.2.0
518 33
		 *
519 33
		 * @param string            $field_id the current field id paramater.
520
		 * @param bool              $updated  Whether the metadata update action occurred.
521 33
		 * @param string            $action   Action performed. Could be "repeatable", "updated", or "removed".
522
		 * @param CMB2_Field object $field    This field object
523
		 */
524
		do_action( 'cmb2_save_field', $field_id, $updated, $action, $this );
525
526 33
		/**
527
		 * Hooks after save field action.
528
		 *
529 33
		 * The dynamic portion of the hook, $field_id, refers to the
530 33
		 * 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 1
		do_action( "cmb2_save_field_{$field_id}", $updated, $action, $this );
539 1
540
		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 1
	 */
548
	public function escaping_exception() {
549
		// These types cannot be escaped
550 1
		return in_array( $this->type(), array(
551
			'file_list',
552
			'multicheck',
553
			'text_datetime_timestamp_timezone',
554 1
		) );
555
	}
556
557
	/**
558 1
	 * 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
	public function repeatable_exception( $type ) {
564
		// These types cannot be escaped
565
		$internal_fields = array(
566
			// Use file_list instead
567
			'file'                => 1,
568 5
			'radio'               => 1,
569 5
			'title'               => 1,
570
			// @todo Ajax load wp_editor: http://wordpress.stackexchange.com/questions/51776/how-to-load-wp-editor-through-ajax-jquery
571
			'wysiwyg'             => 1,
572
			'checkbox'            => 1,
573
			'radio_inline'        => 1,
574
			'taxonomy_radio'      => 1,
575
			'taxonomy_select'     => 1,
576
			'taxonomy_multicheck' => 1,
577
		);
578 5
579 5
		/**
580 5
		 * Filter field types that are non-repeatable.
581
		 *
582 5
		 * Note that this does *not* allow overriding the default non-repeatable types.
583
		 *
584
		 * @since 2.1.1
585
		 *
586 5
		 * @param array $fields Array of fields designated as non-repeatable. Note that the field names are *keys*,
587 5
		 *                      and not values. The value can be anything, because it is meaningless. Example:
588 5
		 *                      array( 'my_custom_field' => 1 )
589
		 */
590
		$all_fields = array_merge( apply_filters( 'cmb2_non_repeatable_fields', array() ), $internal_fields );
591
		return isset( $all_fields[ $type ] );
592
	}
593
594
	/**
595 5
	 * 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 5
	 * @param  mixed    $meta_value Meta value
599
	 * @return mixed                Final value
600
	 */
601
	public function escaped_value( $func = 'esc_attr', $meta_value = '' ) {
602
603 5
		if ( null !== $this->escaped_value ) {
604
			return $this->escaped_value;
605
		}
606
607 5
		$meta_value = $meta_value ? $meta_value : $this->value();
608
609 5
		// Check if the field has a registered escaping callback
610
		if ( $cb = $this->maybe_callback( 'escape_cb' ) ) {
611 5
			// 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
		$esc = apply_filters( "cmb2_types_esc_{$this->type()}", null, $meta_value, $this->args(), $this );
617
		if ( null !== $esc ) {
618
			return $esc;
619
		}
620 5
621 5
		if ( false === $cb || $this->escaping_exception() ) {
622 5
			// If requesting NO escaping, return meta value
623
			return $this->val_or_default( $meta_value );
624 5
		}
625
626
		// escaping function passed in?
627 5
		$func       = $func ? $func : 'esc_attr';
628
		$meta_value = $this->val_or_default( $meta_value );
629 5
630 5
		if ( is_array( $meta_value ) ) {
631
			foreach ( $meta_value as $key => $value ) {
632 5
				$meta_value[ $key ] = call_user_func( $func, $value );
633
			}
634 5
		} else {
635
			$meta_value = call_user_func( $func, $meta_value );
636 5
		}
637 5
638
		$this->escaped_value = $meta_value;
639
		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 5
	 * @param  mixed $meta_value Field value
646 5
	 * @return mixed             Field value, or default value
647 5
	 */
648 5
	public function val_or_default( $meta_value ) {
649 5
		return ! cmb2_utils()->isempty( $meta_value ) ? $meta_value : $this->get_default();
650
	}
651 5
652
	/**
653 5
	 * Offset a time value based on timezone
654 5
	 * @since  1.0.0
655 5
	 * @return string Offset time string
656
	 */
657 5
	public function field_timezone_offset() {
658 3
		return cmb2_utils()->timezone_offset( $this->field_timezone() );
659 3
	}
660
661 5
	/**
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 38
			$value = $this->args( 'timezone' );
672 38
		}
673
		// Is there another meta key with a timezone stored as its value we should use?
674 2
		else if ( $this->args( 'timezone_meta_key' ) ) {
675 2
			$value = $this->get_data( $this->args( 'timezone_meta_key' ) );
676
		}
677
678
		return $value;
679 38
	}
680 38
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
	public function format_timestamp( $meta_value, $format = 'date_format' ) {
689
		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 14
	public function get_timestamp_format( $format = 'date_format', $meta_value = 0 ) {
700 14
		$meta_value = $meta_value ? $meta_value : $this->escaped_value();
701 5
		$meta_value = cmb2_utils()->make_valid_time_stamp( $meta_value );
702 4
703
		if ( empty( $meta_value ) ) {
704
			return '';
705 1
		}
706
707
		return is_array( $meta_value )
708 14
			? array_map( array( $this, 'format_timestamp' ), $meta_value, $format )
709
			: $this->format_timestamp( $meta_value, $format );
710 14
	}
711 1
712
	/**
713 1
	 * Get timestamp from text date
714 1
	 * @since  2.2.0
715 1
	 * @param  string $value Date value
716 1
	 * @return mixed         Unix timestamp representing the date.
717
	 */
718 14
	public function get_timestamp_from_value( $value ) {
719 4
		return cmb2_utils()->get_timestamp_from_value( $value, $this->args( 'date_format' ) );
720
	}
721
722 11
	/**
723
	 * Get field render callback and Render the field row
724
	 * @since 1.0.0
725
	 */
726
	public function render_field() {
727
		$this->render_context = 'edit';
728
729
		$this->peform_param_callback( 'render_row_cb' );
730 46
731
		// For chaining
732
		return $this;
733 46
	}
734 46
735 46
	/**
736 46
	 * Default field render callback
737 46
	 * @since 2.1.1
738 46
	 */
739 46
	public function render_field_callback() {
740 46
741 46
		// If field is requesting to not be shown on the front-end
742 46
		if ( ! is_admin() && ! $this->args( 'on_front' ) ) {
743 46
			return;
744 46
		}
745 46
746 46
		// If field is requesting to be conditionally shown
747 46
		if ( ! $this->should_show() ) {
748 46
			return;
749 46
		}
750 46
751 46
		$this->peform_param_callback( 'before_row' );
752 46
753 46
		printf( "<div class=\"cmb-row %s\" data-fieldtype=\"%s\">\n", $this->row_classes(), $this->type() );
754 46
755
		if ( ! $this->args( 'show_names' ) ) {
756
			echo "\n\t<div class=\"cmb-td\">\n";
757
758 46
			$this->peform_param_callback( 'label_cb' );
759
760 46
		} else {
761 46
762
			if ( $this->get_param_callback_result( 'label_cb' ) ) {
763
				echo '<div class="cmb-th">', $this->peform_param_callback( 'label_cb' ), '</div>';
764 46
			}
765
766
			echo "\n\t<div class=\"cmb-td\">\n";
767
		}
768
769 46
		$this->peform_param_callback( 'before' );
770
771
		$field_type = new CMB2_Types( $this );
772 46
		$field_type->render();
773
774 46
		$this->peform_param_callback( 'after' );
775 46
776
		echo "\n\t</div>\n</div>";
777 46
778
		$this->peform_param_callback( 'after_row' );
779
780
		// For chaining
781
		return $this;
782
	}
783 46
784 1
	/**
785 1
	 * The default label_cb callback (if not a title field)
786 1
	 *
787
	 * @since  2.1.1
788 46
	 * @return string Label html markup
789
	 */
790 46
	public function label() {
791
		if ( ! $this->args( 'name' ) ) {
792 2
			return '';
793 2
		}
794
795 2
		$style = ! $this->args( 'show_names' ) ? ' style="display:none;"' : '';
796
797 46
		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 36
	 */
806 36
	public function row_classes() {
807
808
		$classes = array();
809
810 1
		/**
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
		$repeat_table_rows_types = apply_filters( 'cmb2_repeat_table_row_types', array(
818
			'text_url', 'text',
819
		) );
820
821
		$conditional_classes = array(
822
			'cmb-type-' . str_replace( '_', '-', sanitize_html_class( $this->type() ) ) => true,
823
			'cmb2-id-' . str_replace( '_', '-', sanitize_html_class( $this->id() ) )    => true,
824
			'cmb-repeat'             => $this->args( 'repeatable' ),
825
			'cmb-repeat-group-field' => $this->group,
826
			'cmb-inline'             => $this->args( 'inline' ),
827
			'table-layout'           => 'edit' === $this->render_context && in_array( $this->type(), $repeat_table_rows_types ),
828
		);
829
830
		foreach ( $conditional_classes as $class => $condition ) {
831
			if ( $condition ) {
832
				$classes[] = $class;
833
			}
834
		}
835
836
		if ( $added_classes = $this->args( 'classes' ) ) {
837
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
838
		} elseif ( $added_classes = $this->get_param_callback_result( 'classes_cb' ) ) {
839
			$added_classes = is_array( $added_classes ) ? implode( ' ', $added_classes ) : (string) $added_classes;
840
		}
841
842
843
		if ( $added_classes ) {
844
			$classes[] = esc_attr( $added_classes );
845
		}
846
847
		/**
848
		 * Globally filter row classes
849
		 *
850
		 * @since 2.0.0
851
		 *
852
		 * @param string            $classes Space-separated list of row classes
853
		 * @param CMB2_Field object $field   This field object
854
		 */
855
		return apply_filters( 'cmb2_row_classes', implode( ' ', $classes ), $this );
856
	}
857
858
859
860
	/**
861
	 * Get field display callback and render the display value in the column.
862
	 * @since 2.2.2
863
	 */
864
	public function render_column() {
865
		$this->render_context = 'display';
866
867
		$this->peform_param_callback( 'display_cb' );
868
869
		// For chaining
870
		return $this;
871
	}
872
873
	/**
874
	 * Default callback to outputs field value in a display format.
875
	 * @since 2.2.2
876
	 */
877
	public function display_value_callback() {
878
		// If field is requesting to be conditionally shown
879
		if ( ! $this->should_show() ) {
880
			return;
881
		}
882
883
		$display = new CMB2_Field_Display( $this );
884
885
		/**
886
		 * A filter to bypass the default display.
887
		 *
888
		 * The dynamic portion of the hook name, $this->type(), refers to the field type.
889
		 *
890
		 * Passing a non-null value to the filter will short-circuit the default display.
891
		 *
892
		 * @param bool|mixed         $pre_output Default null value.
893
		 * @param CMB2_Field         $field      This field object.
894
		 * @param CMB2_Field_Display $display    The `CMB2_Field_Display` object.
895
		 */
896
		$pre_output = apply_filters( "cmb2_pre_field_display_{$this->type()}", null, $this, $display );
897
898
		if ( null !== $pre_output ) {
899
			echo $pre_output;
900
			return;
901
		}
902
903
		$this->peform_param_callback( 'before_display_wrap' );
904
905
		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...
906
907
		$this->peform_param_callback( 'before_display' );
908
909
		CMB2_Field_Display::get( $this )->display();
910
911
		$this->peform_param_callback( 'after_display' );
912
913
		echo "\n</div>";
914
915
		$this->peform_param_callback( 'after_display_wrap' );
916
917
		// For chaining
918
		return $this;
919
	}
920
921
	/**
922
	 * Replaces a hash key - {#} - with the repeatable index
923
	 * @since  1.2.0
924
	 * @param  string $value Value to update
925
	 * @return string        Updated value
926
	 */
927
	public function replace_hash( $value ) {
928
		// Replace hash with 1 based count
929
		return str_replace( '{#}', ( $this->index + 1 ), $value );
930
	}
931
932
	/**
933
	 * Retrieve text parameter from field's text array (if it has one), or use fallback text
934
	 * For back-compatibility, falls back to checking the options array.
935
	 *
936
	 * @since  2.2.2
937
	 * @param  string  $text_key Key in field's text array
938
	 * @param  string  $fallback Fallback text
939
	 * @return string            Text
940
	 */
941
	public function string( $text_key, $fallback ) {
942
		// If null, populate with our field strings values.
943
		if ( null === $this->strings ) {
944
			$this->strings = (array) $this->args['text'];
945
946 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...
947
				$strings = call_user_func( $this->args['text_cb'], $this );
948
949
				if ( $strings && is_array( $strings ) ) {
950
					$this->strings += $strings;
951
				}
952
			}
953
		}
954
955
		// If we have that string value, send it back.
956
		if ( isset( $this->strings[ $text_key ] ) ) {
957
			return $this->strings[ $text_key ];
958
		}
959
960
		// Check options for back-compat.
961
		$string = $this->options( $text_key );
962
963
		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 963 which is incompatible with the return type documented by CMB2_Field::string of type string.
Loading history...
964
	}
965
966
	/**
967
	 * Retrieve options args. Calls options_cb if it exists.
968
	 * @since  2.0.0
969
	 * @param  string  $key Specific option to retrieve
970
	 * @return array        Array of options
971
	 */
972
	public function options( $key = '' ) {
973
		if ( ! empty( $this->field_options ) ) {
974
			if ( $key ) {
975
				return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
976
			}
977
978
			return $this->field_options;
979
		}
980
981
		$this->field_options = (array) $this->args['options'];
982
983 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...
984
			$options = call_user_func( $this->args['options_cb'], $this );
985
986
			if ( $options && is_array( $options ) ) {
987
				$this->field_options = $options + $this->field_options;
988
			}
989
		}
990
991
		if ( $key ) {
992
			return array_key_exists( $key, $this->field_options ) ? $this->field_options[ $key ] : false;
993
		}
994
995
		return $this->field_options;
996
	}
997
998
	/**
999
	 * Get CMB2_Field default value, either from default param or default_cb param.
1000
	 *
1001
	 * @since  0.2.2
1002
	 *
1003
	 * @return mixed  Default field value
1004
	 */
1005
	public function get_default() {
1006
		if ( null !== $this->args['default'] ) {
1007
			return $this->args['default'];
1008
		}
1009
1010
		$param = is_callable( $this->args['default_cb'] ) ? 'default_cb' : 'default';
1011
		$default = $this->get_param_callback_result( $param );
1012
1013
		// Allow a filter override of the default value
1014
		$this->args['default'] = apply_filters( 'cmb2_default_filter', $default, $this );
1015
1016
		return $this->args['default'];
1017
	}
1018
1019
	/**
1020
	 * Fills in empty field parameters with defaults
1021
	 * @since 1.1.0
1022
	 * @param array $args Metabox field config array
1023
	 */
1024
	public function _set_field_defaults( $args, $blah ) {
0 ignored issues
show
Unused Code introduced by
The parameter $blah is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1025
1026
		/*
1027
		 * Deprecated parameters:
1028
		 *
1029
		 * 'std' -- use 'default' (no longer works)
1030
		 * 'row_classes' -- use 'class', or 'class_cb'
1031
		 * 'default' -- as callback (use default_cb)
1032
		 */
1033
1034
1035
		// Set up blank or default values for empty ones
1036
		$args = wp_parse_args( $args, array(
1037
			'type'              => '',
1038
			'name'              => '',
1039
			'desc'              => '',
1040
			'before'            => '',
1041
			'after'             => '',
1042
			'options'           => array(),
1043
			'options_cb'        => '',
1044
			'text'              => array(),
1045
			'text_cb'           => '',
1046
			'attributes'        => array(),
1047
			'protocols'         => null,
1048
			'default'           => null,
1049
			'default_cb'        => '',
1050
			'classes'           => null,
1051
			'classes_cb'        => '',
1052
			'select_all_button' => true,
1053
			'multiple'          => false,
1054
			'repeatable'        => isset( $args['type'] ) && 'group' == $args['type'],
1055
			'inline'            => false,
1056
			'on_front'          => true,
1057
			'show_names'        => true,
1058
			'save_field'        => true, // Will not save if false
1059
			'date_format'       => 'm\/d\/Y',
1060
			'time_format'       => 'h:i A',
1061
			'description'       => isset( $args['desc'] ) ? $args['desc'] : '',
1062
			'preview_size'      => 'file' == $args['type'] ? array( 350, 350 ) : array( 50, 50 ),
1063
			'render_row_cb'     => array( $this, 'render_field_callback' ),
1064
			'display_cb'        => array( $this, 'display_value_callback' ),
1065
			'label_cb'          => 'title' != $args['type'] ? array( $this, 'label' ) : '',
1066
			'column'            => false,
1067
		) );
1068
1069
		/*
1070
		 * Deprecated usage.
1071
		 */
1072
1073
		if ( isset( $args['row_classes'] ) ) {
1074
1075
			// row_classes param could be a callback
1076
			if ( is_callable( $args['row_classes'] ) ) {
1077
				$args['classes_cb'] = $args['row_classes'];
1078
				$args['classes'] = null;
1079
			} else {
1080
				$args['classes'] = $args['row_classes'];
1081
			}
1082
1083
			unset( $args['row_classes'] );
1084
		}
1085
1086
		// default param can be passed a callback as well
1087
		if ( is_callable( $args['default'] ) ) {
1088
			$args['default_cb'] = $args['default'];
1089
			$args['default'] = null;
1090
		}
1091
1092
		/*
1093
		 * END deprecated usage.
1094
		 */
1095
1096
		$args['repeatable'] = $args['repeatable'] && ! $this->repeatable_exception( $args['type'] );
1097
		$args['inline']     = $args['inline'] || false !== stripos( $args['type'], '_inline' );
1098
1099
		// options param can be passed a callback as well
1100
		if ( is_callable( $args['options'] ) ) {
1101
			$args['options_cb'] = $args['options'];
1102
			$args['options'] = array();
1103
		}
1104
1105
		$args['options']    = 'group' == $args['type'] ? wp_parse_args( $args['options'], array(
1106
			'add_button'    => esc_html__( 'Add Group', 'cmb2' ),
1107
			'remove_button' => esc_html__( 'Remove Group', 'cmb2' ),
1108
		) ) : $args['options'];
1109
1110
		$args['_id']        = $args['id'];
1111
		$args['_name']      = $args['id'];
1112
1113
		if ( $this->group ) {
1114
1115
			$args['id']    = $this->group->args( 'id' ) . '_' . $this->group->index . '_' . $args['id'];
1116
			$args['_name'] = $this->group->args( 'id' ) . '[' . $this->group->index . '][' . $args['_name'] . ']';
1117
		}
1118
1119
		if ( 'wysiwyg' == $args['type'] ) {
1120
			$args['id'] = strtolower( str_ireplace( '-', '_', $args['id'] ) );
1121
			$args['options']['textarea_name'] = $args['_name'];
1122
		}
1123
1124
		$option_types = apply_filters( 'cmb2_all_or_nothing_types', array( 'select', 'radio', 'radio_inline', 'taxonomy_select', 'taxonomy_radio', 'taxonomy_radio_inline' ), $this );
1125
1126
		if ( in_array( $args['type'], $option_types, true ) ) {
1127
1128
			$args['show_option_none'] = isset( $args['show_option_none'] ) ? $args['show_option_none'] : null;
1129
			$args['show_option_none'] = true === $args['show_option_none'] ? esc_html__( 'None', 'cmb2' ) : $args['show_option_none'];
1130
1131
			if ( null === $args['show_option_none'] ) {
1132
				$off_by_default = in_array( $args['type'], array( 'select', 'radio', 'radio_inline' ), true );
1133
				$args['show_option_none'] = $off_by_default ? false : esc_html__( 'None', 'cmb2' );
1134
			}
1135
1136
		}
1137
1138
		$args['has_supporting_data'] = in_array(
1139
			$args['type'],
1140
			array(
1141
				// 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...
1142
				'file',
1143
				// 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...
1144
				'text_datetime_timestamp_timezone',
1145
			),
1146
			true
1147
		);
1148
1149
		return $args;
1150
	}
1151
1152
	/**
1153
	 * Get default field arguments specific to this CMB2 object.
1154
	 * @since  2.2.0
1155
	 * @param  array      $field_args  Metabox field config array.
1156
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1157
	 * @return array                   Array of field arguments.
1158
	 */
1159
	protected function get_default_args( $field_args, $field_group = null ) {
1160
		$args = parent::get_default_args( array(), $this->group );
1161
1162
		if ( isset( $field_args['field_args'] ) ) {
1163
			$args = wp_parse_args( $field_args, $args );
1164
		} else {
1165
			$args['field_args'] = wp_parse_args( $field_args, $this->args );
1166
		}
1167
1168
		return $args;
1169
	}
1170
1171
	/**
1172
	 * Returns a cloned version of this field object with, but with
1173
	 * modified/overridden field arguments.
1174
	 *
1175
	 * @since  2.2.2
1176
	 * @param  array  $field_args Array of field arguments, or entire array of
1177
	 *                            arguments for CMB2_Field
1178
	 *
1179
	 * @return CMB2_Field         The new CMB2_Field instance.
1180
	 */
1181
	public function get_field_clone( $field_args ) {
1182
		return $this->get_new_field( $field_args );
1183
	}
1184
1185
	/**
1186
	 * Returns the CMB2 instance this field is registered to.
1187
	 *
1188
	 * @since  2.2.2
1189
	 *
1190
	 * @return CMB2|WP_Error If new CMB2_Field is called without cmb_id arg, returns error.
1191
	 */
1192
	public function get_cmb() {
1193
		if ( ! $this->cmb_id ) {
1194
			return new WP_Error( 'no_cmb_id', esc_html__( 'Sorry, this field does not have a cmb_id specified.', 'cmb2' ) );
1195
		}
1196
1197
		return cmb2_get_metabox( $this->cmb_id, $this->object_id, $this->object_type );
1198
	}
1199
1200
}
1201