CMB2_Base   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 497
Duplicated Lines 4.63 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 57.62%

Importance

Changes 0
Metric Value
dl 23
loc 497
ccs 87
cts 151
cp 0.5762
rs 6.433
c 0
b 0
f 0
wmc 57
lcom 1
cbo 1

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 4
A object_id() 0 7 2
A object_type() 0 7 2
A current_object_type() 0 18 4
A set_prop() 0 5 1
A prop() 0 7 3
A get_default_args() 17 17 2
A get_new_field() 0 3 1
A should_show() 0 11 2
A peform_param_callback() 0 3 1
B get_param_callback_result() 0 31 5
A do_callback() 0 3 1
B maybe_callback() 0 20 6
A maybe_hook_parameter() 0 11 1
A maybe_hook() 0 9 2
C deprecated_param() 6 55 10
B __get() 0 17 8
B __call() 0 28 2

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_Base 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_Base, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * CMB2 Base - Base object functionality.
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 $args        The objects array of properties/arguments.
12
 * @property-read $meta_box    The objects array of properties/arguments.
13
 * @property-read $properties  The objects array of properties/arguments.
14
 * @property-read $cmb_id      Current CMB2 instance ID
15
 * @property-read $object_id   Object ID
16
 * @property-read $object_type Type of object being handled. (e.g., post, user, comment, or term)
17
 */
18
abstract class 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...
19
20
	/**
21
	 * Current CMB2 instance ID
22
	 *
23
	 * @var   string
24
	 * @since 2.2.3
25
	 */
26
	protected $cmb_id = '';
27
28
	/**
29
	 * The object properties name.
30
	 *
31
	 * @var   string
32
	 * @since 2.2.3
33
	 */
34
	protected $properties_name = 'meta_box';
35
36
	/**
37
	 * Object ID
38
	 *
39
	 * @var   mixed
40
	 * @since 2.2.3
41
	 */
42
	protected $object_id = 0;
43
44
	/**
45
	 * Type of object being handled. (e.g., post, user, comment, or term)
46
	 *
47
	 * @var   string
48
	 * @since 2.2.3
49
	 */
50
	protected $object_type = '';
51
52
	/**
53
	 * Array of key => value data for saving. Likely $_POST data.
54
	 *
55
	 * @var   array
56
	 * @since 2.2.3
57
	 */
58
	public $data_to_save = array();
59
60
	/**
61
	 * Array of field param callback results
62
	 *
63
	 * @var   array
64
	 * @since 2.0.0
65
	 */
66
	protected $callback_results = array();
67
68
	/**
69
	 * The deprecated_param method deprecated param message signature.
70
	 */
71
	const DEPRECATED_PARAM = 1;
72
73
	/**
74
	 * The deprecated_param method deprecated callback param message signature.
75
	 */
76
	const DEPRECATED_CB_PARAM = 2;
77
78
	/**
79
	 * Get started
80
	 *
81
	 * @since 2.2.3
82
	 * @param array $args Object properties array
83
	 */
84
	public function __construct( $args = array() ) {
85
		if ( ! empty( $args ) ) {
86
			foreach ( array(
87
				'cmb_id',
88
				'properties_name',
89
				'object_id',
90
				'object_type',
91
				'data_to_save',
92
			) as $object_prop ) {
93
				if ( isset( $args[ $object_prop ] ) ) {
94
					$this->{$object_prop} = $args[ $object_prop ];
95
				}
96
			}
97
		}
98
	}
99
100
	/**
101
	 * Returns the object ID
102
	 *
103
	 * @since  2.2.3
104
	 * @param  integer $object_id Object ID
105
	 * @return integer Object ID
106
	 */
107 6
	public function object_id( $object_id = 0 ) {
108 6
		if ( $object_id ) {
109
			$this->object_id = $object_id;
110
		}
111
112 6
		return $this->object_id;
113
	}
114
115
	/**
116
	 * Returns the object type
117
	 *
118
	 * @since  2.2.3
119
	 * @param  string $object_type Object Type
120
	 * @return string Object type
121
	 */
122 6
	public function object_type( $object_type = '' ) {
123 6
		if ( $object_type ) {
124
			$this->object_type = $object_type;
125
		}
126
127 6
		return $this->object_type;
128
	}
129
130
	/**
131
	 * Get the object type for the current page, based on the $pagenow global.
132
	 *
133
	 * @since  2.2.2
134
	 * @return string  Page object type name.
135
	 */
