Completed
Push — trunk ( ab581e...c10641 )
by Justin
06:05
created

CMB2::save_fields()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 4
nop 3
dl 0
loc 16
ccs 10
cts 10
cp 1
crap 3
rs 9.4285
1
<?php
2
/**
3
 * CMB2 - The core metabox object
4
 *
5
 * @category  WordPress_Plugin
6
 * @package   CMB2
7
 * @author    WebDevStudios
8
 * @license   GPL-2.0+
9
 * @link      http://webdevstudios.com
10
 *
11
 * @property-read string $cmb_id
12
 * @property-read array $meta_box
13
 * @property-read array $updated
14
 * @property-read bool  $has_columns
15
 * @property-read array $tax_metaboxes_to_remove
16
 */
17
class CMB2 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 = 'meta_box';
25
26
	/**
27
	 * Metabox Config array
28
	 * @var   array
29
	 * @since 0.9.0
30
	 */
31
	protected $meta_box = array();
32
33
	/**
34
	 * Type of object registered for metabox. (e.g., post, user, or comment)
35
	 * @var   string
36
	 * @since 1.0.0
37
	 */
38
	protected $mb_object_type = null;
39
40
	/**
41
	 * List of fields that are changed/updated on save
42
	 * @var   array
43
	 * @since 1.1.0
44
	 */
45
	protected $updated = array();
46
47
	/**
48
	 * Metabox Defaults
49
	 * @var   array
50
	 * @since 1.0.1
51
	 */
52
	protected $mb_defaults = array(
53
		'id'               => '',
54
		'title'            => '',
55
		'object_types'     => array(), // Post type
56
		'context'          => 'normal',
57
		'priority'         => 'high',
58
		'show_names'       => true, // Show field names on the left
59
		'show_on_cb'       => null, // Callback to determine if metabox should display.
60
		'show_on'          => array(), // Post IDs or page templates to display this metabox. overrides 'show_on_cb'
61
		'cmb_styles'       => true, // Include CMB2 stylesheet
62
		'enqueue_js'       => true, // Include CMB2 JS
63
		'fields'           => array(),
64
		'hookup'           => true,
65
		'save_fields'      => true, // Will not save during hookup if false
66
		'closed'           => false, // Default to metabox being closed?
67
		'taxonomies'       => array(),
68
		'new_user_section' => 'add-new-user', // or 'add-existing-user'
69
		'new_term_section' => true,
70
	);
71
72
	/**
73
	 * Metabox field objects
74
	 * @var   array
75
	 * @since 2.0.3
76
	 */
77
	protected $fields = array();
78
79
	/**
80
	 * An array of hidden fields to output at the end of the form
81
	 * @var   array
82
	 * @since 2.0.0
83
	 */
84
	protected $hidden_fields = array();
85
86
	/**
87
	 * Array of key => value data for saving. Likely $_POST data.
88
	 * @var   string
89
	 * @since 2.0.0
90
	 */
91
	protected $generated_nonce = '';
92
93
	/**
94
	 * Whether there are fields to be shown in columns. Set in CMB2::add_field().
95
	 * @var   bool
96
	 * @since 2.2.2
97
	 */
98
	protected $has_columns = false;
99
100
	/**
101
	 * If taxonomy field is requesting to remove_default, we store the taxonomy here.
102
	 * @var   array
103
	 * @since 2.2.3
104
	 */
105
	protected $tax_metaboxes_to_remove = array();
106
107
	/**
108
	 * Get started
109
	 * @since 0.4.0
110
	 * @param array   $config    Metabox config array
111
	 * @param integer $object_id Optional object id
112
	 */
113 46
	public function __construct( $config, $object_id = 0 ) {
114
115 46
		if ( empty( $config['id'] ) ) {
116 1
			wp_die( esc_html__( 'Metabox configuration is required to have an ID parameter.', 'cmb2' ) );
117
		}
118
119 46
		$this->meta_box = wp_parse_args( $config, $this->mb_defaults );
120 46
		$this->meta_box['fields'] = array();
121
122 46
		$this->object_id( $object_id );
123 46
		$this->mb_object_type();
124 46
		$this->cmb_id = $config['id'];
125
126 46
		if ( ! empty( $config['fields'] ) && is_array( $config['fields'] ) ) {
127 43
			$this->add_fields( $config['fields'] );
128 43
		}
129
130 46
		CMB2_Boxes::add( $this );
131
132
		/**
133
		 * Hook during initiation of CMB2 object
134
		 *
135
		 * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id.
136
		 *
137
		 * @param array $cmb This CMB2 object
138
		 */
139 46
		do_action( "cmb2_init_{$this->cmb_id}", $this );
140 46
	}
141
142
	/**
143
	 * Loops through and displays fields
144
	 * @since 1.0.0
145
	 * @param int    $object_id   Object ID
146
	 * @param string $object_type Type of object being saved. (e.g., post, user, or comment)
147
	 */
148 1
	public function show_form( $object_id = 0, $object_type = '' ) {
149 1
		$this->render_form_open( $object_id, $object_type );
150
151 1
		foreach ( $this->prop( 'fields' ) as $field_args ) {
152 1
			$this->render_field( $field_args );
153 1
		}
154
155 1
		$this->render_form_close( $object_id, $object_type );
156 1
	}
157
158
	/**
159
	 * Outputs the opening form markup and runs corresponding hooks:
160
	 * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}"
161
	 * @since  2.2.0
162
	 * @param  integer $object_id   Object ID
163
	 * @param  string  $object_type Object type
164
	 * @return void
165
	 */
