Completed
Push — trunk ( da902c...dba577 )
by Justin
05:50
created

CMB2::update_field_property()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

namespace YourVendor;

class YourClass { }

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

Loading history...
16
17
	/**
18
	 * Current CMB2 instance ID
19
	 * @var   string
20
	 * @since 2.0.0
21
	 */
22
	protected $cmb_id = '';
23
24
	/**
25
	 * Metabox Config array
26
	 * @var   array
27
	 * @since 0.9.0
28
	 */
29
	protected $meta_box = array();
30
31
	/**
32
	 * Object ID for metabox meta retrieving/saving
33
	 * @var   mixed
34
	 * @since 1.0.0
35
	 */
36
	protected $object_id = 0;
37
38
	/**
39
	 * Type of object being saved. (e.g., post, user, or comment)
40
	 * @var   string
41
	 * @since 1.0.0
42
	 */
43
	protected $object_type = 'post';
44
45
	/**
46
	 * Type of object registered for metabox. (e.g., post, user, or comment)
47
	 * @var   string
48
	 * @since 1.0.0
49
	 */
50
	protected $mb_object_type = null;
51
52
	/**
53
	 * List of fields that are changed/updated on save
54
	 * @var   array
55
	 * @since 1.1.0
56
	 */
57
	protected $updated = array();
58
59
	/**
60
	 * Metabox Defaults
61
	 * @var   array
62
	 * @since 1.0.1
63
	 */
64
	protected $mb_defaults = array(
65
		'id'               => '',
66
		'title'            => '',
67
		'type'             => '',
68
		'object_types'     => array(), // Post type
69
		'context'          => 'normal',
70
		'priority'         => 'high',
71
		'show_names'       => true, // Show field names on the left
72
		'show_on_cb'       => null, // Callback to determine if metabox should display.
73
		'show_on'          => array(), // Post IDs or page templates to display this metabox. overrides 'show_on_cb'
74
		'cmb_styles'       => true, // Include CMB2 stylesheet
75
		'enqueue_js'       => true, // Include CMB2 JS
76
		'fields'           => array(),
77
		'hookup'           => true,
78
		'save_fields'      => true, // Will not save during hookup if false
79
		'closed'           => false, // Default to metabox being closed?
80
		'taxonomies'       => array(),
81
		'new_user_section' => 'add-new-user', // or 'add-existing-user'
82
		'new_term_section' => true,
83
	);
84
85
	/**
86
	 * Metabox field objects
87
	 * @var   array
88
	 * @since 2.0.3
89
	 */
90
	protected $fields = array();
91
92
	/**
93
	 * An array of hidden fields to output at the end of the form
94
	 * @var   array
95
	 * @since 2.0.0
96
	 */
97
	protected $hidden_fields = array();
98
99
	/**
100
	 * Array of key => value data for saving. Likely $_POST data.
101
	 * @var   array
102
	 * @since 2.0.0
103
	 */
104
	public $data_to_save = array();
105
106
	/**
107
	 * Array of key => value data for saving. Likely $_POST data.
108
	 * @var   string
109
	 * @since 2.0.0
110
	 */
111
	protected $generated_nonce = '';
112
113
	/**
114
	 * Get started
115
	 * @since 0.4.0
116
	 * @param array   $meta_box  Metabox config array
117
	 * @param integer $object_id Optional object id
118
	 */
119 44
	public function __construct( $meta_box, $object_id = 0 ) {
120
121 44
		if ( empty( $meta_box['id'] ) ) {
122 1
			wp_die( __( 'Metabox configuration is required to have an ID parameter', 'cmb2' ) );
123
		}
124
125 44
		$this->meta_box = wp_parse_args( $meta_box, $this->mb_defaults );
126 44
		$this->meta_box['fields'] = array();
127
128 44
		$this->object_id( $object_id );
129 44
		$this->mb_object_type();
130 44
		$this->cmb_id = $meta_box['id'];
131
132 44
		if ( ! empty( $meta_box['fields'] ) && is_array( $meta_box['fields'] ) ) {
133 41
			$this->add_fields( $meta_box['fields'] );
134 41
		}
135
136 44
		CMB2_Boxes::add( $this );
137
138
		/**
139
		 * Hook during initiation of CMB2 object
140
		 *
141
		 * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id.
142
		 *
143
		 * @param array $cmb This CMB2 object
144
		 */
145 44
		do_action( "cmb2_init_{$this->cmb_id}", $this );
146 44
	}
147
148
	/**
149
	 * Loops through and displays fields
150
	 * @since 1.0.0
151
	 * @param int    $object_id   Object ID
152
	 * @param string $object_type Type of object being saved. (e.g., post, user, or comment)
153
	 */
154 1
	public function show_form( $object_id = 0, $object_type = '' ) {
155 1
		$this->render_form_open( $object_id, $object_type );
156
157 1
		foreach ( $this->prop( 'fields' ) as $field_args ) {
158 1
			$this->render_field( $field_args );
159 1
		}
160
161 1
		$this->render_form_close( $object_id, $object_type );
162 1
	}