136
	public function current_object_type() {
137
		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...
138
		$type = 'post';
139
140
		if ( in_array( $pagenow, array( 'user-edit.php', 'profile.php', 'user-new.php' ), true ) ) {
141
			$type = 'user';
142
		}
143
144
		if ( in_array( $pagenow, array( 'edit-comments.php', 'comment.php' ), true ) ) {
145
			$type = 'comment';
146
		}
147
148
		if ( in_array( $pagenow, array( 'edit-tags.php', 'term.php' ), true ) ) {
149
			$type = 'term';
150
		}
151
152
		return $type;
153
	}
154
155
	/**
156
	 * Set object property.
157
	 *
158
	 * @since  2.2.2
159
	 * @param  string $property Metabox config property to retrieve
160
	 * @param  mixed  $value    Value to set if no value found
161
	 * @return mixed            Metabox config property value or false
162
	 */
163
	public function set_prop( $property, $value ) {
164
		$this->{$this->properties_name}[ $property ] = $value;
165
166
		return $this->prop( $property );
167
	}
168
169
	/**
170
	 * Get object property and optionally set a fallback
171
	 *
172
	 * @since  2.0.0
173
	 * @param  string $property Metabox config property to retrieve
174
	 * @param  mixed  $fallback Fallback value to set if no value found
175
	 * @return mixed            Metabox config property value or false
176
	 */
177 43
	public function prop( $property, $fallback = null ) {
178 43
		if ( array_key_exists( $property, $this->{$this->properties_name} ) ) {
179 1
			return $this->{$this->properties_name}[ $property ];
180 42
		} elseif ( $fallback ) {
181
			return $this->{$this->properties_name}[ $property ] = $fallback;
182
		}
183 42
	}
184
185
	/**
186
	 * Get default field arguments specific to this CMB2 object.
187
	 *
188
	 * @since  2.2.0
189
	 * @param  array      $field_args  Metabox field config array.
190
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
191
	 * @return array                   Array of field arguments.
192
	 */
193 7 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...
194 7
		if ( $field_group ) {
195
			$args = array(
196 1
				'field_args'  => $field_args,
197 1
				'group_field' => $field_group,
198 1
			);
199 1
		} else {
200
			$args = array(
201 6
				'field_args'  => $field_args,
202 6
				'object_type' => $this->object_type(),
203 6
				'object_id'   => $this->object_id(),
204 6
				'cmb_id'      => $this->cmb_id,
205 6
			);
206
		}
207
208 7
		return $args;
209
	}
210
211
	/**
212
	 * Get a new field object specific to this CMB2 object.
213
	 *
214
	 * @since  2.2.0
215
	 * @param  array      $field_args  Metabox field config array.
216
	 * @param  CMB2_Field $field_group (optional) CMB2_Field object (group parent)
217
	 * @return CMB2_Field CMB2_Field object
218
	 */
219 7
	protected function get_new_field( $field_args, $field_group = null ) {
220 7
		return new CMB2_Field( $this->get_default_args( $field_args, $field_group ) );
221
	}
222
223
	/**
224
	 * Determine whether this cmb object should show, based on the 'show_on_cb' callback.
225
	 *
226
	 * @since 2.0.9
227
	 *
228
	 * @return bool Whether this cmb should be shown.
229
	 */
230 43
	public function should_show() {
231
		// Default to showing this cmb
232 43
		$show = true;
233
234
		// Use the callback to determine showing the cmb, if it exists
235 43
		if ( is_callable( $this->prop( 'show_on_cb' ) ) ) {
236 1
			$show = (bool) call_user_func( $this->prop( 'show_on_cb' ), $this );
237 1
		}
238
239 43
		return $show;
240
	}
241
242
	/**
243
	 * Displays the results of the param callbacks.
244
	 *
245
	 * @since 2.0.0
246
	 * @param string $param Field parameter
247
	 */
248 80
	public function peform_param_callback( $param ) {
249 80
		echo $this->get_param_callback_result( $param );
250 80
	}
251
252
	/**
253
	 * Store results of the param callbacks for continual access
254
	 *
255
	 * @since  2.0.0
256
	 * @param  string $param Field parameter
257
	 * @return mixed         Results of param/param callback
258
	 */
259 86
	public function get_param_callback_result( $param ) {
260
261
		// If we've already retrieved this param's value,
262 86
		if ( array_key_exists( $param, $this->callback_results ) ) {
263
264
			// send it back
265 10
			return $this->callback_results[ $param ];
266
		}
267
268
		// Check if parameter has registered a callback.
269 86
		if ( $cb = $this->maybe_callback( $param ) ) {
270
271
			// Ok, callback is good, let's run it and store the result.
272 49
			ob_start();
273 49
			$returned = $this->do_callback( $cb );
274
275
			// Grab the result from the output buffer and store it.
276 49
			$echoed = ob_get_clean();
277
278
			// This checks if the user returned or echoed their callback.
279
			// Defaults to using the echoed value.
280 49
			$this->callback_results[ $param ] = $echoed ? $echoed : $returned;
281
282 49
		} else {
283
284
			// Otherwise just get whatever is there.
285 81
			$this->callback_results[ $param ] = isset( $this->{$this->properties_name}[ $param ] ) ? $this->{$this->properties_name}[ $param ] : false;
286
		}
287
288 86
		return $this->callback_results[ $param ];
289
	}