166 1
	public function render_form_open( $object_id = 0, $object_type = '' ) {
167 1
		$object_type = $this->object_type( $object_type );
168 1
		$object_id = $this->object_id( $object_id );
169
170 1
		echo "\n<!-- Begin CMB2 Fields -->\n";
171
172 1
		$this->nonce_field();
173
174
		/**
175
		 * Hook before form table begins
176
		 *
177
		 * @param array  $cmb_id      The current box ID
178
		 * @param int    $object_id   The ID of the current object
179
		 * @param string $object_type The type of object you are working with.
180
		 *	                           Usually `post` (this applies to all post-types).
181
		 *	                           Could also be `comment`, `user` or `options-page`.
182
		 * @param array  $cmb         This CMB2 object
183
		 */
184 1
		do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this );
185
186
		/**
187
		 * Hook before form table begins
188
		 *
189
		 * The first dynamic portion of the hook name, $object_type, is the type of object
190
		 * you are working with. Usually `post` (this applies to all post-types).
191
		 * Could also be `comment`, `user` or `options-page`.
192
		 *
193
		 * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
194
		 *
195
		 * @param array  $cmb_id      The current box ID
196
		 * @param int    $object_id   The ID of the current object
197
		 * @param array  $cmb         This CMB2 object
198
		 */
199 1
		do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
200
201 1
		echo '<div class="', $this->box_classes(), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">';
202
203 1
	}
204
205
	/**
206
	 * Defines the classes for the CMB2 form/wrap.
207
	 *
208
	 * @since  2.0.0
209
	 * @return string Space concatenated list of classes
210
	 */
211 1
	public function box_classes() {
212
213 1
		$classes = array( 'cmb2-wrap', 'form-table' );
214
215
		// Use the callback to fetch classes.
216 1 View Code Duplication
		if ( $added_classes = $this->get_param_callback_result( 'classes_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...
217 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
218 1
			$classes = array_merge( $classes, $added_classes );
219 1
		}
220
221 1 View Code Duplication
		if ( $added_classes = $this->prop( 'classes' ) ) {
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...
222 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
223 1
			$classes = array_merge( $classes, $added_classes );
224 1
		}
225
226
		/**
227
		 * Globally filter box wrap classes
228
		 *
229
		 * @since 2.2.2
230
		 *
231
		 * @param string $classes Array of classes for the cmb2-wrap.
232
		 * @param CMB2   $cmb     This CMB2 object.
233
		 */
234 1
		$classes = apply_filters( 'cmb2_wrap_classes', $classes, $this );
235
236
		// Clean up.
237 1
		$classes = array_map( 'strip_tags', array_filter( $classes ) );
238
239
		// Make a string.
240 1
		return implode( ' ', $classes );
241
	}
242
243
	/**
244
	 * Outputs the closing form markup and runs corresponding hooks:
245
	 * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}"
246
	 * @since  2.2.0
247
	 * @param  integer $object_id   Object ID
248
	 * @param  string  $object_type Object type
249
	 * @return void
250
	 */
251 1
	public function render_form_close( $object_id = 0, $object_type = '' ) {
252 1
		$object_type = $this->object_type( $object_type );
253 1
		$object_id = $this->object_id( $object_id );
254
255 1
		echo '</div></div>';
256
257 1
		$this->render_hidden_fields();
258
259
		/**
260
		 * Hook after form form has been rendered
261
		 *
262
		 * @param array  $cmb_id      The current box ID
263
		 * @param int    $object_id   The ID of the current object
264
		 * @param string $object_type The type of object you are working with.
265
		 *	                           Usually `post` (this applies to all post-types).
266
		 *	                           Could also be `comment`, `user` or `options-page`.
267
		 * @param array  $cmb         This CMB2 object
268
		 */
269 1
		do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this );
270
271
		/**
272
		 * Hook after form form has been rendered
273
		 *
274
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
275
		 *
276
		 * The first dynamic portion of the hook name, $object_type, is the type of object
277
		 * you are working with. Usually `post` (this applies to all post-types).
278
		 * Could also be `comment`, `user` or `options-page`.
279
		 *
280
		 * @param int    $object_id   The ID of the current object
281
		 * @param array  $cmb         This CMB2 object
282
		 */
283 1
		do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
284
285 1
		echo "\n<!-- End CMB2 Fields -->\n";
286
287 1
	}
288
289
	/**
290
	 * Renders a field based on the field type
291
	 * @since  2.2.0
292
	 * @param  array $field_args A field configuration array.
293
	 * @return mixed CMB2_Field object if successful.
294
	 */
295 1
	public function render_field( $field_args ) {
296 1
		$field_args['context'] = $this->prop( 'context' );
297
298 1
		if ( 'group' == $field_args['type'] ) {
299
300
			if ( ! isset( $field_args['show_names'] ) ) {
301
				$field_args['show_names'] = $this->prop( 'show_names' );
302
			}
303
			$field = $this->render_group( $field_args );
304
305 1
		} elseif ( 'hidden' == $field_args['type'] && $this->get_field( $field_args )->should_show() ) {
306
			// Save rendering for after the metabox
307
			$field = $this->add_hidden_field( $field_args );
308
309
		} else {
310
311 1
			$field_args['show_names'] = $this->prop( 'show_names' );
312
313
			// Render default fields
314 1
			$field = $this->get_field( $field_args )->render_field();
315
		}
316
317 1
		return $field;
318
	}
319
320
	/**
321
	 * Render a repeatable group.
322
	 * @param array $args Array of field arguments for a group field parent.
323
	 * @return CMB2_Field|null Group field object.
324
	 */
325 2
	public function render_group( $args ) {
326
327 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
328
			return;
329
		}
330
331 2
		$field_group = $this->get_field( $args );
332
333
		// If field is requesting to be conditionally shown
334 2
		if ( ! $field_group || ! $field_group->should_show() ) {
335
			return;
336
		}
337
338 2
		$desc            = $field_group->args( 'description' );
339 2
		$label           = $field_group->args( 'name' );
