Completed
Pull Request — trunk (#541)
by Justin
28:03 queued 25:22
created

CMB2::get_field()   C

Complexity

Conditions 7
Paths 21

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.0178

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 21
nop 2
dl 0
loc 25
ccs 13
cts 14
cp 0.9286
crap 7.0178
rs 6.7272
c 0
b 0
f 0
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
		'show_in_rest'     => false,
71
	);
72
73
	/**
74
	 * Metabox field objects
75
	 * @var   array
76
	 * @since 2.0.3
77
	 */
78
	protected $fields = array();
79
80
	/**
81
	 * An array of hidden fields to output at the end of the form
82
	 * @var   array
83
	 * @since 2.0.0
84
	 */
85
	protected $hidden_fields = array();
86
87
	/**
88
	 * Array of key => value data for saving. Likely $_POST data.
89
	 * @var   string
90
	 * @since 2.0.0
91
	 */
92
	protected $generated_nonce = '';
93
94
	/**
95
	 * Whether there are fields to be shown in columns. Set in CMB2::add_field().
96
	 * @var   bool
97
	 * @since 2.2.2
98
	 */
99
	protected $has_columns = false;
100
101
	/**
102
	 * If taxonomy field is requesting to remove_default, we store the taxonomy here.
103
	 * @var   array
104
	 * @since 2.2.3
105
	 */
106
	protected $tax_metaboxes_to_remove = array();
107
108
	/**
109
	 * Get started
110
	 * @since 0.4.0
111
	 * @param array   $config    Metabox config array
112
	 * @param integer $object_id Optional object id
113
	 */
114 84
	public function __construct( $config, $object_id = 0 ) {
115
116 84
		if ( empty( $config['id'] ) ) {
117 1
			wp_die( esc_html__( 'Metabox configuration is required to have an ID parameter.', 'cmb2' ) );
118
		}
119
120 84
		$this->meta_box = wp_parse_args( $config, $this->mb_defaults );
121 84
		$this->meta_box['fields'] = array();
122
123 84
		$this->object_id( $object_id );
124 84
		$this->mb_object_type();
125 84
		$this->cmb_id = $config['id'];
126
127 84
		if ( ! empty( $config['fields'] ) && is_array( $config['fields'] ) ) {
128 81
			$this->add_fields( $config['fields'] );
129 81
		}
130
131 84
		CMB2_Boxes::add( $this );
132
133
		/**
134
		 * Hook during initiation of CMB2 object
135
		 *
136
		 * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id.
137
		 *
138
		 * @param array $cmb This CMB2 object
139
		 */
140 84
		do_action( "cmb2_init_{$this->cmb_id}", $this );
141 84
	}
142
143
	/**
144
	 * Loops through and displays fields
145
	 * @since 1.0.0
146
	 * @param int    $object_id   Object ID
147
	 * @param string $object_type Type of object being saved. (e.g., post, user, or comment)
148
	 */
149 1
	public function show_form( $object_id = 0, $object_type = '' ) {
150 1
		$this->render_form_open( $object_id, $object_type );
151
152 1
		foreach ( $this->prop( 'fields' ) as $field_args ) {
153 1
			$this->render_field( $field_args );
154 1
		}
155
156 1
		$this->render_form_close( $object_id, $object_type );
157 1
	}
158
159
	/**
160
	 * Outputs the opening form markup and runs corresponding hooks:
161
	 * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}"
162
	 * @since  2.2.0
163
	 * @param  integer $object_id   Object ID
164
	 * @param  string  $object_type Object type
165
	 * @return void
166
	 */
167 1
	public function render_form_open( $object_id = 0, $object_type = '' ) {
168 1
		$object_type = $this->object_type( $object_type );
169 1
		$object_id = $this->object_id( $object_id );
170
171 1
		echo "\n<!-- Begin CMB2 Fields -->\n";
172
173 1
		$this->nonce_field();
174
175
		/**
176
		 * Hook before form table begins
177
		 *
178
		 * @param array  $cmb_id      The current box ID
179
		 * @param int    $object_id   The ID of the current object
180
		 * @param string $object_type The type of object you are working with.
181
		 *	                           Usually `post` (this applies to all post-types).
182
		 *	                           Could also be `comment`, `user` or `options-page`.
183
		 * @param array  $cmb         This CMB2 object
184
		 */
185 1
		do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this );
186
187
		/**
188
		 * Hook before form table begins
189
		 *
190
		 * The first dynamic portion of the hook name, $object_type, is the type of object
191
		 * you are working with. Usually `post` (this applies to all post-types).
192
		 * Could also be `comment`, `user` or `options-page`.
193
		 *
194
		 * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
195
		 *
196
		 * @param array  $cmb_id      The current box ID
197
		 * @param int    $object_id   The ID of the current object
198
		 * @param array  $cmb         This CMB2 object
199
		 */
200 1
		do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
201
202 1
		echo '<div class="', $this->box_classes(), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">';
203
204 1
	}
205
206
	/**
207
	 * Defines the classes for the CMB2 form/wrap.
208
	 *
209
	 * @since  2.0.0
210
	 * @return string Space concatenated list of classes
211
	 */
