Completed
Push — trunk ( 3edf8a...58eb32 )
by Justin
10s
created

CMB2::save_group_field()   D

Complexity

Conditions 16
Paths 196

Size

Total Lines 77
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 16.0224

Importance

Changes 0
Metric Value
cc 16
eloc 40
nc 196
nop 1
dl 0
loc 77
ccs 43
cts 45
cp 0.9556
crap 16.0224
rs 4.8329
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * CMB2 - The core metabox object
4
 *
5
 * @category  WordPress_Plugin
6
 * @package   CMB2
7
 * @author    WebDevStudios
8
 * @license   GPL-2.0+
9
 * @link      http://webdevstudios.com
10
 *
11
 * @property-read string $cmb_id
12
 * @property-read array $meta_box
13
 * @property-read array $updated
14
 * @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
		// Post type slug, or 'user', 'term', 'comment', or 'options-page'
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% 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...
56
		'object_types'     => array(),
57
		 /*
58
		 * The context within the screen where the boxes should display. Available contexts vary
59
		 * from screen to screen. Post edit screen contexts include 'normal', 'side', and 'advanced'.
60
		 *
61
		 * For placement in locations outside of a metabox, other options include:
62
		 * 'form_top', 'before_permalink', 'after_title', 'after_editor'
63
		 *
64
		 * Comments screen contexts include 'normal' and 'side'. Default is 'normal'.
65
		 */
66
		'context'          => 'normal',
67
		'priority'         => 'high',
68
		'show_names'       => true, // Show field names on the left
69
		'show_on_cb'       => null, // Callback to determine if metabox should display.
70
		'show_on'          => array(), // Post IDs or page templates to display this metabox. overrides 'show_on_cb'
71
		'cmb_styles'       => true, // Include CMB2 stylesheet
72
		'enqueue_js'       => true, // Include CMB2 JS
73
		'fields'           => array(),
74
		'hookup'           => true,
75
		'save_fields'      => true, // Will not save during hookup if false
76
		'closed'           => false, // Default to metabox being closed?
77
		'taxonomies'       => array(),
78
		'new_user_section' => 'add-new-user', // or 'add-existing-user'
79
		'new_term_section' => true,
80
		'show_in_rest'     => false,
81
	);
82
83
	/**
84
	 * Metabox field objects
85
	 * @var   array
86
	 * @since 2.0.3
87
	 */
88
	protected $fields = array();
89
90
	/**
91
	 * An array of hidden fields to output at the end of the form
92
	 * @var   array
93
	 * @since 2.0.0
94
	 */
95
	protected $hidden_fields = array();
96
97
	/**
98
	 * Array of key => value data for saving. Likely $_POST data.
99
	 * @var   string
100
	 * @since 2.0.0
101
	 */
102
	protected $generated_nonce = '';
103
104
	/**
105
	 * Whether there are fields to be shown in columns. Set in CMB2::add_field().
106
	 * @var   bool
107
	 * @since 2.2.2
108
	 */
109
	protected $has_columns = false;
110
111
	/**
112
	 * If taxonomy field is requesting to remove_default, we store the taxonomy here.
113
	 * @var   array
114
	 * @since 2.2.3
115
	 */
116
	protected $tax_metaboxes_to_remove = array();
117
118
	/**
119
	 * Get started
120
	 * @since 0.4.0
121
	 * @param array   $config    Metabox config array
122
	 * @param integer $object_id Optional object id
123
	 */
124 48
	public function __construct( $config, $object_id = 0 ) {
125
126 48
		if ( empty( $config['id'] ) ) {
127 1
			wp_die( esc_html__( 'Metabox configuration is required to have an ID parameter.', 'cmb2' ) );
128
		}
129
130 48
		$this->meta_box = wp_parse_args( $config, $this->mb_defaults );
131 48
		$this->meta_box['fields'] = array();
132
133 48
		$this->object_id( $object_id );
134 48
		$this->mb_object_type();
135 48
		$this->cmb_id = $config['id'];
136
137 48
		if ( ! empty( $config['fields'] ) && is_array( $config['fields'] ) ) {
138 45
			$this->add_fields( $config['fields'] );
139 45
		}
140
141 48
		CMB2_Boxes::add( $this );
142
143
		/**
144
		 * Hook during initiation of CMB2 object
145
		 *
146
		 * The dynamic portion of the hook name, $this->cmb_id, is this meta_box id.
147
		 *
148
		 * @param array $cmb This CMB2 object
149
		 */
150 48
		do_action( "cmb2_init_{$this->cmb_id}", $this );
151 48
	}
152
153
	/**
154
	 * Loops through and displays fields
155
	 * @since 1.0.0
156
	 * @param int    $object_id   Object ID
157
	 * @param string $object_type Type of object being saved. (e.g., post, user, or comment)
158
	 */
159 1
	public function show_form( $object_id = 0, $object_type = '' ) {
160 1
		$this->render_form_open( $object_id, $object_type );
161
162 1
		foreach ( $this->prop( 'fields' ) as $field_args ) {
163 1
			$this->render_field( $field_args );
164 1
		}
165
166 1
		$this->render_form_close( $object_id, $object_type );
167 1
	}
168
169
	/**
170
	 * Outputs the opening form markup and runs corresponding hooks:
171
	 * 'cmb2_before_form' and "cmb2_before_{$object_type}_form_{$this->cmb_id}"
172
	 * @since  2.2.0
173
	 * @param  integer $object_id   Object ID
174
	 * @param  string  $object_type Object type
175
	 * @return void
176
	 */