340 2
		$group_val       = (array) $field_group->value();
341 2
		$remove_disabled = count( $group_val ) <= 1 ? 'disabled="disabled" ' : '';
342 2
		$field_group->index = 0;
343
344 2
		$field_group->peform_param_callback( 'before_group' );
345
346 2
		echo '<div class="cmb-row cmb-repeat-group-wrap ', $field_group->row_classes(), '" data-fieldtype="group"><div class="cmb-td"><div data-groupid="', $field_group->id(), '" id="', $field_group->id(), '_repeat" ', $this->group_wrap_attributes( $field_group ), '>';
347
348 2
		if ( $desc || $label ) {
349 2
			$class = $desc ? ' cmb-group-description' : '';
350 2
			echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
351 2
				if ( $label ) {
352 2
					echo '<h2 class="cmb-group-name">', $label, '</h2>';
353 2
				}
354 2
				if ( $desc ) {
355 1
					echo '<p class="cmb2-metabox-description">', $desc, '</p>';
356 1
				}
357 2
			echo '</div></div>';
358 2
		}
359
360 2
		if ( ! empty( $group_val ) ) {
361
			foreach ( $group_val as $group_key => $field_id ) {
362
				$this->render_group_row( $field_group, $remove_disabled );
363
				$field_group->index++;
364
			}
365
		} else {
366 2
			$this->render_group_row( $field_group, $remove_disabled );
367
		}
368
369 2
		if ( $field_group->args( 'repeatable' ) ) {
370 1
			echo '<div class="cmb-row"><div class="cmb-td"><p class="cmb-add-row"><button type="button" data-selector="', $field_group->id(), '_repeat" data-grouptitle="', $field_group->options( 'group_title' ), '" class="cmb-add-group-row button">', $field_group->options( 'add_button' ), '</button></p></div></div>';
371 1
		}
372
373 2
		echo '</div></div></div>';
374
375 2
		$field_group->peform_param_callback( 'after_group' );
376
377 2
		return $field_group;
378
	}
379
380
	/**
381
	 * Get the group wrap attributes, which are passed through a filter.
382
	 * @since  2.2.3
383
	 * @param  CMB2_Field $field_group The group CMB2_Field object.
384
	 * @return string                  The attributes string.
385
	 */
386 2
	public function group_wrap_attributes( $field_group ) {
387 2
		$classes = 'cmb-nested cmb-field-list cmb-repeatable-group';
388 2
		$classes .= $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
389 2
		$classes .= $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
390
391
		$group_wrap_attributes = array(
392 2
			'class' => $classes,
393 2
			'style' => 'width:100%;',
394 2
		);
395
396
		/**
397
		 * Allow for adding additional HTML attributes to a group wrapper.
398
		 *
399
		 * The attributes will be an array of key => value pairs for each attribute.
400
		 *
401
		 * @since 2.2.2
402
		 *
403
		 * @param string     $group_wrap_attributes Current attributes array.
404
		 *
405
		 * @param CMB2_Field $field_group           The group CMB2_Field object.
406
		 */
407 2
		$group_wrap_attributes = apply_filters( 'cmb2_group_wrap_attributes', $group_wrap_attributes, $field_group );
408
409 2
		return CMB2_Utils::concat_attrs( $group_wrap_attributes );
410
	}
411
412
	/**
413
	 * Render a repeatable group row
414
	 * @since  1.0.2
415
	 * @param  CMB2_Field $field_group  CMB2_Field group field object
416
	 * @param  string  $remove_disabled Attribute string to disable the remove button
417
	 */
418 2
	public function render_group_row( $field_group, $remove_disabled ) {
419
420 2
		$field_group->peform_param_callback( 'before_group_row' );
421 2
		$closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
422
423
		echo '
424 2
		<div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
425
426 2
			if ( $field_group->args( 'repeatable' ) ) {
427 1
				echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row"></button>';
428 1
			}
429
430
			echo '
431 2
			<div class="cmbhandle" title="' , esc_attr__( 'Click to toggle', 'cmb2' ), '"><br></div>
432 2
			<h3 class="cmb-group-title cmbhandle-title"><span>', $field_group->replace_hash( $field_group->options( 'group_title' ) ), '</span></h3>
0 ignored issues
show
Documentation introduced by
$field_group->options('group_title') is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
433
434
			<div class="inside cmb-td cmb-nested cmb-field-list">';
435
				// Loop and render repeatable group fields
436 2
				foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) {
437 2
					if ( 'hidden' == $field_args['type'] ) {
438
439
						// Save rendering for after the metabox
440
						$this->add_hidden_field( $field_args, $field_group );
441
442
					} else {
443
444 2
						$field_args['show_names'] = $field_group->args( 'show_names' );
445 2
						$field_args['context']    = $field_group->args( 'context' );
446
447 2
						$field = $this->get_field( $field_args, $field_group )->render_field();
0 ignored issues
show
Unused Code introduced by
$field is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
448
					}
449 2
				}
450 2
				if ( $field_group->args( 'repeatable' ) ) {
451
					echo '
452
					<div class="cmb-row cmb-remove-field-row">
453
						<div class="cmb-remove-row">
454 1
							<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="button cmb-remove-group-row alignright">', $field_group->options( 'remove_button' ), '</button>
455
						</div>
456
					</div>
457
					';
458 1
				}
459
			echo '
460
			</div>
461
		</div>
462 2
		';
463
464 2
		$field_group->peform_param_callback( 'after_group_row' );
465 2
	}
466
467
	/**
468
	 * Add a hidden field to the list of hidden fields to be rendered later
469
	 * @since 2.0.0
470
	 * @param array  $field_args Array of field arguments to be passed to CMB2_Field
471
	 */
