CMB2_REST_Controller_Fields   B
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 486
Duplicated Lines 9.88 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
dl 48
loc 486
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 2
cbo 5

17 Methods

Rating   Name   Duplication   Size   Complexity  
A register_routes() 0 55 1
A get_items_permissions_check() 0 14 1
B get_items() 6 26 6
A get_item_permissions_check() 0 8 2
A delete_item_permissions_check() 18 18 2
A delete_item() 0 5 1
A get_item_permissions_check_filter() 0 11 1
A get_item() 0 9 2
A update_item_permissions_check() 18 18 2
A update_item() 0 11 2
C modify_field_value() 3 34 8
A prepare_read_field() 3 11 2
A prepare_field_response() 0 8 1
D prepare_field_data() 0 50 16
A prepare_links() 0 19 1
A maybe_hook_registered_callback() 0 14 3
A maybe_unhook_registered_callback() 0 8 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_REST_Controller_Fields 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_REST_Controller_Fields, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * CMB2 objects/fields endpoint for WordPres REST API.
4
 * Allows access to fields registered to a specific box.
5
 *
6
 * @todo  Add better documentation.
7
 * @todo  Research proper schema.
8
 *
9
 * @since 2.2.3
10
 *
11
 * @category  WordPress_Plugin
12
 * @package   CMB2
13
 * @author    WebDevStudios
14
 * @license   GPL-2.0+
15
 * @link      http://webdevstudios.com
16
 */
17
class CMB2_REST_Controller_Fields extends CMB2_REST_Controller_Boxes {
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...
18
19
	/**
20
	 * Register the routes for the objects of the controller.
21
	 *
22
	 * @since 2.2.3
23
	 */
24
	public function register_routes() {
25
		$args = array(
26
			'_embed' => array(
27
				'description' => __( 'Includes the box object which the fields are registered to in the response.', 'cmb2' ),
28
			),
29
			'_rendered' => array(
30
				'description' => __( 'When the \'_rendered\' argument is passed, the renderable field attributes will be returned fully rendered. By default, the names of the callback handers for the renderable attributes will be returned.', 'cmb2' ),
31
			),
32
			'object_id' => array(
33
				'description' => __( 'To view or modify the field\'s value, the \'object_id\' and \'object_type\' arguments are required.', 'cmb2' ),
34
			),
35
			'object_type' => array(
36
				'description' => __( 'To view or modify the field\'s value, the \'object_id\' and \'object_type\' arguments are required.', 'cmb2' ),
37
			),
38
		);
39
40
		// Returns specific box's fields.
41
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<cmb_id>[\w-]+)/fields/', array(
42
			array(
43
				'methods'             => WP_REST_Server::READABLE,
44
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
45
				'callback'            => array( $this, 'get_items' ),
46
				'args'                => $args,
47
			),
48
			'schema' => array( $this, 'get_item_schema' ),
49
		) );
50
51
		$delete_args = $args;
52
		$delete_args['object_id']['required'] = true;
53
		$delete_args['object_type']['required'] = true;
54
55
		// Returns specific field data.
56
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<cmb_id>[\w-]+)/fields/(?P<field_id>[\w-]+)', array(
57
			array(
58
				'methods'             => WP_REST_Server::READABLE,
59
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
60
				'callback'            => array( $this, 'get_item' ),
61
				'args'                => $args,
62
			),
63
			array(
64
				'methods'             => WP_REST_Server::EDITABLE,
65
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
66
				'callback'            => array( $this, 'update_item' ),
67
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
68
				'args'                => $args,
69
			),
70
			array(
71
				'methods'             => WP_REST_Server::DELETABLE,
72
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
73
				'callback'            => array( $this, 'delete_item' ),
74
				'args'                => $delete_args,
75
			),
76
			'schema' => array( $this, 'get_item_schema' ),
77
		) );
78
	}
79
80
	/**
81
	 * Check if a given request has access to get fields.
82
	 * By default, no special permissions needed, but filtering return value.
83
	 *
84
	 * @since 2.2.3
85
	 *
86
	 * @param  WP_REST_Request $request Full data about the request.
87
	 * @return WP_Error|boolean
88
	 */
