Completed
Push — trunk ( 2096f9...2bf2b7 )
by Justin
21:19
created

CMB2::object_id()   D

Complexity

Conditions 17
Paths 54

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 25.4193

Importance

Changes 0
Metric Value
cc 17
eloc 25
c 0
b 0
f 0
nc 54
nop 1
dl 0
loc 39
ccs 18
cts 26
cp 0.6923
crap 25.4193
rs 4.9807

How to fix   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
 * @property-read bool  $has_columns
15
 * @property-read array $tax_metaboxes_to_remove
16
 */
17
class CMB2 extends CMB2_Base {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
18
19
	/**
20
	 * The object properties name.
21
	 * @var   string
22
	 * @since 2.2.3
23
	 */
24
	protected $properties_name = 'meta_box';
25
26
	/**
27
	 * Metabox Config array
28
	 * @var   array
29
	 * @since 0.9.0
30
	 */
31
	protected $meta_box = array();
32
33
	/**
34
	 * Type of object registered for metabox. (e.g., post, user, or comment)
35
	 * @var   string
36
	 * @since 1.0.0
37
	 */
38
	protected $mb_object_type = null;
39
40
	/**
41
	 * List of fields that are changed/updated on save
42
	 * @var   array
43
	 * @since 1.1.0
44
	 */
45
	protected $updated = array();
46
47
	/**
48
	 * Metabox Defaults
49
	 * @var   array
50
	 * @since 1.0.1
51
	 */
52
	protected $mb_defaults = array(
53
		'id'               => '',
54
		'title'            => '',
55
		'object_types'     => array(), // Post type
56
		'context'          => 'normal',
57
		'priority'         => 'high',
58
		'show_names'       => true, // Show field names on the left
59
		'show_on_cb'       => null, // Callback to determine if metabox should display.
60
		'show_on'          => array(), // Post IDs or page templates to display this metabox. overrides 'show_on_cb'
61
		'cmb_styles'       => true, // Include CMB2 stylesheet
62
		'enqueue_js'       => true, // Include CMB2 JS
63
		'fields'           => array(),
64
		'hookup'           => true,
65
		'save_fields'      => true, // Will not save during hookup if false
66
		'closed'           => false, // Default to metabox being closed?
67
		'taxonomies'       => array(),
68
		'new_user_section' => 'add-new-user', // or 'add-existing-user'
69
		'new_term_section' => true,
70
		'show_in_rest'     => false,
71
	);
72
73
	/**
74
	 * Metabox field objects
75
	 * @var   array
76
	 * @since 2.0.3
77
	 */
78
	protected $fields = array();
79
80
	/**
81
	 * An array of hidden fields to output at the end of the form
82
	 * @var   array
83
	 * @since 2.0.0
84
	 */
85
	protected $hidden_fields = array();
86
87
	/**
88
	 * Array of key => value data for saving. Likely $_POST data.
89
	 * @var   string
90
	 * @since 2.0.0
91
	 */
92
	protected $generated_nonce = '';
93
94
	/**
95
	 * Whether there are fields to be shown in columns. Set in CMB2::add_field().
96
	 * @var   bool
97
	 * @since 2.2.2
98
	 */
99
	protected $has_columns = false;
100
101
	/**
102
	 * If taxonomy field is requesting to remove_default, we store the taxonomy here.
103
	 * @var   array
104
	 * @since 2.2.3
105
	 */
106
	protected $tax_metaboxes_to_remove = array();
107
108
	/**
109
	 * Get started
110
	 * @since 0.4.0
111
	 * @param array   $config    Metabox config array
112
	 * @param integer $object_id Optional object id
113
	 */
114 48
	public function __construct( $config, $object_id = 0 ) {
115
116 48
		if ( empty( $config['id'] ) ) {
117 1
			wp_die( esc_html__( 'Metabox configuration is required to have an ID parameter.', 'cmb2' ) );
118
		}
119
120 48
		$this->meta_box = wp_parse_args( $config, $this->mb_defaults );
121 48
		$this->meta_box['fields'] = array();
122
123 48
		$this->object_id( $object_id );
124 48
		$this->mb_object_type();
125 48
		$this->cmb_id = $config['id'];
126
127 48
		if ( ! empty( $config['fields'] ) && is_array( $config['fields'] ) ) {
128 45
			$this->add_fields( $config['fields'] );
129 45
		}
130
131 48
		CMB2_Boxes::add( $this );
132
133
		/**
134
		 * Hook during initiation of CMB2 object
135
		 *
136
		 * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id.
137
		 *
138
		 * @param array $cmb This CMB2 object
139
		 */
140 48
		do_action( "cmb2_init_{$this->cmb_id}", $this );
141 48
	}
142
143
	/**
144
	 * Loops through and displays fields
145
	 * @since 1.0.0
146
	 * @param int    $object_id   Object ID
147
	 * @param string $object_type Type of object being saved. (e.g., post, user, or comment)
148
	 */
149 1
	public function show_form( $object_id = 0, $object_type = '' ) {
150 1
		$this->render_form_open( $object_id, $object_type );
151
152 1
		foreach ( $this->prop( 'fields' ) as $field_args ) {
153 1
			$this->render_field( $field_args );
154 1
		}
155
156 1
		$this->render_form_close( $object_id, $object_type );
157 1
	}
158
159
	/**
160
	 * Outputs the opening form markup and runs corresponding hooks:
161
	 * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}"
162
	 * @since  2.2.0
163
	 * @param  integer $object_id   Object ID
164
	 * @param  string  $object_type Object type
165
	 * @return void
166
	 */
167 1
	public function render_form_open( $object_id = 0, $object_type = '' ) {
168 1
		$object_type = $this->object_type( $object_type );
169 1
		$object_id = $this->object_id( $object_id );
170
171 1
		echo "\n<!-- Begin CMB2 Fields -->\n";
172
173 1
		$this->nonce_field();
174
175
		/**
176
		 * Hook before form table begins
177
		 *
178
		 * @param array  $cmb_id      The current box ID
179
		 * @param int    $object_id   The ID of the current object
180
		 * @param string $object_type The type of object you are working with.
181
		 *	                           Usually `post` (this applies to all post-types).
182
		 *	                           Could also be `comment`, `user` or `options-page`.
183
		 * @param array  $cmb         This CMB2 object
184
		 */
185 1
		do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this );
186
187
		/**
188
		 * Hook before form table begins
189
		 *
190
		 * The first dynamic portion of the hook name, $object_type, is the type of object
191
		 * you are working with. Usually `post` (this applies to all post-types).
192
		 * Could also be `comment`, `user` or `options-page`.
193
		 *
194
		 * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
195
		 *
196
		 * @param array  $cmb_id      The current box ID
197
		 * @param int    $object_id   The ID of the current object
198
		 * @param array  $cmb         This CMB2 object
199
		 */
200 1
		do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
201
202 1
		echo '<div class="', $this->box_classes(), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">';
203
204 1
	}