163
164
	/**
165
	 * Outputs the opening form markup and runs corresponding hooks:
166
	 * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}"
167
	 * @since  2.2.0
168
	 * @param  integer $object_id   Object ID
169
	 * @param  string  $object_type Object type
170
	 * @return void
171
	 */
172 1
	public function render_form_open( $object_id = 0, $object_type = '' ) {
173 1
		$object_type = $this->object_type( $object_type );
174 1
		$object_id = $this->object_id( $object_id );
175
176 1
		$this->nonce_field();
177
178 1
		echo "\n<!-- Begin CMB2 Fields -->\n";
179
180
		/**
181
		 * Hook before form table begins
182
		 *
183
		 * @param array  $cmb_id      The current box ID
184
		 * @param int    $object_id   The ID of the current object
185
		 * @param string $object_type The type of object you are working with.
186
		 *	                           Usually `post` (this applies to all post-types).
187
		 *	                           Could also be `comment`, `user` or `options-page`.
188
		 * @param array  $cmb         This CMB2 object
189
		 */
190 1
		do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this );
191
192
		/**
193
		 * Hook before form table begins
194
		 *
195
		 * The first dynamic portion of the hook name, $object_type, is the type of object
196
		 * you are working with. Usually `post` (this applies to all post-types).
197
		 * Could also be `comment`, `user` or `options-page`.
198
		 *
199
		 * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
200
		 *
201
		 * @param array  $cmb_id      The current box ID
202
		 * @param int    $object_id   The ID of the current object
203
		 * @param array  $cmb         This CMB2 object
204
		 */
205 1
		do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
206
207 1
		echo '<div class="', $this->box_classes(), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">';
208
209 1
	}
210
211
	/**
212
	 * Defines the classes for the CMB2 form/wrap.
213
	 *
214
	 * @since  2.0.0
215
	 * @return string Space concatenated list of classes
216
	 */
217 1
	public function box_classes() {
218
219 1
		$classes = array( 'cmb2-wrap', 'form-table' );
220
221 1
		$cb = $this->prop( 'classes_cb' );
222
223
		// Use the callback to fetch classes.
224 1
		if ( is_callable( $cb ) && ( $added_classes = call_user_func( $cb, $this ) ) ) {
225 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
226 1
			$classes = array_merge( $classes, $added_classes );
227 1
		}
228
229 1
		if ( $added_classes = $this->prop( 'classes' ) ) {
230 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
231 1
			$classes = array_merge( $classes, $added_classes );
232 1
		}
233
234
		/**
235
		 * Globally filter box wrap classes
236
		 *
237
		 * @since 2.2.2
238
		 *
239
		 * @param string $classes Array of classes for the cmb2-wrap.
240
		 * @param CMB2   $cmb     This CMB2 object.
241
		 */
242 1
		$classes = apply_filters( 'cmb2_wrap_classes', $classes, $this );
243
244
		// Clean up.
245 1
		$classes = array_map( 'strip_tags', array_filter( $classes ) );
246
247
		// Make a string.
248 1
		return implode( ' ', $classes );
249
	}
250
251
	/**
252
	 * Outputs the closing form markup and runs corresponding hooks:
253
	 * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}"
254
	 * @since  2.2.0
255
	 * @param  integer $object_id   Object ID
256
	 * @param  string  $object_type Object type
257
	 * @return void
258
	 */
259 1
	public function render_form_close( $object_id = 0, $object_type = '' ) {
260 1
		$object_type = $this->object_type( $object_type );
261 1
		$object_id = $this->object_id( $object_id );
262
263 1
		echo '</div></div>';
264
265 1
		$this->render_hidden_fields();
266
267
		/**
268
		 * Hook after form form has been rendered
269
		 *
270
		 * @param array  $cmb_id      The current box ID
271
		 * @param int    $object_id   The ID of the current object
272
		 * @param string $object_type The type of object you are working with.
273
		 *	                           Usually `post` (this applies to all post-types).
274
		 *	                           Could also be `comment`, `user` or `options-page`.
275
		 * @param array  $cmb         This CMB2 object
276
		 */
277 1
		do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this );
278
279
		/**
280
		 * Hook after form form has been rendered
281
		 *
282
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
283
		 *
284
		 * The first dynamic portion of the hook name, $object_type, is the type of object
285
		 * you are working with. Usually `post` (this applies to all post-types).
286
		 * Could also be `comment`, `user` or `options-page`.
287
		 *
288
		 * @param int    $object_id   The ID of the current object
289
		 * @param array  $cmb         This CMB2 object
290
		 */
291 1
		do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
292
293 1
		echo "\n<!-- End CMB2 Fields -->\n";
294
295 1
	}
296
297
	/**
298
	 * Renders a field based on the field type
299
	 * @since  2.2.0
300
	 * @param  array $field_args A field configuration array.
301
	 * @return mixed CMB2_Field object if successful.
302
	 */