89
	public function get_items_permissions_check( $request ) {
90
		$this->initiate_rest_read_box( $request, 'fields_read' );
91
		$can_access = true;
92
93
		/**
94
		 * By default, no special permissions needed.
95
		 *
96
		 * @since 2.2.3
97
		 *
98
		 * @param bool   $can_access Whether this CMB2 endpoint can be accessed.
99
		 * @param object $controller This CMB2_REST_Controller object.
100
		 */
101
		return $this->maybe_hook_callback_and_apply_filters( 'cmb2_api_get_fields_permissions_check', $can_access );
102
	}
103
104
	/**
105
	 * Get all public CMB2 box fields.
106
	 *
107
	 * @since 2.2.3
108
	 *
109
	 * @param  WP_REST_Request $request Full data about the request.
110
	 * @return WP_Error|WP_REST_Response
111
	 */
112
	public function get_items( $request ) {
113
		if ( ! $this->rest_box ) {
114
			$this->initiate_rest_read_box( $request, 'fields_read' );
115
		}
116
117
		if ( is_wp_error( $this->rest_box ) ) {
118
			return $this->rest_box;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->rest_box; (CMB2_REST) is incompatible with the return type of the parent method CMB2_REST_Controller_Boxes::get_items of type WP_Error|WP_REST_Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
119
		}
120
121
		$fields = array();
122
		foreach ( $this->rest_box->cmb->prop( 'fields', array() ) as $field ) {
123
124
			// Make sure this field can be read.
125
			$this->field = $this->rest_box->field_can_read( $field['id'], true );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rest_box->field_c...ead($field['id'], true) can also be of type boolean. However, the property $field is declared as type object<CMB2_Field>. 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...
126
127
			// And make sure current user can view this box.
128 View Code Duplication
			if ( $this->field && $this->get_item_permissions_check_filter() ) {
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...
129
				$fields[ $field['id'] ] = $this->server->response_to_data(
130
					$this->prepare_field_response(),
131
					isset( $this->request['_embed'] )
132
				);
133
			}
134
		}
135
136
		return $this->prepare_item( $fields );
137
	}
138
139
	/**
140
	 * Check if a given request has access to a field.
141
	 * By default, no special permissions needed, but filtering return value.
142
	 *
143
	 * @since 2.2.3
144
	 *
145
	 * @param  WP_REST_Request $request Full details about the request.
146
	 * @return WP_Error|boolean
147
	 */
148
	public function get_item_permissions_check( $request ) {
149
		$this->initiate_rest_read_box( $request, 'field_read' );
150
		if ( ! is_wp_error( $this->rest_box ) ) {
151
			$this->field = $this->rest_box->field_can_read( $this->request->get_param( 'field_id' ), true );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rest_box->field_c...aram('field_id'), true) can also be of type boolean. However, the property $field is declared as type object<CMB2_Field>. 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...
152
		}
153
154
		return $this->get_item_permissions_check_filter();
155
	}
156
157
	/**
158
	 * Check by filter if a given request has access to a field.
159
	 * By default, no special permissions needed, but filtering return value.
160
	 *
161
	 * @since 2.2.3
162
	 *
163
	 * @param  bool $can_access Whether the current request has access to view the field by default.
164
	 * @return WP_Error|boolean
165
	 */
166
	public function get_item_permissions_check_filter( $can_access = true ) {
167
		/**
168
		 * By default, no special permissions needed.
169
		 *
170
		 * @since 2.2.3
171
		 *
172
		 * @param bool   $can_access Whether this CMB2 endpoint can be accessed.
173
		 * @param object $controller This CMB2_REST_Controller object.
174
		 */
175
		return $this->maybe_hook_callback_and_apply_filters( 'cmb2_api_get_field_permissions_check', $can_access );
176
	}
177
178
	/**
179
	 * Get one CMB2 field from the collection.
180
	 *
181
	 * @since 2.2.3
182
	 *
183
	 * @param  WP_REST_Request $request Full data about the request.
184
	 * @return WP_Error|WP_REST_Response
185
	 */
186
	public function get_item( $request ) {
187
		$this->initiate_rest_read_box( $request, 'field_read' );
188
189
		if ( is_wp_error( $this->rest_box ) ) {
190
			return $this->rest_box;
191
		}
192
193
		return $this->prepare_read_field( $this->request->get_param( 'field_id' ) );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->prepare_read_fiel...get_param('field_id')); of type WP_Error|WP_REST_Response adds the type WP_Error to the return on line 193 which is incompatible with the return type of the parent method CMB2_REST_Controller_Boxes::get_item of type CMB2_REST|WP_REST_Response.