205
206
	/**
207
	 * Defines the classes for the CMB2 form/wrap.
208
	 *
209
	 * @since  2.0.0
210
	 * @return string Space concatenated list of classes
211
	 */
212 1
	public function box_classes() {
213
214 1
		$classes = array( 'cmb2-wrap', 'form-table' );
215
216
		// Use the callback to fetch classes.
217 1 View Code Duplication
		if ( $added_classes = $this->get_param_callback_result( 'classes_cb' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
218 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
219 1
			$classes = array_merge( $classes, $added_classes );
220 1
		}
221
222 1 View Code Duplication
		if ( $added_classes = $this->prop( 'classes' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
223 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
224 1
			$classes = array_merge( $classes, $added_classes );
225 1
		}
226
227
		/**
228
		 * Globally filter box wrap classes
229
		 *
230
		 * @since 2.2.2
231
		 *
232
		 * @param string $classes Array of classes for the cmb2-wrap.
233
		 * @param CMB2   $cmb     This CMB2 object.
234
		 */
235 1
		$classes = apply_filters( 'cmb2_wrap_classes', $classes, $this );
236
237
		// Clean up.
238 1
		$classes = array_map( 'strip_tags', array_filter( $classes ) );
239
240
		// Make a string.
241 1
		return implode( ' ', $classes );
242
	}
243
244
	/**
245
	 * Outputs the closing form markup and runs corresponding hooks:
246
	 * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}"
247
	 * @since  2.2.0
248
	 * @param  integer $object_id   Object ID
249
	 * @param  string  $object_type Object type
250
	 * @return void
251
	 */
252 1
	public function render_form_close( $object_id = 0, $object_type = '' ) {
253 1
		$object_type = $this->object_type( $object_type );
254 1
		$object_id = $this->object_id( $object_id );
255
256 1
		echo '</div></div>';
257
258 1
		$this->render_hidden_fields();
259
260
		/**
261
		 * Hook after form form has been rendered
262
		 *
263
		 * @param array  $cmb_id      The current box ID
264
		 * @param int    $object_id   The ID of the current object
265
		 * @param string $object_type The type of object you are working with.
266
		 *	                           Usually `post` (this applies to all post-types).
267
		 *	                           Could also be `comment`, `user` or `options-page`.
268
		 * @param array  $cmb         This CMB2 object
269
		 */
270 1
		do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this );
271
272
		/**
273
		 * Hook after form form has been rendered
274
		 *
275
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
276
		 *
277
		 * The first dynamic portion of the hook name, $object_type, is the type of object
278
		 * you are working with. Usually `post` (this applies to all post-types).
279
		 * Could also be `comment`, `user` or `options-page`.
280
		 *
281
		 * @param int    $object_id   The ID of the current object
282
		 * @param array  $cmb         This CMB2 object
283
		 */
284 1
		do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
285
286 1
		echo "\n<!-- End CMB2 Fields -->\n";
287
288 1
	}
289
290
	/**
291
	 * Renders a field based on the field type
292
	 * @since  2.2.0
293
	 * @param  array $field_args A field configuration array.
294
	 * @return mixed CMB2_Field object if successful.
295
	 */
296 1
	public function render_field( $field_args ) {
297 1
		$field_args['context'] = $this->prop( 'context' );
298
299 1
		if ( 'group' == $field_args['type'] ) {
300
301
			if ( ! isset( $field_args['show_names'] ) ) {
302
				$field_args['show_names'] = $this->prop( 'show_names' );
303
			}
304
			$field = $this->render_group( $field_args );
305
306 1
		} elseif ( 'hidden' == $field_args['type'] && $this->get_field( $field_args )->should_show() ) {
307
			// Save rendering for after the metabox
308
			$field = $this->add_hidden_field( $field_args );
309
310
		} else {
311
312 1
			$field_args['show_names'] = $this->prop( 'show_names' );
313
314
			// Render default fields
315 1
			$field = $this->get_field( $field_args )->render_field();
316
		}
317
318 1
		return $field;
319
	}
320
321
	/**
322
	 * Render a repeatable group.
323
	 * @param array $args Array of field arguments for a group field parent.
324
	 * @return CMB2_Field|null Group field object.
325
	 */
326 2
	public function render_group( $args ) {
327
328 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
329
			return;
330
		}
331
332 2
		$field_group = $this->get_field( $args );
333
334
		// If field is requesting to be conditionally shown
335 2
		if ( ! $field_group || ! $field_group->should_show() ) {
336
			return;
337
		}
338
339 2
		$desc            = $field_group->args( 'description' );
340 2
		$label           = $field_group->args( 'name' );
341 2
		$group_val       = (array) $field_group->value();
342 2
		$remove_disabled = count( $group_val ) <= 1 ? 'disabled="disabled" ' : '';
343 2
		$field_group->index = 0;
344
345 2
		$field_group->peform_param_callback( 'before_group' );
346
347 2
		echo '<div class="cmb-row cmb-repeat-group-wrap ', $field_group->row_classes(), '" data-fieldtype="group"><div class="cmb-td"><div data-groupid="', $field_group->id(), '" id="', $field_group->id(), '_repeat" ', $this->group_wrap_attributes( $field_group ), '>';
348
349 2
		if ( $desc || $label ) {
350 2
			$class = $desc ? ' cmb-group-description' : '';
351 2
			echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
352 2
				if ( $label ) {
353 2
					echo '<h2 class="cmb-group-name">', $label, '</h2>';
354 2
				}
355 2
				if ( $desc ) {
356 1
					echo '<p class="cmb2-metabox-description">', $desc, '</p>';
357 1
				}
358 2
			echo '</div></div>';
359 2
		}
360
361 2
		if ( ! empty( $group_val ) ) {
362
			foreach ( $group_val as $group_key => $field_id ) {
363
				$this->render_group_row( $field_group, $remove_disabled );
364
				$field_group->index++;
365
			}
366
		} else {
367 2
			$this->render_group_row( $field_group, $remove_disabled );
368
		}
369
370 2
		if ( $field_group->args( 'repeatable' ) ) {
371 1
			echo '<div class="cmb-row"><div class="cmb-td"><p class="cmb-add-row"><button type="button" data-selector="', $field_group->id(), '_repeat" data-grouptitle="', $field_group->options( 'group_title' ), '" class="cmb-add-group-row button">', $field_group->options( 'add_button' ), '</button></p></div></div>';
372 1
		}
373
374 2
		echo '</div></div></div>';
375
376 2
		$field_group->peform_param_callback( 'after_group' );
377
378 2
		return $field_group;
379
	}
