Completed
Pull Request — trunk (#541)
by Justin
05:37
created

CMB2   D

Complexity

Total Complexity 165

Size/Duplication

Total Lines 1184
Duplicated Lines 7.77 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 79.32%
Metric Value
wmc 165
lcom 1
cbo 5
dl 92
loc 1184
ccs 372
cts 469
cp 0.7932
rs 4

37 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 28 4
A show_form() 0 9 2
B render_form_open() 38 38 1
B render_form_close() 37 37 1
B render_field() 0 28 5
D render_group() 3 58 16
B render_group_row() 0 51 6
A add_hidden_field() 0 6 1
A render_hidden_fields() 0 7 3
B get_sanitized_values() 0 24 1
A save_fields() 0 16 3
A process_fields() 0 14 2
B process_field() 0 32 5
A pre_process() 0 15 1
B after_save() 0 35 1
A save_group() 3 11 3
C save_group_field() 0 72 15
D object_id() 0 39 17
C mb_object_type() 0 50 12
A is_options_page_mb() 0 3 3
B object_type() 0 27 6
A set_prop() 0 5 1
A prop() 0 7 3
C get_field() 0 25 7
B get_field_args() 0 25 5
B add_fields() 0 18 5
A add_field() 0 13 3
B add_group_field() 0 23 4
A _add_field_to_array() 0 7 2
A remove_field() 0 20 3
A update_field_property() 0 17 3
D get_field_ids() 0 27 9
A search_old_school_array() 0 5 2
A should_show() 11 11 2
A nonce_field() 0 3 1
A nonce() 0 7 2
B __get() 0 12 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CMB2 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CMB2, and based on these observations, apply Extract Interface, too.

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

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

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

Loading history...
1146
		// Default to showing this cmb
1147
		$show = true;
1148
1149
		// Use the callback to determine showing the cmb, if it exists
1150
		if ( is_callable( $this->prop( 'show_on_cb' ) ) ) {
1151
			$show = (bool) call_user_func( $this->prop( 'show_on_cb' ), $this );
1152
		}
1153
1154
		return $show;
1155
	}
1156
1157
	/**
1158
	 * Generate a unique nonce field for each registered meta_box
1159
	 * @since  2.0.0
1160
	 * @return string unique nonce hidden input
1161
	 */
1162 1
	public function nonce_field() {
1163 1
		wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1164 1
	}
1165
1166
	/**
1167
	 * Generate a unique nonce for each registered meta_box
1168
	 * @since  2.0.0
1169
	 * @return string unique nonce string
1170
	 */
1171 1
	public function nonce() {
1172 1
		if ( $this->generated_nonce ) {
1173 1
			return $this->generated_nonce;
1174
		}
1175 1
		$this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1176 1
		return $this->generated_nonce;
1177
	}
1178
1179
	/**
1180
	 * Magic getter for our object.
1181
	 * @param string $field
1182
	 * @throws Exception Throws an exception if the field is invalid.
1183
	 * @return mixed
1184
	 */
1185 42
	public function __get( $field ) {
1186
		switch ( $field ) {
1187 42
			case 'cmb_id':
1188 42
			case 'meta_box':
1189 42
			case 'updated':
1190 42
				return $this->{$field};
1191 3
			case 'object_id':
1192 1
				return $this->object_id();
1193 2
			default:
1194 2
				throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field );
1195 2
		}
1196
	}
1197
1198
}
1199