177 1
	public function render_form_open( $object_id = 0, $object_type = '' ) {
178 1
		$object_type = $this->object_type( $object_type );
179 1
		$object_id = $this->object_id( $object_id );
180
181 1
		echo "\n<!-- Begin CMB2 Fields -->\n";
182
183 1
		$this->nonce_field();
184
185
		/**
186
		 * Hook before form table begins
187
		 *
188
		 * @param array  $cmb_id      The current box ID
189
		 * @param int    $object_id   The ID of the current object
190
		 * @param string $object_type The type of object you are working with.
191
		 *	                           Usually `post` (this applies to all post-types).
192
		 *	                           Could also be `comment`, `user` or `options-page`.
193
		 * @param array  $cmb         This CMB2 object
194
		 */
195 1
		do_action( 'cmb2_before_form', $this->cmb_id, $object_id, $object_type, $this );
196
197
		/**
198
		 * Hook before form table begins
199
		 *
200
		 * The first dynamic portion of the hook name, $object_type, is the type of object
201
		 * you are working with. Usually `post` (this applies to all post-types).
202
		 * Could also be `comment`, `user` or `options-page`.
203
		 *
204
		 * The second dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
205
		 *
206
		 * @param array  $cmb_id      The current box ID
207
		 * @param int    $object_id   The ID of the current object
208
		 * @param array  $cmb         This CMB2 object
209
		 */
210 1
		do_action( "cmb2_before_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
211
212 1
		echo '<div class="', $this->box_classes(), '"><div id="cmb2-metabox-', sanitize_html_class( $this->cmb_id ), '" class="cmb2-metabox cmb-field-list">';
213
214 1
	}
215
216
	/**
217
	 * Defines the classes for the CMB2 form/wrap.
218
	 *
219
	 * @since  2.0.0
220
	 * @return string Space concatenated list of classes
221
	 */
222 1
	public function box_classes() {
223
224 1
		$classes = array( 'cmb2-wrap', 'form-table' );
225
226
		// Use the callback to fetch classes.
227 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...
228 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
229 1
			$classes = array_merge( $classes, $added_classes );
230 1
		}
231
232 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...
233 1
			$added_classes = is_array( $added_classes ) ? $added_classes : array( $added_classes );
234 1
			$classes = array_merge( $classes, $added_classes );
235 1
		}
236
237
		/**
238
		 * Add our context classes for non-standard metaboxes.
239
		 *
240
		 * @since 2.2.4
241
		 */
242 1
		if ( $this->is_alternate_context_box() ) {
243
244
			// Include custom class if requesting no title.
245
			if ( ! $this->prop( 'title' ) && ! $this->prop( 'remove_box_wrap' ) ) {
246
				$context[] = 'cmb2-context-wrap-no-title';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$context was never initialized. Although not strictly required by PHP, it is generally a good practice to add $context = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
247
			}
248
249
			// Include a generic context wrapper.
250
			$context[] = 'cmb2-context-wrap';
0 ignored issues
show
Bug introduced by
The variable $context does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
251
252
			// Include a context-type based context wrapper.
253
			$context[] = 'cmb2-context-wrap-' . $this->prop( 'context' );
254
255
			// Include an ID based context wrapper as well.
256
			$context[] = 'cmb2-context-wrap-' . $this->prop( 'id' );
257
258
			// And merge all the classes back into the array.
259
			$classes = array_merge( $classes, $context );
260
		}
261
262
		/**
263
		 * Globally filter box wrap classes
264
		 *
265
		 * @since 2.2.2
266
		 *
267
		 * @param string $classes Array of classes for the cmb2-wrap.
268
		 * @param CMB2   $cmb     This CMB2 object.
269
		 */
270 1
		$classes = apply_filters( 'cmb2_wrap_classes', $classes, $this );
271
272
		// Clean up.
273 1
		$classes = array_map( 'strip_tags', array_filter( $classes ) );
274
275
		// Remove any duplicates.
276 1
		$classes = array_unique( $classes );
277
278
		// Make a string.
279 1
		return implode( ' ', $classes );
280
	}
281
282
	/**
283
	 * Outputs the closing form markup and runs corresponding hooks:
284
	 * 'cmb2_after_form' and "cmb2_after_{$object_type}_form_{$this->cmb_id}"
285
	 * @since  2.2.0
286
	 * @param  integer $object_id   Object ID
287
	 * @param  string  $object_type Object type
288
	 * @return void
289
	 */
290 1
	public function render_form_close( $object_id = 0, $object_type = '' ) {
291 1
		$object_type = $this->object_type( $object_type );
292 1
		$object_id = $this->object_id( $object_id );
293
294 1
		echo '</div></div>';
295
296 1
		$this->render_hidden_fields();
297
298
		/**
299
		 * Hook after form form has been rendered
300
		 *
301
		 * @param array  $cmb_id      The current box ID
302
		 * @param int    $object_id   The ID of the current object
303
		 * @param string $object_type The type of object you are working with.
304
		 *	                           Usually `post` (this applies to all post-types).
305
		 *	                           Could also be `comment`, `user` or `options-page`.
306
		 * @param array  $cmb         This CMB2 object
307
		 */
308 1
		do_action( 'cmb2_after_form', $this->cmb_id, $object_id, $object_type, $this );
309
310
		/**
311
		 * Hook after form form has been rendered
312
		 *
313
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
314
		 *
315
		 * The first dynamic portion of the hook name, $object_type, is the type of object
316
		 * you are working with. Usually `post` (this applies to all post-types).
317
		 * Could also be `comment`, `user` or `options-page`.
318
		 *
319
		 * @param int    $object_id   The ID of the current object
320
		 * @param array  $cmb         This CMB2 object
321
		 */
322 1
		do_action( "cmb2_after_{$object_type}_form_{$this->cmb_id}", $object_id, $this );
323
324 1
		echo "\n<!-- End CMB2 Fields -->\n";
325
326 1
	}
327
328
	/**
329
	 * Renders a field based on the field type
330
	 * @since  2.2.0
331
	 * @param  array $field_args A field configuration array.
332
	 * @return mixed CMB2_Field object if successful.
333
	 */
334 1
	public function render_field( $field_args ) {
335 1
		$field_args['context'] = $this->prop( 'context' );
336
337 1
		if ( 'group' == $field_args['type'] ) {
338
339
			if ( ! isset( $field_args['show_names'] ) ) {
340
				$field_args['show_names'] = $this->prop( 'show_names' );
341
			}
342
			$field = $this->render_group( $field_args );
343
344 1
		} elseif ( 'hidden' == $field_args['type'] && $this->get_field( $field_args )->should_show() ) {
345
			// Save rendering for after the metabox
346
			$field = $this->add_hidden_field( $field_args );
347
348
		} else {
349
350 1
			$field_args['show_names'] = $this->prop( 'show_names' );
351
352
			// Render default fields
353 1
			$field = $this->get_field( $field_args )->render_field();
354
		}
355
356 1
		return $field;
357
	}
358
359
	/**
360
	 * Render a repeatable group.
361
	 * @param array $args Array of field arguments for a group field parent.
362
	 * @return CMB2_Field|null Group field object.
363
	 */
364 2
	public function render_group( $args ) {
365
366 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
367
			return;
368
		}
369
370 2
		$field_group = $this->get_field( $args );
371
372
		// If field is requesting to be conditionally shown
373 2
		if ( ! $field_group || ! $field_group->should_show() ) {
374
			return;
375
		}
