Completed
Pull Request — trunk (#541)
by Justin
04:53
created

CMB2::save_group_field()   C

Complexity

Conditions 15
Paths 195

Size

Total Lines 70
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 15.0028
Metric Value
dl 0
loc 70
ccs 42
cts 43
cp 0.9767
rs 5.1677
cc 15
eloc 38
nc 195
nop 1
crap 15.0028

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * CMB2 - 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 CMB2 instance 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 44
	public function __construct( $meta_box, $object_id = 0 ) {
121
122 44
		if ( empty( $meta_box['id'] ) ) {
123 1
			wp_die( __( 'Metabox configuration is required to have an ID parameter', 'cmb2' ) );
124
		}
125
126 44
		$this->meta_box = wp_parse_args( $meta_box, $this->mb_defaults );
127 44
		$this->meta_box['fields'] = array();
128
129 44
		$this->object_id( $object_id );
130 44
		$this->mb_object_type();
131 44
		$this->cmb_id = $meta_box['id'];
132
133 44
		if ( ! empty( $meta_box['fields'] ) && is_array( $meta_box['fields'] ) ) {
134 41
			$this->add_fields( $meta_box['fields'] );
135 41
		}
136
137 44
		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 44
		do_action( "cmb2_init_{$this->cmb_id}", $this );
147 44
	}
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( $field_args );
277
278
		} else {
279
280 1
			$field_args['show_names'] = $this->prop( 'show_names' );
281
282
			// Render default fields
283 1
			$field = $this->get_field( $field_args )->render_field();
284
		}
285
286 1
		return $field;
287
	}
288
289
	/**
290
	 * Render a repeatable group.
291
	 * @param array $args Array of field arguments for a group field parent.
292
	 * @return CMB2_Field|null Group field object.
293
	 */
294 2
	public function render_group( $args ) {
295
296 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
297
			return;
298
		}
299
300 2
		$field_group = $this->get_field( $args );
301
302
		// If field is requesting to be conditionally shown
303 2
		if ( ! $field_group || ! $field_group->should_show() ) {
304
			return;
305
		}
306
307 2
		$desc            = $field_group->args( 'description' );
308 2
		$label           = $field_group->args( 'name' );
309 2
		$sortable        = $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
310 2
		$repeat_class    = $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
311 2
		$group_val       = (array) $field_group->value();
312 2
		$nrows           = count( $group_val );
313 2
		$remove_disabled = $nrows <= 1 ? 'disabled="disabled" ' : '';
314 2
		$field_group->index = 0;
315
316 2
		$field_group->peform_param_callback( 'before_group' );
317
318 2
		echo '<div class="cmb-row cmb-repeat-group-wrap ', $field_group->row_classes(), '" 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%;">';
319
320 2
		if ( $desc || $label ) {
321 2
			$class = $desc ? ' cmb-group-description' : '';
322 2
			echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
323 2
				if ( $label ) {
324 2
					echo '<h2 class="cmb-group-name">', $label, '</h2>';
325 2
				}
326 2
				if ( $desc ) {
327 1
					echo '<p class="cmb2-metabox-description">', $desc, '</p>';
328 1
				}
329 2
			echo '</div></div>';
330 2
		}
331
332 2
		if ( ! empty( $group_val ) ) {
333
334
			foreach ( $group_val as $group_key => $field_id ) {
335
				$this->render_group_row( $field_group, $remove_disabled );
336
				$field_group->index++;
337
			}
338
		} else {
339 2
			$this->render_group_row( $field_group, $remove_disabled );
340
		}
341
342 2
		if ( $field_group->args( 'repeatable' ) ) {
343 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>';
344 1
		}
345
346 2
		echo '</div></div></div>';
347
348 2
		$field_group->peform_param_callback( 'after_group' );
349
350 2
		return $field_group;
351
	}
352
353
	/**
354
	 * Render a repeatable group row
355
	 * @since  1.0.2
356
	 * @param  CMB2_Field $field_group  CMB2_Field group field object
357
	 * @param  string  $remove_disabled Attribute string to disable the remove button
358
	 */
