Completed
Push — trunk ( b974f1...832514 )
by Justin
06:32
created

CMB2_REST_Controller   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 407
Duplicated Lines 4.91 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 20
loc 407
rs 8.6
c 0
b 0
f 0
wmc 37
lcom 1
cbo 3

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A maybe_hook_callback_and_apply_filters() 0 21 3
A maybe_hook_registered_callback() 0 13 4
A maybe_unhook_registered_callback() 0 8 3
A prepare_item() 0 3 1
A get_cb_results() 0 8 1
A prepare_item_for_response() 0 14 1
A initiate_rest_read_box() 7 7 3
A initiate_rest_edit_box() 7 7 3
A initiate_rest_box() 6 20 4
B initiate_request() 0 15 5
A get_intial_request_type() 0 3 1
A get_intial_route() 0 3 1
B get_item_schema() 0 26 1
prepare_links() 0 1 ?
B get_query_string() 0 22 5

How to fix   Duplicated Code   

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:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 22 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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    WebDevStudios
19
 * @license   GPL-2.0+
20
 * @link      http://webdevstudios.com
21
 */
22
abstract class CMB2_REST_Controller extends WP_REST_Controller {
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...
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
	 * @var WP_REST_Request $request
41
	 * @since 2.2.3
42
	 */
43
	public $request;
44
45
	/**
46
	 * The current server object
47
	 * @var WP_REST_Server $server
48
	 * @since 2.2.3
49
	 */
50
	public $server;
51
52
	/**
53
	 * Box object id
54
	 * @var   mixed
55
	 * @since 2.2.3
56
	 */
57
	public $object_id = null;
58
59
	/**
60
	 * Box object type
61
	 * @var   string
62
	 * @since 2.2.3
63
	 */
64
	public $object_type = '';
65
66
	/**
67
	 * CMB2 Instance
68
	 *
69
	 * @var CMB2_REST
70
	 */
71
	protected $rest_box;
72
73
	/**
74
	 * CMB2_Field Instance
75
	 *
76
	 * @var CMB2_Field
77
	 */
78
	protected $field;
79
80
	/**
81
	 * The initial route
82
	 * @var   string
83
	 * @since 2.2.3
84
	 */
85
	protected static $route = '';
86
87
	/**
88
	 * Defines which endpoint the initial request is.
89
	 * @var string $request_type
90
	 * @since 2.2.3
91
	 */
92
	protected static $request_type = '';
93
94
	/**
95
	 * Constructor
96
	 * @since 2.2.3
97
	 */
98
	public function __construct( WP_REST_Server $wp_rest_server ) {
99
		$this->server = $wp_rest_server;
100
	}
101
102
	/**
103
	 * A wrapper for `apply_filters` which checks for box/field properties to hook to the filter.
104
	 *
105
	 * Checks if a CMB object callback property exists, and if it does,
106
	 * hook it to the permissions filter.
107
	 *
108
	 * @since  2.2.3
109
	 *
110
	 * @param  string $filter         The name of the filter to apply.
111
	 * @param  string $default_access The default access for this request.
112
	 *
113
	 * @return void
114
	 */
115
	public function maybe_hook_callback_and_apply_filters( $filter, $default_access ) {
116
		if ( ! $this->rest_box && $this->request->get_param( 'cmb_id' ) ) {
117
			$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 object<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...
118
		}
119
120
		$default_access = $this->maybe_hook_registered_callback( $filter, $default_access );
0 ignored issues
show
Documentation introduced by
$default_access is of type string, but the function expects a boolean.

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...
121
122
		/**
123
		 * Apply the permissions check filter.
124
		 *
125
		 * @since 2.2.3
126
		 *
127
		 * @param bool   $default_access Whether this CMB2 endpoint can be accessed.
128
		 * @param object $controller     This CMB2_REST_Controller object.
129
		 */
130
		$default_access = apply_filters( $filter, $default_access, $this );
131
132
		$this->maybe_unhook_registered_callback( $filter );
133
134
		return $default_access;
135
	}
136
137
	/**
138
	 * Checks if the CMB2 box has any registered callback parameters for the given filter.
139
	 *
140
	 * The registered handlers will have a property name which matches the filter, except:
141
	 * - The 'cmb2_api' prefix will be removed
142
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
143
	 *
144
	 * @since  2.2.3
145
	 *
146
	 * @param  string $filter      The filter name.
147
	 * @param  bool   $default_val The default filter value.
148
	 *
149
	 * @return bool                The possibly-modified filter value (if the '*_cb' param is non-callable).
150
	 */
151
	public function maybe_hook_registered_callback( $filter, $default_val ) {
152
		if ( ! $this->rest_box || is_wp_error( $this->rest_box ) ) {
153
			return $default_val;
154
		}
155
156
		// Hook box specific filter callbacks.
157
		$val = $this->rest_box->cmb->maybe_hook_parameter( $filter, $default_val );
158
		if ( null !== $val ) {
159
			$default_val = $val;
160
		}
161
162
		return $default_val;
163
	}
164
165
	/**
166
	 * Unhooks any CMB2 box registered callback parameters for the given filter.
167
	 *
168
	 * @since  2.2.3
169
	 *
170
	 * @param  string $filter The filter name.
171
	 *
172
	 * @return void
173
	 */
174
	public function maybe_unhook_registered_callback( $filter ) {
175
		if ( ! $this->rest_box || is_wp_error( $this->rest_box ) ) {
176
			return;
177
		}
178
179
		// Unhook box specific filter callbacks.
180
		$this->rest_box->cmb->maybe_hook_parameter( $filter, null, 'remove_filter' );
181
	}
182
183
	/**
184
	 * Prepare a CMB2 object for serialization
185
	 *
186
	 * @since 2.2.3
187
	 *
188
	 * @param  mixed $data
189
	 * @return array $data
190
	 */
191
	public function prepare_item( $data ) {
192
		return $this->prepare_item_for_response( $data, $this->request );
193
	}
194
195
	/**
196
	 * Output buffers a callback and returns the results.
197
	 *
198
	 * @since  2.2.3
199
	 *
200
	 * @param  mixed $cb Callable function/method.
201
	 * @return mixed     Results of output buffer after calling function/method.
202
	 */
203
	public function get_cb_results( $cb ) {
204
		$args = func_get_args();
205
		array_shift( $args ); // ignore $cb
206
		ob_start();
207
		call_user_func_array( $cb, $args );
208
209
		return ob_get_clean();
210
	}
211
212
	/**
213
	 * Prepare the CMB2 item for the REST response.
214
	 *
215
	 * @since 2.2.3
216
	 *
217
	 * @param  mixed            $item     WordPress representation of the item.
0 ignored issues
show
Bug introduced by
There is no parameter named $item. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
218
	 * @param  WP_REST_Request  $request  Request object.
219
	 * @return WP_REST_Response $response
220
	 */
221
	public function prepare_item_for_response( $data, $request = null ) {
222
		$data = $this->filter_response_by_context( $data, $this->request['context'] );
223
224
		/**
225
		 * Filter the prepared CMB2 item response.
226
		 *
227
		 * @since 2.2.3
228
		 *
229
		 * @param mixed  $data           Prepared data
230
		 * @param object $request        The WP_REST_Request object
231
		 * @param object $cmb2_endpoints This endpoints object
232
		 */
233
		return apply_filters( 'cmb2_rest_prepare', rest_ensure_response( $data ), $this->request, $this );
234
	}
235
236
	/**
237
	 * Initiates the request property and the rest_box property if box is readable.
238
	 *
239
	 * @since  2.2.3
240
	 *
241
	 * @param  WP_REST_Request $request      Request object.
242
	 * @param  string          $request_type A description of the type of request being made.
243
	 *
244
	 * @return void
245
	 */
246 View Code Duplication
	protected function initiate_rest_read_box( $request, $request_type ) {
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...
247
		$this->initiate_rest_box( $request, $request_type );
248
249
		if ( ! is_wp_error( $this->rest_box ) && ! $this->rest_box->rest_read ) {
0 ignored issues
show
Documentation introduced by
The property $rest_read is declared protected in CMB2_REST. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
250
			$this->rest_box = new WP_Error( 'cmb2_rest_no_read_error', __( 'This box does not have read permissions.', 'cmb2' ), array( 'status' => 403 ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like new \WP_Error('cmb2_rest...array('status' => 403)) of type object<WP_Error> is incompatible with the declared type object<CMB2_REST> of property $rest_box.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
251
		}
252
	}
253
254
	/**
255
	 * Initiates the request property and the rest_box property if box is writeable.
256
	 *
257
	 * @since  2.2.3
258
	 *
259
	 * @param  WP_REST_Request $request      Request object.
260
	 * @param  string          $request_type A description of the type of request being made.
261
	 *
262
	 * @return void
263
	 */
264 View Code Duplication
	protected function initiate_rest_edit_box( $request, $request_type ) {
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...
265
		$this->initiate_rest_box( $request, $request_type );
266
267
		if ( ! is_wp_error( $this->rest_box ) && ! $this->rest_box->rest_edit ) {
0 ignored issues
show
Documentation introduced by
The property $rest_edit is declared protected in CMB2_REST. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
268
			$this->rest_box = new WP_Error( 'cmb2_rest_no_write_error', __( 'This box does not have write permissions.', 'cmb2' ), array( 'status' => 403 ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like new \WP_Error('cmb2_rest...array('status' => 403)) of type object<WP_Error> is incompatible with the declared type object<CMB2_REST> of property $rest_box.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
269
		}
270
	}
271
272
	/**
273
	 * Initiates the request property and the rest_box property.
274
	 *
275
	 * @since  2.2.3
276
	 *
277
	 * @param  WP_REST_Request $request      Request object.
278
	 * @param  string          $request_type A description of the type of request being made.
279
	 *
280
	 * @return void
281
	 */
282
	protected function initiate_rest_box( $request, $request_type ) {
283
		$this->initiate_request( $request, $request_type );
284
285
		$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 object<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...
286
287
		if ( ! $this->rest_box ) {
288
289
			$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( 'status' => 403 ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like new \WP_Error('cmb2_rest...array('status' => 403)) of type object<WP_Error> is incompatible with the declared type object<CMB2_REST> of property $rest_box.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
290
291
		} else {
292
293 View Code Duplication
			if ( isset( $this->request['object_id'] ) ) {
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...
294
				$this->rest_box->cmb->object_id( sanitize_text_field( $this->request['object_id'] ) );
295
			}
296
297 View Code Duplication
			if ( isset( $this->request['object_type'] ) ) {
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...
298
				$this->rest_box->cmb->object_type( sanitize_text_field( $this->request['object_type'] ) );
299
			}
300
		}
301
	}
302
303
	/**
304
	 * Initiates the request property and sets up the initial static properties.
305
	 *
306
	 * @since  2.2.3
307
	 *
308
	 * @param  WP_REST_Request $request      Request object.
309
	 * @param  string          $request_type A description of the type of request being made.
310
	 *
311
	 * @return void
312
	 */
313
	public function initiate_request( $request, $request_type ) {
314
		$this->request = $request;
315
316
		if ( ! isset( $this->request['context'] ) || empty( $this->request['context'] ) ) {
317
			$this->request['context'] = 'view';
318
		}
319
320
		if ( ! self::$request_type ) {
321
			self::$request_type = $request_type;
322
		}
323
324
		if ( ! self::$route ) {
325
			self::$route = $this->request->get_route();
326
		}
327
	}
328
329
	/**
330
	 * Useful when getting `_embed`-ed items
331
	 *
332
	 * @since  2.2.3
333
	 *
334
	 * @return string  Initial requested type.
335
	 */
336
	public static function get_intial_request_type() {
337
		return self::$request_type;
338
	}
339
340
	/**
341
	 * Useful when getting `_embed`-ed items
342
	 *
343
	 * @since  2.2.3
344
	 *
345
	 * @return string  Initial requested route.
346
	 */
347
	public static function get_intial_route() {
348
		return self::$route;
349
	}
350
351
	/**
352
	 * Get CMB2 fields schema, conforming to JSON Schema
353
	 *
354
	 * @since 2.2.3
355
	 *
356
	 * @return array
357
	 */
358
	public function get_item_schema() {
359
		$schema = array(
360
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
361
			'title'                => 'CMB2',
362
			'type'                 => 'object',
363
			'properties'           => array(
364
				'description' => array(
365
					'description'  => __( 'A human-readable description of the object.', 'cmb2' ),
366
					'type'         => 'string',
367
					'context'      => array( 'view' ),
368
					),
369
					'name'             => array(
370
						'description'  => __( 'The id for the object.', 'cmb2' ),
371
						'type'         => 'integer',
372
						'context'      => array( 'view' ),
373
					),
374
				'name' => array(
375
					'description'  => __( 'The title for the object.', 'cmb2' ),
376
					'type'         => 'string',
377
					'context'      => array( 'view' ),
378
				),
379
			),
380
		);
381
382
		return $this->add_additional_fields_schema( $schema );
383
	}
384
385
	/**
386
	 * Return an array of contextual links for endpoint/object
387
	 * @link http://v2.wp-api.org/extending/linking/
388
	 * @link http://www.iana.org/assignments/link-relations/link-relations.xhtml
389
	 *
390
	 * @since  2.2.3
391
	 *
392
	 * @param  mixed  $object Object to build links from.
393
	 *
394
	 * @return array          Array of links
395
	 */
396
	abstract protected function prepare_links( $object );
397
398
	/**
399
	 * Get whitelisted query strings from URL for appending to link URLS.
400
	 *
401
	 * @since  2.2.3
402
	 *
403
	 * @return string URL query stringl
404
	 */
405
	public function get_query_string() {
406
		$defaults = array(
407
			'object_id'   => 0,
408
			'object_type' => '',
409
			'_rendered'   => '',
410
			// '_embed'      => '',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
411
		);
412
413
		$query_string = '';
414
415
		foreach ( $defaults as $key => $value ) {
416
			if ( isset( $this->request[ $key ] ) ) {
417
				$query_string .= $query_string ? '&' : '?';
418
				$query_string .= $key;
419
				if ( $value = sanitize_text_field( $this->request[ $key ] ) ) {
420
					$query_string .= '=' . $value;
421
				}
422
			}
423
		}
424
425
		return $query_string;
426
	}
427
428
}
429