376
377 2
		$desc            = $field_group->args( 'description' );
378 2
		$label           = $field_group->args( 'name' );
379 2
		$group_val       = (array) $field_group->value();
380 2
		$remove_disabled = count( $group_val ) <= 1 ? 'disabled="disabled" ' : '';
381 2
		$field_group->index = 0;
382
383 2
		$field_group->peform_param_callback( 'before_group' );
384
385 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 ), '>';
386
387 2
		if ( $desc || $label ) {
388 2
			$class = $desc ? ' cmb-group-description' : '';
389 2
			echo '<div class="cmb-row', $class, '"><div class="cmb-th">';
390 2
				if ( $label ) {
391 2
					echo '<h2 class="cmb-group-name">', $label, '</h2>';
392 2
				}
393 2
				if ( $desc ) {
394 1
					echo '<p class="cmb2-metabox-description">', $desc, '</p>';
395 1
				}
396 2
			echo '</div></div>';
397 2
		}
398
399 2
		if ( ! empty( $group_val ) ) {
400
			foreach ( $group_val as $group_key => $field_id ) {
401
				$this->render_group_row( $field_group, $remove_disabled );
402
				$field_group->index++;
403
			}
404
		} else {
405 2
			$this->render_group_row( $field_group, $remove_disabled );
406
		}
407
408 2
		if ( $field_group->args( 'repeatable' ) ) {
409 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>';
410 1
		}
411
412 2
		echo '</div></div></div>';
413
414 2
		$field_group->peform_param_callback( 'after_group' );
415
416 2
		return $field_group;
417
	}
418
419
	/**
420
	 * Get the group wrap attributes, which are passed through a filter.
421
	 * @since  2.2.3
422
	 * @param  CMB2_Field $field_group The group CMB2_Field object.
423
	 * @return string                  The attributes string.
424
	 */
425 2
	public function group_wrap_attributes( $field_group ) {
426 2
		$classes = 'cmb-nested cmb-field-list cmb-repeatable-group';
427 2
		$classes .= $field_group->options( 'sortable' ) ? ' sortable' : ' non-sortable';
428 2
		$classes .= $field_group->args( 'repeatable' ) ? ' repeatable' : ' non-repeatable';
429
430
		$group_wrap_attributes = array(
431 2
			'class' => $classes,
432 2
			'style' => 'width:100%;',
433 2
		);
434
435
		/**
436
		 * Allow for adding additional HTML attributes to a group wrapper.
437
		 *
438
		 * The attributes will be an array of key => value pairs for each attribute.
439
		 *
440
		 * @since 2.2.2
441
		 *
442
		 * @param string     $group_wrap_attributes Current attributes array.
443
		 *
444
		 * @param CMB2_Field $field_group           The group CMB2_Field object.
445
		 */
446 2
		$group_wrap_attributes = apply_filters( 'cmb2_group_wrap_attributes', $group_wrap_attributes, $field_group );
447
448 2
		return CMB2_Utils::concat_attrs( $group_wrap_attributes );
449
	}
450
451
	/**
452
	 * Render a repeatable group row
453
	 * @since  1.0.2
454
	 * @param  CMB2_Field $field_group  CMB2_Field group field object
455
	 * @param  string  $remove_disabled Attribute string to disable the remove button
456
	 */
457 2
	public function render_group_row( $field_group, $remove_disabled ) {
458
459 2
		$field_group->peform_param_callback( 'before_group_row' );
460 2
		$closed_class = $field_group->options( 'closed' ) ? ' closed' : '';
461
462
		echo '
463 2
		<div class="postbox cmb-row cmb-repeatable-grouping', $closed_class, '" data-iterator="', $field_group->index, '">';
464
465 2
			if ( $field_group->args( 'repeatable' ) ) {
466 1
				echo '<button type="button" ', $remove_disabled, 'data-selector="', $field_group->id(), '_repeat" class="dashicons-before dashicons-no-alt cmb-remove-group-row" title="', esc_attr( $field_group->options( 'remove_button' ) ), '"></button>';
467 1
			}
468
469
			echo '
470 2
			<div class="cmbhandle" title="' , esc_attr__( 'Click to toggle', 'cmb2' ), '"><br></div>
471 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...
472
473
			<div class="inside cmb-td cmb-nested cmb-field-list">';
474
				// Loop and render repeatable group fields
475 2
				foreach ( array_values( $field_group->args( 'fields' ) ) as $field_args ) {
476 2
					if ( 'hidden' == $field_args['type'] ) {
477
478
						// Save rendering for after the metabox
479
						$this->add_hidden_field( $field_args, $field_group );
480
481
					} else {
482
483 2
						$field_args['show_names'] = $field_group->args( 'show_names' );
484 2
						$field_args['context']    = $field_group->args( 'context' );
485
486 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...
487
					}
488 2
				}
489 2
				if ( $field_group->args( 'repeatable' ) ) {
490
					echo '
491
					<div class="cmb-row cmb-remove-field-row">
492
						<div class="cmb-remove-row">
493 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>
494
						</div>
495
					</div>
496
					';
497 1
				}
498
			echo '
499
			</div>
500
		</div>
501 2
		';
502
503 2
		$field_group->peform_param_callback( 'after_group_row' );
504 2
	}
505
506
	/**
507
	 * Add a hidden field to the list of hidden fields to be rendered later
508
	 * @since 2.0.0
509
	 * @param array           $field_args  Array of field arguments to be passed to CMB2_Field
510
	 * @param CMB2_Field|null $field_group CMB2_Field group field object
511
	 */
512
	public function add_hidden_field( $field_args, $field_group = null ) {
513
		if ( isset( $field_args['field_args'] ) ) {
514
			// For back-compatibility.
515
			$field = new CMB2_Field( $field_args );
516
		} else {
517
			$field = $this->get_new_field( $field_args, $field_group );
518
		}
519
520
		$types = new CMB2_Types( $field );
521
522
		if ( $field_group ) {
523
			$types->iterator = $field_group->index;
524
		}
525
526
		$this->hidden_fields[] = $types;
527
528
		return $field;
529
	}
530
531
	/**
532
	 * Loop through and output hidden fields
533
	 * @since  2.0.0
534
	 */
535 1
	public function render_hidden_fields() {
536 1
		if ( ! empty( $this->hidden_fields ) ) {
537
			foreach ( $this->hidden_fields as $hidden ) {
538
				$hidden->render();
539
			}
540
		}
541 1
	}