359 2
	public function render_group_row( $field_group, $remove_disabled ) {
360
361 2
		$field_group->peform_param_callback( 'before_group_row' );
362 2
		$closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
363
364
		echo '
365 2
		<div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
366
367 2
			if ( $field_group->args( 'repeatable' ) ) {
368 1
				echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row"></button>';
369 1
			}
370
371
			echo '
372 2
			<div class="cmbhandle" title="' , __( 'Click to toggle', 'cmb2' ), '"><br></div>
373 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...
374
375
			<div class="inside cmb-td cmb-nested cmb-field-list">';
376
				// Loop and render repeatable group fields
377 2
				foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) {
378 2
					if ( 'hidden' == $field_args['type'] ) {
379
380
						// Save rendering for after the metabox
381
						$this->add_hidden_field( $field_args, $field_group );
382
383
					} else {
384
385 2
						$field_args['show_names'] = $field_group->args( 'show_names' );
386 2
						$field_args['context']    = $field_group->args( 'context' );
387
388 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...
389
					}
390 2
				}
391 2
				if ( $field_group->args( 'repeatable' ) ) {
392
					echo '
393
					<div class="cmb-row cmb-remove-field-row">
394
						<div class="cmb-remove-row">
395 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>
396
						</div>
397
					</div>
398
					';
399 1
				}
400
			echo '
401
			</div>
402
		</div>
403 2
		';
404
405 2
		$field_group->peform_param_callback( 'after_group_row' );
406 2
	}
407
408
	/**
409
	 * Add a hidden field to the list of hidden fields to be rendered later
410
	 * @since 2.0.0
411
	 * @param array  $field_args Array of field arguments to be passed to CMB2_Field
412
	 */
413
	public function add_hidden_field( $field_args, $field_group = null ) {
414
		if ( isset( $field_args['field_args'] ) ) {
415
			// For back-compatibility.
416
			$field = new CMB2_Field( $field_args );
417
		} else {
418
			$field = $this->get_new_field( $field_args, $field_group );
419
		}
420
421
		$this->hidden_fields[] = new CMB2_Types( $field );
422
423
		return $field;
424
	}
425
426
	/**
427
	 * Loop through and output hidden fields
428
	 * @since  2.0.0
429
	 */
430 1
	public function render_hidden_fields() {
431 1
		if ( ! empty( $this->hidden_fields ) ) {
432
			foreach ( $this->hidden_fields as $hidden ) {
433
				$hidden->render();
434
			}
435
		}
436 1
	}
437
438
	/**
439
	 * Returns array of sanitized field values (without saving them)
440
	 * @since  2.0.3
441
	 * @param  array  $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
442
	 */
443 2
	public function get_sanitized_values( array $data_to_sanitize ) {
444 2
		$this->data_to_save = $data_to_sanitize;
445 2
		$stored_id          = $this->object_id();
446
447
		// We do this So CMB will sanitize our data for us, but not save it
448 2
		$this->object_id( '_' );
449
450
		// Ensure temp. data store is empty
451 2
		cmb2_options( 0 )->set();
452
453
		// Process/save fields
454 2
		$this->process_fields();
455
456
		// Get data from temp. data store
457 2
		$sanitized_values = cmb2_options( 0 )->get_options();
458
459
		// Empty out temp. data store again
460 2
		cmb2_options( 0 )->set();
461
462
		// Reset the object id
463 2
		$this->object_id( $stored_id );
464
465 2
		return $sanitized_values;
466
	}
467
468
	/**
469
	 * Loops through and saves field data
470
	 * @since  1.0.0
471
	 * @param  int    $object_id    Object ID
472
	 * @param  string $object_type  Type of object being saved. (e.g., post, user, or comment)
473
	 * @param  array  $data_to_save Array of key => value data for saving. Likely $_POST data.
474
	 */
475 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...
476
477
		// Fall-back to $_POST data
478 1
		$this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
479 1
		$object_id = $this->object_id( $object_id );
480 1
		$object_type = $this->object_type( $object_type );
481
482 1
		$this->process_fields();
483
484
		// If options page, save the updated options
485 1
		if ( 'options-page' == $object_type ) {
486 1
			cmb2_options( $object_id )->set();
487 1
		}
488
489 1
		$this->after_save();
490 1
	}
491
492
	/**
493
	 * Process and save form fields
494
	 * @since  2.0.0
495
	 */