380
381
	/**
382
	 * Get the group wrap attributes, which are passed through a filter.
383
	 * @since  2.2.3
384
	 * @param  CMB2_Field $field_group The group CMB2_Field object.
385
	 * @return string                  The attributes string.
386
	 */
387 2
	public function group_wrap_attributes( $field_group ) {
388 2
		$classes = 'cmb-nested cmb-field-list cmb-repeatable-group';
389 2
		$classes .= $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
390 2
		$classes .= $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
391
392
		$group_wrap_attributes = array(
393 2
			'class' => $classes,
394 2
			'style' => 'width:100%;',
395 2
		);
396
397
		/**
398
		 * Allow for adding additional HTML attributes to a group wrapper.
399
		 *
400
		 * The attributes will be an array of key => value pairs for each attribute.
401
		 *
402
		 * @since 2.2.2
403
		 *
404
		 * @param string     $group_wrap_attributes Current attributes array.
405
		 *
406
		 * @param CMB2_Field $field_group           The group CMB2_Field object.
407
		 */
408 2
		$group_wrap_attributes = apply_filters( 'cmb2_group_wrap_attributes', $group_wrap_attributes, $field_group );
409
410 2
		return CMB2_Utils::concat_attrs( $group_wrap_attributes );
411
	}
412
413
	/**
414
	 * Render a repeatable group row
415
	 * @since  1.0.2
416
	 * @param  CMB2_Field $field_group  CMB2_Field group field object
417
	 * @param  string  $remove_disabled Attribute string to disable the remove button
418
	 */