472
	public function add_hidden_field( $field_args, $field_group = null ) {
473
		if ( isset( $field_args['field_args'] ) ) {
474
			// For back-compatibility.
475
			$field = new CMB2_Field( $field_args );
476
		} else {
477
			$field = $this->get_new_field( $field_args, $field_group );
478
		}
479
480
		$type = new CMB2_Types( $field );
481
482
		if ( $field_group ) {
483
			$type->iterator = $field_group->index;
484
		}
485
486
		$this->hidden_fields[] = $type;
487
488
		return $field;
489
	}
490
491
	/**
492
	 * Loop through and output hidden fields
493
	 * @since  2.0.0
494
	 */
495 1
	public function render_hidden_fields() {
496 1
		if ( ! empty( $this->hidden_fields ) ) {
497
			foreach ( $this->hidden_fields as $hidden ) {
498
				$hidden->render();
499
			}
500
		}
501 1
	}
502
503
	/**
504
	 * Returns array of sanitized field values (without saving them)
505
	 * @since  2.0.3
506
	 * @param  array  $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
507
	 */
508 2
	public function get_sanitized_values( array $data_to_sanitize ) {
509 2
		$this->data_to_save = $data_to_sanitize;
510 2
		$stored_id          = $this->object_id();
511
512
		// We do this So CMB will sanitize our data for us, but not save it
513 2
		$this->object_id( '_' );
514
515
		// Ensure temp. data store is empty
516 2
		cmb2_options( 0 )->set();
517
518
		// Process/save fields
519 2
		$this->process_fields();
520
521
		// Get data from temp. data store
522 2
		$sanitized_values = cmb2_options( 0 )->get_options();
523
524
		// Empty out temp. data store again
525 2
		cmb2_options( 0 )->set();
526
527
		// Reset the object id
528 2
		$this->object_id( $stored_id );
529
530 2
		return $sanitized_values;
531
	}
532
533
	/**
534
	 * Loops through and saves field data
535
	 * @since  1.0.0
536
	 * @param  int    $object_id    Object ID
537
	 * @param  string $object_type  Type of object being saved. (e.g., post, user, or comment)
538
	 * @param  array  $data_to_save Array of key => value data for saving. Likely $_POST data.
539
	 */
540 1
	public function save_fields( $object_id = 0, $object_type = '', $data_to_save = array() ) {
0 ignored issues
show
Coding Style introduced by
save_fields uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
541
542
		// Fall-back to $_POST data
543 1
		$this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
544 1
		$object_id = $this->object_id( $object_id );
545 1
		$object_type = $this->object_type( $object_type );
546
547 1
		$this->process_fields();
548
549
		// If options page, save the updated options
550 1
		if ( 'options-page' == $object_type ) {
551 1
			cmb2_options( $object_id )->set();
552 1
		}
553
554 1
		$this->after_save();
555 1
	}
556
557
	/**
558
	 * Process and save form fields
559
	 * @since  2.0.0
560
	 */
561 3
	public function process_fields() {
562
563 3
		$this->pre_process();
564
565
		// Remove the show_on properties so saving works
566 3
		$this->prop( 'show_on', array() );
567
568
		// save field ids of those that are updated
569 3
		$this->updated = array();
570
571 3
		foreach ( $this->prop( 'fields' ) as $field_args ) {
572 3
			$this->process_field( $field_args );
573 3
		}
574 3
	}
575
576
	/**
577
	 * Process and save a field
578
	 * @since  2.0.0
579
	 * @param  array  $field_args Array of field arguments
580
	 */
581 3
	public function process_field( $field_args ) {
582
583 3
		switch ( $field_args['type'] ) {
584
585 3
			case 'group':
586 1
				if ( $this->save_group( $field_args ) ) {
587 1
					$this->updated[] = $field_args['id'];
588 1
				}
589
590 1
				break;
591
592 2
			case 'title':
593
				// Don't process title fields
594
				break;
595
596 2
			default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
597
598 2
				$field = $this->get_new_field( $field_args );
599
600 2
				if ( $field->save_field_from_data( $this->data_to_save ) ) {
601 2
					$this->updated[] = $field->id();
602 2
				}
603
604 2
				break;
605 3
		}
606
607 3
	}
608
609 3
	public function pre_process() {
610
		/**
611
		 * Fires before fields have been processed/saved.
612
		 *
613
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
614
		 *
615
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
616
		 * 	Usually `post` (this applies to all post-types).
617
		 *  	Could also be `comment`, `user` or `options-page`.
618
		 *
619
		 * @param array $cmb       This CMB2 object
620
		 * @param int   $object_id The ID of the current object
621
		 */
622 3
		do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
623 3
	}
624
625 1
	public function after_save() {
626 1
		$object_type = $this->object_type();
627 1
		$object_id   = $this->object_id();
628
629
		/**
630
		 * Fires after all fields have been saved.
631
		 *
632
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
633
		 * 	Usually `post` (this applies to all post-types).
634
		 *  	Could also be `comment`, `user` or `options-page`.
635
		 *
636
		 * @param int    $object_id   The ID of the current object
637
		 * @param array  $cmb_id      The current box ID
638
		 * @param string $updated     Array of field ids that were updated.
639
		 *                            Will only include field ids that had values change.
640
		 * @param array  $cmb         This CMB2 object
641
		 */
642 1
		do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
643
644
		/**
645
		 * Fires after all fields have been saved.
646
		 *
647
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
648
		 *
649
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
650
		 * 	Usually `post` (this applies to all post-types).
651
		 *  	Could also be `comment`, `user` or `options-page`.
652
		 *
653
		 * @param int    $object_id   The ID of the current object
654
		 * @param string $updated     Array of field ids that were updated.
655
		 *                            Will only include field ids that had values change.
656
		 * @param array  $cmb         This CMB2 object
657
		 */
658 1
		do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
659 1
	}