496 3
	public function process_fields() {
497
498 3
		$this->pre_process();
499
500
		// Remove the show_on properties so saving works
501 3
		$this->prop( 'show_on', array() );
502
503
		// save field ids of those that are updated
504 3
		$this->updated = array();
505
506 3
		foreach ( $this->prop( 'fields' ) as $field_args ) {
507 3
			$this->process_field( $field_args );
508 3
		}
509 3
	}
510
511
	/**
512
	 * Process and save a field
513
	 * @since  2.0.0
514
	 * @param  array  $field_args Array of field arguments
515
	 */
516 3
	public function process_field( $field_args ) {
517
518 3
		switch ( $field_args['type'] ) {
519
520 3
			case 'group':
521 1
				if ( $this->save_group( $field_args ) ) {
522 1
					$this->updated[] = $field_args['id'];
523 1
				}
524
525 1
				break;
526
527 2
			case 'title':
528
				// Don't process title fields
529
				break;
530
531 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...
532
533 2
				$field = $this->get_new_field( $field_args );
534
535 2
				if ( $field->save_field_from_data( $this->data_to_save ) ) {
536 2
					$this->updated[] = $field->id();
537 2
				}
538
539 2
				break;
540 3
		}
541
542 3
	}
543
544 3
	public function pre_process() {
545
		/**
546
		 * Fires before fields have been processed/saved.
547
		 *
548
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
549
		 *
550
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
551
		 * 	Usually `post` (this applies to all post-types).
552
		 *  	Could also be `comment`, `user` or `options-page`.
553
		 *
554
		 * @param array $cmb       This CMB2 object
555
		 * @param int   $object_id The ID of the current object
556
		 */
557 3
		do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
558 3
	}
559
560 1
	public function after_save() {
561 1
		$object_type = $this->object_type();
562 1
		$object_id   = $this->object_id();
563
564
		/**
565
		 * Fires after all fields have been saved.
566
		 *
567
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
568
		 * 	Usually `post` (this applies to all post-types).
569
		 *  	Could also be `comment`, `user` or `options-page`.
570
		 *
571
		 * @param int    $object_id   The ID of the current object
572
		 * @param array  $cmb_id      The current box ID
573
		 * @param string $updated     Array of field ids that were updated.
574
		 *                            Will only include field ids that had values change.
575
		 * @param array  $cmb         This CMB2 object
576
		 */
577 1
		do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
578
579
		/**
580
		 * Fires after all fields have been saved.
581
		 *
582
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
583
		 *
584
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
585
		 * 	Usually `post` (this applies to all post-types).
586
		 *  	Could also be `comment`, `user` or `options-page`.
587
		 *
588
		 * @param int    $object_id   The ID of the current object
589
		 * @param string $updated     Array of field ids that were updated.
590
		 *                            Will only include field ids that had values change.
591
		 * @param array  $cmb         This CMB2 object
592
		 */
593 1
		do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
594 1
	}
595
596
	/**
597
	 * Save a repeatable group
598
	 * @since  1.x.x
599
	 * @param  array  $args Field arguments array
600
	 * @return mixed        Return of CMB2_Field::update_data()
601
	 */
602 1
	public function save_group( $args ) {
603 1
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
604
			return;
605
		}
606
607 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...
608
	}
609
610
	/**
611
	 * Save a repeatable group
612
	 * @since  1.x.x
613
	 * @param  array $field_group CMB2_Field group field object
614
	 * @return mixed              Return of CMB2_Field::update_data()
615
	 */
616 1
	public function save_group_field( $field_group ) {
617 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...
618
619 1
		if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
620
			return;
621
		}
622
623 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...
624
		// Check if group field has sanitization_cb
625 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...
626 1
		$saved      = array();
627
628 1
		$field_group->index = 0;
629 1
		$field_group->data_to_save = $this->data_to_save;
630
631 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...
632
633 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...
634 1
			$sub_id = $field->id( true );
635
636 1
			foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
637
638
				// Get value
639 1
				$new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
640 1
					? $group_vals[ $field_group->index ][ $sub_id ]
641 1
					: false;
642
643
				// Sanitize
644 1
				$new_val = $field->sanitization_cb( $new_val );
645
646 1
				if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
647 1
					if ( $field->args( 'repeatable' ) ) {
648 1
						$_new_val = array();
649 1
						foreach ( $new_val as $group_index => $grouped_data ) {
650
							// Add the supporting data to the $saved array stack
651 1
							$saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
652
							// Reset var to the actual value
653 1
							$_new_val[ $group_index ] = $grouped_data['value'];
654 1
						}
655 1
						$new_val = $_new_val;
656 1
					} else {
657
						// Add the supporting data to the $saved array stack
658 1
						$saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
659
						// Reset var to the actual value
660 1
						$new_val = $new_val['value'];
661
					}