Loading history...
194
	}
195
196
	/**
197
	 * Check if a given request has access to update a field value.
198
	 * By default, requires 'edit_others_posts' capability, but filtering return value.
199
	 *
200
	 * @since 2.2.3
201
	 *
202
	 * @param  WP_REST_Request $request Full details about the request.
203
	 * @return WP_Error|boolean
204
	 */
205 View Code Duplication
	public function update_item_permissions_check( $request ) {
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...
206
		$this->initiate_rest_read_box( $request, 'field_value_update' );
207
		if ( ! is_wp_error( $this->rest_box ) ) {
208
			$this->field = $this->rest_box->field_can_edit( $this->request->get_param( 'field_id' ), true );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rest_box->field_c...aram('field_id'), true) can also be of type boolean. However, the property $field is declared as type object<CMB2_Field>. 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...
209
		}
210
211
		$can_update = current_user_can( 'edit_others_posts' );
212
213
		/**
214
		 * By default, 'edit_others_posts' is required capability.
215
		 *
216
		 * @since 2.2.3
217
		 *
218
		 * @param bool   $can_update Whether this CMB2 endpoint can be accessed.
219
		 * @param object $controller This CMB2_REST_Controller object.
220
		 */
221
		return $this->maybe_hook_callback_and_apply_filters( 'cmb2_api_update_field_value_permissions_check', $can_update );
222
	}
223
224
	/**
225
	 * Update CMB2 field value.
226
	 *
227
	 * @since 2.2.3
228
	 *
229
	 * @param  WP_REST_Request $request Full data about the request.
230
	 * @return WP_Error|WP_REST_Response
231
	 */
232
	public function update_item( $request ) {
233
		$this->initiate_rest_read_box( $request, 'field_value_update' );
234
235
		if ( ! $this->request['value'] ) {
236
			return new WP_Error( 'cmb2_rest_update_field_error', __( 'CMB2 Field value cannot be updated without the value parameter specified.', 'cmb2' ), array(
237
				'status' => 400,
238
			) );
239
		}
240
241
		return $this->modify_field_value( 'updated' );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->modify_field_value('updated'); of type WP_Error|CMB2_REST|WP_REST_Response adds the type CMB2_REST to the return on line 241 which is incompatible with the return type of the parent method WP_REST_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
242
	}
243
244
	/**
245
	 * Check if a given request has access to delete a field value.
246
	 * By default, requires 'delete_others_posts' capability, but filtering return value.
247
	 *
248
	 * @since 2.2.3
249
	 *
250
	 * @param  WP_REST_Request $request Full details about the request.
251
	 * @return WP_Error|boolean
252
	 */
253 View Code Duplication
	public function delete_item_permissions_check( $request ) {
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...
254
		$this->initiate_rest_read_box( $request, 'field_value_delete' );
255
		if ( ! is_wp_error( $this->rest_box ) ) {
256
			$this->field = $this->rest_box->field_can_edit( $this->request->get_param( 'field_id' ), true );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rest_box->field_c...aram('field_id'), true) can also be of type boolean. However, the property $field is declared as type object<CMB2_Field>. 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...
257
		}
258
259
		$can_delete = current_user_can( 'delete_others_posts' );
260
261
		/**
262
		 * By default, 'delete_others_posts' is required capability.
263
		 *
264
		 * @since 2.2.3
265
		 *
266
		 * @param bool   $can_delete Whether this CMB2 endpoint can be accessed.
267
		 * @param object $controller This CMB2_REST_Controller object.
268
		 */
269
		return $this->maybe_hook_callback_and_apply_filters( 'cmb2_api_delete_field_value_permissions_check', $can_delete );
270
	}
271
272
	/**
273
	 * Delete CMB2 field value.
274
	 *
275
	 * @since 2.2.3
276
	 *
277
	 * @param  WP_REST_Request $request Full data about the request.
278
	 * @return WP_Error|WP_REST_Response
279
	 */