303 1
	public function render_field( $field_args ) {
304 1
		$field_args['context'] = $this->prop( 'context' );
305
306 1
		if ( 'group' == $field_args['type'] ) {
307
308
			if ( ! isset( $field_args['show_names'] ) ) {
309
				$field_args['show_names'] = $this->prop( 'show_names' );
310
			}
311
			$field = $this->render_group( $field_args );
312
313 1
		} elseif ( 'hidden' == $field_args['type'] && $this->get_field( $field_args )->should_show() ) {
314
			// Save rendering for after the metabox
315
			$field = $this->add_hidden_field( $field_args );
316
317
		} else {
318
319 1
			$field_args['show_names'] = $this->prop( 'show_names' );
320
321
			// Render default fields
322 1
			$field = $this->get_field( $field_args )->render_field();
323
		}
324
325 1
		return $field;
326
	}
327
328
	/**
329
	 * Render a repeatable group.
330
	 * @param array $args Array of field arguments for a group field parent.
331
	 * @return CMB2_Field|null Group field object.
332
	 */
333 2
	public function render_group( $args ) {
334
335 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
336
			return;
337
		}
338
339 2
		$field_group = $this->get_field( $args );
340
341
		// If field is requesting to be conditionally shown
342 2
		if ( ! $field_group || ! $field_group->should_show() ) {
343
			return;
344
		}
345
346 2
		$desc            = $field_group->args( 'description' );
347 2
		$label           = $field_group->args( 'name' );
348 2
		$sortable        = $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
349 2
		$repeat_class    = $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
350 2
		$group_val       = (array) $field_group->value();
351 2
		$nrows           = count( $group_val );
352 2
		$remove_disabled = $nrows <= 1 ? 'disabled="disabled" ' : '';
353 2
		$field_group->index = 0;
354
355 2
		$field_group->peform_param_callback( 'before_group' );
356
357 2
		echo '<div class="cmb-row cmb-repeat-group-wrap ', $field_group->row_classes(), '" data-fieldtype="group"><div class="cmb-td"><div id="', $field_group->id(), '_repeat" class="cmb-nested cmb-field-list cmb-repeatable-group', $sortable, $repeat_class, '" style="width:100%;">';
358
359 2
		if ( $desc || $label ) {
360 2
			$class = $desc ? ' cmb-group-description' : '';
361 2
			echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
362 2
				if ( $label ) {
363 2
					echo '<h2 class="cmb-group-name">', $label, '</h2>';
364 2
				}
365 2
				if ( $desc ) {
366 1
					echo '<p class="cmb2-metabox-description">', $desc, '</p>';
367 1
				}
368 2
			echo '</div></div>';
369 2
		}
370
371 2
		if ( ! empty( $group_val ) ) {
372
373
			foreach ( $group_val as $group_key => $field_id ) {
374
				$this->render_group_row( $field_group, $remove_disabled );
375
				$field_group->index++;
376
			}
377
		} else {
378 2
			$this->render_group_row( $field_group, $remove_disabled );
379
		}
380
381 2
		if ( $field_group->args( 'repeatable' ) ) {
382 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>';
383 1
		}
384
385 2
		echo '</div></div></div>';
386
387 2
		$field_group->peform_param_callback( 'after_group' );
388
389 2
		return $field_group;
390
	}
391
392
	/**
393
	 * Render a repeatable group row
394
	 * @since  1.0.2
395
	 * @param  CMB2_Field $field_group  CMB2_Field group field object
396
	 * @param  string  $remove_disabled Attribute string to disable the remove button
397
	 */
398 2
	public function render_group_row( $field_group, $remove_disabled ) {
399
400 2
		$field_group->peform_param_callback( 'before_group_row' );
401 2
		$closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
402
403
		echo '
404 2
		<div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
405
406 2
			if ( $field_group->args( 'repeatable' ) ) {
407 1
				echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row"></button>';
408 1
			}
409
410
			echo '
411 2
			<div class="cmbhandle" title="' , __( 'Click to toggle', 'cmb2' ), '"><br></div>
412 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...
413
414
			<div class="inside cmb-td cmb-nested cmb-field-list">';
415
				// Loop and render repeatable group fields
416 2
				foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) {
417 2
					if ( 'hidden' == $field_args['type'] ) {
418
419
						// Save rendering for after the metabox
420
						$this->add_hidden_field( $field_args, $field_group );
421
422
					} else {
423
424 2
						$field_args['show_names'] = $field_group->args( 'show_names' );
425 2
						$field_args['context']    = $field_group->args( 'context' );
426
427 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...
428
					}
429 2
				}
430 2
				if ( $field_group->args( 'repeatable' ) ) {
431
					echo '
432
					<div class="cmb-row cmb-remove-field-row">
433
						<div class="cmb-remove-row">
434 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>
435
						</div>
436
					</div>
437
					';
438 1
				}
439
			echo '
440
			</div>
441
		</div>
442 2
		';
443
444 2
		$field_group->peform_param_callback( 'after_group_row' );
445 2
	}
446
447
	/**
448
	 * Add a hidden field to the list of hidden fields to be rendered later
449
	 * @since 2.0.0
450
	 * @param array  $field_args Array of field arguments to be passed to CMB2_Field
451
	 */
452
	public function add_hidden_field( $field_args, $field_group = null ) {
453
		if ( isset( $field_args['field_args'] ) ) {
454
			// For back-compatibility.
455
			$field = new CMB2_Field( $field_args );
456
		} else {
457
			$field = $this->get_new_field( $field_args, $field_group );
458
		}
459
460
		$this->hidden_fields[] = new CMB2_Types( $field );
461
462
		return $field;
463
	}
