CMB2_REST_Controller   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 424
Duplicated Lines 0 %

Test Coverage

Coverage 87.78%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 37
eloc 95
c 1
b 0
f 0
dl 0
loc 424
ccs 79
cts 90
cp 0.8778
rs 9.44

15 Methods

Rating   Name   Duplication   Size   Complexity  
A maybe_hook_callback_and_apply_filters() 0 20 3
A maybe_unhook_registered_callback() 0 7 3
A maybe_hook_registered_callback() 0 12 4
A get_intial_request_type() 0 2 1
A prepare_item() 0 2 1
A prepare_item_for_response() 0 13 1
A get_query_string() 0 21 5
A initiate_rest_edit_box() 0 6 3
A initiate_rest_read_box() 0 6 3
A get_item_schema() 0 31 1
A get_intial_route() 0 2 1
A initiate_request() 0 13 5
A get_cb_results() 0 7 1
A initiate_rest_box() 0 19 4
A __construct() 0 2 1
1
<?php
2
if ( ! class_exists( 'WP_REST_Controller' ) ) {
3
	// Shim the WP_REST_Controller class if wp-api plugin not installed, & not in core.
4
	require_once cmb2_dir( 'includes/shim/WP_REST_Controller.php' );
5
}
6
7
/**
8
 * Creates CMB2 objects/fields endpoint for WordPres REST API.
9
 * Allows access to fields registered to a specific post type and more.
10
 *
11
 * @todo  Add better documentation.
12
 * @todo  Research proper schema.
13
 *
14
 * @since 2.2.3
15
 *
16
 * @category  WordPress_Plugin
17
 * @package   CMB2
18
 * @author    CMB2 team
19
 * @license   GPL-2.0+
20
 * @link      https://cmb2.io
21
 */