290
291
	/**
292
	 * Handles the parameter callbacks, and passes this object as parameter.
293
	 *
294
	 * @since  2.2.3
295
	 * @param  callable $cb The callback method/function/closure
296
	 * @return mixed        Return of the callback function.
297
	 */
298 49
	protected function do_callback( $cb ) {
299 49
		return call_user_func( $cb, $this->{$this->properties_name}, $this );
300
	}
301
302
	/**
303
	 * Checks if field has a callback value
304
	 *
305
	 * @since  1.0.1
306
	 * @param  string $cb Callback string
307
	 * @return mixed      NULL, false for NO validation, or $cb string if it exists.
308
	 */
309 100
	public function maybe_callback( $cb ) {
310 100
		$args = $this->{$this->properties_name};
311 100
		if ( ! isset( $args[ $cb ] ) ) {
312 94
			return null;
313
		}
314
315
		// Check if requesting explicitly false
316 49
		$cb = false !== $args[ $cb ] && 'false' !== $args[ $cb ] ? $args[ $cb ] : false;
317
318
		// If requesting NO validation, return false
319 49
		if ( ! $cb ) {
320 42
			return false;
321
		}
322
323 49
		if ( is_callable( $cb ) ) {
324 49
			return $cb;
325
		}
326
327 4
		return null;
328
	}
329
330
	/**
331
	 * Checks if this object has parameter corresponding to the given filter
332
	 * which is callable. If so, it registers the callback, and if not,
333
	 * converts the maybe-modified $val to a boolean for return.
334
	 *
335
	 * The registered handlers will have a parameter name which matches the filter, except:
336
	 * - The 'cmb2_api' prefix will be removed
337
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
338
	 *
339
	 * @since  2.2.3
340
	 *
341
	 * @param  string $hook_name     The hook name.
342
	 * @param  bool   $val           The default value.
343
	 * @param  string $hook_function The hook function. Default: 'add_filter'
344
	 *
345
	 * @return null|bool             Null if hook is registered, or bool for value.
346
	 */
347
	public function maybe_hook_parameter( $hook_name, $val = null, $hook_function = 'add_filter' ) {
348
349
		// Remove filter prefix, add param suffix.
350
		$parameter = substr( $hook_name, strlen( 'cmb2_api_' ) ) . '_cb';
351
352
		return self::maybe_hook(
353
			$this->prop( $parameter, $val ),
354
			$hook_name,
355
			$hook_function
356
		);
357
	}
358
359
	/**
360
	 * Checks if given value is callable, and registers the callback.
361
	 * If is non-callable, converts the $val to a boolean for return.
362
	 *
363
	 * @since  2.2.3
364
	 *
365
	 * @param  bool   $val           The default value.
366
	 * @param  string $hook_name     The hook name.
367
	 * @param  string $hook_function The hook function.
368
	 *
369
	 * @return null|bool         Null if hook is registered, or bool for value.
370
	 */
371
	public static function maybe_hook( $val, $hook_name, $hook_function ) {
372
		if ( is_callable( $val ) ) {
373
			$hook_function( $hook_name, $val, 10, 2 );
374
			return null;
375
		}
376
377
		// Cast to bool.
378
		return ! ! $val;
379
	}
380
381
	/**
382
	 * Mark a param as deprecated and inform when it has been used.
383
	 *
384
	 * There is a default WordPress hook deprecated_argument_run that will be called
385
	 * that can be used to get the backtrace up to what file and function used the
386
	 * deprecated argument.
387
	 *
388
	 * The current behavior is to trigger a user error if WP_DEBUG is true.
389
	 *
390
	 * @since 2.2.3
391
	 *
392
	 * @param string $function The function that was called.
393
	 * @param string $version  The version of CMB2 that deprecated the argument used.
394
	 * @param string $message  Optional. A message regarding the change, or numeric
395
	 *                         key to generate message from additional arguments.
396
	 *                         Default null.
397
	 */