464
465
	/**
466
	 * Loop through and output hidden fields
467
	 * @since  2.0.0
468
	 */
469 1
	public function render_hidden_fields() {
470 1
		if ( ! empty( $this->hidden_fields ) ) {
471
			foreach ( $this->hidden_fields as $hidden ) {
472
				$hidden->render();
473
			}
474
		}
475 1
	}
476
477
	/**
478
	 * Returns array of sanitized field values (without saving them)
479
	 * @since  2.0.3
480
	 * @param  array  $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
481
	 */
482 2
	public function get_sanitized_values( array $data_to_sanitize ) {
483 2
		$this->data_to_save = $data_to_sanitize;
484 2
		$stored_id          = $this->object_id();
485
486
		// We do this So CMB will sanitize our data for us, but not save it
487 2
		$this->object_id( '_' );
488
489
		// Ensure temp. data store is empty
490 2
		cmb2_options( 0 )->set();
491
492
		// Process/save fields
493 2
		$this->process_fields();
494
495
		// Get data from temp. data store
496 2
		$sanitized_values = cmb2_options( 0 )->get_options();
497
498
		// Empty out temp. data store again
499 2
		cmb2_options( 0 )->set();
500
501
		// Reset the object id
502 2
		$this->object_id( $stored_id );
503
504 2
		return $sanitized_values;
505
	}
506
507
	/**
508
	 * Loops through and saves field data
509
	 * @since  1.0.0
510
	 * @param  int    $object_id    Object ID
511
	 * @param  string $object_type  Type of object being saved. (e.g., post, user, or comment)
512
	 * @param  array  $data_to_save Array of key => value data for saving. Likely $_POST data.
513
	 */
514 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...
515
516
		// Fall-back to $_POST data
517 1
		$this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
518 1
		$object_id = $this->object_id( $object_id );
519 1
		$object_type = $this->object_type( $object_type );
520
521 1
		$this->process_fields();
522
523
		// If options page, save the updated options
524 1
		if ( 'options-page' == $object_type ) {
525 1
			cmb2_options( $object_id )->set();
526 1
		}
527
528 1
		$this->after_save();
529 1
	}
530
531
	/**
532
	 * Process and save form fields
533
	 * @since  2.0.0
534
	 */
535 3
	public function process_fields() {
536
537 3
		$this->pre_process();
538
539
		// Remove the show_on properties so saving works
540 3
		$this->prop( 'show_on', array() );
541
542
		// save field ids of those that are updated
543 3
		$this->updated = array();
544
545 3
		foreach ( $this->prop( 'fields' ) as $field_args ) {
546 3
			$this->process_field( $field_args );
547 3
		}
548 3
	}
549
550
	/**
551
	 * Process and save a field
552
	 * @since  2.0.0
553
	 * @param  array  $field_args Array of field arguments
554
	 */
555 3
	public function process_field( $field_args ) {
556
557 3
		switch ( $field_args['type'] ) {
558
559 3
			case 'group':
560 1
				if ( $this->save_group( $field_args ) ) {
561 1
					$this->updated[] = $field_args['id'];
562 1
				}
563
564 1
				break;
565
566 2
			case 'title':
567
				// Don't process title fields
568
				break;
569
570 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...
571
572 2
				$field = $this->get_new_field( $field_args );
573
574 2
				if ( $field->save_field_from_data( $this->data_to_save ) ) {
575 2
					$this->updated[] = $field->id();
576 2
				}
577
578 2
				break;
579 3
		}
580
581 3
	}
582
583 3
	public function pre_process() {
584
		/**
585
		 * Fires before fields have been processed/saved.
586
		 *
587
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
588
		 *
589
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
590
		 * 	Usually `post` (this applies to all post-types).
591
		 *  	Could also be `comment`, `user` or `options-page`.
592
		 *
593
		 * @param array $cmb       This CMB2 object
594
		 * @param int   $object_id The ID of the current object
595
		 */
596 3
		do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
597 3
	}
598
599 1
	public function after_save() {
600 1
		$object_type = $this->object_type();
601 1
		$object_id   = $this->object_id();
602
603
		/**
604
		 * Fires after all fields have been saved.
605
		 *
606
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
607
		 * 	Usually `post` (this applies to all post-types).
608
		 *  	Could also be `comment`, `user` or `options-page`.
609
		 *
610
		 * @param int    $object_id   The ID of the current object
611
		 * @param array  $cmb_id      The current box ID
612
		 * @param string $updated     Array of field ids that were updated.
613
		 *                            Will only include field ids that had values change.
614
		 * @param array  $cmb         This CMB2 object
615
		 */
616 1
		do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
617
618
		/**
619
		 * Fires after all fields have been 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 int    $object_id   The ID of the current object
628
		 * @param string $updated     Array of field ids that were updated.
629
		 *                            Will only include field ids that had values change.
630
		 * @param array  $cmb         This CMB2 object
631
		 */
632 1
		do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
633 1
	}
634
635
	/**
636
	 * Save a repeatable group
637
	 * @since  1.x.x
638
	 * @param  array  $args Field arguments array
639
	 * @return mixed        Return of CMB2_Field::update_data()
640
	 */