212 1
	public function box_classes() {
213
214 1
		$classes = array( 'cmb2-wrap', 'form-table' );
215
216
		// Use the callback to fetch classes.
217 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...
218 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
219 1
			$classes = array_merge( $classes, $added_classes );
220 1
		}
221
222 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...
223 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
224 1
			$classes = array_merge( $classes, $added_classes );
225 1
		}
226
227
		/**
228
		 * Globally filter box wrap classes
229
		 *
230
		 * @since 2.2.2
231
		 *
232
		 * @param string $classes Array of classes for the cmb2-wrap.
233
		 * @param CMB2   $cmb     This CMB2 object.
234
		 */
235 1
		$classes = apply_filters( 'cmb2_wrap_classes', $classes, $this );
236
237
		// Clean up.
238 1
		$classes = array_map( 'strip_tags', array_filter( $classes ) );
239
240
		// Make a string.
241 1
		return implode( ' ', $classes );
242
	}
243
244
	/**
245
	 * Outputs the closing form markup and runs corresponding hooks:
246
	 * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}"
247
	 * @since  2.2.0
248
	 * @param  integer $object_id   Object ID
249
	 * @param  string  $object_type Object type
250
	 * @return void
251
	 */
252 1
	public function render_form_close( $object_id = 0, $object_type = '' ) {
253 1
		$object_type = $this->object_type( $object_type );
254 1
		$object_id = $this->object_id( $object_id );
255
256 1
		echo '</div></div>';
257
258 1
		$this->render_hidden_fields();
259
260
		/**
261
		 * Hook after form form has been rendered
262
		 *
263
		 * @param array  $cmb_id      The current box ID
264
		 * @param int    $object_id   The ID of the current object
265
		 * @param string $object_type The type of object you are working with.
266
		 *	                           Usually `post` (this applies to all post-types).
267
		 *	                           Could also be `comment`, `user` or `options-page`.
268
		 * @param array  $cmb         This CMB2 object
269
		 */
270 1
		do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this );
271
272
		/**
273
		 * Hook after form form has been rendered
274
		 *
275
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
276
		 *
277
		 * The first dynamic portion of the hook name, $object_type, is the type of object
278
		 * you are working with. Usually `post` (this applies to all post-types).
279
		 * Could also be `comment`, `user` or `options-page`.
280
		 *
281
		 * @param int    $object_id   The ID of the current object
282
		 * @param array  $cmb         This CMB2 object
283
		 */
284 1
		do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
285
286 1
		echo "\n<!-- End CMB2 Fields -->\n";
287
288 1
	}
289
290
	/**
291
	 * Renders a field based on the field type
292
	 * @since  2.2.0
293
	 * @param  array $field_args A field configuration array.
294
	 * @return mixed CMB2_Field object if successful.
295
	 */
296 1
	public function render_field( $field_args ) {
297 1
		$field_args['context'] = $this->prop( 'context' );
298
299 1
		if ( 'group' == $field_args['type'] ) {
300
301
			if ( ! isset( $field_args['show_names'] ) ) {
302
				$field_args['show_names'] = $this->prop( 'show_names' );
303
			}
304
			$field = $this->render_group( $field_args );
305
306 1
		} elseif ( 'hidden' == $field_args['type'] && $this->get_field( $field_args )->should_show() ) {
307
			// Save rendering for after the metabox
308
			$field = $this->add_hidden_field( $field_args );
309
310
		} else {
311
312 1
			$field_args['show_names'] = $this->prop( 'show_names' );
313
314
			// Render default fields
315 1
			$field = $this->get_field( $field_args )->render_field();
316
		}
317
318 1
		return $field;
319
	}
320
321
	/**
322
	 * Render a repeatable group.
323
	 * @param array $args Array of field arguments for a group field parent.
324
	 * @return CMB2_Field|null Group field object.
325
	 */
326 2
	public function render_group( $args ) {
327
328 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
329
			return;
330
		}
331
332 2
		$field_group = $this->get_field( $args );
333
334
		// If field is requesting to be conditionally shown
335 2
		if ( ! $field_group || ! $field_group->should_show() ) {
336
			return;
337
		}
338
339 2
		$desc            = $field_group->args( 'description' );
340 2
		$label           = $field_group->args( 'name' );
341 2
		$group_val       = (array) $field_group->value();
342 2
		$remove_disabled = count( $group_val ) <= 1 ? 'disabled="disabled" ' : '';
343 2
		$field_group->index = 0;
344
345 2
		$field_group->peform_param_callback( 'before_group' );
346
347 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 ), '>';
348
349 2
		if ( $desc || $label ) {
350 2
			$class = $desc ? ' cmb-group-description' : '';
351 2
			echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
352 2
				if ( $label ) {
353 2
					echo '<h2 class="cmb-group-name">', $label, '</h2>';
354 2
				}
355 2
				if ( $desc ) {
356 1
					echo '<p class="cmb2-metabox-description">', $desc, '</p>';
357 1
				}
358 2
			echo '</div></div>';
359 2
		}