542
543
	/**
544
	 * Returns array of sanitized field values (without saving them)
545
	 * @since  2.0.3
546
	 * @param  array  $data_to_sanitize Array of field_id => value data for sanitizing (likely $_POST data).
547
	 */
548 4
	public function get_sanitized_values( array $data_to_sanitize ) {
549 4
		$this->data_to_save = $data_to_sanitize;
550 4
		$stored_id          = $this->object_id();
551
552
		// We do this So CMB will sanitize our data for us, but not save it
553 4
		$this->object_id( '_' );
554
555
		// Ensure temp. data store is empty
556 4
		cmb2_options( 0 )->set();
557
558
		// We want to get any taxonomy values back.
559 4
		add_filter( "cmb2_return_taxonomy_values_{$this->cmb_id}", '__return_true' );
560
561
		// Process/save fields
562 4
		$this->process_fields();
563
564
		// Put things back the way they were.
565 4
		remove_filter( "cmb2_return_taxonomy_values_{$this->cmb_id}", '__return_true' );
566
567
		// Get data from temp. data store
568 4
		$sanitized_values = cmb2_options( 0 )->get_options();
569
570
		// Empty out temp. data store again
571 4
		cmb2_options( 0 )->set();
572
573
		// Reset the object id
574 4
		$this->object_id( $stored_id );
575
576 4
		return $sanitized_values;
577
	}
578
579
	/**
580
	 * Loops through and saves field data
581
	 * @since  1.0.0
582
	 * @param  int    $object_id    Object ID
583
	 * @param  string $object_type  Type of object being saved. (e.g., post, user, or comment)
584
	 * @param  array  $data_to_save Array of key => value data for saving. Likely $_POST data.
585
	 */
586 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...
587
588
		// Fall-back to $_POST data
589 1
		$this->data_to_save = ! empty( $data_to_save ) ? $data_to_save : $_POST;
590 1
		$object_id = $this->object_id( $object_id );
591 1
		$object_type = $this->object_type( $object_type );
592
593 1
		$this->process_fields();
594
595
		// If options page, save the updated options
596 1
		if ( 'options-page' == $object_type ) {
597 1
			cmb2_options( $object_id )->set();
598 1
		}
599
600 1
		$this->after_save();
601 1
	}
602
603
	/**
604
	 * Process and save form fields
605
	 * @since  2.0.0
606
	 */
607 5
	public function process_fields() {
608
609 5
		$this->pre_process();
610
611
		// Remove the show_on properties so saving works
612 5
		$this->prop( 'show_on', array() );
613
614
		// save field ids of those that are updated
615 5
		$this->updated = array();
616
617 5
		foreach ( $this->prop( 'fields' ) as $field_args ) {
618 5
			$this->process_field( $field_args );
619 5
		}
620 5
	}
621
622
	/**
623
	 * Process and save a field
624
	 * @since  2.0.0
625
	 * @param  array  $field_args Array of field arguments
626
	 */
627 5
	public function process_field( $field_args ) {
628
629 5
		switch ( $field_args['type'] ) {
630
631 5
			case 'group':
632 2
				if ( $this->save_group( $field_args ) ) {
633 2
					$this->updated[] = $field_args['id'];
634 2
				}
635
636 2
				break;
637
638 3
			case 'title':
639
				// Don't process title fields
640
				break;
641
642 3
			default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
643
644 3
				$field = $this->get_new_field( $field_args );
645
646 3
				if ( $field->save_field_from_data( $this->data_to_save ) ) {
647 3
					$this->updated[] = $field->id();
648 3
				}
649
650 3
				break;
651 5
		}
652
653 5
	}
654
655 5
	public function pre_process() {
656
		/**
657
		 * Fires before fields have been processed/saved.
658
		 *
659
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
660
		 *
661
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
662
		 * 	Usually `post` (this applies to all post-types).
663
		 *  	Could also be `comment`, `user` or `options-page`.
664
		 *
665
		 * @param array $cmb       This CMB2 object
666
		 * @param int   $object_id The ID of the current object
667
		 */
668 5
		do_action( "cmb2_{$this->object_type()}_process_fields_{$this->cmb_id}", $this, $this->object_id() );
669 5
	}
670
671 1
	public function after_save() {
672 1
		$object_type = $this->object_type();
673 1
		$object_id   = $this->object_id();
674
675
		/**
676
		 * Fires after all fields have been saved.
677
		 *
678
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
679
		 * 	Usually `post` (this applies to all post-types).
680
		 *  	Could also be `comment`, `user` or `options-page`.
681
		 *
682
		 * @param int    $object_id   The ID of the current object
683
		 * @param array  $cmb_id      The current box ID
684
		 * @param string $updated     Array of field ids that were updated.
685
		 *                            Will only include field ids that had values change.
686
		 * @param array  $cmb         This CMB2 object
687
		 */
688 1
		do_action( "cmb2_save_{$object_type}_fields", $object_id, $this->cmb_id, $this->updated, $this );
689
690
		/**
691
		 * Fires after all fields have been saved.
692
		 *
693
		 * The dynamic portion of the hook name, $this->cmb_id, is the meta_box id.
694
		 *
695
		 * The dynamic portion of the hook name, $object_type, refers to the metabox/form's object type
696
		 * 	Usually `post` (this applies to all post-types).
697
		 *  	Could also be `comment`, `user` or `options-page`.
698
		 *
699
		 * @param int    $object_id   The ID of the current object
700
		 * @param string $updated     Array of field ids that were updated.
701
		 *                            Will only include field ids that had values change.
702
		 * @param array  $cmb         This CMB2 object
703
		 */
704 1
		do_action( "cmb2_save_{$object_type}_fields_{$this->cmb_id}", $object_id, $this->updated, $this );
705 1
	}
706
707
	/**
708
	 * Save a repeatable group
709
	 * @since  1.x.x
710
	 * @param  array  $args Field arguments array
711
	 * @return mixed        Return of CMB2_Field::update_data()
712
	 */
713 2
	public function save_group( $args ) {
714 2
		if ( ! isset( $args['id'], $args['fields'] ) || ! is_array( $args['fields'] ) ) {
715
			return;
716
		}
717
718 2
		return $this->save_group_field( $this->get_new_field( $args ) );
719
	}
720
721
	/**
722
	 * Save a repeatable group
723
	 * @since  1.x.x
724
	 * @param  CMB2_Field $field_group CMB2_Field group field object
725
	 * @return mixed                   Return of CMB2_Field::update_data()
726
	 */