641 1
	public function save_group( $args ) {
642 1
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
643
			return;
644
		}
645
646 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...
647
	}
648
649
	/**
650
	 * Save a repeatable group
651
	 * @since  1.x.x
652
	 * @param  array $field_group CMB2_Field group field object
653
	 * @return mixed              Return of CMB2_Field::update_data()
654
	 */
655 1
	public function save_group_field( $field_group ) {
656 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...
657
658 1
		if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
659
			return;
660
		}
661
662 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...
663
		// Check if group field has sanitization_cb
664 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...
665 1
		$saved      = array();
666
667 1
		$field_group->index = 0;
668 1
		$field_group->data_to_save = $this->data_to_save;
669
670 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...
671
672 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...
673 1
			$sub_id = $field->id( true );
674
675 1
			foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
676
677
				// Get value
678 1
				$new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
679 1
					? $group_vals[ $field_group->index ][ $sub_id ]
680 1
					: false;
681
682
				// Sanitize
683 1
				$new_val = $field->sanitization_cb( $new_val );
684
685 1
				if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
686 1
					if ( $field->args( 'repeatable' ) ) {
687 1
						$_new_val = array();
688 1
						foreach ( $new_val as $group_index => $grouped_data ) {
689
							// Add the supporting data to the $saved array stack
690 1
							$saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
691
							// Reset var to the actual value
692 1
							$_new_val[ $group_index ] = $grouped_data['value'];
693 1
						}
694 1
						$new_val = $_new_val;
695 1
					} else {
696
						// Add the supporting data to the $saved array stack
697 1
						$saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
698
						// Reset var to the actual value
699 1
						$new_val = $new_val['value'];
700
					}
701 1
				}
702
703
				// Get old value
704 1
				$old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
705 1
					? $old[ $field_group->index ][ $sub_id ]
706 1
					: false;
707
708 1
				$is_updated = ( ! cmb2_utils()->isempty( $new_val ) && $new_val !== $old_val );
709 1
				$is_removed = ( cmb2_utils()->isempty( $new_val ) && ! cmb2_utils()->isempty( $old_val ) );
710
711
				// Compare values and add to `$updated` array
712 1
				if ( $is_updated || $is_removed ) {
713 1
					$this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
714 1
				}
715
716
				// 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...
717 1
				$saved[ $field_group->index ][ $sub_id ] = $new_val;
718
719 1
			}
720
721 1
			$saved[ $field_group->index ] = cmb2_utils()->filter_empty( $saved[ $field_group->index ] );
722 1
		}
723
724 1
		$saved = cmb2_utils()->filter_empty( $saved );
725
726 1
		return $field_group->update_data( $saved, true );
0 ignored issues
show
Bug introduced by
The method update_data cannot be called on $field_group (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
727
	}
728
729
	/**
730
	 * Get object id from global space if no id is provided
731
	 * @since  1.0.0
732
	 * @param  integer $object_id Object ID
733
	 * @return integer $object_id Object ID
734
	 */
735 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...
736 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...
737
738 48
		if ( $object_id ) {
739 18
			$this->object_id = $object_id;
740 18
			return $this->object_id;
741
		}
742
743 45
		if ( $this->object_id ) {
744 13
			return $this->object_id;
745
		}
746
747
		// Try to get our object ID from the global space
748 42
		switch ( $this->object_type() ) {
749 42
			case 'user':
750
				$object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id;
751
				$object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
752
				break;
753
754 42
			case 'comment':
755
				$object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id;
756
				$object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
757
				break;
758
759 42
			case 'term':
760
				$object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id;
761
				break;
762
763 42
			default:
764 42
				$object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
765 42
				$object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id;
766 42
				break;
767 42
		}
768
769
		// reset to id or 0
770 42
		$this->object_id = $object_id ? $object_id : 0;
771
772 42
		return $this->object_id;
773
	}
774
775
	/**
776
	 * Sets the $object_type based on metabox settings
777
	 * @since  1.0.0
778
	 * @return string Object type
779
	 */
780 44
	public function mb_object_type() {
781 44
		if ( null !== $this->mb_object_type ) {
782 12
			return $this->mb_object_type;
783
		}
784
785 44
		if ( $this->is_options_page_mb() ) {
786 36
			$this->mb_object_type = 'options-page';
787 36
			return $this->mb_object_type;
788
		}
789
790 43
		$registered_types = $this->prop( 'object_types' );
791
792 43
		if ( ! $registered_types ) {
793 40
			$this->mb_object_type = 'post';
794 40
			return $this->mb_object_type;
795
		}
796
797 4
		$type = false;
798
799
		// check if 'object_types' is a string
800 4
		if ( is_string( $registered_types ) ) {
801
			$type = $registered_types;
802
		}
803
804
		// if it's an array of one, extract it
805 4
		elseif ( is_array( $registered_types ) && 1 === count( $registered_types ) ) {
806 4
			$last = end( $registered_types );
807 4
			if ( is_string( $last ) ) {
808 4
				$type = $last;
809 4
			}
810 4
		} elseif ( is_array( $registered_types ) ) {
811
			$page_type = $this->current_object_type();
812
813
			if ( in_array( $page_type, $registered_types, true ) ) {
814
				$type = $page_type;
815
			}
816
		}
817
818
		// Get our object type
819
		switch ( $type ) {
820
821 4
			case 'user':
822 4
			case 'comment':
823 4
			case 'term':
824 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...
825 1
				break;
826
827 3
			default:
828 3
				$this->mb_object_type = 'post';
829 3
				break;
830 3
		}
831
832 4
		return $this->mb_object_type;
833
	}