360
361 2
		if ( ! empty( $group_val ) ) {
362
			foreach ( $group_val as $group_key => $field_id ) {
363
				$this->render_group_row( $field_group, $remove_disabled );
364
				$field_group->index++;
365
			}
366
		} else {
367 2
			$this->render_group_row( $field_group, $remove_disabled );
368
		}
369
370 2
		if ( $field_group->args( 'repeatable' ) ) {
371 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>';
372 1
		}
373
374 2
		echo '</div></div></div>';
375
376 2
		$field_group->peform_param_callback( 'after_group' );
377
378 2
		return $field_group;
379
	}
380
381
	/**
382
	 * Get the group wrap attributes, which are passed through a filter.
383
	 * @since  2.2.3
384
	 * @param  CMB2_Field $field_group The group CMB2_Field object.
385
	 * @return string                  The attributes string.
386
	 */
387 2
	public function group_wrap_attributes( $field_group ) {
388 2
		$classes = 'cmb-nested cmb-field-list cmb-repeatable-group';
389 2
		$classes .= $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
390 2
		$classes .= $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
391
392
		$group_wrap_attributes = array(
393 2
			'class' => $classes,
394 2
			'style' => 'width:100%;',
395 2
		);
396
397
		/**
398
		 * Allow for adding additional HTML attributes to a group wrapper.
399
		 *
400
		 * The attributes will be an array of key => value pairs for each attribute.
401
		 *
402
		 * @since 2.2.2
403
		 *
404
		 * @param string     $group_wrap_attributes Current attributes array.
405
		 *
406
		 * @param CMB2_Field $field_group           The group CMB2_Field object.
407
		 */
408 2
		$group_wrap_attributes = apply_filters( 'cmb2_group_wrap_attributes', $group_wrap_attributes, $field_group );
409
410 2
		return CMB2_Utils::concat_attrs( $group_wrap_attributes );
411
	}
412
413
	/**
414
	 * Render a repeatable group row
415
	 * @since  1.0.2
416
	 * @param  CMB2_Field $field_group  CMB2_Field group field object
417
	 * @param  string  $remove_disabled Attribute string to disable the remove button
418
	 */
419 2
	public function render_group_row( $field_group, $remove_disabled ) {
420
421 2
		$field_group->peform_param_callback( 'before_group_row' );
422 2
		$closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
423
424
		echo '
425 2
		<div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
426
427 2
			if ( $field_group->args( 'repeatable' ) ) {
428 1
				echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row"></button>';
429 1
			}
430
431
			echo '
432 2
			<div class="cmbhandle" title="' , esc_attr__( 'Click to toggle', 'cmb2' ), '"><br></div>
433 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...
434
435
			<div class="inside cmb-td cmb-nested cmb-field-list">';
436
				// Loop and render repeatable group fields
437 2
				foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) {
438 2
					if ( 'hidden' == $field_args['type'] ) {
439
440
						// Save rendering for after the metabox
441
						$this->add_hidden_field( $field_args, $field_group );
442
443
					} else {
444
445 2
						$field_args['show_names'] = $field_group->args( 'show_names' );
446 2
						$field_args['context']    = $field_group->args( 'context' );
447
448 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...
449
					}
450 2
				}
451 2
				if ( $field_group->args( 'repeatable' ) ) {
452
					echo '
453
					<div class="cmb-row cmb-remove-field-row">
454
						<div class="cmb-remove-row">
455 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>
456
						</div>
457
					</div>
458
					';
459 1
				}
460
			echo '
461
			</div>
462
		</div>
463 2
		';
464
465 2
		$field_group->peform_param_callback( 'after_group_row' );
466 2
	}
467
468
	/**
469
	 * Add a hidden field to the list of hidden fields to be rendered later
470
	 * @since 2.0.0
471
	 * @param array  $field_args Array of field arguments to be passed to CMB2_Field
472
	 */
473
	public function add_hidden_field( $field_args, $field_group = null ) {
474
		if ( isset( $field_args['field_args'] ) ) {
475
			// For back-compatibility.
476
			$field = new CMB2_Field( $field_args );
477
		} else {
478
			$field = $this->get_new_field( $field_args, $field_group );
479
		}
480
481
		$type = new CMB2_Types( $field );
482
483
		if ( $field_group ) {
484
			$type->iterator = $field_group->index;
485
		}
486
487
		$this->hidden_fields[] = $type;
488
489
		return $field;
490
	}
491
492
	/**
493
	 * Loop through and output hidden fields
494
	 * @since  2.0.0
495
	 */
496 1
	public function render_hidden_fields() {
497 1
		if ( ! empty( $this->hidden_fields ) ) {
498
			foreach ( $this->hidden_fields as $hidden ) {
499
				$hidden->render();
500
			}
501
		}
502 1
	}
503
504
	/**
505
	 * Returns array of sanitized field values (without saving them)
506
	 * @since  2.0.3
507
	 * @param  array  $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
508
	 */