660
661
	/**
662
	 * Save a repeatable group
663
	 * @since  1.x.x
664
	 * @param  array  $args Field arguments array
665
	 * @return mixed        Return of CMB2_Field::update_data()
666
	 */
667 1
	public function save_group( $args ) {
668 1
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
669
			return;
670
		}
671
672 1
		return $this->save_group_field( $this->get_new_field( $args ) );
0 ignored issues
show
Documentation introduced by
$this->get_new_field($args) is of type object<CMB2_Field>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
673
	}
674
675
	/**
676
	 * Save a repeatable group
677
	 * @since  1.x.x
678
	 * @param  array $field_group CMB2_Field group field object
679
	 * @return mixed              Return of CMB2_Field::update_data()
680
	 */
681 1
	public function save_group_field( $field_group ) {
682 1
		$base_id = $field_group->id();
0 ignored issues
show
Bug introduced by
The method id cannot be called on $field_group (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
683
684 1
		if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
685
			return;
686
		}
687
688 1
		$old        = $field_group->get_data();
0 ignored issues
show
Bug introduced by
The method get_data cannot be called on $field_group (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
689
		// Check if group field has sanitization_cb
690 1
		$group_vals = $field_group->sanitization_cb( $this->data_to_save[ $base_id ] );
0 ignored issues
show
Bug introduced by
The method sanitization_cb cannot be called on $field_group (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
691 1
		$saved      = array();
692
693 1
		$field_group->index = 0;
694 1
		$field_group->data_to_save = $this->data_to_save;
695
696 1
		foreach ( array_values( $field_group->fields() ) as $field_args ) {
0 ignored issues
show
Bug introduced by
The method fields cannot be called on $field_group (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
697
698 1
			$field  = $this->get_new_field( $field_args, $field_group );
0 ignored issues
show
Documentation introduced by
$field_group is of type array, but the function expects a object<CMB2_Field>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
699 1
			$sub_id = $field->id( true );
700
701 1
			foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
702
703
				// Get value
704 1
				$new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
705 1
					? $group_vals[ $field_group->index ][ $sub_id ]
706 1
					: false;
707
708
				// Sanitize
709 1
				$new_val = $field->sanitization_cb( $new_val );
710
711 1
				if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
712 1
					if ( $field->args( 'repeatable' ) ) {
713 1
						$_new_val = array();
714 1
						foreach ( $new_val as $group_index => $grouped_data ) {
715
							// Add the supporting data to the $saved array stack
716 1
							$saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
717
							// Reset var to the actual value
718 1
							$_new_val[ $group_index ] = $grouped_data['value'];
719 1
						}
720 1
						$new_val = $_new_val;
721 1
					} else {
722
						// Add the supporting data to the $saved array stack
723 1
						$saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
724
						// Reset var to the actual value
725 1
						$new_val = $new_val['value'];
726
					}
727 1
				}
728
729
				// Get old value
730 1
				$old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
731 1
					? $old[ $field_group->index ][ $sub_id ]
732 1
					: false;
733
734 1
				$is_updated = ( ! CMB2_Utils::isempty( $new_val ) && $new_val !== $old_val );
735 1
				$is_removed = ( CMB2_Utils::isempty( $new_val ) && ! CMB2_Utils::isempty( $old_val ) );
736
737
				// Compare values and add to `$updated` array
738 1
				if ( $is_updated || $is_removed ) {
739 1
					$this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
740 1
				}
741
742
				// Add to `$saved` array
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% 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...
743 1
				$saved[ $field_group->index ][ $sub_id ] = $new_val;
744
745 1
			}
746
747 1
			$saved[ $field_group->index ] = CMB2_Utils::filter_empty( $saved[ $field_group->index ] );
748 1
		}
749
750 1
		$saved = CMB2_Utils::filter_empty( $saved );
751
752 1
		return $field_group->update_data( $saved, true );
0 ignored issues
show
Bug introduced by
The method update_data cannot be called on $field_group (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
753
	}
754
755
	/**
756
	 * Get object id from global space if no id is provided
757
	 * @since  1.0.0
758
	 * @param  integer $object_id Object ID
759
	 * @return integer $object_id Object ID
760
	 */
761 50
	public function object_id( $object_id = 0 ) {
0 ignored issues
show
Coding Style introduced by
object_id uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
object_id uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
762 50
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
763
764 50
		if ( $object_id ) {
765 18
			$this->object_id = $object_id;
766 18
			return $this->object_id;
767
		}
768
769 47
		if ( $this->object_id ) {
770 13
			return $this->object_id;
771
		}
772
773
		// Try to get our object ID from the global space
774 44
		switch ( $this->object_type() ) {
775 44
			case 'user':
776
				$object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id;
777
				$object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
778
				break;
779
780 44
			case 'comment':
781
				$object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id;
782
				$object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
783
				break;
784
785 44
			case 'term':
786
				$object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id;
787
				break;
788
789 44
			default:
790 44
				$object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
791 44
				$object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id;
792 44
				break;
793 44
		}
794
795
		// reset to id or 0
796 44
		$this->object_id = $object_id ? $object_id : 0;
797
798 44
		return $this->object_id;
799
	}
800
801
	/**
802
	 * Sets the $object_type based on metabox settings
803
	 * @since  1.0.0
804
	 * @return string Object type
805
	 */
806 46
	public function mb_object_type() {
807 46
		if ( null !== $this->mb_object_type ) {
808 12
			return $this->mb_object_type;
809
		}
810
811 46
		if ( $this->is_options_page_mb() ) {
812 38
			$this->mb_object_type = 'options-page';
813 38
			return $this->mb_object_type;
814
		}
815
816 45
		$registered_types = $this->box_types();
817
818 45
		$type = false;
819
820
		// if it's an array of one, extract it
821 45
		if ( 1 === count( $registered_types ) ) {
822 45
			$last = end( $registered_types );
823 45
			if ( is_string( $last ) ) {
824 45
				$type = $last;
825 45
			}
826 45
		} elseif ( in_array( $this->current_object_type(), $registered_types, true ) ) {
827
			$type = $page_type;
0 ignored issues
show
Bug introduced by
The variable $page_type does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
828
		}
829
830
		// Get our object type
831
		switch ( $type ) {
832
833 45
			case 'user':
834 45
			case 'comment':
835 45
			case 'term':
836 1
				$this->mb_object_type = $type;
837 1
				break;
838
839 45
			default:
840 45
				$this->mb_object_type = 'post';
841 45
				break;
842 45
		}
843
844 45
		return $this->mb_object_type;
845
	}
846
847
	/**
848
	 * Gets the box 'object_types' array based on box settings.
849
	 * @since  2.2.4
850
	 * @return array Object types
851
	 */
852 45
	public function box_types() {
853 45
		return CMB2_Utils::ensure_array( $this->prop( 'object_types' ), array( 'post' ) );
854
	}
855
856
	/**
857
	 * Determines if metabox is for an options page
858
	 * @since  1.0.1
859
	 * @return boolean True/False
860
	 */
861 46
	public function is_options_page_mb() {
862 46
		return ( isset( $this->meta_box['show_on']['key'] ) && 'options-page' === $this->meta_box['show_on']['key'] || array_key_exists( 'options-page', $this->meta_box['show_on'] ) );
863
	}
864
865
	/**
866
	 * Returns the object type
867
	 * @since  1.0.0
868
	 * @return string Object type
869
	 */
870 50
	public function object_type( $object_type = '' ) {
871 50
		if ( $object_type ) {
872 18
			$this->object_type = $object_type;
873 18
			return $this->object_type;
874
		}
875
876 47
		if ( $this->object_type ) {
877 47
			return $this->object_type;
878
		}
879
880
		$this->object_type = $this->current_object_type();
881
882
		return $this->object_type;
883
	}
884
885
	/**
886
	 * Get the object type for the current page, based on the $pagenow global.
887
	 * @since  2.2.2
888
	 * @return string  Page object type name.
889
	 */
890 View Code Duplication
	public function current_object_type() {
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...
891
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
892
		$type = 'post';
893
894
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
895
			$type = 'user';
896
		}
897
898
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
899
			$type = 'comment';
900
		}
901
902
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
903
			$type = 'term';
904
		}