662 1
				}
663
664
				// Get old value
665 1
				$old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
666 1
					? $old[ $field_group->index ][ $sub_id ]
667 1
					: false;
668
669 1
				$is_updated = ( ! empty( $new_val ) && $new_val != $old_val );
670 1
				$is_removed = ( empty( $new_val ) && ! empty( $old_val ) );
671
				// Compare values and add to `$updated` array
672 1
				if ( $is_updated || $is_removed ) {
673 1
					$this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
674 1
				}
675
676
				// 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...
677 1
				$saved[ $field_group->index ][ $sub_id ] = $new_val;
678
679 1
			}
680 1
			$saved[ $field_group->index ] = array_filter( $saved[ $field_group->index ] );
681 1
		}
682 1
		$saved = array_filter( $saved );
683
684 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...
685
	}
686
687
	/**
688
	 * Get object id from global space if no id is provided
689
	 * @since  1.0.0
690
	 * @param  integer $object_id Object ID
691
	 * @return integer $object_id Object ID
692
	 */
693 48
	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...
694 48
		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...
695
696 48
		if ( $object_id ) {
697 18
			$this->object_id = $object_id;
698 18
			return $this->object_id;
699
		}
700
701 45
		if ( $this->object_id ) {
702 13
			return $this->object_id;
703
		}
704
705
		// Try to get our object ID from the global space
706 42
		switch ( $this->object_type() ) {
707 42
			case 'user':
708
				$object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id;
709
				$object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
710
				break;
711
712 42
			case 'comment':
713
				$object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id;
714
				$object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
715
				break;
716
717 42
			case 'term':
718
				$object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id;
719
				break;
720
721 42
			default:
722 42
				$object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
723 42
				$object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id;
724 42
				break;
725 42
		}
726
727
		// reset to id or 0
728 42
		$this->object_id = $object_id ? $object_id : 0;
729
730 42
		return $this->object_id;
731
	}
732
733
	/**
734
	 * Sets the $object_type based on metabox settings
735
	 * @since  1.0.0
736
	 * @return string Object type
737
	 */
738 44
	public function mb_object_type() {
739 44
		if ( null !== $this->mb_object_type ) {
740 12
			return $this->mb_object_type;
741
		}
742
743 44
		if ( $this->is_options_page_mb() ) {
744 36
			$this->mb_object_type = 'options-page';
745 36
			return $this->mb_object_type;
746
		}
747
748 43
		$registered_types = $this->prop( 'object_types' );
749
750 43
		if ( ! $registered_types ) {
751 40
			$this->mb_object_type = 'post';
752 40
			return $this->mb_object_type;
753
		}
754
755 4
		$type = false;
756
757
		// check if 'object_types' is a string
758 4
		if ( is_string( $registered_types ) ) {
759
			$type = $registered_types;
760
		}
761
762
		// if it's an array of one, extract it
763 4
		elseif ( is_array( $registered_types ) && 1 === count( $registered_types ) ) {
764 4
			$last = end( $registered_types );
765 4
			if ( is_string( $last ) ) {
766 4
				$type = $last;
767 4
			}
768 4
		} elseif ( is_array( $registered_types ) ) {
769
			$page_type = $this->current_object_type();
770
771
			if ( in_array( $page_type, $registered_types, true ) ) {
772
				$type = $page_type;
773
			}
774
		}
775
776
		// Get our object type
777
		switch ( $type ) {
778
779 4
			case 'user':
780 4
			case 'comment':
781 4
			case 'term':
782 1
				$this->mb_object_type = $type;
0 ignored issues
show
Documentation Bug introduced by
It seems like $type can also be of type false. However, the property $mb_object_type is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
783 1
				break;
784
785 3
			default:
786 3
				$this->mb_object_type = 'post';
787 3
				break;
788 3
		}
789
790 4
		return $this->mb_object_type;
791
	}
792
793
	/**
794
	 * Determines if metabox is for an options page
795
	 * @since  1.0.1
796
	 * @return boolean True/False
797
	 */