509 2
	public function get_sanitized_values( array $data_to_sanitize ) {
510 2
		$this->data_to_save = $data_to_sanitize;
511 2
		$stored_id          = $this->object_id();
512
513
		// We do this So CMB will sanitize our data for us, but not save it
514 2
		$this->object_id( '_' );
515
516
		// Ensure temp. data store is empty
517 2
		cmb2_options( 0 )->set();
518
519
		// Process/save fields
520 2
		$this->process_fields();
521
522
		// Get data from temp. data store
523 2
		$sanitized_values = cmb2_options( 0 )->get_options();
524
525
		// Empty out temp. data store again
526 2
		cmb2_options( 0 )->set();
527
528
		// Reset the object id
529 2
		$this->object_id( $stored_id );
530
531 2
		return $sanitized_values;
532
	}
533
534
	/**
535
	 * Loops through and saves field data
536
	 * @since  1.0.0
537
	 * @param  int    $object_id    Object ID
538
	 * @param  string $object_type  Type of object being saved. (e.g., post, user, or comment)
539
	 * @param  array  $data_to_save Array of key => value data for saving. Likely $_POST data.
540
	 */
541 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...
542
543
		// Fall-back to $_POST data
544 1
		$this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
545 1
		$object_id = $this->object_id( $object_id );
546 1
		$object_type = $this->object_type( $object_type );
547
548 1
		$this->process_fields();
549
550
		// If options page, save the updated options
551 1
		if ( 'options-page' == $object_type ) {
552 1
			cmb2_options( $object_id )->set();
553 1
		}
554
555 1
		$this->after_save();
556 1
	}
557
558
	/**
559
	 * Process and save form fields
560
	 * @since  2.0.0
561
	 */
562 3
	public function process_fields() {
563
564 3
		$this->pre_process();
565
566
		// Remove the show_on properties so saving works
567 3
		$this->prop( 'show_on', array() );
568
569
		// save field ids of those that are updated
570 3
		$this->updated = array();
571
572 3
		foreach ( $this->prop( 'fields' ) as $field_args ) {
573 3
			$this->process_field( $field_args );
574 3
		}
575 3
	}
576
577
	/**
578
	 * Process and save a field
579
	 * @since  2.0.0
580
	 * @param  array  $field_args Array of field arguments
581
	 */
582 3
	public function process_field( $field_args ) {
583
584 3
		switch ( $field_args['type'] ) {
585
586 3
			case 'group':
587 1
				if ( $this->save_group( $field_args ) ) {
588 1
					$this->updated[] = $field_args['id'];
589 1
				}
590
591 1
				break;
592
593 2
			case 'title':
594
				// Don't process title fields
595
				break;
596
597 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...
598
599 2
				$field = $this->get_new_field( $field_args );
600
601 2
				if ( $field->save_field_from_data( $this->data_to_save ) ) {
602 2
					$this->updated[] = $field->id();
603 2
				}
604
605 2
				break;
606 3
		}
607
608 3
	}
609
610 8
	public function pre_process() {
611
		/**
612
		 * Fires before fields have been processed/saved.
613
		 *
614
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
615
		 *
616
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
617
		 * 	Usually `post` (this applies to all post-types).
618
		 *  	Could also be `comment`, `user` or `options-page`.
619
		 *
620
		 * @param array $cmb       This CMB2 object
621
		 * @param int   $object_id The ID of the current object
622
		 */
623 8
		do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
624 8
	}
625
626 6
	public function after_save() {
627 6
		$object_type = $this->object_type();
628 6
		$object_id   = $this->object_id();
629
630
		/**
631
		 * Fires after all fields have been saved.
632
		 *
633
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
634
		 * 	Usually `post` (this applies to all post-types).
635
		 *  	Could also be `comment`, `user` or `options-page`.
636
		 *
637
		 * @param int    $object_id   The ID of the current object
638
		 * @param array  $cmb_id      The current box ID
639
		 * @param string $updated     Array of field ids that were updated.
640
		 *                            Will only include field ids that had values change.
641
		 * @param array  $cmb         This CMB2 object
642
		 */
643 6
		do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
644
645
		/**
646
		 * Fires after all fields have been saved.
647
		 *
648
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
649
		 *
650
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
651
		 * 	Usually `post` (this applies to all post-types).
652
		 *  	Could also be `comment`, `user` or `options-page`.
653
		 *
654
		 * @param int    $object_id   The ID of the current object
655
		 * @param string $updated     Array of field ids that were updated.
656
		 *                            Will only include field ids that had values change.
657
		 * @param array  $cmb         This CMB2 object
658
		 */
659 6
		do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
660 6
	}
661
662
	/**
663
	 * Save a repeatable group
664
	 * @since  1.x.x
665
	 * @param  array  $args Field arguments array
666
	 * @return mixed        Return of CMB2_Field::update_data()
667
	 */
668 1
	public function save_group( $args ) {
669 1
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
670
			return;
671
		}
672
673 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...
674
	}
675
676
	/**
677
	 * Save a repeatable group
678
	 * @since  1.x.x
679
	 * @param  array $field_group CMB2_Field group field object
680
	 * @return mixed              Return of CMB2_Field::update_data()
681
	 */
682 1
	public function save_group_field( $field_group ) {
683 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...
684
685 1
		if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
686
			return;
687
		}
688
689 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...
690
		// Check if group field has sanitization_cb
