Completed
Pull Request — trunk (#541)
by Justin
12:21 queued 08:58
created

CMB2::get_new_field()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * CMB2 - The core metabox object
4
 *
5
 * @category  WordPress_Plugin
6
 * @package   CMB2
7
 * @author    WebDevStudios
8
 * @license   GPL-2.0+
9
 * @link      http://webdevstudios.com
10
 *
11
 * @property-read string $cmb_id
12
 * @property-read array $meta_box
13
 * @property-read array $updated
14
 * @property-read bool  $has_columns
15
 * @property-read array $tax_metaboxes_to_remove
16
 */
17
class CMB2 extends CMB2_Base {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

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

namespace YourVendor;

class YourClass { }

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

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