798 44
	public function is_options_page_mb() {
799 44
		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'] ) );
800
	}
801
802
	/**
803
	 * Returns the object type
804
	 * @since  1.0.0
805
	 * @return string Object type
806
	 */
807 48
	public function object_type( $object_type = '' ) {
808 48
		if ( $object_type ) {
809 18
			$this->object_type = $object_type;
810 18
			return $this->object_type;
811
		}
812
813 45
		if ( $this->object_type ) {
814 45
			return $this->object_type;
815
		}
816
817
		$this->object_type = $this->current_object_type();
818
819
		return $this->object_type;
820
	}
821
822
	/**
823
	 * Get the object type for the current page, based on the $pagenow global.
824
	 * @since  2.2.2
825
	 * @return string  Page object type name.
826
	 */
827
	public function current_object_type() {
828
		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...
829
		$type = 'post';
830
831
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
832
			$type = 'user';
833
		}
834
835
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
836
			$type = 'comment';
837
		}
838
839
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
840
			$type = 'term';
841
		}
842
843
		return $type;
844
	}
845
846
	/**
847
	 * Set metabox property.
848
	 * @since  2.2.0
849
	 * @param  string $property Metabox config property to retrieve
850
	 * @param  mixed  $value    Value to set if no value found
851
	 * @return mixed            Metabox config property value or false
852
	 */
853 1
	public function set_prop( $property, $value ) {
854 1
		$this->meta_box[ $property ] = $value;
855
856 1
		return $this->prop( $property );
857
	}
858
859
	/**
860
	 * Get metabox property and optionally set a fallback
861
	 * @since  2.0.0
862
	 * @param  string $property Metabox config property to retrieve
863
	 * @param  mixed  $fallback Fallback value to set if no value found
864
	 * @return mixed            Metabox config property value or false
865
	 */
866 44
	public function prop( $property, $fallback = null ) {
867 44
		if ( array_key_exists( $property, $this->meta_box ) ) {
868 44
			return $this->meta_box[ $property ];
869 1
		} elseif ( $fallback ) {
870 1
			return $this->meta_box[ $property ] = $fallback;
871
		}
872
	}
873
874
	/**
875
	 * Get a field object
876
	 * @since  2.0.3
877
	 * @param  string|array|CMB2_Field $field       Metabox field id or field config array or CMB2_Field object
878
	 * @param  CMB2_Field              $field_group (optional) CMB2_Field object (group parent)
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 2
			return $this->get_default_args( $field_args, $field_group );
925
		}
926
927 13
		if ( is_array( $field_args ) ) {
928 2
			$this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
929 2
		}
930
931 13
		return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
932
	}
933
934
	/**
935
	 * Get default field arguments specific to this CMB2 object.
936
	 * @since  2.2.0
937
	 * @param  array      $field_args  Metabox field config array.
938
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
939
	 * @return array                   Array of field arguments.
940
	 */
941 17
	protected function get_default_args( $field_args, $field_group = null ) {
942 17
		if ( $field_group ) {
943
			$args = array(
944 3
				'field_args'  => $field_args,
945 3
				'group_field' => $field_group,
946 3
			);
947 3
		} else {
948
			$args = array(
949 17
				'field_args'  => $field_args,
950 17
				'object_type' => $this->object_type(),
951 17
				'object_id'   => $this->object_id(),
952 17
				'cmb_id'      => $this->cmb_id,
953 17
			);
954
		}
955
956 17
		return $args;
957
	}
958
959
	/**
960
	 * Get a new field object specific to this CMB2 object.
961
	 * @since  2.2.0
962
	 * @param  array      $field_args  Metabox field config array.
963
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
964
	 * @return CMB2_Field CMB2_Field object
965
	 */
966 5
	protected function get_new_field( $field_args, $field_group = null ) {
967 5
		return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) );
968
	}
969
970
	/**
971
	 * When fields are added in the old-school way, intitate them as they should be
972
	 * @since 2.1.0
973
	 * @param array $fields          Array of fields to add
974
	 * @param mixed $parent_field_id Parent field id or null
975
	 */