727 2
	public function save_group_field( $field_group ) {
728 2
		$base_id = $field_group->id();
729
730 2
		if ( ! isset( $this->data_to_save[ $base_id ] ) ) {
731
			return;
732
		}
733
734 2
		$old        = $field_group->get_data();
735
		// Check if group field has sanitization_cb
736 2
		$group_vals = $field_group->sanitization_cb( $this->data_to_save[ $base_id ] );
737 2
		$saved      = array();
738
739 2
		$field_group->index = 0;
740 2
		$field_group->data_to_save = $this->data_to_save;
741
742 2
		foreach ( array_values( $field_group->fields() ) as $field_args ) {
743 2
			if ( 'title' === $field_args['type'] ) {
744
				// Don't process title fields
745
				continue;
746
			}
747
748 2
			$field  = $this->get_new_field( $field_args, $field_group );
749 2
			$sub_id = $field->id( true );
750
751 2
			foreach ( (array) $group_vals as $field_group->index => $post_vals ) {
752
753
				// Get value
754 2
				$new_val = isset( $group_vals[ $field_group->index ][ $sub_id ] )
755 2
					? $group_vals[ $field_group->index ][ $sub_id ]
756 2
					: false;
757
758
				// Sanitize
759 2
				$new_val = $field->sanitization_cb( $new_val );
760
761 2
				if ( is_array( $new_val ) && $field->args( 'has_supporting_data' ) ) {
762 2
					if ( $field->args( 'repeatable' ) ) {
763 1
						$_new_val = array();
764 1
						foreach ( $new_val as $group_index => $grouped_data ) {
765
							// Add the supporting data to the $saved array stack
766 1
							$saved[ $field_group->index ][ $grouped_data['supporting_field_id'] ][] = $grouped_data['supporting_field_value'];
767
							// Reset var to the actual value
768 1
							$_new_val[ $group_index ] = $grouped_data['value'];
769 1
						}
770 1
						$new_val = $_new_val;
771 1
					} else {
772
						// Add the supporting data to the $saved array stack
773 2
						$saved[ $field_group->index ][ $new_val['supporting_field_id'] ] = $new_val['supporting_field_value'];
774
						// Reset var to the actual value
775 2
						$new_val = $new_val['value'];
776
					}
777 2
				}
778
779
				// Get old value
780 2
				$old_val = is_array( $old ) && isset( $old[ $field_group->index ][ $sub_id ] )
781 2
					? $old[ $field_group->index ][ $sub_id ]
782 2
					: false;
783
784 2
				$is_updated = ( ! CMB2_Utils::isempty( $new_val ) && $new_val !== $old_val );
785 2
				$is_removed = ( CMB2_Utils::isempty( $new_val ) && ! CMB2_Utils::isempty( $old_val ) );
786
787
				// Compare values and add to `$updated` array
788 2
				if ( $is_updated || $is_removed ) {
789 2
					$this->updated[] = $base_id . '::' . $field_group->index . '::' . $sub_id;
790 2
				}
791
792
				// 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...
793 2
				$saved[ $field_group->index ][ $sub_id ] = $new_val;
794
795 2
			}
796
797 2
			$saved[ $field_group->index ] = CMB2_Utils::filter_empty( $saved[ $field_group->index ] );
798 2
		}
799
800 2
		$saved = CMB2_Utils::filter_empty( $saved );
801
802 2
		return $field_group->update_data( $saved, true );
803
	}
804
805
	/**
806
	 * Get object id from global space if no id is provided
807
	 * @since  1.0.0
808
	 * @param  integer $object_id Object ID
809
	 * @return integer $object_id Object ID
810
	 */
811 52
	public function object_id( $object_id = 0 ) {
0 ignored issues
show
Coding Style introduced by
object_id uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
object_id uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
812 52
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
813
814 52
		if ( $object_id ) {
815 20
			$this->object_id = $object_id;
816 20
			return $this->object_id;
817
		}
818
819 49
		if ( $this->object_id ) {
820 15
			return $this->object_id;
821
		}
822
823
		// Try to get our object ID from the global space
824 46
		switch ( $this->object_type() ) {
825 46
			case 'user':
826
				$object_id = isset( $_REQUEST['user_id'] ) ? $_REQUEST['user_id'] : $object_id;
827
				$object_id = ! $object_id && 'user-new.php' != $pagenow && isset( $GLOBALS['user_ID'] ) ? $GLOBALS['user_ID'] : $object_id;
828
				break;
829
830 46
			case 'comment':
831
				$object_id = isset( $_REQUEST['c'] ) ? $_REQUEST['c'] : $object_id;
832
				$object_id = ! $object_id && isset( $GLOBALS['comments']->comment_ID ) ? $GLOBALS['comments']->comment_ID : $object_id;
833
				break;
834
835 46
			case 'term':
836
				$object_id = isset( $_REQUEST['tag_ID'] ) ? $_REQUEST['tag_ID'] : $object_id;
837
				break;
838
839 46
			default:
840 46
				$object_id = isset( $GLOBALS['post']->ID ) ? $GLOBALS['post']->ID : $object_id;
841 46
				$object_id = isset( $_REQUEST['post'] ) ? $_REQUEST['post'] : $object_id;
842 46
				break;
843 46
		}
844
845
		// reset to id or 0
846 46
		$this->object_id = $object_id ? $object_id : 0;
847
848 46
		return $this->object_id;
849
	}
850
851
	/**
852
	 * Sets the $object_type based on metabox settings
853
	 * @since  1.0.0
854
	 * @return string Object type
855
	 */
856 48
	public function mb_object_type() {
857 48
		if ( null !== $this->mb_object_type ) {
858 12
			return $this->mb_object_type;
859
		}
860
861 48
		if ( $this->is_options_page_mb() ) {
862 39
			$this->mb_object_type = 'options-page';
863 39
			return $this->mb_object_type;
864
		}
865
866 47
		$registered_types = $this->box_types();
867
868 47
		$type = '';
869
870
		// if it's an array of one, extract it
871 47
		if ( 1 === count( $registered_types ) ) {
872 47
			$last = end( $registered_types );
873 47
			if ( is_string( $last ) ) {
874 47
				$type = $last;
875 47
			}
876 47
		} elseif ( ( $curr_type = $this->current_object_type() ) && in_array( $curr_type, $registered_types, true ) ) {
877
			$type = $curr_type;
878
		}
879
880
		// Get our object type
881
		switch ( $type ) {
882
883 47
			case 'user':
884 47
			case 'comment':
885 47
			case 'term':
886 1
				$this->mb_object_type = $type;
887 1
				break;
888
889 47
			default:
890 47
				$this->mb_object_type = 'post';
891 47
				break;
892 47
		}
893
894 47
		return $this->mb_object_type;
895
	}