834
835
	/**
836
	 * Determines if metabox is for an options page
837
	 * @since  1.0.1
838
	 * @return boolean True/False
839
	 */
840 44
	public function is_options_page_mb() {
841 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'] ) );
842
	}
843
844
	/**
845
	 * Returns the object type
846
	 * @since  1.0.0
847
	 * @return string Object type
848
	 */
849 48
	public function object_type( $object_type = '' ) {
850 48
		if ( $object_type ) {
851 18
			$this->object_type = $object_type;
852 18
			return $this->object_type;
853
		}
854
855 45
		if ( $this->object_type ) {
856 45
			return $this->object_type;
857
		}
858
859
		$this->object_type = $this->current_object_type();
860
861
		return $this->object_type;
862
	}
863
864
	/**
865
	 * Get the object type for the current page, based on the $pagenow global.
866
	 * @since  2.2.2
867
	 * @return string  Page object type name.
868
	 */
869
	public function current_object_type() {
870
		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...
871
		$type = 'post';
872
873
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
874
			$type = 'user';
875
		}
876
877
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
878
			$type = 'comment';
879
		}
880
881
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
882
			$type = 'term';
883
		}
884
885
		return $type;
886
	}
887
888
	/**
889
	 * Set metabox property.
890
	 * @since  2.2.0
891
	 * @param  string $property Metabox config property to retrieve
892
	 * @param  mixed  $value    Value to set if no value found
893
	 * @return mixed            Metabox config property value or false
894
	 */
895 1
	public function set_prop( $property, $value ) {
896 1
		$this->meta_box[ $property ] = $value;
897
898 1
		return $this->prop( $property );
899
	}
900
901
	/**
902
	 * Get metabox property and optionally set a fallback
903
	 * @since  2.0.0
904
	 * @param  string $property Metabox config property to retrieve
905
	 * @param  mixed  $fallback Fallback value to set if no value found
906
	 * @return mixed            Metabox config property value or false
907
	 */
908 44
	public function prop( $property, $fallback = null ) {
909 44
		if ( array_key_exists( $property, $this->meta_box ) ) {
910 44
			return $this->meta_box[ $property ];
911 1
		} elseif ( $fallback ) {
912 1
			return $this->meta_box[ $property ] = $fallback;
913
		}
914
	}
915
916
	/**
917
	 * Get a field object
918
	 * @since  2.0.3
919
	 * @param  string|array|CMB2_Field $field       Metabox field id or field config array or CMB2_Field object
920
	 * @param  CMB2_Field              $field_group (optional) CMB2_Field object (group parent)
921
	 * @return CMB2_Field|false CMB2_Field object (or false)
922
	 */
923 15
	public function get_field( $field, $field_group = null ) {
924 15
		if ( is_a( $field, 'CMB2_Field' ) ) {
925
			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...
926
		}
927
928 15
		$field_id = is_string( $field ) ? $field : $field['id'];
929
930 15
		$parent_field_id = ! empty( $field_group ) ? $field_group->id() : '';
931 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...
932
933 15
		if ( ! $ids ) {
934
			return false;
935
		}
936
937 15
		list( $field_id, $sub_field_id ) = $ids;
938
939 15
		$index = implode( '', $ids ) . ( $field_group ? $field_group->index : '' );
940 15
		if ( array_key_exists( $index, $this->fields ) ) {
941 3
			return $this->fields[ $index ];
942
		}
943
944 13
		$this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) );
945
946 13
		return $this->fields[ $index ];
947
	}
948
949
	/**
950
	 * Handles determining which type of arguments to pass to CMB2_Field
951
	 * @since  2.0.7
952
	 * @param  mixed  $field_id     Field (or group field) ID
953
	 * @param  mixed  $field_args   Array of field arguments
954
	 * @param  mixed  $sub_field_id Sub field ID (if field_group exists)
955
	 * @param  mixed  $field_group  If a sub-field, will be the parent group CMB2_Field object
956
	 * @return array                Array of CMB2_Field arguments
957
	 */
958 13
	public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) {
959
960
		// Check if group is passed and if fields were added in the old-school fields array
961 13
		if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) {
962
963
			// Update the fields array w/ any modified properties inherited from the group field
964 2
			$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args;
965
966 2
			return $this->get_default_args( $field_args, $field_group );
967
		}
968
969 13
		if ( is_array( $field_args ) ) {
970 2
			$this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
971 2
		}
972
973 13
		return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
974
	}
975
976
	/**
977
	 * Get default field arguments specific to this CMB2 object.
978
	 * @since  2.2.0
979
	 * @param  array      $field_args  Metabox field config array.
980
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
981
	 * @return array                   Array of field arguments.
982
	 */