280
	public function delete_item( $request ) {
281
		$this->initiate_rest_read_box( $request, 'field_value_delete' );
282
283
		return $this->modify_field_value( 'deleted' );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->modify_field_value('deleted'); of type WP_Error|CMB2_REST|WP_REST_Response adds the type CMB2_REST to the return on line 283 which is incompatible with the return type of the parent method WP_REST_Controller::delete_item of type WP_Error|WP_REST_Response.
Loading history...
284
	}
285
286
	/**
287
	 * Modify CMB2 field value.
288
	 *
289
	 * @since 2.2.3
290
	 *
291
	 * @param  string $activity The modification activity (updated or deleted).
292
	 * @return WP_Error|WP_REST_Response
293
	 */
294
	public function modify_field_value( $activity ) {
295
296
		if ( ! $this->request['object_id'] || ! $this->request['object_type'] ) {
297
			return new WP_Error( 'cmb2_rest_modify_field_value_error', __( 'CMB2 Field value cannot be modified without the object_id and object_type parameters specified.', 'cmb2' ), array(
298
				'status' => 400,
299
			) );
300
		}
301
302
		if ( is_wp_error( $this->rest_box ) ) {
303
			return $this->rest_box;
304
		}
305
306
		$this->field = $this->rest_box->field_can_edit(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rest_box->field_c...aram('field_id'), true) can also be of type boolean. However, the property $field is declared as type object<CMB2_Field>. 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...
307
			$this->field ? $this->field : $this->request->get_param( 'field_id' ),
308
			true
309
		);
310
311 View Code Duplication
		if ( ! $this->field ) {
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...
312
			return new WP_Error( 'cmb2_rest_no_field_by_id_error', __( 'No field found by that id.', 'cmb2' ), array(
313
				'status' => 403,
314
			) );
315
		}
316
317
		$this->field->args[ "value_{$activity}" ] = (bool) 'deleted' === $activity
318
			? $this->field->remove_data()
319
			: $this->field->save_field( $this->request['value'] );
320
321
		// If options page, save the $activity options
322
		if ( 'options-page' == $this->request['object_type'] ) {
323
			$this->field->args[ "value_{$activity}" ] = cmb2_options( $this->request['object_id'] )->set();
324
		}
325
326
		return $this->prepare_read_field( $this->field );
327
	}
328
329
	/**
330
	 * Get a response object for a specific field ID.
331
	 *
332
	 * @since 2.2.3
333
	 *
334
	 * @param  string\CMB2_Field Field id or Field object.
335
	 * @return WP_Error|WP_REST_Response
336
	 */
337
	public function prepare_read_field( $field ) {
338
		$this->field = $this->rest_box->field_can_read( $field, true );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rest_box->field_can_read($field, true) can also be of type boolean. However, the property $field is declared as type object<CMB2_Field>. 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...
339
340 View Code Duplication
		if ( ! $this->field ) {
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...
341
			return new WP_Error( 'cmb2_rest_no_field_by_id_error', __( 'No field found by that id.', 'cmb2' ), array(
342
				'status' => 403,
343
			) );
344
		}
345
346
		return $this->prepare_item( $this->prepare_field_response() );
347
	}
348
349
	/**
350
	 * Get a specific field response.
351
	 *
352
	 * @since 2.2.3
353
	 *
354
	 * @param  CMB2_Field Field object.
355
	 * @return array      Response array.
356
	 */
357
	public function prepare_field_response() {
358
		$field_data = $this->prepare_field_data( $this->field );
359
		$response = rest_ensure_response( $field_data );
360
361
		$response->add_links( $this->prepare_links( $this->field ) );
362
363
		return $response;
364
	}
365
366
	/**
367
	 * Prepare the field data array for JSON.
368
	 *
369
	 * @since  2.2.3
370
	 *
371
	 * @param  CMB2_Field $field field object.
372
	 *
373
	 * @return array             Array of field data.
374
	 */
