CMB2   D
last analyzed

Complexity

Total Complexity 195

Size/Duplication

Total Lines 1442
Duplicated Lines 1.73 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 83.3%

Importance

Changes 0
Metric Value
dl 25
loc 1442
ccs 439
cts 527
cp 0.833
rs 4.4102
c 0
b 0
f 0
wmc 195
lcom 1
cbo 6

45 Methods

Rating   Name   Duplication   Size   Complexity  
B get_sanitized_values() 0 30 1
B __construct() 0 28 4
A show_form() 0 9 2
B render_form_open() 0 38 1
B box_classes() 8 59 8
B render_form_close() 0 37 1
B render_field() 0 24 5
C render_group() 0 54 14
B group_wrap_attributes() 0 25 3
B render_group_row() 0 48 6
A add_hidden_field() 0 18 3
A render_hidden_fields() 0 7 3
A save_fields() 0 16 3
A process_fields() 0 14 2
B process_field() 0 27 5
A pre_process() 0 15 1
B after_save() 0 35 1
A save_group() 0 7 3
D save_group_field() 0 77 16
D object_id() 0 39 17
D mb_object_type() 0 40 10
A box_types() 0 3 1
A is_options_page_mb() 0 3 3
A object_type() 0 14 3
C current_object_type() 0 22 7
A set_prop() 0 5 1
A prop() 0 7 3
C get_field() 0 25 7
B get_field_args() 0 17 5
A get_default_args() 17 17 2
A get_new_field() 0 3 1
B add_fields() 0 18 5
D add_field() 0 37 10
A define_field_column() 0 12 3
B add_group_field() 0 23 4
A _add_field_to_array() 0 7 2
B remove_field() 0 24 5
A update_field_property() 0 17 3
D get_field_ids() 0 27 9
A search_old_school_array() 0 5 2
A do_callback() 0 3 1
A nonce_field() 0 3 1
A nonce() 0 7 2
A is_alternate_context_box() 0 3 2
A __get() 0 10 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CMB2 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CMB2, and based on these observations, apply Extract Interface, too.

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