976 41
	protected function add_fields( $fields, $parent_field_id = null ) {
977 41
		foreach ( $fields as $field ) {
978
979 41
			$sub_fields = false;
980 41
			if ( array_key_exists( 'fields', $field ) ) {
981
				$sub_fields = $field['fields'];
982
				unset( $field['fields'] );
983
			}
984
985
			$field_id = $parent_field_id
986 41
				? $this->add_group_field( $parent_field_id, $field )
987 41
				: $this->add_field( $field );
988
989 41
			if ( $sub_fields ) {
990
				$this->add_fields( $sub_fields, $field_id );
991
			}
992 41
		}
993 41
	}
994
995
	/**
996
	 * Add a field to the metabox
997
	 * @since  2.0.0
998
	 * @param  array  $field           Metabox field config array
999
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1000
	 * @return mixed                   Field id or false
1001
	 */
1002 43
	public function add_field( array $field, $position = 0 ) {
1003 43
		if ( ! is_array( $field ) || ! array_key_exists( 'id', $field ) ) {
1004
			return false;
1005
		}
1006
1007 43
		if ( 'oembed' === $field['type'] ) {
1008
			// Initiate oembed Ajax hooks
1009 1
			cmb2_ajax();
1010 1
		}
1011
1012 43
		$this->_add_field_to_array(
1013 43
			$field,
1014 43
			$this->meta_box['fields'],
1015
			$position
1016 43
		);
1017
1018 43
		return $field['id'];
1019
	}
1020
1021
	/**
1022
	 * Add a field to a group
1023
	 * @since  2.0.0
1024
	 * @param  string $parent_field_id The field id of the group field to add the field
1025
	 * @param  array  $field           Metabox field config array
1026
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1027
	 * @return mixed                   Array of parent/field ids or false
1028
	 */
1029 3
	public function add_group_field( $parent_field_id, array $field, $position = 0 ) {
1030 3
		if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) {
1031
			return false;
1032
		}
1033
1034 3
		$parent_field = $this->meta_box['fields'][ $parent_field_id ];
1035
1036 3
		if ( 'group' !== $parent_field['type'] ) {
1037
			return false;
1038
		}
1039
1040 3
		if ( ! isset( $parent_field['fields'] ) ) {
1041 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'] = array();
1042 3
		}
1043
1044 3
		$this->_add_field_to_array(
1045 3
			$field,
1046 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'],
1047
			$position
1048 3
		);
1049
1050 3
		return array( $parent_field_id, $field['id'] );
1051
	}
1052
1053
	/**
1054
	 * Add a field array to a fields array in desired position
1055
	 * @since 2.0.2
1056
	 * @param array   $field    Metabox field config array
1057
	 * @param array   &$fields  Array (passed by reference) to append the field (array) to
1058
	 * @param integer $position Optionally specify a position in the array to be inserted
1059
	 */
1060 43
	protected function _add_field_to_array( $field, &$fields, $position = 0 ) {
1061 43
		if ( $position ) {
1062 1
			cmb2_utils()->array_insert( $fields, array( $field['id'] => $field ), $position );
1063 1
		} else {
1064 43
			$fields[ $field['id'] ] = $field;
1065
		}
1066 43
	}
1067
1068
	/**
1069
	 * Remove a field from the metabox
1070
	 * @since 2.0.0
1071
	 * @param  string $field_id        The field id of the field to remove
1072
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1073
	 * @return bool                    True if field was removed
1074
	 */
1075 2
	public function remove_field( $field_id, $parent_field_id = '' ) {
1076 2
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1077
1078 2
		if ( ! $ids ) {
1079
			return false;
1080
		}
1081
1082 2
		list( $field_id, $sub_field_id ) = $ids;
1083
1084 2
		unset( $this->fields[ implode( '', $ids ) ] );
1085
1086 2
		if ( ! $sub_field_id ) {
1087 1
			unset( $this->meta_box['fields'][ $field_id ] );
1088 1
			return true;
1089
		}
1090
1091 1
		if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) {
1092 1
			unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] );
1093 1
		}
1094 1
		if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) {
1095 1
			unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] );
1096 1
		}
1097 1
		return true;
1098
	}
1099
1100
	/**
1101
	 * Update or add a property to a field
1102
	 * @since  2.0.0
1103
	 * @param  string $field_id        Field id
1104
	 * @param  string $property        Field property to set/update
1105
	 * @param  mixed  $value           Value to set the field property
1106
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1107
	 * @return mixed                   Field id. Strict compare to false, as success can return a falsey value (like 0)
1108
	 */