983 17
	protected function get_default_args( $field_args, $field_group = null ) {
984 17
		if ( $field_group ) {
985
			$args = array(
986 3
				'field_args'  => $field_args,
987 3
				'group_field' => $field_group,
988 3
			);
989 3
		} else {
990
			$args = array(
991 17
				'field_args'  => $field_args,
992 17
				'object_type' => $this->object_type(),
993 17
				'object_id'   => $this->object_id(),
994 17
				'cmb_id'      => $this->cmb_id,
995 17
			);
996
		}
997
998 17
		return $args;
999
	}
1000
1001
	/**
1002
	 * Get a new field object specific to this CMB2 object.
1003
	 * @since  2.2.0
1004
	 * @param  array      $field_args  Metabox field config array.
1005
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1006
	 * @return CMB2_Field CMB2_Field object
1007
	 */
1008 5
	protected function get_new_field( $field_args, $field_group = null ) {
1009 5
		return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) );
1010
	}
1011
1012
	/**
1013
	 * When fields are added in the old-school way, intitate them as they should be
1014
	 * @since 2.1.0
1015
	 * @param array $fields          Array of fields to add
1016
	 * @param mixed $parent_field_id Parent field id or null
1017
	 */
1018 41
	protected function add_fields( $fields, $parent_field_id = null ) {
1019 41
		foreach ( $fields as $field ) {
1020
1021 41
			$sub_fields = false;
1022 41
			if ( array_key_exists( 'fields', $field ) ) {
1023
				$sub_fields = $field['fields'];
1024
				unset( $field['fields'] );
1025
			}
1026
1027
			$field_id = $parent_field_id
1028 41
				? $this->add_group_field( $parent_field_id, $field )
1029 41
				: $this->add_field( $field );
1030
1031 41
			if ( $sub_fields ) {
1032
				$this->add_fields( $sub_fields, $field_id );
1033
			}
1034 41
		}
1035 41
	}
1036
1037
	/**
1038
	 * Add a field to the metabox
1039
	 * @since  2.0.0
1040
	 * @param  array  $field           Metabox field config array
1041
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1042
	 * @return mixed                   Field id or false
1043
	 */
1044 43
	public function add_field( array $field, $position = 0 ) {
1045 43
		if ( ! is_array( $field ) || ! array_key_exists( 'id', $field ) ) {
1046
			return false;
1047
		}
1048
1049 43
		if ( 'oembed' === $field['type'] ) {
1050
			// Initiate oembed Ajax hooks
1051 1
			cmb2_ajax();
1052 1
		}
1053
1054 43
		$this->_add_field_to_array(
1055 43
			$field,
1056 43
			$this->meta_box['fields'],
1057
			$position
1058 43
		);
1059
1060 43
		return $field['id'];
1061
	}
1062
1063
	/**
1064
	 * Add a field to a group
1065
	 * @since  2.0.0
1066
	 * @param  string $parent_field_id The field id of the group field to add the field
1067
	 * @param  array  $field           Metabox field config array
1068
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1069
	 * @return mixed                   Array of parent/field ids or false
1070
	 */
1071 3
	public function add_group_field( $parent_field_id, array $field, $position = 0 ) {
1072 3
		if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) {
1073
			return false;
1074
		}
1075
1076 3
		$parent_field = $this->meta_box['fields'][ $parent_field_id ];
1077
1078 3
		if ( 'group' !== $parent_field['type'] ) {
1079
			return false;
1080
		}
1081
1082 3
		if ( ! isset( $parent_field['fields'] ) ) {
1083 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'] = array();
1084 3
		}
1085
1086 3
		$this->_add_field_to_array(
1087 3
			$field,
1088 3
			$this->meta_box['fields'][ $parent_field_id ]['fields'],
1089
			$position
1090 3
		);
1091
1092 3
		return array( $parent_field_id, $field['id'] );
1093
	}
1094
1095
	/**
1096
	 * Add a field array to a fields array in desired position
1097
	 * @since 2.0.2
1098
	 * @param array   $field    Metabox field config array
1099
	 * @param array   &$fields  Array (passed by reference) to append the field (array) to
1100
	 * @param integer $position Optionally specify a position in the array to be inserted
1101
	 */
1102 43
	protected function _add_field_to_array( $field, &$fields, $position = 0 ) {
1103 43
		if ( $position ) {
1104 1
			cmb2_utils()->array_insert( $fields, array( $field['id'] => $field ), $position );
1105 1
		} else {
1106 43
			$fields[ $field['id'] ] = $field;
1107
		}
1108 43
	}
1109
1110
	/**
1111
	 * Remove a field from the metabox
1112
	 * @since 2.0.0
1113
	 * @param  string $field_id        The field id of the field to remove
1114
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1115
	 * @return bool                    True if field was removed
1116
	 */
1117 2
	public function remove_field( $field_id, $parent_field_id = '' ) {
1118 2
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1119
1120 2
		if ( ! $ids ) {
1121
			return false;
1122
		}
1123
1124 2
		list( $field_id, $sub_field_id ) = $ids;
1125
1126 2
		unset( $this->fields[ implode( '', $ids ) ] );
1127
1128 2
		if ( ! $sub_field_id ) {
1129 1
			unset( $this->meta_box['fields'][ $field_id ] );
1130 1
			return true;
1131
		}
1132
1133 1
		if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) {
1134 1
			unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] );