22
abstract class CMB2_REST_Controller extends WP_REST_Controller {
0 ignored issues
show
Bug introduced by
The type WP_REST_Controller was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
24
	/**
25
	 * The namespace of this controller's route.
26
	 *
27
	 * @var string
28
	 */
29
	protected $namespace = CMB2_REST::NAME_SPACE;
30
31
	/**
32
	 * The base of this controller's route.
33
	 *
34
	 * @var string
35
	 */
36
	protected $rest_base;
37
38
	/**
39
	 * The current request object
40
	 *
41
	 * @var WP_REST_Request $request
0 ignored issues
show
Bug introduced by
The type WP_REST_Request was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
42
	 * @since 2.2.3
43
	 */
44
	public $request;
45
46
	/**
47
	 * The current server object
48
	 *
49
	 * @var WP_REST_Server $server
0 ignored issues
show
Bug introduced by
The type WP_REST_Server was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
50
	 * @since 2.2.3
51
	 */
52
	public $server;
53
54
	/**
55
	 * Box object id
56
	 *
57
	 * @var   mixed
58
	 * @since 2.2.3
59
	 */
60
	public $object_id = null;
61
62
	/**
63
	 * Box object type
64
	 *
65
	 * @var   string
66
	 * @since 2.2.3
67
	 */
68
	public $object_type = '';
69
70
	/**
71
	 * CMB2 Instance
72
	 *
73
	 * @var CMB2_REST
74
	 */
75
	protected $rest_box;
76
77
	/**
78
	 * CMB2_Field Instance
79
	 *
80
	 * @var CMB2_Field
81
	 */
82
	protected $field;
83
84
	/**
85
	 * The initial route
86
	 *
87
	 * @var   string
88
	 * @since 2.2.3
89
	 */
90
	protected static $route = '';
91
92
	/**
93
	 * Defines which endpoint the initial request is.
94
	 *
95
	 * @var string $request_type
96
	 * @since 2.2.3
97
	 */
98
	protected static $request_type = '';
99
100
	/**
101
	 * Constructor
102
	 *
103
	 * @since 2.2.3
104
	 */
105 1
	public function __construct( WP_REST_Server $wp_rest_server ) {
106 1
		$this->server = $wp_rest_server;
107 1
	}
108
109
	/**
110
	 * A wrapper for `apply_filters` which checks for box/field properties to hook to the filter.
111
	 *
112
	 * Checks if a CMB object callback property exists, and if it does,
113
	 * hook it to the permissions filter.
114
	 *
115
	 * @since  2.2.3
116
	 *
117
	 * @param  string $filter         The name of the filter to apply.
118
	 * @param  bool   $default_access The default access for this request.
119
	 *
120
	 * @return void
121
	 */
122 13
	public function maybe_hook_callback_and_apply_filters( $filter, $default_access ) {
123 13
		if ( ! $this->rest_box && $this->request->get_param( 'cmb_id' ) ) {
124
			$this->rest_box = CMB2_REST::get_rest_box( $this->request->get_param( 'cmb_id' ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like CMB2_REST::get_rest_box(...t->get_param('cmb_id')) can also be of type false. However, the property $rest_box is declared as type CMB2_REST. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
125
		}
126
127 13
		$default_access = $this->maybe_hook_registered_callback( $filter, $default_access );
128
129
		/**
130
		 * Apply the permissions check filter.
131
		 *
132
		 * @since 2.2.3
133
		 *
134
		 * @param bool   $default_access Whether this CMB2 endpoint can be accessed.
135
		 * @param object $controller     This CMB2_REST_Controller object.
136
		 */
137 13
		$default_access = apply_filters( $filter, $default_access, $this );
138
139 11
		$this->maybe_unhook_registered_callback( $filter );
140
141 11
		return $default_access;
142
	}
143
144
	/**
145
	 * Checks if the CMB2 box has any registered callback parameters for the given filter.
146
	 *
147
	 * The registered handlers will have a property name which matches the filter, except:
148
	 * - The 'cmb2_api' prefix will be removed
149
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
150
	 *
151
	 * @since  2.2.3
152
	 *
153
	 * @param  string $filter      The filter name.
154
	 * @param  bool   $default_val The default filter value.
155
	 *
156
	 * @return bool                The possibly-modified filter value (if the '*_cb' param is non-callable).
157
	 */
158 13
	public function maybe_hook_registered_callback( $filter, $default_val ) {
159 13
		if ( ! $this->rest_box || is_wp_error( $this->rest_box ) ) {
0 ignored issues
show
Bug introduced by
The function is_wp_error 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

159
		if ( ! $this->rest_box || /** @scrutinizer ignore-call */ is_wp_error( $this->rest_box ) ) {
Loading history...
160 1
			return $default_val;
161
		}
162
163
		// Hook box specific filter callbacks.
164 13
		$val = $this->rest_box->cmb->maybe_hook_parameter( $filter, $default_val );
165 13
		if ( null !== $val ) {
166 11
			$default_val = $val;
167
		}
168
169 13
		return $default_val;
170
	}
171
172
	/**
173
	 * Unhooks any CMB2 box registered callback parameters for the given filter.
174
	 *
175
	 * @since  2.2.3
176
	 *
177
	 * @param  string $filter The filter name.
178
	 *
179
	 * @return void
180
	 */
181 11
	public function maybe_unhook_registered_callback( $filter ) {
182 11
		if ( ! $this->rest_box || is_wp_error( $this->rest_box ) ) {
0 ignored issues
show
Bug introduced by
The function is_wp_error 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

182
		if ( ! $this->rest_box || /** @scrutinizer ignore-call */ is_wp_error( $this->rest_box ) ) {
Loading history...
183 1
			return;
184
		}
185
186
		// Unhook box specific filter callbacks.
187 11
		$this->rest_box->cmb->maybe_hook_parameter( $filter, null, 'remove_filter' );
188 11
	}
189
190
	/**
191
	 * Prepare a CMB2 object for serialization
192
	 *
193
	 * @since 2.2.3
194
	 *
195
	 * @param  mixed $data
196
	 * @return array $data
197
	 */
198 7
	public function prepare_item( $data ) {
199 7
		return $this->prepare_item_for_response( $data, $this->request );
200
	}
201
202
	/**
203
	 * Output buffers a callback and returns the results.
204
	 *
205
	 * @since  2.2.3
206
	 *
207
	 * @param  mixed $cb Callable function/method.
208
	 * @return mixed     Results of output buffer after calling function/method.
209
	 */
210 5
	public function get_cb_results( $cb ) {
211 5
		$args = func_get_args();
212 5
		array_shift( $args ); // ignore $cb
213 5
		ob_start();
214 5
		call_user_func_array( $cb, $args );
215
216 5
		return ob_get_clean();
217
	}
218
219
	/**
220
	 * Prepare the CMB2 item for the REST response.
221
	 *
222
	 * @since 2.2.3
223
	 *
224
	 * @param  mixed           $item     WordPress representation of the item.
225
	 * @param  WP_REST_Request $request  Request object.
226
	 * @return WP_REST_Response $response
0 ignored issues
show
Bug introduced by
The type WP_REST_Response was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
227
	 */
228 7
	public function prepare_item_for_response( $data, $request = null ) {
229 7
		$data = $this->filter_response_by_context( $data, $this->request['context'] );
230
231
		/**
232
		 * Filter the prepared CMB2 item response.
233
		 *
234
		 * @since 2.2.3
235
		 *
236
		 * @param mixed  $data           Prepared data
237
		 * @param object $request        The WP_REST_Request object
238
		 * @param object $cmb2_endpoints This endpoints object
239
		 */
240 7
		return apply_filters( 'cmb2_rest_prepare', rest_ensure_response( $data ), $this->request, $this );
0 ignored issues
show
Bug introduced by
The function rest_ensure_response 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

240
		return apply_filters( 'cmb2_rest_prepare', /** @scrutinizer ignore-call */ rest_ensure_response( $data ), $this->request, $this );
Loading history...
241
	}
242
243
	/**
244
	 * Initiates the request property and the rest_box property if box is readable.
245
	 *
246
	 * @since  2.2.3
247
	 *
248
	 * @param  WP_REST_Request $request      Request object.
249
	 * @param  string          $request_type A description of the type of request being made.
250
	 *
251
	 * @return void
252
	 */
253 12
	protected function initiate_rest_read_box( $request, $request_type ) {
254 12
		$this->initiate_rest_box( $request, $request_type );
255
256 12
		if ( ! is_wp_error( $this->rest_box ) && ! $this->rest_box->rest_read ) {
0 ignored issues
show
Bug introduced by
The function is_wp_error 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

256
		if ( ! /** @scrutinizer ignore-call */ is_wp_error( $this->rest_box ) && ! $this->rest_box->rest_read ) {
Loading history...
257 1
			$this->rest_box = new WP_Error( 'cmb2_rest_no_read_error', __( 'This box does not have read permissions.', 'cmb2' ), array(
0 ignored issues
show
Bug introduced by
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

257
			$this->rest_box = new WP_Error( 'cmb2_rest_no_read_error', /** @scrutinizer ignore-call */ __( 'This box does not have read permissions.', 'cmb2' ), array(
Loading history...
258 1
				'status' => 403,
259
			) );
260
		}
261 12
	}
262
263
	/**
264
	 * Initiates the request property and the rest_box property if box is writeable.
265
	 *
266
	 * @since  2.2.3
267
	 *
268
	 * @param  WP_REST_Request $request      Request object.
269
	 * @param  string          $request_type A description of the type of request being made.
270
	 *
271
	 * @return void
272
	 */
273
	protected function initiate_rest_edit_box( $request, $request_type ) {
274
		$this->initiate_rest_box( $request, $request_type );
275
276
		if ( ! is_wp_error( $this->rest_box ) && ! $this->rest_box->rest_edit ) {
0 ignored issues
show
Bug introduced by
The function is_wp_error 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

276
		if ( ! /** @scrutinizer ignore-call */ is_wp_error( $this->rest_box ) && ! $this->rest_box->rest_edit ) {
Loading history...
277
			$this->rest_box = new WP_Error( 'cmb2_rest_no_write_error', __( 'This box does not have write permissions.', 'cmb2' ), array(
0 ignored issues
show
Bug introduced by
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

277
			$this->rest_box = new WP_Error( 'cmb2_rest_no_write_error', /** @scrutinizer ignore-call */ __( 'This box does not have write permissions.', 'cmb2' ), array(
Loading history...
278
				'status' => 403,
279
			) );
280
		}
281
	}
282
283
	/**
284
	 * Initiates the request property and the rest_box property.
285
	 *
286
	 * @since  2.2.3
287
	 *
288
	 * @param  WP_REST_Request $request      Request object.
289
	 * @param  string          $request_type A description of the type of request being made.
290
	 *
291
	 * @return void
292
	 */
293 12
	protected function initiate_rest_box( $request, $request_type ) {
294 12
		$this->initiate_request( $request, $request_type );
295
296 12
		$this->rest_box = CMB2_REST::get_rest_box( $this->request->get_param( 'cmb_id' ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like CMB2_REST::get_rest_box(...t->get_param('cmb_id')) can also be of type false. However, the property $rest_box is declared as type CMB2_REST. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
297
298 12
		if ( ! $this->rest_box ) {
299
300 1
			$this->rest_box = new WP_Error( 'cmb2_rest_box_not_found_error', __( 'No box found by that id. A box needs to be registered with the "show_in_rest" parameter configured.', 'cmb2' ), array(
0 ignored issues
show
Bug introduced by
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

300
			$this->rest_box = new WP_Error( 'cmb2_rest_box_not_found_error', /** @scrutinizer ignore-call */ __( 'No box found by that id. A box needs to be registered with the "show_in_rest" parameter configured.', 'cmb2' ), array(
Loading history...
301 1
				'status' => 403,
302
			) );
303
304
		} else {
305
306 12
			if ( isset( $this->request['object_id'] ) ) {
307 6
				$this->rest_box->cmb->object_id( sanitize_text_field( $this->request['object_id'] ) );
308
			}
309
310 12
			if ( isset( $this->request['object_type'] ) ) {
311 5
				$this->rest_box->cmb->object_type( sanitize_text_field( $this->request['object_type'] ) );
312
			}
313
		}
314 12
	}
315
316
	/**
317
	 * Initiates the request property and sets up the initial static properties.
318
	 *
319
	 * @since  2.2.3
320
	 *
321
	 * @param  WP_REST_Request $request      Request object.
322
	 * @param  string          $request_type A description of the type of request being made.
323
	 *
324
	 * @return void
325
	 */
326 13
	public function initiate_request( $request, $request_type ) {
327 13
		$this->request = $request;
328
329 13
		if ( ! isset( $this->request['context'] ) || empty( $this->request['context'] ) ) {
330 13
			$this->request['context'] = 'view';
331
		}
332
333 13
		if ( ! self::$request_type ) {
334 1
			self::$request_type = $request_type;
335
		}
336
337 13
		if ( ! self::$route ) {
338 1
			self::$route = $this->request->get_route();
339
		}
340 13
	}
341
342
	/**
343
	 * Useful when getting `_embed`-ed items
344
	 *
345
	 * @since  2.2.3
346
	 *
347
	 * @return string  Initial requested type.
348
	 */
349
	public static function get_intial_request_type() {
350
		return self::$request_type;
351
	}
352
353
	/**
354
	 * Useful when getting `_embed`-ed items
355
	 *
356
	 * @since  2.2.3
357
	 *
358
	 * @return string  Initial requested route.
359
	 */
360
	public static function get_intial_route() {
361
		return self::$route;
362
	}
363
364
	/**
365
	 * Get CMB2 fields schema, conforming to JSON Schema
366
	 *
367
	 * @since 2.2.3
368
	 *
369
	 * @return array
370
	 */
371 8
	public function get_item_schema() {
372
		$schema = array(
373 8
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
374 8
			'title'                => 'CMB2',
375 8
			'type'                 => 'object',
376
			'properties'           => array(
377
				'description' => array(
378 8
					'description' => __( 'A human-readable description of the object.', 'cmb2' ),
0 ignored issues
show
Bug introduced by
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

378
					'description' => /** @scrutinizer ignore-call */ __( 'A human-readable description of the object.', 'cmb2' ),
Loading history...
379 8
					'type'        => 'string',
380
					'context'     => array(
381
						'view',
382
					),
383
				),
384
				'name' => array(
385 8
					'description' => __( 'The id for the object.', 'cmb2' ),
386 8
					'type'        => 'integer',
387
					'context'     => array(
388
						'view',
389
					),
390
				),
391
				'name' => array(
392 8
					'description' => __( 'The title for the object.', 'cmb2' ),
393 8
					'type'        => 'string',
394
					'context'     => array(
395
						'view',
396
					),
397
				),
398
			),
399
		);
400
401 8
		return $this->add_additional_fields_schema( $schema );
402
	}
403
404
	/**
405
	 * Return an array of contextual links for endpoint/object
406
	 *
407
	 * @link http://v2.wp-api.org/extending/linking/
408
	 * @link http://www.iana.org/assignments/link-relations/link-relations.xhtml
409
	 *
410
	 * @since  2.2.3
411
	 *
412
	 * @param  mixed $object Object to build links from.
413
	 *
414
	 * @return array          Array of links
415
	 */
416
	abstract protected function prepare_links( $object );
417
418
	/**
419
	 * Get whitelisted query strings from URL for appending to link URLS.
420
	 *
421
	 * @since  2.2.3
422
	 *
423
	 * @return string URL query stringl
424
	 */
425 7
	public function get_query_string() {
426
		$defaults = array(
427 7
			'object_id'   => 0,
428
			'object_type' => '',
429
			'_rendered'   => '',
430
			// '_embed'      => '',
431
		);
432
433 7
		$query_string = '';
434
435 7
		foreach ( $defaults as $key => $value ) {
436 7
			if ( isset( $this->request[ $key ] ) ) {
437 3
				$query_string .= $query_string ? '&' : '?';
438 3
				$query_string .= $key;
439 3
				if ( $value = sanitize_text_field( $this->request[ $key ] ) ) {
440 7
					$query_string .= '=' . $value;
441
				}
442
			}
443
		}
444
445 7
		return $query_string;
446
	}
447
448
}
449