398 1
	protected function deprecated_param( $function, $version, $message = null ) {
399
400 1
		if ( is_numeric( $message ) ) {
401 1
			$args = func_get_args();
402
403
			switch ( $message ) {
404
405 1 View Code Duplication
				case self::DEPRECATED_PARAM:
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...
406
					$message = sprintf( __( 'The "%1$s" field parameter has been deprecated in favor of the "%1$s" parameter.', 'cmb2' ), $args[3], $args[4] );
407
					break;
408
409 1 View Code Duplication
				case self::DEPRECATED_CB_PARAM:
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...
410 1
					$message = sprintf( __( 'Using the "%1$s" field parameter as a callback has been deprecated in favor of the "%1$s" parameter.', 'cmb2' ), $args[3], $args[4] );
411 1
					break;
412
413
				default:
414
					$message = null;
415
					break;
416
			}
417 1
		}
418
419
		/**
420
		 * Fires when a deprecated argument is called. This is a WP core action.
421
		 *
422
		 * @since 2.2.3
423
		 *
424
		 * @param string $function The function that was called.
425
		 * @param string $message  A message regarding the change.
426
		 * @param string $version  The version of CMB2 that deprecated the argument used.
427
		 */
428 1
		do_action( 'deprecated_argument_run', $function, $message, $version );
429
430
		/**
431
		 * Filters whether to trigger an error for deprecated arguments. This is a WP core filter.
432
		 *
433
		 * @since 2.2.3
434
		 *
435
		 * @param bool $trigger Whether to trigger the error for deprecated arguments. Default true.
436
		 */
437 1
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) {
438
			if ( function_exists( '__' ) ) {
439
				if ( ! is_null( $message ) ) {
440
					trigger_error( sprintf( __( '%1$s was called with a parameter that is <strong>deprecated</strong> since version %2$s! %3$s', 'cmb2' ), $function, $version, $message ) );
441
				} else {
442
					trigger_error( sprintf( __( '%1$s was called with a parameter that is <strong>deprecated</strong> since version %2$s with no alternative available.', 'cmb2' ), $function, $version ) );
443
				}
444
			} else {
445
				if ( ! is_null( $message ) ) {
446
					trigger_error( sprintf( '%1$s was called with a parameter that is <strong>deprecated</strong> since version %2$s! %3$s', $function, $version, $message ) );
447
				} else {
448
					trigger_error( sprintf( '%1$s was called with a parameter that is <strong>deprecated</strong> since version %2$s with no alternative available.', $function, $version ) );
449
				}
450
			}
451
		}
452 1
	}
453
454
	/**
455
	 * Magic getter for our object.
456
	 *
457
	 * @param string $field
458
	 * @throws Exception Throws an exception if the field is invalid.
459
	 * @return mixed
460
	 */
461 69
	public function __get( $field ) {
462
		switch ( $field ) {
463 69
			case 'args':
464 69
			case 'meta_box':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
465 3
				if ( $field === $this->properties_name ) {
466 3
					return $this->{$this->properties_name};
467
				}
468 69
			case 'properties':
469
				return $this->{$this->properties_name};
470 69
			case 'cmb_id':
471 69
			case 'object_id':
472 69
			case 'object_type':
473 69
				return $this->{$field};
474 2
			default:
475 2
				throw new Exception( sprintf( esc_html__( 'Invalid %1$s property: %2$s', 'cmb2' ), __CLASS__, $field ) );
476 2
		}
477
	}
478
479
	/**
480
	 * Allows overloading the object with methods... Whooaaa oooh it's magic, y'knoooow.
481
	 *
482
	 * @since 1.0.0
483
	 * @param string $method Non-existent method.
484
	 * @param array  $args   All arguments passed to the method
485
	 */
486 2
	public function __call( $method, $args ) {
487 2
		$object_class = strtolower( get_class( $this ) );
488
489 2
		if ( ! has_filter( "{$object_class}_inherit_{$method}" ) ) {
490 1
			throw new Exception( sprintf( esc_html__( 'Invalid %1$s method: %2$s', 'cmb2' ), get_class( $this ), $method ) );
491
		}
492
493 1
		array_unshift( $args, $this );
494
495
		/**
496
		 * Allows overloading the object (CMB2 or CMB2_Field) with additional capabilities
497
		 * by registering hook callbacks.
498
		 *
499
		 * The first dynamic portion of the hook name, $object_class, refers to the object class,
500
		 * either cmb2 or cmb2_field.
501
		 *
502
		 * The second dynamic portion of the hook name, $method, is the non-existent method being
503
		 * called on the object. To avoid possible future methods encroaching on your hooks,
504
		 * use a unique method (aka, $cmb->prefix_my_method()).
505
		 *
506
		 * When registering your callback, you will need to ensure that you register the correct
507
		 * number of `$accepted_args`, accounting for this object instance being the first argument.
508
		 *
509
		 * @param array $args The arguments to be passed to the hook.
510
		 *                    The first argument will always be this object instance.
511
		 */
512 1
		return apply_filters_ref_array( "{$object_class}_inherit_{$method}", $args );
513
	}
514
}
515