419 2
	public function render_group_row( $field_group, $remove_disabled ) {
420
421 2
		$field_group->peform_param_callback( 'before_group_row' );
422 2
		$closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
423
424
		echo '
425 2
		<div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
426
427 2
			if ( $field_group->args( 'repeatable' ) ) {
428 1
				echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row" title="', esc_attr( $field_group->options( 'remove_button' ) ), '"></button>';
429 1
			}
430
431
			echo '
432 2
			<div class="cmbhandle" title="' , esc_attr__( 'Click to toggle', 'cmb2' ), '"><br></div>
433 2
			<h3 class="cmb-group-title cmbhandle-title"><span>', $field_group->replace_hash( $field_group->options( 'group_title' ) ), '</span></h3>
0 ignored issues
show
Documentation introduced by
$field_group->options('group_title') is of type array, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

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

Loading history...
449
					}
450 2
				}
451 2
				if ( $field_group->args( 'repeatable' ) ) {
452
					echo '
453
					<div class="cmb-row cmb-remove-field-row">
454
						<div class="cmb-remove-row">
455 1
							<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="button cmb-remove-group-row alignright">', $field_group->options( 'remove_button' ), '</button>
456
						</div>
457
					</div>
458
					';
459 1
				}
460
			echo '
461
			</div>
462
		</div>
463 2
		';
464
465 2
		$field_group->peform_param_callback( 'after_group_row' );
466 2
	}
467
468
	/**
469
	 * Add a hidden field to the list of hidden fields to be rendered later
470
	 * @since 2.0.0
471
	 * @param array           $field_args  Array of field arguments to be passed to CMB2_Field
472
	 * @param CMB2_Field|null $field_group CMB2_Field group field object
473
	 */