691 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...
692 1
		$saved      = array();
693
694 1
		$field_group->index = 0;
695 1
		$field_group->data_to_save = $this->data_to_save;
696
697 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...
698
699 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...
700 1
			$sub_id = $field->id( true );
701
702 1
			foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
703
704
				// Get value
705 1
				$new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
706 1
					? $group_vals[ $field_group->index ][ $sub_id ]
707 1
					: false;
708
709
				// Sanitize
710 1
				$new_val = $field->sanitization_cb( $new_val );
711
712 1
				if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
713 1
					if ( $field->args( 'repeatable' ) ) {
714 1
						$_new_val = array();
715 1
						foreach ( $new_val as $group_index => $grouped_data ) {
716
							// Add the supporting data to the $saved array stack
717 1
							$saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
718
							// Reset var to the actual value
719 1
							$_new_val[ $group_index ] = $grouped_data['value'];
720 1
						}
721 1
						$new_val = $_new_val;
722 1
					} else {
723
						// Add the supporting data to the $saved array stack
724 1
						$saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
725
						// Reset var to the actual value
726 1
						$new_val = $new_val['value'];
727
					}
728 1
				}
729
730
				// Get old value
731 1
				$old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
732 1
					? $old[ $field_group->index ][ $sub_id ]
733 1
					: false;
734
735 1
				$is_updated = ( ! CMB2_Utils::isempty( $new_val ) && $new_val !== $old_val );
736 1
				$is_removed = ( CMB2_Utils::isempty( $new_val ) && ! CMB2_Utils::isempty( $old_val ) );
737
738
				// Compare values and add to `$updated` array
739 1
				if ( $is_updated || $is_removed ) {
740 1
					$this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
741 1
				}
742
743
				// 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...
744 1
				$saved[ $field_group->index ][ $sub_id ] = $new_val;
745
746 1
			}
747
748 1
			$saved[ $field_group->index ] = CMB2_Utils::filter_empty( $saved[ $field_group->index ] );
749 1
		}
750
751 1
		$saved = CMB2_Utils::filter_empty( $saved );
752
753 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...
754
	}
755
756
	/**
757
	 * Get object id from global space if no id is provided
758
	 * @since  1.0.0
759
	 * @param  integer $object_id Object ID
760
	 * @return integer $object_id Object ID
761
	 */
762 88
	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...
763 88
		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...
764
765 88
		if ( $object_id ) {
766 32
			$this->object_id = $object_id;
767 32
			return $this->object_id;
768
		}
769
770 85
		if ( $this->object_id ) {
771 26
			return $this->object_id;
772
		}
773
774
		// Try to get our object ID from the global space
775 82
		switch ( $this->object_type() ) {
776 82
			case 'user':
777
				$object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id;
778
				$object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
779
				break;
780
781 82
			case 'comment':
782
				$object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id;
783
				$object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
784
				break;
785
786 82
			case 'term':
787
				$object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id;
788
				break;
789
790 82
			default:
791 82
				$object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
792 82
				$object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id;
793 82
				break;
794 82
		}
795
796
		// reset to id or 0
797 82
		$this->object_id = $object_id ? $object_id : 0;
798
799 82
		return $this->object_id;
800
	}
801
802
	/**
803
	 * Sets the $object_type based on metabox settings
804
	 * @since  1.0.0
805
	 * @return string Object type
806
	 */
807 84
	public function mb_object_type() {
808 84
		if ( null !== $this->mb_object_type ) {
809 12
			return $this->mb_object_type;
810
		}
811
812 84
		if ( $this->is_options_page_mb() ) {
813 38
			$this->mb_object_type = 'options-page';
814 38
			return $this->mb_object_type;
815
		}
816
817 83
		$registered_types = $this->box_types();
818
819 83
		$type = false;
820
821
		// if it's an array of one, extract it
822 83
		if ( 1 === count( $registered_types ) ) {
823 83
			$last = end( $registered_types );
824 83
			if ( is_string( $last ) ) {
825 83
				$type = $last;
826 83
			}
827 83
		} elseif ( in_array( $this->current_object_type(), $registered_types, true ) ) {
828
			$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...
829
		}
830
831
		// Get our object type
832
		switch ( $type ) {
833
834 83
			case 'user':
835 83
			case 'comment':
836 83
			case 'term':
837 11
				$this->mb_object_type = $type;
838 11
				break;
839
840 83
			default:
841 83
				$this->mb_object_type = 'post';
842 83
				break;
843 83
		}
844
845 83
		return $this->mb_object_type;
846
	}
847
848
	/**
849
	 * Gets the box 'object_types' array based on box settings.
850
	 * @since  2.2.4
851
	 * @return array Object types
852
	 */
853 83
	public function box_types() {
854 83
		return CMB2_Utils::ensure_array( $this->prop( 'object_types' ), array( 'post' ) );
855
	}
856
857
	/**
858
	 * Determines if metabox is for an options page
859
	 * @since  1.0.1
860
	 * @return boolean True/False
861
	 */
862 84
	public function is_options_page_mb() {
863 84
		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'] ) );
864
	}