896
897
	/**
898
	 * Gets the box 'object_types' array based on box settings.
899
	 * @since  2.2.3
900
	 * @return array Object types
901
	 */
902 47
	public function box_types() {
903 47
		return CMB2_Utils::ensure_array( $this->prop( 'object_types' ), array( 'post' ) );
904
	}
905
906
	/**
907
	 * Determines if metabox is for an options page
908
	 * @since  1.0.1
909
	 * @return boolean True/False
910
	 */
911 48
	public function is_options_page_mb() {
912 48
		return ( isset( $this->meta_box['show_on']['key'] ) && 'options-page' === $this->meta_box['show_on']['key'] || array_key_exists( 'options-page', $this->meta_box['show_on'] ) );
913
	}
914
915
	/**
916
	 * Returns the object type
917
	 * @since  1.0.0
918
	 * @return string Object type
919
	 */
920 52
	public function object_type( $object_type = '' ) {
921 52
		if ( $object_type ) {
922 18
			$this->object_type = $object_type;
923 18
			return $this->object_type;
924
		}
925
926 49
		if ( $this->object_type ) {
927 19
			return $this->object_type;
928
		}
929
930 47
		$this->object_type = $this->current_object_type();
931
932 47
		return $this->object_type;
933
	}
934
935
	/**
936
	 * Get the object type for the current page, based on the $pagenow global.
937
	 * @since  2.2.2
938
	 * @return string  Page object type name.
939
	 */
940 47
	public function current_object_type() {
0 ignored issues
show
Coding Style introduced by
current_object_type 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...
941 47
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
942 47
		$type = 'post';
943
944 47
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
945
			$type = 'user';
946
		}
947
948 47
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
949
			$type = 'comment';
950
		}
951
952 47
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
953
			$type = 'term';
954
		}
955
956 47
		if ( defined( 'DOING_AJAX' ) && isset( $_POST['action'] ) && 'add-tag' === $_POST['action'] ) {
957
			$type = 'term';
958
		}
959
960 47
		return $type;
961
	}
962
963
	/**
964
	 * Set metabox property.
965
	 * @since  2.2.2
966
	 * @param  string $property Metabox config property to retrieve
967
	 * @param  mixed  $value    Value to set if no value found
968
	 * @return mixed            Metabox config property value or false
969
	 */
970 1
	public function set_prop( $property, $value ) {
971 1
		$this->meta_box[ $property ] = $value;
972
973 1
		return $this->prop( $property );
974
	}
975
976
	/**
977
	 * Get metabox property and optionally set a fallback
978
	 * @since  2.0.0
979
	 * @param  string $property Metabox config property to retrieve
980
	 * @param  mixed  $fallback Fallback value to set if no value found
981
	 * @return mixed            Metabox config property value or false
982
	 */
983 48
	public function prop( $property, $fallback = null ) {
984 48
		if ( array_key_exists( $property, $this->meta_box ) ) {
985 48
			return $this->meta_box[ $property ];
986 1
		} elseif ( $fallback ) {
987 1
			return $this->meta_box[ $property ] = $fallback;
988
		}
989 1
	}
990
991
	/**
992
	 * Get a field object
993
	 * @since  2.0.3
994
	 * @param  string|array|CMB2_Field $field       Metabox field id or field config array or CMB2_Field object
995
	 * @param  CMB2_Field|null         $field_group (optional) CMB2_Field object (group parent)
996
	 * @return CMB2_Field|false                     CMB2_Field object (or false)
997
	 */
998 15
	public function get_field( $field, $field_group = null ) {
999 15
		if ( $field instanceof CMB2_Field ) {
1000
			return $field;
1001
		}
1002
1003 15
		$field_id = is_string( $field ) ? $field : $field['id'];
1004
1005 15
		$parent_field_id = ! empty( $field_group ) ? $field_group->id() : '';
1006 15
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1007
1008 15
		if ( ! $ids ) {
1009
			return false;
1010
		}
1011
1012 15
		list( $field_id, $sub_field_id ) = $ids;
1013
1014 15
		$index = implode( '', $ids ) . ( $field_group ? $field_group->index : '' );
1015 15
		if ( array_key_exists( $index, $this->fields ) ) {
1016 4
			return $this->fields[ $index ];
1017
		}
1018
1019 13
		$this->fields[ $index ] = new CMB2_Field( $this->get_field_args( $field_id, $field, $sub_field_id, $field_group ) );
1020
1021 13
		return $this->fields[ $index ];
1022
	}
1023
1024
	/**
1025
	 * Handles determining which type of arguments to pass to CMB2_Field
1026
	 * @since  2.0.7
1027
	 * @param  mixed           $field_id     Field (or group field) ID
1028
	 * @param  mixed           $field_args   Array of field arguments
1029
	 * @param  mixed           $sub_field_id Sub field ID (if field_group exists)
1030
	 * @param  CMB2_Field|null $field_group  If a sub-field, will be the parent group CMB2_Field object
1031
	 * @return array                         Array of CMB2_Field arguments
1032
	 */
1033 13
	public function get_field_args( $field_id, $field_args, $sub_field_id, $field_group ) {
1034
1035
		// Check if group is passed and if fields were added in the old-school fields array
1036 13
		if ( $field_group && ( $sub_field_id || 0 === $sub_field_id ) ) {
1037
1038
			// Update the fields array w/ any modified properties inherited from the group field
1039 2
			$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] = $field_args;
1040
1041 2
			return $this->get_default_args( $field_args, $field_group );
1042
		}
1043
1044 13
		if ( is_array( $field_args ) ) {
1045 2
			$this->meta_box['fields'][ $field_id ] = array_merge( $field_args, $this->meta_box['fields'][ $field_id ] );
1046 2
		}
1047
1048 13
		return $this->get_default_args( $this->meta_box['fields'][ $field_id ] );
1049
	}
1050
1051
	/**
1052
	 * Get default field arguments specific to this CMB2 object.
1053
	 * @since  2.2.0
1054
	 * @param  array      $field_args  Metabox field config array.
1055
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1056
	 * @return array                   Array of field arguments.
1057
	 */
