Issues (408)

includes/CMB2_Base.php (6 issues)

1
<?php
2
/**
3
 * CMB2 Base - Base object functionality.
4
 *
5
 * @category  WordPress_Plugin
6
 * @package   CMB2
7
 * @author    CMB2 team
8
 * @license   GPL-2.0+
9
 * @link      https://cmb2.io
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
 */
0 ignored issues
show
Documentation Bug introduced by
The doc comment $args at position 0 could not be parsed: Unknown type name '$args' at position 0 in $args.
Loading history...
18
abstract class CMB2_Base {
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 17
	public function object_id( $object_id = 0 ) {
108 17
		if ( $object_id ) {
109 10
			$this->object_id = $object_id;
110
		}
111
112 17
		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 17
	public function object_type( $object_type = '' ) {
123 17
		if ( $object_type ) {
124 10
			$this->object_type = $object_type;
125
		}
126
127 17
		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;
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 60
	public function prop( $property, $fallback = null ) {
178 60
		if ( array_key_exists( $property, $this->{$this->properties_name} ) ) {
179 8
			return $this->{$this->properties_name}[ $property ];
180 59
		} elseif ( $fallback ) {
181 7
			return $this->{$this->properties_name}[ $property ] = $fallback;
182
		}
183 58
	}
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 8
	protected function get_default_args( $field_args, $field_group = null ) {
194 8
		if ( $field_group ) {
195
			$args = array(
196 1
				'field_args'  => $field_args,
197 1
				'group_field' => $field_group,
198
			);
199
		} else {
200
			$args = array(
201 7
				'field_args'  => $field_args,
202 7
				'object_type' => $this->object_type(),
203 7
				'object_id'   => $this->object_id(),
204 7
				'cmb_id'      => $this->cmb_id,
205
			);
206
		}
207
208 8
		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 14
	protected function get_new_field( $field_args, $field_group = null ) {
220 14
		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 48
	public function should_show() {
231
		// Default to showing this cmb
232 48
		$show = true;
233
234
		// Use the callback to determine showing the cmb, if it exists.
235 48
		if ( is_callable( $this->prop( 'show_on_cb' ) ) ) {
236 1
			$show = (bool) call_user_func( $this->prop( 'show_on_cb' ), $this );
237
		}
238
239 48
		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 91
	public function peform_param_callback( $param ) {
249 91
		echo $this->get_param_callback_result( $param );
250 91
	}
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 98
	public function get_param_callback_result( $param ) {
260
261
		// If we've already retrieved this param's value.
262 98
		if ( array_key_exists( $param, $this->callback_results ) ) {
263
264
			// Send it back.
265 16
			return $this->callback_results[ $param ];
266
		}
267
268
		// Check if parameter has registered a callback.
269 98
		if ( $cb = $this->maybe_callback( $param ) ) {
270
271
			// Ok, callback is good, let's run it and store the result.
272 54
			ob_start();
273 54
			$returned = $this->do_callback( $cb );
274
275
			// Grab the result from the output buffer and store it.
276 54
			$echoed = ob_get_clean();
277
278
			// This checks if the user returned or echoed their callback.
279
			// Defaults to using the echoed value.
280 54
			$this->callback_results[ $param ] = $echoed ? $echoed : $returned;
281
282
		} else {
283
284
			// Otherwise just get whatever is there.
285 93
			$this->callback_results[ $param ] = isset( $this->{$this->properties_name}[ $param ] ) ? $this->{$this->properties_name}[ $param ] : false;
286
		}
287
288 98
		return $this->callback_results[ $param ];
289
	}
290
291
	/**
292
	 * Unset the cached results of the param callback.
293
	 *
294
	 * @since  2.2.6
295
	 * @param  string $param Field parameter.
296
	 * @return CMB2_Base
297
	 */
298 1
	public function unset_param_callback_cache( $param ) {
299 1
		if ( isset( $this->callback_results[ $param ] ) ) {
300 1
			unset( $this->callback_results[ $param ] );
301
		}
302
303 1
		return $this;
304
	}
305
306
	/**
307
	 * Handles the parameter callbacks, and passes this object as parameter.
308
	 *
309
	 * @since  2.2.3
310
	 * @param  callable $cb                The callback method/function/closure.
311
	 * @param  mixed    $additional_params Any additoinal parameters which should be passed to the callback.
312
	 * @return mixed                       Return of the callback function.
313
	 */
314 54
	protected function do_callback( $cb, $additional_params = null ) {
315 54
		return call_user_func( $cb, $this->{$this->properties_name}, $this, $additional_params );
316
	}
317
318
	/**
319
	 * Checks if field has a callback value
320
	 *
321
	 * @since  1.0.1
322
	 * @param  string $cb Callback string.
323
	 * @return mixed      NULL, false for NO validation, or $cb string if it exists.
324
	 */
325 119
	public function maybe_callback( $cb ) {
326 119
		$args = $this->{$this->properties_name};
327 119
		if ( ! isset( $args[ $cb ] ) ) {
328 113
			return null;
329
		}
330
331
		// Check if requesting explicitly false.
332 55
		$cb = false !== $args[ $cb ] && 'false' !== $args[ $cb ] ? $args[ $cb ] : false;
333
334
		// If requesting NO validation, return false.
335 55
		if ( ! $cb ) {
336 48
			return false;
337
		}
338
339 54
		if ( is_callable( $cb ) ) {
340 54
			return $cb;
341
		}
342
343 4
		return null;
344
	}
345
346
	/**
347
	 * Checks if this object has parameter corresponding to the given filter
348
	 * which is callable. If so, it registers the callback, and if not,
349
	 * converts the maybe-modified $val to a boolean for return.
350
	 *
351
	 * The registered handlers will have a parameter name which matches the filter, except:
352
	 * - The 'cmb2_api' prefix will be removed
353
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
354
	 *
355
	 * @since  2.2.3
356
	 *
357
	 * @param  string $hook_name     The hook name.
358
	 * @param  bool   $val           The default value.
359
	 * @param  string $hook_function The hook function. Default: 'add_filter'.
360
	 *
361
	 * @return null|bool             Null if hook is registered, or bool for value.
362
	 */
363 13
	public function maybe_hook_parameter( $hook_name, $val = null, $hook_function = 'add_filter' ) {
364
365
		// Remove filter prefix, add param suffix.
366 13
		$parameter = substr( $hook_name, strlen( 'cmb2_api_' ) ) . '_cb';
367
368 13
		return self::maybe_hook(
369 13
			$this->prop( $parameter, $val ),
370 13
			$hook_name,
371 13
			$hook_function
372
		);
373
	}
374
375
	/**
376
	 * Checks if given value is callable, and registers the callback.
377
	 * If is non-callable, converts the $val to a boolean for return.
378
	 *
379
	 * @since  2.2.3
380
	 *
381
	 * @param  bool   $val           The default value.
382
	 * @param  string $hook_name     The hook name.
383
	 * @param  string $hook_function The hook function.
384
	 *
385
	 * @return null|bool         Null if hook is registered, or bool for value.
386
	 */
387 13
	public static function maybe_hook( $val, $hook_name, $hook_function ) {
388 13
		if ( is_callable( $val ) ) {
389 2
			call_user_func( $hook_function, $hook_name, $val, 10, 2 );
390 2
			return null;
391
		}
392
393
		// Cast to bool.
394 11
		return ! ! $val;
395
	}
396
397
	/**
398
	 * Mark a param as deprecated and inform when it has been used.
399
	 *
400
	 * There is a default WordPress hook deprecated_argument_run that will be called
401
	 * that can be used to get the backtrace up to what file and function used the
402
	 * deprecated argument.
403
	 *
404
	 * The current behavior is to trigger a user error if WP_DEBUG is true.
405
	 *
406
	 * @since 2.2.3
407
	 *
408
	 * @param string $function The function that was called.
409
	 * @param string $version  The version of CMB2 that deprecated the argument used.
410
	 * @param string $message  Optional. A message regarding the change, or numeric
411
	 *                         key to generate message from additional arguments.
412
	 *                         Default null.
413
	 */
414 1
	protected function deprecated_param( $function, $version, $message = null ) {
415
416 1
		if ( is_numeric( $message ) ) {
417 1
			$args = func_get_args();
418
419
			switch ( $message ) {
420
421 1
				case self::DEPRECATED_PARAM:
422
					$message = sprintf( __( 'The "%1$s" field parameter has been deprecated in favor of the "%2$s" parameter.', 'cmb2' ), $args[3], $args[4] );
0 ignored issues
show
The function __ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

422
					$message = sprintf( /** @scrutinizer ignore-call */ __( 'The "%1$s" field parameter has been deprecated in favor of the "%2$s" parameter.', 'cmb2' ), $args[3], $args[4] );
Loading history...
423
					break;
424
425 1
				case self::DEPRECATED_CB_PARAM:
426 1
					$message = sprintf( __( 'Using the "%1$s" field parameter as a callback has been deprecated in favor of the "%2$s" parameter.', 'cmb2' ), $args[3], $args[4] );
427 1
					break;
428
429
				default:
430
					$message = null;
431
					break;
432
			}
433
		}
434
435
		/**
436
		 * Fires when a deprecated argument is called. This is a WP core action.
437
		 *
438
		 * @since 2.2.3
439
		 *
440
		 * @param string $function The function that was called.
441
		 * @param string $message  A message regarding the change.
442
		 * @param string $version  The version of CMB2 that deprecated the argument used.
443
		 */
444 1
		do_action( 'deprecated_argument_run', $function, $message, $version );
445
446
		/**
447
		 * Filters whether to trigger an error for deprecated arguments. This is a WP core filter.
448
		 *
449
		 * @since 2.2.3
450
		 *
451
		 * @param bool $trigger Whether to trigger the error for deprecated arguments. Default true.
452
		 */
453 1
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) {
0 ignored issues
show
The constant WP_DEBUG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
454
			if ( function_exists( '__' ) ) {
455
				if ( ! is_null( $message ) ) {
456
					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 ) );
457
				} else {
458
					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 ) );
459
				}
460
			} else {
461
				if ( ! is_null( $message ) ) {
462
					trigger_error( sprintf( '%1$s was called with a parameter that is <strong>deprecated</strong> since version %2$s! %3$s', $function, $version, $message ) );
463
				} else {
464
					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 ) );
465
				}
466
			}
467
		}