474
	public function add_hidden_field( $field_args, $field_group = null ) {
475
		if ( isset( $field_args['field_args'] ) ) {
476
			// For back-compatibility.
477
			$field = new CMB2_Field( $field_args );
478
		} else {
479
			$field = $this->get_new_field( $field_args, $field_group );
480
		}
481
482
		$type = new CMB2_Types( $field );
483
484
		if ( $field_group ) {
485
			$type->iterator = $field_group->index;
486
		}
487
488
		$this->hidden_fields[] = $type;
489
490
		return $field;
491
	}
492
493
	/**
494
	 * Loop through and output hidden fields
495
	 * @since  2.0.0
496
	 */
497 1
	public function render_hidden_fields() {
498 1
		if ( ! empty( $this->hidden_fields ) ) {
499
			foreach ( $this->hidden_fields as $hidden ) {
500
				$hidden->render();
501
			}
502
		}
503 1
	}
504
505
	/**
506
	 * Returns array of sanitized field values (without saving them)
507
	 * @since  2.0.3
508
	 * @param  array  $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
509
	 */
510 4
	public function get_sanitized_values( array $data_to_sanitize ) {
511 4
		$this->data_to_save = $data_to_sanitize;
512 4
		$stored_id          = $this->object_id();
513
514
		// We do this So CMB will sanitize our data for us, but not save it
515 4
		$this->object_id( '_' );
516
517
		// Ensure temp. data store is empty
518 4
		cmb2_options( 0 )->set();
519
520
		// We want to get any taxonomy values back.
521 4
		add_filter( "cmb2_return_taxonomy_values_{$this->cmb_id}", '__return_true' );
522
523
		// Process/save fields
524 4
		$this->process_fields();
525
526
		// Put things back the way they were.
527 4
		remove_filter( "cmb2_return_taxonomy_values_{$this->cmb_id}", '__return_true' );
528
529
		// Get data from temp. data store
530 4
		$sanitized_values = cmb2_options( 0 )->get_options();
531
532
		// Empty out temp. data store again
533 4
		cmb2_options( 0 )->set();
534
535
		// Reset the object id
536 4
		$this->object_id( $stored_id );
537
538 4
		return $sanitized_values;
539
	}
540
541
	/**
542
	 * Loops through and saves field data
543
	 * @since  1.0.0
544
	 * @param  int    $object_id    Object ID
545
	 * @param  string $object_type  Type of object being saved. (e.g., post, user, or comment)
546
	 * @param  array  $data_to_save Array of key => value data for saving. Likely $_POST data.
547
	 */
548 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...
549
550
		// Fall-back to $_POST data
551 1
		$this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
552 1
		$object_id = $this->object_id( $object_id );
553 1
		$object_type = $this->object_type( $object_type );
554
555 1
		$this->process_fields();
556
557
		// If options page, save the updated options
558 1
		if ( 'options-page' == $object_type ) {
559 1
			cmb2_options( $object_id )->set();
560 1
		}
561
562 1
		$this->after_save();
563 1
	}
564
565
	/**
566
	 * Process and save form fields
567
	 * @since  2.0.0
568
	 */
569 5
	public function process_fields() {
570
571 5
		$this->pre_process();
572
573
		// Remove the show_on properties so saving works
574 5
		$this->prop( 'show_on', array() );
575
576
		// save field ids of those that are updated
577 5
		$this->updated = array();
578
579 5
		foreach ( $this->prop( 'fields' ) as $field_args ) {
580 5
			$this->process_field( $field_args );
581 5
		}
582 5
	}
583
584
	/**
585
	 * Process and save a field
586
	 * @since  2.0.0
587
	 * @param  array  $field_args Array of field arguments
588
	 */
589 5
	public function process_field( $field_args ) {
590
591 5
		switch ( $field_args['type'] ) {
592
593 5
			case 'group':
594 2
				if ( $this->save_group( $field_args ) ) {
595 2
					$this->updated[] = $field_args['id'];
596 2
				}
597
598 2
				break;
599
600 3
			case 'title':
601
				// Don't process title fields
602
				break;
603
604 3
			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...
605
606 3
				$field = $this->get_new_field( $field_args );
607
608 3
				if ( $field->save_field_from_data( $this->data_to_save ) ) {
609 3
					$this->updated[] = $field->id();
610 3
				}
611
612 3
				break;
613 5
		}
614
615 5
	}