905
906
		return $type;
907
	}
908
909
	/**
910
	 * Set metabox property.
911
	 * @since  2.2.2
912
	 * @param  string $property Metabox config property to retrieve
913
	 * @param  mixed  $value    Value to set if no value found
914
	 * @return mixed            Metabox config property value or false
915
	 */
916 1
	public function set_prop( $property, $value ) {
917 1
		$this->meta_box[ $property ] = $value;
918
919 1
		return $this->prop( $property );
920
	}
921
922
	/**
923
	 * Get metabox property and optionally set a fallback
924
	 * @since  2.0.0
925
	 * @param  string $property Metabox config property to retrieve
926
	 * @param  mixed  $fallback Fallback value to set if no value found
927
	 * @return mixed            Metabox config property value or false
928
	 */
929 46
	public function prop( $property, $fallback = null ) {
930 46
		if ( array_key_exists( $property, $this->meta_box ) ) {
931 46
			return $this->meta_box[ $property ];
932 1
		} elseif ( $fallback ) {
933 1
			return $this->meta_box[ $property ] = $fallback;
934
		}
935 1
	}
936
937
	/**
938
	 * Get a field object
939
	 * @since  2.0.3
940
	 * @param  string|array|CMB2_Field $field       Metabox field id or field config array or CMB2_Field object
941
	 * @param  CMB2_Field              $field_group (optional) CMB2_Field object (group parent)
942
	 * @return CMB2_Field|false CMB2_Field object (or false)
943
	 */
944 15
	public function get_field( $field, $field_group = null ) {
945 15
		if ( is_a( $field, 'CMB2_Field' ) ) {
946
			return $field;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $field; (string|array|CMB2_Field) is incompatible with the return type documented by CMB2::get_field of type CMB2_Field|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
947
		}
948
949 15
		$field_id = is_string( $field ) ? $field : $field['id'];
950
951 15
		$parent_field_id = ! empty( $field_group ) ? $field_group->id() : '';
952 15
		$ids = $this->get_field_ids( $field_id, $parent_field_id, true );
0 ignored issues
show
Unused Code introduced by
The call to CMB2::get_field_ids() has too many arguments starting with true.

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...
953
954 15
		if ( ! $ids ) {
955
			return false;
956
		}
957
958 15
		list( $field_id, $sub_field_id ) = $ids;
959
960 15
		$index = implode( '', $ids ) . ( $field_group ? $field_group->index : '' );
961 15
		if ( array_key_exists( $index, $this->fields ) ) {
962 4
			return $this->fields[ $index ];
963
		}
964
965 13
		$this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) );
966
967 13
		return $this->fields[ $index ];
968
	}
969
970
	/**
971
	 * Handles determining which type of arguments to pass to CMB2_Field
972
	 * @since  2.0.7
973
	 * @param  mixed  $field_id     Field (or group field) ID
974
	 * @param  mixed  $field_args   Array of field arguments
975
	 * @param  mixed  $sub_field_id Sub field ID (if field_group exists)
976
	 * @param  mixed  $field_group  If a sub-field, will be the parent group CMB2_Field object
977
	 * @return array                Array of CMB2_Field arguments
978
	 */
979 13
	public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) {
980
981
		// Check if group is passed and if fields were added in the old-school fields array
982 13
		if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) {
983
984
			// Update the fields array w/ any modified properties inherited from the group field
985 2
			$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args;
986
987 2
			return $this->get_default_args( $field_args, $field_group );
988
		}