865
866
	/**
867
	 * Returns the object type
868
	 * @since  1.0.0
869
	 * @return string Object type
870
	 */
871 88
	public function object_type( $object_type = '' ) {
872 88
		if ( $object_type ) {
873 31
			$this->object_type = $object_type;
874 31
			return $this->object_type;
875
		}
876
877 85
		if ( $this->object_type ) {
878 85
			return $this->object_type;
879
		}
880
881
		$this->object_type = $this->current_object_type();
882
883
		return $this->object_type;
884
	}
885
886
	/**
887
	 * Get the object type for the current page, based on the $pagenow global.
888
	 * @since  2.2.2
889
	 * @return string  Page object type name.
890
	 */
891 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...
892
		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...
893
		$type = 'post';
894
895
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
896
			$type = 'user';
897
		}
898
899
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
900
			$type = 'comment';
901
		}
902
903
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
904
			$type = 'term';
905
		}
906
907
		return $type;
908
	}
909
910
	/**
911
	 * Set metabox property.
912
	 * @since  2.2.2
913
	 * @param  string $property Metabox config property to retrieve
914
	 * @param  mixed  $value    Value to set if no value found
915
	 * @return mixed            Metabox config property value or false
916
	 */
917 1
	public function set_prop( $property, $value ) {
918 1
		$this->meta_box[ $property ] = $value;
919
920 1
		return $this->prop( $property );
921
	}
922
923
	/**
924
	 * Get metabox property and optionally set a fallback
925
	 * @since  2.0.0
926
	 * @param  string $property Metabox config property to retrieve
927
	 * @param  mixed  $fallback Fallback value to set if no value found
928
	 * @return mixed            Metabox config property value or false
929
	 */
930 84
	public function prop( $property, $fallback = null ) {
931 84
		if ( array_key_exists( $property, $this->meta_box ) ) {
932 84
			return $this->meta_box[ $property ];
933 11
		} elseif ( $fallback ) {
934 1
			return $this->meta_box[ $property ] = $fallback;
935
		}
936 11
	}
937
938
	/**
939
	 * Get a field object
940
	 * @since  2.0.3
941
	 * @param  string|array|CMB2_Field $field       Metabox field id or field config array or CMB2_Field object
942
	 * @param  CMB2_Field              $field_group (optional) CMB2_Field object (group parent)
943
	 * @return CMB2_Field|false CMB2_Field object (or false)
944
	 */
945 33
	public function get_field( $field, $field_group = null ) {
946 33
		if ( is_a( $field, 'CMB2_Field' ) ) {
947 2
			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...
948
		}
949
950 33
		$field_id = is_string( $field ) ? $field : $field['id'];
951
952 33
		$parent_field_id = ! empty( $field_group ) ? $field_group->id() : '';
953 33
		$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...
954
955 33
		if ( ! $ids ) {
956
			return false;
957
		}
958
959 33
		list( $field_id, $sub_field_id ) = $ids;
960
961 33
		$index = implode( '', $ids ) . ( $field_group ? $field_group->index : '' );
962 33
		if ( array_key_exists( $index, $this->fields ) ) {
963 12
			return $this->fields[ $index ];
964
		}
965
966 31
		$this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) );
967
968 31
		return $this->fields[ $index ];
969
	}
970
971
	/**
972
	 * Handles determining which type of arguments to pass to CMB2_Field
973
	 * @since  2.0.7
974
	 * @param  mixed  $field_id     Field (or group field) ID
975
	 * @param  mixed  $field_args   Array of field arguments
976
	 * @param  mixed  $sub_field_id Sub field ID (if field_group exists)
977
	 * @param  mixed  $field_group  If a sub-field, will be the parent group CMB2_Field object
978
	 * @return array                Array of CMB2_Field arguments
979
	 */
980 31
	public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) {
981
982
		// Check if group is passed and if fields were added in the old-school fields array
983 31
		if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) {
984
985
			// Update the fields array w/ any modified properties inherited from the group field
986 2
			$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args;
987
988 2
			return $this->get_default_args( $field_args, $field_group );
989
		}
990
991 31
		if ( is_array( $field_args ) ) {
992 2
			$this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
993 2
		}
994
995 31
		return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
996
	}
997
998
	/**
999
	 * Get default field arguments specific to this CMB2 object.
1000
	 * @since  2.2.0
1001
	 * @param  array      $field_args  Metabox field config array.
1002
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1003
	 * @return array                   Array of field arguments.
1004
	 */
1005 35 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...
1006 35
		if ( $field_group ) {
1007
			$args = array(
1008 3
				'field_args'  => $field_args,
1009 3
				'group_field' => $field_group,
1010 3
			);
1011 3
		} else {
1012
			$args = array(
1013 35
				'field_args'  => $field_args,
1014 35
				'object_type' => $this->object_type(),
1015 35
				'object_id'   => $this->object_id(),
1016 35
				'cmb_id'      => $this->cmb_id,
1017 35
			);
1018
		}
1019
1020 35
		return $args;
1021
	}
1022
1023
	/**
1024
	 * Get a new field object specific to this CMB2 object.
1025
	 * @since  2.2.0
1026
	 * @param  array      $field_args  Metabox field config array.
1027
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1028
	 * @return CMB2_Field CMB2_Field object
1029
	 */