375
	protected function prepare_field_data( CMB2_Field $field ) {
376
		$field_data = array();
377
		$params_to_ignore = array( 'show_in_rest', 'options' );
378
		$params_to_rename = array(
379
			'label_cb' => 'label',
380
			'options_cb' => 'options',
381
		);
382
383
		// Run this first so the js_dependencies arg is populated.
384
		$rendered = ( $cb = $field->maybe_callback( 'render_row_cb' ) )
385
			// Ok, callback is good, let's run it.
386
			? $this->get_cb_results( $cb, $field->args(), $field )
387
			: false;
388
389
		$field_args = $field->args();
390
391
		foreach ( $field_args as $key => $value ) {
392
			if ( in_array( $key, $params_to_ignore, true ) ) {
393
				continue;
394
			}
395
396
			if ( 'options_cb' === $key ) {
397
				$value = $field->options();
398
			} elseif ( in_array( $key, CMB2_Field::$callable_fields, true ) ) {
399
400
				if ( isset( $this->request['_rendered'] ) ) {
401
					$value = $key === 'render_row_cb' ? $rendered : $field->get_param_callback_result( $key );
402
				} elseif ( is_array( $value ) ) {
403
					// We need to rewrite callbacks as string as they will cause
404
					// JSON recursion errors.
405
					$class = is_string( $value[0] ) ? $value[0] : get_class( $value[0] );
406
					$value = $class . '::' . $value[1];
407
				}
408
			}
409
410
			$key = isset( $params_to_rename[ $key ] ) ? $params_to_rename[ $key ] : $key;
411
412
			if ( empty( $value ) || is_scalar( $value ) || is_array( $value ) ) {
413
				$field_data[ $key ] = $value;
414
			} else {
415
				$field_data[ $key ] = sprintf( __( 'Value Error for %s', 'cmb2' ), $key );
416
			}
417
		}
418
419
		if ( $this->request['object_id'] && $this->request['object_type'] ) {
420
			$field_data['value'] = $field->get_data();
421
		}
422
423
		return $field_data;
424
	}
425
426
	/**
427
	 * Return an array of contextual links for field/fields.
428
	 *
429
	 * @since  2.2.3
430
	 *
431
	 * @param  CMB2_Field $field Field object to build links from.
432
	 *
433
	 * @return array             Array of links
434
	 */
435
	protected function prepare_links( $field ) {
436
		$boxbase      = $this->namespace_base . '/' . $this->rest_box->cmb->cmb_id;
0 ignored issues
show
Documentation introduced by
The property $cmb_id is declared protected in CMB2_Base. 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...
437
		$query_string = $this->get_query_string();
438
439
		$links = array(
440
			'self' => array(
441
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields/' . $field->_id() . $query_string ),
442
			),
443
			'collection' => array(
444
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields' . $query_string ),
445
			),
446
			'up' => array(
447
				'embeddable' => true,
448
				'href' => rest_url( $boxbase . $query_string ),
449
			),
450
		);
451
452
		return $links;
453
	}
454
455
	/**
456
	 * Checks if the CMB2 box or field has any registered callback parameters for the given filter.
457
	 *
458
	 * The registered handlers will have a property name which matches the filter, except:
459
	 * - The 'cmb2_api' prefix will be removed
460
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
461
	 *
462
	 * @since  2.2.3
463
	 *
464
	 * @param  string $filter      The filter name.
465
	 * @param  bool   $default_val The default filter value.
466
	 *
467
	 * @return bool                The possibly-modified filter value (if the _cb param is a non-callable).
468
	 */
469
	public function maybe_hook_registered_callback( $filter, $default_val ) {
470
		$default_val = parent::maybe_hook_registered_callback( $filter, $default_val );
471
472
		if ( $this->field ) {
473
474
			// Hook field specific filter callbacks.
475
			$val = $this->field->maybe_hook_parameter( $filter, $default_val );
476
			if ( null !== $val ) {
477
				$default_val = $val;
478
			}
479
		}
480
481
		return $default_val;
482
	}
483
484
	/**
485
	 * Unhooks any CMB2 box or field registered callback parameters for the given filter.
486
	 *
487
	 * @since  2.2.3
488
	 *
489
	 * @param  string $filter The filter name.
490
	 *
491
	 * @return void
492
	 */
493
	public function maybe_unhook_registered_callback( $filter ) {
494
		parent::maybe_unhook_registered_callback( $filter );
495
496
		if ( $this->field ) {
497
			// Unhook field specific filter callbacks.
498
			$this->field->maybe_hook_parameter( $filter, null, 'remove_filter' );
499
		}
500
	}
501
502
}
503