1135 1
		}
1136 1
		if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) {
1137 1
			unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] );
1138 1
		}
1139 1
		return true;
1140
	}
1141
1142
	/**
1143
	 * Update or add a property to a field
1144
	 * @since  2.0.0
1145
	 * @param  string $field_id        Field id
1146
	 * @param  string $property        Field property to set/update
1147
	 * @param  mixed  $value           Value to set the field property
1148
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1149
	 * @return mixed                   Field id. Strict compare to false, as success can return a falsey value (like 0)
1150
	 */
1151 4
	public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) {
1152 4
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1153
1154 4
		if ( ! $ids ) {
1155 2
			return false;
1156
		}
1157
1158 2
		list( $field_id, $sub_field_id ) = $ids;
1159
1160 2
		if ( ! $sub_field_id ) {
1161 2
			$this->meta_box['fields'][ $field_id ][ $property ] = $value;
1162 2
			return $field_id;
1163
		}
1164
1165
		$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value;
1166
		return $field_id;
1167
	}
1168
1169
	/**
1170
	 * Check if field ids match a field and return the index/field id
1171
	 * @since  2.0.2
1172
	 * @param  string  $field_id        Field id
1173
	 * @param  string  $parent_field_id (optional) Parent field id
1174
	 * @return mixed                    Array of field/parent ids, or false
1175
	 */
1176 19
	public function get_field_ids( $field_id, $parent_field_id = '' ) {
1177 19
		$sub_field_id = $parent_field_id ? $field_id : '';
1178 19
		$field_id     = $parent_field_id ? $parent_field_id : $field_id;
1179 19
		$fields       =& $this->meta_box['fields'];
1180
1181 19
		if ( ! array_key_exists( $field_id, $fields ) ) {
1182 2
			$field_id = $this->search_old_school_array( $field_id, $fields );
1183 2
		}
1184
1185 19
		if ( false === $field_id ) {
1186 2
			return false;
1187
		}
1188
1189 17
		if ( ! $sub_field_id ) {
1190 17
			return array( $field_id, $sub_field_id );
1191
		}
1192
1193 3
		if ( 'group' !== $fields[ $field_id ]['type'] ) {
1194
			return false;
1195
		}
1196
1197 3
		if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) {
1198
			$sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] );
1199
		}
1200
1201 3
		return false === $sub_field_id ? false : array( $field_id, $sub_field_id );
1202
	}
1203
1204
	/**
1205
	 * When using the old array filter, it is unlikely field array indexes will be the field id
1206
	 * @since  2.0.2
1207
	 * @param  string $field_id The field id
1208
	 * @param  array  $fields   Array of fields to search
1209
	 * @return mixed            Field index or false
1210
	 */
1211 2
	public function search_old_school_array( $field_id, $fields ) {
1212 2
		$ids = wp_list_pluck( $fields, 'id' );
1213 2
		$index = array_search( $field_id, $ids );
1214 2
		return false !== $index ? $index : false;
1215
	}
1216
1217
	/**
1218
	 * Determine whether this cmb object should show, based on the 'show_on_cb' callback.
1219
	 *
1220
	 * @since 2.0.9
1221
	 *
1222
	 * @return bool Whether this cmb should be shown.
1223
	 */
1224 View Code Duplication
	public function should_show() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1225
		// Default to showing this cmb
1226
		$show = true;
1227
1228
		// Use the callback to determine showing the cmb, if it exists
1229
		if ( is_callable( $this->prop( 'show_on_cb' ) ) ) {
1230
			$show = (bool) call_user_func( $this->prop( 'show_on_cb' ), $this );
1231
		}
1232
1233
		return $show;
1234
	}
1235
1236
	/**
1237
	 * Generate a unique nonce field for each registered meta_box
1238
	 * @since  2.0.0
1239
	 * @return string unique nonce hidden input
1240
	 */
1241 1
	public function nonce_field() {
1242 1
		wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1243 1
	}
1244
1245
	/**
1246
	 * Generate a unique nonce for each registered meta_box
1247
	 * @since  2.0.0
1248
	 * @return string unique nonce string
1249
	 */
1250 1
	public function nonce() {
1251 1
		if ( $this->generated_nonce ) {
1252 1
			return $this->generated_nonce;
1253
		}
1254 1
		$this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1255 1
		return $this->generated_nonce;
1256
	}
1257
1258
	/**
1259
	 * Magic getter for our object.
1260
	 * @param string $field
1261
	 * @throws Exception Throws an exception if the field is invalid.
1262
	 * @return mixed
1263
	 */
1264 44
	public function __get( $field ) {
1265
		switch ( $field ) {
1266 44
			case 'cmb_id':
1267 44
			case 'meta_box':
1268 44
			case 'updated':
1269 44
				return $this->{$field};
1270 3
			case 'object_id':
1271 1
				return $this->object_id();
1272 2
			default:
1273 2
				throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field );
1274 2
		}
1275
	}
1276
1277
}
1278