1058 19 View Code Duplication
	protected function get_default_args( $field_args, $field_group = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1059 19
		if ( $field_group ) {
1060
			$args = array(
1061 4
				'field_args'  => $field_args,
1062 4
				'group_field' => $field_group,
1063 4
			);
1064 4
		} else {
1065
			$args = array(
1066 19
				'field_args'  => $field_args,
1067 19
				'object_type' => $this->object_type(),
1068 19
				'object_id'   => $this->object_id(),
1069 19
				'cmb_id'      => $this->cmb_id,
1070 19
			);
1071
		}
1072
1073 19
		return $args;
1074
	}
1075
1076
	/**
1077
	 * Get a new field object specific to this CMB2 object.
1078
	 * @since  2.2.0
1079
	 * @param  array      $field_args  Metabox field config array.
1080
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
1081
	 * @return CMB2_Field CMB2_Field object
1082
	 */
1083 7
	protected function get_new_field( $field_args, $field_group = null ) {
1084 7
		return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) );
1085
	}
1086
1087
	/**
1088
	 * When fields are added in the old-school way, intitate them as they should be
1089
	 * @since 2.1.0
1090
	 * @param array $fields          Array of fields to add
1091
	 * @param mixed $parent_field_id Parent field id or null
1092
	 */
1093 45
	protected function add_fields( $fields, $parent_field_id = null ) {
1094 45
		foreach ( $fields as $field ) {
1095
1096 45
			$sub_fields = false;
1097 45
			if ( array_key_exists( 'fields', $field ) ) {
1098 1
				$sub_fields = $field['fields'];
1099 1
				unset( $field['fields'] );
1100 1
			}
1101
1102
			$field_id = $parent_field_id
1103 45
				? $this->add_group_field( $parent_field_id, $field )
1104 45
				: $this->add_field( $field );
1105
1106 45
			if ( $sub_fields ) {
1107 1
				$this->add_fields( $sub_fields, $field_id );
1108 1
			}
1109 45
		}
1110 45
	}
1111
1112
	/**
1113
	 * Add a field to the metabox
1114
	 * @since  2.0.0
1115
	 * @param  array  $field           Metabox field config array
1116
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1117
	 * @return string|false            Field id or false
1118
	 */
1119 47
	public function add_field( array $field, $position = 0 ) {
1120 47
		if ( ! is_array( $field ) || ! array_key_exists( 'id', $field ) ) {
1121
			return false;
1122
		}
1123
1124
		// Perform some field-type-specific initiation actions.
1125 47
		switch ( $field['type'] ) {
1126 47
			case 'file':
1127 47
			case 'file_list':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1128
1129
				// Initiate attachment JS hooks
1130
				add_filter( 'wp_prepare_attachment_for_js', array( 'CMB2_Type_File_Base', 'prepare_image_sizes_for_js' ), 10, 3 );
1131
				break;
1132
1133 47
			case 'oembed':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1134
1135
				// Initiate oembed Ajax hooks
1136 1
				cmb2_ajax();
1137 1
				break;
1138 47
		}
1139
1140 47
		if ( isset( $field['column'] ) && false !== $field['column'] ) {
1141
			$field = $this->define_field_column( $field );
1142
		}
1143
1144 47
		if ( isset( $field['taxonomy'] ) && ! empty( $field['remove_default'] ) ) {
1145
			$this->tax_metaboxes_to_remove[ $field['taxonomy'] ] = $field['taxonomy'];
1146
		}
1147
1148 47
		$this->_add_field_to_array(
1149 47
			$field,
1150 47
			$this->meta_box['fields'],
1151
			$position
1152 47
		);
1153
1154 47
		return $field['id'];
1155
	}
1156
1157
	/**
1158
	 * Defines a field's column if requesting to be show in admin columns.
1159
	 * @since  2.2.3
1160
	 * @param  array  $field Metabox field config array.
1161
	 * @return array         Modified metabox field config array.
1162
	 */
1163
	protected function define_field_column( array $field ) {
1164
		$this->has_columns = true;
1165
1166
		$column = is_array( $field['column'] ) ? $field['column'] : array();
1167
1168
		$field['column'] = wp_parse_args( $column, array(
1169
			'name'     => isset( $field['name'] ) ? $field['name'] : '',
1170
			'position' => false,
1171
		) );
1172
1173
		return $field;
1174
	}
1175
1176
	/**
1177
	 * Add a field to a group
1178
	 * @since  2.0.0
1179
	 * @param  string $parent_field_id The field id of the group field to add the field
1180
	 * @param  array  $field           Metabox field config array
1181
	 * @param  int    $position        (optional) Position of metabox. 1 for first, etc
1182
	 * @return mixed                   Array of parent/field ids or false
1183
	 */
1184 5
	public function add_group_field( $parent_field_id, array $field, $position = 0 ) {
1185 5
		if ( ! array_key_exists( $parent_field_id, $this->meta_box['fields'] ) ) {
1186
			return false;
1187
		}
1188
1189 5
		$parent_field = $this->meta_box['fields'][ $parent_field_id ];
1190
1191 5
		if ( 'group' !== $parent_field['type'] ) {
1192
			return false;
1193
		}
1194
1195 5
		if ( ! isset( $parent_field['fields'] ) ) {
1196 4
			$this->meta_box['fields'][ $parent_field_id ]['fields'] = array();
1197 4
		}
1198
1199 5
		$this->_add_field_to_array(
1200 5
			$field,
1201 5
			$this->meta_box['fields'][ $parent_field_id ]['fields'],
1202
			$position
1203 5
		);
1204
1205 5
		return array( $parent_field_id, $field['id'] );
1206
	}
1207
1208
	/**
1209
	 * Add a field array to a fields array in desired position
1210
	 * @since 2.0.2
1211
	 * @param array   $field    Metabox field config array
1212
	 * @param array   &$fields  Array (passed by reference) to append the field (array) to
1213
	 * @param integer $position Optionally specify a position in the array to be inserted
1214
	 */
1215 47
	protected function _add_field_to_array( $field, &$fields, $position = 0 ) {
1216 47
		if ( $position ) {
1217 1
			CMB2_Utils::array_insert( $fields, array( $field['id'] => $field ), $position );
1218 1
		} else {
1219 47
			$fields[ $field['id'] ] = $field;
1220
		}
1221 47
	}
1222
1223
	/**
1224
	 * Remove a field from the metabox
1225
	 * @since 2.0.0
1226
	 * @param  string $field_id        The field id of the field to remove
1227
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1228
	 * @return bool                    True if field was removed
1229
	 */