989
990 13
		if ( is_array( $field_args ) ) {
991 2
			$this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
992 2
		}
993
994 13
		return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
995
	}
996
997
	/**
998
	 * Get default field arguments specific to this CMB2 object.
999
	 * @since  2.2.0
1000
	 * @param  array      $field_args  Metabox field config array.
1001
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1002
	 * @return array                   Array of field arguments.
1003
	 */
1004 17 View Code Duplication
	protected function get_default_args( $field_args, $field_group = null ) {
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...
1005 17
		if ( $field_group ) {
1006
			$args = array(
1007 3
				'field_args'  => $field_args,
1008 3
				'group_field' => $field_group,
1009 3
			);
1010 3
		} else {
1011
			$args = array(
1012 17
				'field_args'  => $field_args,
1013 17
				'object_type' => $this->object_type(),
1014 17
				'object_id'   => $this->object_id(),
1015 17
				'cmb_id'      => $this->cmb_id,
1016 17
			);
1017
		}
1018
1019 17
		return $args;
1020
	}
1021
1022
	/**
1023
	 * Get a new field object specific to this CMB2 object.
1024
	 * @since  2.2.0
1025
	 * @param  array      $field_args  Metabox field config array.
1026
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1027
	 * @return CMB2_Field CMB2_Field object
1028
	 */
1029 5
	protected function get_new_field( $field_args, $field_group = null ) {
1030 5
		return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) );
1031
	}
1032
1033
	/**
1034
	 * When fields are added in the old-school way, intitate them as they should be
1035
	 * @since 2.1.0
1036
	 * @param array $fields          Array of fields to add
1037
	 * @param mixed $parent_field_id Parent field id or null
1038
	 */
1039 43
	protected function add_fields( $fields, $parent_field_id = null ) {
1040 43
		foreach ( $fields as $field ) {
1041
1042 43
			$sub_fields = false;
1043 43
			if ( array_key_exists( 'fields', $field ) ) {
1044
				$sub_fields = $field['fields'];
1045
				unset( $field['fields'] );
1046
			}
1047
1048
			$field_id = $parent_field_id
1049 43
				? $this->add_group_field( $parent_field_id, $field )
1050 43
				: $this->add_field( $field );
1051
1052 43
			if ( $sub_fields ) {
1053
				$this->add_fields( $sub_fields, $field_id );
1054
			}
1055 43
		}
1056 43
	}
1057
1058
	/**
1059
	 * Add a field to the metabox
1060
	 * @since  2.0.0
1061
	 * @param  array  $field           Metabox field config array
1062
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1063
	 * @return mixed                   Field id or false
1064
	 */
1065 45
	public function add_field( array $field, $position = 0 ) {
1066 45
		if ( ! is_array( $field ) || ! array_key_exists( 'id', $field ) ) {
1067
			return false;
1068
		}
1069
1070 45
		if ( 'oembed' === $field['type'] ) {
1071
			// Initiate oembed Ajax hooks
1072 1
			cmb2_ajax();
1073 1
		}
1074
1075 45
		if ( isset( $field['column'] ) && false !== $field['column'] ) {
1076
			$field = $this->define_field_column( $field );
1077
		}
1078
1079 45
		if ( isset( $field['taxonomy'] ) && ! empty( $field['remove_default'] ) ) {
1080
			$this->tax_metaboxes_to_remove[ $field['taxonomy'] ] = $field['taxonomy'];
1081
		}
1082
1083 45
		$this->_add_field_to_array(
1084 45
			$field,
1085 45
			$this->meta_box['fields'],
1086
			$position
1087 45
		);
1088
1089 45
		return $field['id'];
1090
	}
1091
1092
	/**
1093
	 * Defines a field's column if requesting to be show in admin columns.
1094
	 * @since  2.2.3
1095
	 * @param  array  $field Metabox field config array.
1096
	 * @return array         Modified metabox field config array.
1097
	 */
1098
	protected function define_field_column( array $field ) {
1099
		$this->has_columns = true;
1100
1101
		$column = is_array( $field['column'] ) ? $field['column'] : array();
1102
1103
		$field['column'] = wp_parse_args( $column, array(
1104
			'name'     => isset( $field['name'] ) ? $field['name'] : '',
1105
			'position' => false,
1106
		) );
1107
1108
		return $field;
1109
	}
1110
1111
	/**
1112
	 * Add a field to a group
1113
	 * @since  2.0.0
1114
	 * @param  string $parent_field_id The field id of the group field to add the field
1115
	 * @param  array  $field           Metabox field config array
1116
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1117
	 * @return mixed                   Array of parent/field ids or false
1118
	 */
1119 3
	public function add_group_field( $parent_field_id, array $field, $position = 0 ) {
1120 3
		if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) {
1121
			return false;
1122
		}
1123
1124 3
		$parent_field = $this->meta_box['fields'][ $parent_field_id ];
1125
1126 3
		if ( 'group' !== $parent_field['type'] ) {
1127
			return false;
1128
		}
1129
1130 3
		if ( ! isset( $parent_field['fields'] ) ) {
1131 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'] = array();
1132 3
		}
1133
1134 3
		$this->_add_field_to_array(
1135 3
			$field,
1136 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'],
1137
			$position
1138 3
		);
1139
1140 3
		return array( $parent_field_id, $field['id'] );
1141
	}
1142
1143
	/**
1144
	 * Add a field array to a fields array in desired position
1145
	 * @since 2.0.2
1146
	 * @param array   $field    Metabox field config array
1147
	 * @param array   &$fields  Array (passed by reference) to append the field (array) to
1148
	 * @param integer $position Optionally specify a position in the array to be inserted
1149
	 */