616
617 5
	public function pre_process() {
618
		/**
619
		 * Fires before fields have been processed/saved.
620
		 *
621
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
622
		 *
623
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
624
		 * 	Usually `post` (this applies to all post-types).
625
		 *  	Could also be `comment`, `user` or `options-page`.
626
		 *
627
		 * @param array $cmb       This CMB2 object
628
		 * @param int   $object_id The ID of the current object
629
		 */
630 5
		do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
631 5
	}
632
633 1
	public function after_save() {
634 1
		$object_type = $this->object_type();
635 1
		$object_id   = $this->object_id();
636
637
		/**
638
		 * Fires after all fields have been saved.
639
		 *
640
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
641
		 * 	Usually `post` (this applies to all post-types).
642
		 *  	Could also be `comment`, `user` or `options-page`.
643
		 *
644
		 * @param int    $object_id   The ID of the current object
645
		 * @param array  $cmb_id      The current box ID
646
		 * @param string $updated     Array of field ids that were updated.
647
		 *                            Will only include field ids that had values change.
648
		 * @param array  $cmb         This CMB2 object
649
		 */
650 1
		do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
651
652
		/**
653
		 * Fires after all fields have been saved.
654
		 *
655
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
656
		 *
657
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
658
		 * 	Usually `post` (this applies to all post-types).
659
		 *  	Could also be `comment`, `user` or `options-page`.
660
		 *
661
		 * @param int    $object_id   The ID of the current object
662
		 * @param string $updated     Array of field ids that were updated.
663
		 *                            Will only include field ids that had values change.
664
		 * @param array  $cmb         This CMB2 object
665
		 */
666 1
		do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
667 1
	}
668
669
	/**
670
	 * Save a repeatable group
671
	 * @since  1.x.x
672
	 * @param  array  $args Field arguments array
673
	 * @return mixed        Return of CMB2_Field::update_data()
674
	 */
675 2
	public function save_group( $args ) {
676 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
677
			return;
678
		}
679
680 2
		return $this->save_group_field( $this->get_new_field( $args ) );
681
	}
682
683
	/**
684
	 * Save a repeatable group
685
	 * @since  1.x.x
686
	 * @param  CMB2_Field $field_group CMB2_Field group field object
687
	 * @return mixed                   Return of CMB2_Field::update_data()
688
	 */
689 2
	public function save_group_field( $field_group ) {
690 2
		$base_id = $field_group->id();
691
692 2
		if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
693
			return;
694
		}
695
696 2
		$old        = $field_group->get_data();
697
		// Check if group field has sanitization_cb
698 2
		$group_vals = $field_group->sanitization_cb( $this->data_to_save[ $base_id ] );
699 2
		$saved      = array();
700
701 2
		$field_group->index = 0;
702 2
		$field_group->data_to_save = $this->data_to_save;
703
704 2
		foreach ( array_values( $field_group->fields() ) as $field_args ) {
705 2
			if ( 'title' === $field_args['type'] ) {
706
				// Don't process title fields
707
				continue;
708
			}
709
710 2
			$field  = $this->get_new_field( $field_args, $field_group );
711 2
			$sub_id = $field->id( true );
712
713 2
			foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
714
715
				// Get value
716 2
				$new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
717 2
					? $group_vals[ $field_group->index ][ $sub_id ]
718 2
					: false;
719
720
				// Sanitize
721 2
				$new_val = $field->sanitization_cb( $new_val );
722
723 2
				if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
724 2
					if ( $field->args( 'repeatable' ) ) {
725 1
						$_new_val = array();
726 1
						foreach ( $new_val as $group_index => $grouped_data ) {
727
							// Add the supporting data to the $saved array stack
728 1
							$saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
729
							// Reset var to the actual value
730 1
							$_new_val[ $group_index ] = $grouped_data['value'];
731 1
						}
732 1
						$new_val = $_new_val;
733 1
					} else {
734
						// Add the supporting data to the $saved array stack
735 2
						$saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
736
						// Reset var to the actual value
737 2
						$new_val = $new_val['value'];
738
					}
739 2
				}
740
741
				// Get old value
742 2
				$old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
743 2
					? $old[ $field_group->index ][ $sub_id ]
744 2
					: false;