468 1
	}
469
470
	/**
471
	 * Magic getter for our object.
472
	 *
473
	 * @param string $field Requested property.
474
	 * @throws Exception Throws an exception if the field is invalid.
475
	 * @return mixed
476
	 */
477 118
	public function __get( $field ) {
478
		switch ( $field ) {
479 118
			case 'args':
480 118
			case 'meta_box':
481 5
				if ( $field === $this->properties_name ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
482 5
					return $this->{$this->properties_name};
483
				}
484 118
			case 'properties':
485
				return $this->{$this->properties_name};
486 118
			case 'cmb_id':
487 42
			case 'object_id':
488 21
			case 'object_type':
489 118
				return $this->{$field};
490
			default:
491 2
				throw new Exception( sprintf( esc_html__( 'Invalid %1$s property: %2$s', 'cmb2' ), __CLASS__, $field ) );
492
		}
493
	}
494
495
	/**
496
	 * Allows overloading the object with methods... Whooaaa oooh it's magic, y'knoooow.
497
	 *
498
	 * @since 1.0.0
499
	 * @throws Exception Invalid method exception.
500
	 *
501
	 * @param string $method Non-existent method.
502 2
	 * @param array  $args   All arguments passed to the method.
503 2
	 * @return mixed
504
	 */
505 2
	public function __call( $method, $args ) {
506 1
		$object_class = strtolower( get_class( $this ) );
507
508
		if ( ! has_filter( "{$object_class}_inherit_{$method}" ) ) {
0 ignored issues
show
The function has_filter was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

508
		if ( ! /** @scrutinizer ignore-call */ has_filter( "{$object_class}_inherit_{$method}" ) ) {
Loading history...
509 1
			throw new Exception( sprintf( esc_html__( 'Invalid %1$s method: %2$s', 'cmb2' ), get_class( $this ), $method ) );
510
		}
511
512
		array_unshift( $args, $this );
513
514
		/**
515
		 * Allows overloading the object (CMB2 or CMB2_Field) with additional capabilities
516
		 * by registering hook callbacks.
517
		 *
518
		 * The first dynamic portion of the hook name, $object_class, refers to the object class,
519
		 * either cmb2 or cmb2_field.
520
		 *
521
		 * The second dynamic portion of the hook name, $method, is the non-existent method being
522
		 * called on the object. To avoid possible future methods encroaching on your hooks,
523
		 * use a unique method (aka, $cmb->prefix_my_method()).
524
		 *
525
		 * When registering your callback, you will need to ensure that you register the correct
526
		 * number of `$accepted_args`, accounting for this object instance being the first argument.
527
		 *
528 1
		 * @param array $args The arguments to be passed to the hook.
529
		 *                    The first argument will always be this object instance.
530
		 */
531
		return apply_filters_ref_array( "{$object_class}_inherit_{$method}", $args );
0 ignored issues
show
The function apply_filters_ref_array was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

531
		return /** @scrutinizer ignore-call */ apply_filters_ref_array( "{$object_class}_inherit_{$method}", $args );
Loading history...
532
	}
533
}
534