1030 5
	protected function get_new_field( $field_args, $field_group = null ) {
1031 5
		return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) );
1032
	}
1033
1034
	/**
1035
	 * When fields are added in the old-school way, intitate them as they should be
1036
	 * @since 2.1.0
1037
	 * @param array $fields          Array of fields to add
1038
	 * @param mixed $parent_field_id Parent field id or null
1039
	 */
1040 81
	protected function add_fields( $fields, $parent_field_id = null ) {
1041 81
		foreach ( $fields as $field ) {
1042
1043 81
			$sub_fields = false;
1044 81
			if ( array_key_exists( 'fields', $field ) ) {
1045
				$sub_fields = $field['fields'];
1046
				unset( $field['fields'] );
1047
			}
1048
1049
			$field_id = $parent_field_id
1050 81
				? $this->add_group_field( $parent_field_id, $field )
1051 81
				: $this->add_field( $field );
1052
1053 81
			if ( $sub_fields ) {
1054
				$this->add_fields( $sub_fields, $field_id );
1055
			}
1056 81
		}
1057 81
	}
1058
1059
	/**
1060
	 * Add a field to the metabox
1061
	 * @since  2.0.0
1062
	 * @param  array  $field           Metabox field config array
1063
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1064
	 * @return mixed                   Field id or false
1065
	 */
1066 83
	public function add_field( array $field, $position = 0 ) {
1067 83
		if ( ! is_array( $field ) || ! array_key_exists( 'id', $field ) ) {
1068
			return false;
1069
		}
1070
1071 83
		if ( 'oembed' === $field['type'] ) {
1072
			// Initiate oembed Ajax hooks
1073 1
			cmb2_ajax();
1074 1
		}
1075
1076 83
		if ( isset( $field['column'] ) && false !== $field['column'] ) {
1077
			$field = $this->define_field_column( $field );
1078
		}
1079
1080 83
		if ( isset( $field['taxonomy'] ) && ! empty( $field['remove_default'] ) ) {
1081
			$this->tax_metaboxes_to_remove[ $field['taxonomy'] ] = $field['taxonomy'];
1082
		}
1083
1084 83
		$this->_add_field_to_array(
1085 83
			$field,
1086 83
			$this->meta_box['fields'],
1087
			$position
1088 83
		);
1089
1090 83
		return $field['id'];
1091
	}
1092
1093
	/**
1094
	 * Defines a field's column if requesting to be show in admin columns.
1095
	 * @since  2.2.3
1096
	 * @param  array  $field Metabox field config array.
1097
	 * @return array         Modified metabox field config array.
1098
	 */
1099
	protected function define_field_column( array $field ) {
1100
		$this->has_columns = true;
1101
1102
		$column = is_array( $field['column'] ) ? $field['column'] : array();
1103
1104
		$field['column'] = wp_parse_args( $column, array(
1105
			'name'     => isset( $field['name'] ) ? $field['name'] : '',
1106
			'position' => false,
1107
		) );
1108
1109
		return $field;
1110
	}
1111
1112
	/**
1113
	 * Add a field to a group
1114
	 * @since  2.0.0
1115
	 * @param  string $parent_field_id The field id of the group field to add the field
1116
	 * @param  array  $field           Metabox field config array
1117
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1118
	 * @return mixed                   Array of parent/field ids or false
1119
	 */
1120 3
	public function add_group_field( $parent_field_id, array $field, $position = 0 ) {
1121 3
		if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) {
1122
			return false;
1123
		}
1124
1125 3
		$parent_field = $this->meta_box['fields'][ $parent_field_id ];
1126
1127 3
		if ( 'group' !== $parent_field['type'] ) {
1128
			return false;
1129
		}
1130
1131 3
		if ( ! isset( $parent_field['fields'] ) ) {
1132 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'] = array();
1133 3
		}
1134
1135 3
		$this->_add_field_to_array(
1136 3
			$field,
1137 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'],
1138
			$position
1139 3
		);
1140
1141 3
		return array( $parent_field_id, $field['id'] );
1142
	}
1143
1144
	/**
1145
	 * Add a field array to a fields array in desired position
1146
	 * @since 2.0.2
1147
	 * @param array   $field    Metabox field config array
1148
	 * @param array   &$fields  Array (passed by reference) to append the field (array) to
1149
	 * @param integer $position Optionally specify a position in the array to be inserted
1150
	 */
1151 83
	protected function _add_field_to_array( $field, &$fields, $position = 0 ) {
1152 83
		if ( $position ) {
1153 1
			CMB2_Utils::array_insert( $fields, array( $field['id'] => $field ), $position );
1154 1
		} else {
1155 83
			$fields[ $field['id'] ] = $field;
1156
		}
1157 83
	}
1158
1159
	/**
1160
	 * Remove a field from the metabox
1161
	 * @since 2.0.0
1162
	 * @param  string $field_id        The field id of the field to remove
1163
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1164
	 * @return bool                    True if field was removed
1165
	 */