1109 4
	public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) {
1110 4
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1111
1112 4
		if ( ! $ids ) {
1113 2
			return false;
1114
		}
1115
1116 2
		list( $field_id, $sub_field_id ) = $ids;
1117
1118 2
		if ( ! $sub_field_id ) {
1119 2
			$this->meta_box['fields'][ $field_id ][ $property ] = $value;
1120 2
			return $field_id;
1121
		}
1122
1123
		$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value;
1124
		return $field_id;
1125
	}
1126
1127
	/**
1128
	 * Check if field ids match a field and return the index/field id
1129
	 * @since  2.0.2
1130
	 * @param  string  $field_id        Field id
1131
	 * @param  string  $parent_field_id (optional) Parent field id
1132
	 * @return mixed                    Array of field/parent ids, or false
1133
	 */
1134 19
	public function get_field_ids( $field_id, $parent_field_id = '' ) {
1135 19
		$sub_field_id = $parent_field_id ? $field_id : '';
1136 19
		$field_id     = $parent_field_id ? $parent_field_id : $field_id;
1137 19
		$fields       =& $this->meta_box['fields'];
1138
1139 19
		if ( ! array_key_exists( $field_id, $fields ) ) {
1140 2
			$field_id = $this->search_old_school_array( $field_id, $fields );
1141 2
		}
1142
1143 19
		if ( false === $field_id ) {
1144 2
			return false;
1145
		}
1146
1147 17
		if ( ! $sub_field_id ) {
1148 17
			return array( $field_id, $sub_field_id );
1149
		}
1150
1151 3
		if ( 'group' !== $fields[ $field_id ]['type'] ) {
1152
			return false;
1153
		}
1154
1155 3
		if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) {
1156
			$sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] );
1157
		}
1158
1159 3
		return false === $sub_field_id ? false : array( $field_id, $sub_field_id );
1160
	}
1161
1162
	/**
1163
	 * When using the old array filter, it is unlikely field array indexes will be the field id
1164
	 * @since  2.0.2
1165
	 * @param  string $field_id The field id
1166
	 * @param  array  $fields   Array of fields to search
1167
	 * @return mixed            Field index or false
1168
	 */
1169 2
	public function search_old_school_array( $field_id, $fields ) {
1170 2
		$ids = wp_list_pluck( $fields, 'id' );
1171 2
		$index = array_search( $field_id, $ids );
1172 2
		return false !== $index ? $index : false;
1173
	}
1174
1175
	/**
1176
	 * Determine whether this cmb object should show, based on the 'show_on_cb' callback.
1177
	 *
1178
	 * @since 2.0.9
1179
	 *
1180
	 * @return bool Whether this cmb should be shown.
1181
	 */
1182 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...
1183
		// Default to showing this cmb
1184
		$show = true;
1185
1186
		// Use the callback to determine showing the cmb, if it exists
1187
		if ( is_callable( $this->prop( 'show_on_cb' ) ) ) {
1188
			$show = (bool) call_user_func( $this->prop( 'show_on_cb' ), $this );
1189
		}
1190
1191
		return $show;
1192
	}
1193
1194
	/**
1195
	 * Generate a unique nonce field for each registered meta_box
1196
	 * @since  2.0.0
1197
	 * @return string unique nonce hidden input
1198
	 */
1199 1
	public function nonce_field() {
1200 1
		wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1201 1
	}
1202
1203
	/**
1204
	 * Generate a unique nonce for each registered meta_box
1205
	 * @since  2.0.0
1206
	 * @return string unique nonce string
1207
	 */
1208 1
	public function nonce() {
1209 1
		if ( $this->generated_nonce ) {
1210 1
			return $this->generated_nonce;
1211
		}
1212 1
		$this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1213 1
		return $this->generated_nonce;
1214
	}
1215
1216
	/**
1217
	 * Magic getter for our object.
1218
	 * @param string $field
1219
	 * @throws Exception Throws an exception if the field is invalid.
1220
	 * @return mixed
1221
	 */
1222 44
	public function __get( $field ) {
1223
		switch ( $field ) {
1224 44
			case 'cmb_id':
1225 44
			case 'meta_box':
1226 44
			case 'updated':
1227 44
				return $this->{$field};
1228 3
			case 'object_id':
1229 1
				return $this->object_id();
1230 2
			default:
1231 2
				throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field );
1232 2
		}
1233
	}
1234
1235
}
1236