1150 45
	protected function _add_field_to_array( $field, &$fields, $position = 0 ) {
1151 45
		if ( $position ) {
1152 1
			CMB2_Utils::array_insert( $fields, array( $field['id'] => $field ), $position );
1153 1
		} else {
1154 45
			$fields[ $field['id'] ] = $field;
1155
		}
1156 45
	}
1157
1158
	/**
1159
	 * Remove a field from the metabox
1160
	 * @since 2.0.0
1161
	 * @param  string $field_id        The field id of the field to remove
1162
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1163
	 * @return bool                    True if field was removed
1164
	 */
1165 2
	public function remove_field( $field_id, $parent_field_id = '' ) {
1166 2
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1167
1168 2
		if ( ! $ids ) {
1169
			return false;
1170
		}
1171
1172 2
		list( $field_id, $sub_field_id ) = $ids;
1173
1174 2
		unset( $this->fields[ implode( '', $ids ) ] );
1175
1176 2
		if ( ! $sub_field_id ) {
1177 1
			unset( $this->meta_box['fields'][ $field_id ] );
1178 1
			return true;
1179
		}
1180
1181 1
		if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) {
1182 1
			unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] );
1183 1
		}
1184 1
		if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) {
1185 1
			unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] );
1186 1
		}
1187 1
		return true;
1188
	}
1189
1190
	/**
1191
	 * Update or add a property to a field
1192
	 * @since  2.0.0
1193
	 * @param  string $field_id        Field id
1194
	 * @param  string $property        Field property to set/update
1195
	 * @param  mixed  $value           Value to set the field property
1196
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1197
	 * @return mixed                   Field id. Strict compare to false, as success can return a falsey value (like 0)
1198
	 */
1199 4
	public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) {
1200 4
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1201
1202 4
		if ( ! $ids ) {
1203 2
			return false;
1204
		}
1205
1206 2
		list( $field_id, $sub_field_id ) = $ids;
1207
1208 2
		if ( ! $sub_field_id ) {
1209 2
			$this->meta_box['fields'][ $field_id ][ $property ] = $value;
1210 2
			return $field_id;
1211
		}
1212
1213
		$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value;
1214
		return $field_id;
1215
	}
1216
1217
	/**
1218
	 * Check if field ids match a field and return the index/field id
1219
	 * @since  2.0.2
1220
	 * @param  string  $field_id        Field id
1221
	 * @param  string  $parent_field_id (optional) Parent field id
1222
	 * @return mixed                    Array of field/parent ids, or false
1223
	 */
1224 19
	public function get_field_ids( $field_id, $parent_field_id = '' ) {
1225 19
		$sub_field_id = $parent_field_id ? $field_id : '';
1226 19
		$field_id     = $parent_field_id ? $parent_field_id : $field_id;
1227 19
		$fields       =& $this->meta_box['fields'];
1228
1229 19
		if ( ! array_key_exists( $field_id, $fields ) ) {
1230 2
			$field_id = $this->search_old_school_array( $field_id, $fields );
1231 2
		}
1232
1233 19
		if ( false === $field_id ) {
1234 2
			return false;
1235
		}
1236
1237 17
		if ( ! $sub_field_id ) {
1238 17
			return array( $field_id, $sub_field_id );
1239
		}
1240
1241 3
		if ( 'group' !== $fields[ $field_id ]['type'] ) {
1242
			return false;
1243
		}
1244
1245 3
		if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) {
1246
			$sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] );
1247
		}
1248
1249 3
		return false === $sub_field_id ? false : array( $field_id, $sub_field_id );
1250
	}
1251
1252
	/**
1253
	 * When using the old array filter, it is unlikely field array indexes will be the field id
1254
	 * @since  2.0.2
1255
	 * @param  string $field_id The field id
1256
	 * @param  array  $fields   Array of fields to search
1257
	 * @return mixed            Field index or false
1258
	 */
1259 2
	public function search_old_school_array( $field_id, $fields ) {
1260 2
		$ids = wp_list_pluck( $fields, 'id' );
1261 2
		$index = array_search( $field_id, $ids );
1262 2
		return false !== $index ? $index : false;
1263
	}
1264
1265
	/**
1266
	 * Handles metabox property callbacks, and passes this $cmb object as property.
1267
	 * @since  2.2.3
1268
	 * @param  callable $cb The callback method/function/closure
1269
	 * @return mixed        Return of the callback function.
1270
	 */
1271 1
	protected function do_callback( $cb ) {
1272 1
		return call_user_func( $cb, $this );
1273
	}
1274
1275
	/**
1276
	 * Generate a unique nonce field for each registered meta_box
1277
	 * @since  2.0.0
1278
	 * @return string unique nonce hidden input
1279
	 */
1280 1
	public function nonce_field() {
1281 1
		wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1282 1
	}
1283
1284
	/**
1285
	 * Generate a unique nonce for each registered meta_box
1286
	 * @since  2.0.0
1287
	 * @return string unique nonce string
1288
	 */
1289 1
	public function nonce() {
1290 1
		if ( $this->generated_nonce ) {
1291 1
			return $this->generated_nonce;
1292
		}
1293 1
		$this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1294 1
		return $this->generated_nonce;
1295
	}
1296
1297
	/**
1298
	 * Magic getter for our object.
1299
	 * @param string $field
1300
	 * @throws Exception Throws an exception if the field is invalid.
1301
	 * @return mixed
1302
	 */
1303 46
	public function __get( $field ) {
1304
		switch ( $field ) {
1305 46
			case 'updated':
1306 46
			case 'has_columns':
1307 46
			case 'tax_metaboxes_to_remove':
1308 1
				return $this->{$field};
1309 46
			default:
1310 46
				return parent::__get( $field );
1311 46
		}
1312
	}
1313
1314
}
1315