1166 2
	public function remove_field( $field_id, $parent_field_id = '' ) {
1167 2
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1168
1169 2
		if ( ! $ids ) {
1170
			return false;
1171
		}
1172
1173 2
		list( $field_id, $sub_field_id ) = $ids;
1174
1175 2
		unset( $this->fields[ implode( '', $ids ) ] );
1176
1177 2
		if ( ! $sub_field_id ) {
1178 1
			unset( $this->meta_box['fields'][ $field_id ] );
1179 1
			return true;
1180
		}
1181
1182 1
		if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) {
1183 1
			unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] );
1184 1
		}
1185 1
		if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) {
1186 1
			unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] );
1187 1
		}
1188 1
		return true;
1189
	}
1190
1191
	/**
1192
	 * Update or add a property to a field
1193
	 * @since  2.0.0
1194
	 * @param  string $field_id        Field id
1195
	 * @param  string $property        Field property to set/update
1196
	 * @param  mixed  $value           Value to set the field property
1197
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1198
	 * @return mixed                   Field id. Strict compare to false, as success can return a falsey value (like 0)
1199
	 */
1200 4
	public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) {
1201 4
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1202
1203 4
		if ( ! $ids ) {
1204 2
			return false;
1205
		}
1206
1207 2
		list( $field_id, $sub_field_id ) = $ids;
1208
1209 2
		if ( ! $sub_field_id ) {
1210 2
			$this->meta_box['fields'][ $field_id ][ $property ] = $value;
1211 2
			return $field_id;
1212
		}
1213
1214
		$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value;
1215
		return $field_id;
1216
	}
1217
1218
	/**
1219
	 * Check if field ids match a field and return the index/field id
1220
	 * @since  2.0.2
1221
	 * @param  string  $field_id        Field id
1222
	 * @param  string  $parent_field_id (optional) Parent field id
1223
	 * @return mixed                    Array of field/parent ids, or false
1224
	 */
1225 37
	public function get_field_ids( $field_id, $parent_field_id = '' ) {
1226 37
		$sub_field_id = $parent_field_id ? $field_id : '';
1227 37
		$field_id     = $parent_field_id ? $parent_field_id : $field_id;
1228 37
		$fields       =& $this->meta_box['fields'];
1229
1230 37
		if ( ! array_key_exists( $field_id, $fields ) ) {
1231 2
			$field_id = $this->search_old_school_array( $field_id, $fields );
1232 2
		}
1233
1234 37
		if ( false === $field_id ) {
1235 2
			return false;
1236
		}
1237
1238 35
		if ( ! $sub_field_id ) {
1239 35
			return array( $field_id, $sub_field_id );
1240
		}
1241
1242 3
		if ( 'group' !== $fields[ $field_id ]['type'] ) {
1243
			return false;
1244
		}
1245
1246 3
		if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) {
1247
			$sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] );
1248
		}
1249
1250 3
		return false === $sub_field_id ? false : array( $field_id, $sub_field_id );
1251
	}
1252
1253
	/**
1254
	 * When using the old array filter, it is unlikely field array indexes will be the field id
1255
	 * @since  2.0.2
1256
	 * @param  string $field_id The field id
1257
	 * @param  array  $fields   Array of fields to search
1258
	 * @return mixed            Field index or false
1259
	 */
1260 2
	public function search_old_school_array( $field_id, $fields ) {
1261 2
		$ids = wp_list_pluck( $fields, 'id' );
1262 2
		$index = array_search( $field_id, $ids );
1263 2
		return false !== $index ? $index : false;
1264
	}
1265
1266
	/**
1267
	 * Handles metabox property callbacks, and passes this $cmb object as property.
1268
	 * @since  2.2.3
1269
	 * @param  callable $cb The callback method/function/closure
1270
	 * @return mixed        Return of the callback function.
1271
	 */
1272 1
	protected function do_callback( $cb ) {
1273 1
		return call_user_func( $cb, $this );
1274
	}
1275
1276
	/**
1277
	 * Generate a unique nonce field for each registered meta_box
1278
	 * @since  2.0.0
1279
	 * @return string unique nonce hidden input
1280
	 */
1281 1
	public function nonce_field() {
1282 1
		wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1283 1
	}
1284
1285
	/**
1286
	 * Generate a unique nonce for each registered meta_box
1287
	 * @since  2.0.0
1288
	 * @return string unique nonce string
1289
	 */
1290 1
	public function nonce() {
1291 1
		if ( $this->generated_nonce ) {
1292 1
			return $this->generated_nonce;
1293
		}
1294 1
		$this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1295 1
		return $this->generated_nonce;
1296
	}
1297
1298
	/**
1299
	 * Magic getter for our object.
1300
	 * @param string $field
1301
	 * @throws Exception Throws an exception if the field is invalid.
1302
	 * @return mixed
1303
	 */
1304 84
	public function __get( $field ) {
1305
		switch ( $field ) {
1306 84
			case 'updated':
1307 84
			case 'has_columns':
1308 84
			case 'tax_metaboxes_to_remove':
1309 1
				return $this->{$field};
1310 84
			default:
1311 84
				return parent::__get( $field );
1312 84
		}
1313
	}
1314
1315
}
1316