1230 2
	public function remove_field( $field_id, $parent_field_id = '' ) {
1231 2
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1232
1233 2
		if ( ! $ids ) {
1234
			return false;
1235
		}
1236
1237 2
		list( $field_id, $sub_field_id ) = $ids;
1238
1239 2
		unset( $this->fields[ implode( '', $ids ) ] );
1240
1241 2
		if ( ! $sub_field_id ) {
1242 1
			unset( $this->meta_box['fields'][ $field_id ] );
1243 1
			return true;
1244
		}
1245
1246 1
		if ( isset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] ) ) {
1247 1
			unset( $this->fields[ $field_id ]->args['fields'][ $sub_field_id ] );
1248 1
		}
1249 1
		if ( isset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] ) ) {
1250 1
			unset( $this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ] );
1251 1
		}
1252 1
		return true;
1253
	}
1254
1255
	/**
1256
	 * Update or add a property to a field
1257
	 * @since  2.0.0
1258
	 * @param  string $field_id        Field id
1259
	 * @param  string $property        Field property to set/update
1260
	 * @param  mixed  $value           Value to set the field property
1261
	 * @param  string $parent_field_id (optional) The field id of the group field to remove field from
1262
	 * @return mixed                   Field id. Strict compare to false, as success can return a falsey value (like 0)
1263
	 */
1264 4
	public function update_field_property( $field_id, $property, $value, $parent_field_id = '' ) {
1265 4
		$ids = $this->get_field_ids( $field_id, $parent_field_id );
1266
1267 4
		if ( ! $ids ) {
1268 2
			return false;
1269
		}
1270
1271 2
		list( $field_id, $sub_field_id ) = $ids;
1272
1273 2
		if ( ! $sub_field_id ) {
1274 2
			$this->meta_box['fields'][ $field_id ][ $property ] = $value;
1275 2
			return $field_id;
1276
		}
1277
1278
		$this->meta_box['fields'][ $field_id ]['fields'][ $sub_field_id ][ $property ] = $value;
1279
		return $field_id;
1280
	}
1281
1282
	/**
1283
	 * Check if field ids match a field and return the index/field id
1284
	 * @since  2.0.2
1285
	 * @param  string  $field_id        Field id
1286
	 * @param  string  $parent_field_id (optional) Parent field id
1287
	 * @return mixed                    Array of field/parent ids, or false
1288
	 */
1289 19
	public function get_field_ids( $field_id, $parent_field_id = '' ) {
1290 19
		$sub_field_id = $parent_field_id ? $field_id : '';
1291 19
		$field_id     = $parent_field_id ? $parent_field_id : $field_id;
1292 19
		$fields       =& $this->meta_box['fields'];
1293
1294 19
		if ( ! array_key_exists( $field_id, $fields ) ) {
1295 2
			$field_id = $this->search_old_school_array( $field_id, $fields );
1296 2
		}
1297
1298 19
		if ( false === $field_id ) {
1299 2
			return false;
1300
		}
1301
1302 17
		if ( ! $sub_field_id ) {
1303 17
			return array( $field_id, $sub_field_id );
1304
		}
1305
1306 3
		if ( 'group' !== $fields[ $field_id ]['type'] ) {
1307
			return false;
1308
		}
1309
1310 3
		if ( ! array_key_exists( $sub_field_id, $fields[ $field_id ]['fields'] ) ) {
1311
			$sub_field_id = $this->search_old_school_array( $sub_field_id, $fields[ $field_id ]['fields'] );
1312
		}
1313
1314 3
		return false === $sub_field_id ? false : array( $field_id, $sub_field_id );
1315
	}
1316
1317
	/**
1318
	 * When using the old array filter, it is unlikely field array indexes will be the field id
1319
	 * @since  2.0.2
1320
	 * @param  string $field_id The field id
1321
	 * @param  array  $fields   Array of fields to search
1322
	 * @return mixed            Field index or false
1323
	 */
1324 2
	public function search_old_school_array( $field_id, $fields ) {
1325 2
		$ids = wp_list_pluck( $fields, 'id' );
1326 2
		$index = array_search( $field_id, $ids );
1327 2
		return false !== $index ? $index : false;
1328
	}
1329
1330
	/**
1331
	 * Handles metabox property callbacks, and passes this $cmb object as property.
1332
	 * @since  2.2.3
1333
	 * @param  callable $cb The callback method/function/closure
1334
	 * @return mixed        Return of the callback function.
1335
	 */
1336 1
	protected function do_callback( $cb ) {
1337 1
		return call_user_func( $cb, $this );
1338
	}
1339
1340
	/**
1341
	 * Generate a unique nonce field for each registered meta_box
1342
	 * @since  2.0.0
1343
	 * @return string unique nonce hidden input
1344
	 */
1345 1
	public function nonce_field() {
1346 1
		wp_nonce_field( $this->nonce(), $this->nonce(), false, true );
1347 1
	}
1348
1349
	/**
1350
	 * Generate a unique nonce for each registered meta_box
1351
	 * @since  2.0.0
1352
	 * @return string unique nonce string
1353
	 */
1354 1
	public function nonce() {
1355 1
		if ( $this->generated_nonce ) {
1356 1
			return $this->generated_nonce;
1357
		}
1358 1
		$this->generated_nonce = sanitize_html_class( 'nonce_' . basename( __FILE__ ) . $this->cmb_id );
1359 1
		return $this->generated_nonce;
1360
	}
1361
1362
	/**
1363
	 * Whether this box is an "alternate context" box. This means the box has a 'context' property defined as:
1364
	 * 'form_top', 'before_permalink', 'after_title', or 'after_editor'.
1365
	 *
1366
	 * @since  2.2.4
1367
	 * @return bool
1368
	 */
1369 1
	public function is_alternate_context_box() {
1370 1
		return $this->prop( 'context' ) && in_array( $this->prop( 'context' ), array( 'form_top', 'before_permalink', 'after_title', 'after_editor' ) );
1371
	}
1372
1373
	/**
1374
	 * Magic getter for our object.
1375
	 * @param string $field
1376
	 * @throws Exception Throws an exception if the field is invalid.
1377
	 * @return mixed
1378
	 */
1379 48
	public function __get( $field ) {
1380
		switch ( $field ) {
1381 48
			case 'updated':
1382 48
			case 'has_columns':
1383 48
			case 'tax_metaboxes_to_remove':
1384 1
				return $this->{$field};
1385 48
			default:
1386 48
				return parent::__get( $field );
1387 48
		}
1388
	}
1389
1390
}
1391