745
746 2
				$is_updated = ( ! CMB2_Utils::isempty( $new_val ) && $new_val !== $old_val );
747 2
				$is_removed = ( CMB2_Utils::isempty( $new_val ) && ! CMB2_Utils::isempty( $old_val ) );
748
749
				// Compare values and add to `$updated` array
750 2
				if ( $is_updated || $is_removed ) {
751 2
					$this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
752 2
				}
753
754
				// 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...
755 2
				$saved[ $field_group->index ][ $sub_id ] = $new_val;
756
757 2
			}
758
759 2
			$saved[ $field_group->index ] = CMB2_Utils::filter_empty( $saved[ $field_group->index ] );
760 2
		}
761
762 2
		$saved = CMB2_Utils::filter_empty( $saved );
763
764 2
		return $field_group->update_data( $saved, true );
765
	}
766
767
	/**
768
	 * Get object id from global space if no id is provided
769
	 * @since  1.0.0
770
	 * @param  integer $object_id Object ID
771
	 * @return integer $object_id Object ID
772
	 */
773 52
	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...
774 52
		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...
775
776 52
		if ( $object_id ) {
777 20
			$this->object_id = $object_id;
778 20
			return $this->object_id;
779
		}
780
781 49
		if ( $this->object_id ) {
782 15
			return $this->object_id;
783
		}
784
785
		// Try to get our object ID from the global space
786 46
		switch ( $this->object_type() ) {
787 46
			case 'user':
788
				$object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id;
789
				$object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
790
				break;
791
792 46
			case 'comment':
793
				$object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id;
794
				$object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
795
				break;
796
797 46
			case 'term':
798
				$object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id;
799
				break;
800
801 46
			default:
802 46
				$object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
803 46
				$object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id;
804 46
				break;
805 46
		}
806
807
		// reset to id or 0
808 46
		$this->object_id = $object_id ? $object_id : 0;
809
810 46
		return $this->object_id;
811
	}
812
813
	/**
814
	 * Sets the $object_type based on metabox settings
815
	 * @since  1.0.0
816
	 * @return string Object type
817
	 */
818 48
	public function mb_object_type() {
819 48
		if ( null !== $this->mb_object_type ) {
820 12
			return $this->mb_object_type;
821
		}
822
823 48
		if ( $this->is_options_page_mb() ) {
824 39
			$this->mb_object_type = 'options-page';
825 39
			return $this->mb_object_type;
826
		}
827
828 47
		$registered_types = $this->box_types();
829
830 47
		$type = '';
831
832
		// if it's an array of one, extract it
833 47
		if ( 1 === count( $registered_types ) ) {
834 47
			$last = end( $registered_types );
835 47
			if ( is_string( $last ) ) {
836 47
				$type = $last;
837 47
			}
838 47
		} elseif ( ( $curr_type = $this->current_object_type() ) && in_array( $curr_type, $registered_types, true ) ) {
839
			$type = $curr_type;
840
		}
841
842
		// Get our object type
843
		switch ( $type ) {
844
845 47
			case 'user':
846 47
			case 'comment':
847 47
			case 'term':
848 1
				$this->mb_object_type = $type;
849 1
				break;
850
851 47
			default:
852 47
				$this->mb_object_type = 'post';
853 47
				break;
854 47
		}
855
856 47
		return $this->mb_object_type;
857
	}
858
859
	/**
860
	 * Gets the box 'object_types' array based on box settings.
861
	 * @since  2.2.3
862
	 * @return array Object types
863
	 */
864 47
	public function box_types() {
865 47
		return CMB2_Utils::ensure_array( $this->prop( 'object_types' ), array( 'post' ) );
866
	}
867
868
	/**
869
	 * Determines if metabox is for an options page
870
	 * @since  1.0.1
871
	 * @return boolean True/False
872
	 */
873 48
	public function is_options_page_mb() {
874 48
		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'] ) );
875
	}
876
877
	/**
878
	 * Returns the object type
879
	 * @since  1.0.0
880
	 * @return string Object type
881
	 */
882 52
	public function object_type( $object_type = '' ) {
883 52
		if ( $object_type ) {
884 18
			$this->object_type = $object_type;
885 18
			return $this->object_type;
886
		}
887
888 49
		if ( $this->object_type ) {
889 19
			return $this->object_type;
890
		}
891
892 47
		$this->object_type = $this->current_object_type();
893
894 47
		return $this->object_type;
895
	}
