Completed
Push — trunk ( 5d123b...5dfd19 )
by Justin
06:53
created

CMB2::process_fields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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