896
897
	/**
898
	 * Get the object type for the current page, based on the $pagenow global.
899
	 * @since  2.2.2
900
	 * @return string  Page object type name.
901
	 */
902 47 View Code Duplication
	public function current_object_type() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
903 47
		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...
904 47
		$type = 'post';
905
906 47
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
907
			$type = 'user';
908
		}
909
910 47
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
911
			$type = 'comment';
912
		}
913
914 47
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
915
			$type = 'term';
916
		}
917
918 47
		return $type;
919
	}
920
921
	/**
922
	 * Set metabox property.
923
	 * @since  2.2.2
924
	 * @param  string $property Metabox config property to retrieve
925
	 * @param  mixed  $value    Value to set if no value found
926
	 * @return mixed            Metabox config property value or false
927
	 */
928 1
	public function set_prop( $property, $value ) {
929 1
		$this->meta_box[ $property ] = $value;
930
931 1
		return $this->prop( $property );
932
	}
933
934
	/**
935
	 * Get metabox property and optionally set a fallback
936
	 * @since  2.0.0
937
	 * @param  string $property Metabox config property to retrieve
938
	 * @param  mixed  $fallback Fallback value to set if no value found
939
	 * @return mixed            Metabox config property value or false
940
	 */
941 48
	public function prop( $property, $fallback = null ) {
942 48
		if ( array_key_exists( $property, $this->meta_box ) ) {
943 48
			return $this->meta_box[ $property ];
944 1
		} elseif ( $fallback ) {
945 1
			return $this->meta_box[ $property ] = $fallback;
946
		}
947 1
	}
948
949
	/**
950
	 * Get a field object
951
	 * @since  2.0.3
952
	 * @param  string|array|CMB2_Field $field       Metabox field id or field config array or CMB2_Field object
953
	 * @param  CMB2_Field|null         $field_group (optional) CMB2_Field object (group parent)
954
	 * @return CMB2_Field|false                     CMB2_Field object (or false)
955
	 */
956 15
	public function get_field( $field, $field_group = null ) {
957 15
		if ( $field instanceof CMB2_Field ) {
958
			return $field;
959
		}
960
961 15
		$field_id = is_string( $field ) ? $field : $field['id'];
962
963 15
		$parent_field_id = ! empty( $field_group ) ? $field_group->id() : '';
964 15
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
965
966 15
		if ( ! $ids ) {
967
			return false;
968
		}
969
970 15
		list( $field_id, $sub_field_id ) = $ids;
971
972 15
		$index = implode( '', $ids ) . ( $field_group ? $field_group->index : '' );
973 15
		if ( array_key_exists( $index, $this->fields ) ) {
974 4
			return $this->fields[ $index ];
975
		}
976
977 13
		$this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) );
978
979 13
		return $this->fields[ $index ];
980
	}
981
982
	/**
983
	 * Handles determining which type of arguments to pass to CMB2_Field
984
	 * @since  2.0.7
985
	 * @param  mixed           $field_id     Field (or group field) ID
986
	 * @param  mixed           $field_args   Array of field arguments
987
	 * @param  mixed           $sub_field_id Sub field ID (if field_group exists)
988
	 * @param  CMB2_Field|null $field_group  If a sub-field, will be the parent group CMB2_Field object
989
	 * @return array                         Array of CMB2_Field arguments
990
	 */
991 13
	public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) {
992
993
		// Check if group is passed and if fields were added in the old-school fields array
994 13
		if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) {
995
996
			// Update the fields array w/ any modified properties inherited from the group field
997 2
			$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args;
998
999 2
			return $this->get_default_args( $field_args, $field_group );
1000
		}
1001
1002 13
		if ( is_array( $field_args ) ) {
1003 2
			$this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
1004 2
		}
1005
1006 13
		return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
1007
	}
1008
1009
	/**
1010
	 * Get default field arguments specific to this CMB2 object.
1011
	 * @since  2.2.0
1012
	 * @param  array      $field_args  Metabox field config array.
1013
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1014
	 * @return array                   Array of field arguments.
1015
	 */
1016 19 View Code Duplication
	protected function get_default_args( $field_args, $field_group = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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