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

get_items_permissions_check()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
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 );
0 ignored issues
show
Documentation introduced by
$can_access is of type boolean, but the function expects a string.

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...
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
		$can_access = true;
168
169
		/**
170
		 * By default, no special permissions needed.
171
		 *
172
		 * @since 2.2.3
173
		 *
174
		 * @param bool   $can_access Whether this CMB2 endpoint can be accessed.
175
		 * @param object $controller This CMB2_REST_Controller object.
176
		 */
177
		return $this->maybe_hook_callback_and_apply_filters( 'cmb2_api_get_field_permissions_check', $can_access );
0 ignored issues
show
Documentation introduced by
$can_access is of type boolean, but the function expects a string.

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...
178
	}
179
180
	/**
181
	 * Get one CMB2 field from the collection.
182
	 *
183
	 * @since 2.2.3
184
	 *
185
	 * @param  WP_REST_Request $request Full data about the request.
186
	 * @return WP_Error|WP_REST_Response
187
	 */
188
	public function get_item( $request ) {
189
		$this->initiate_rest_read_box( $request, 'field_read' );
190
191
		if ( is_wp_error( $this->rest_box ) ) {
192
			return $this->rest_box;
193
		}
194
195
		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 195 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...
196
	}
197
198
	/**
199
	 * Check if a given request has access to update a field value.
200
	 * By default, requires 'edit_others_posts' capability, but filtering return value.
201
	 *
202
	 * @since 2.2.3
203
	 *
204
	 * @param  WP_REST_Request $request Full details about the request.
205
	 * @return WP_Error|boolean
206
	 */
207 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...
208
		$this->initiate_rest_read_box( $request, 'field_value_update' );
209
		if ( ! is_wp_error( $this->rest_box ) ) {
210
			$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...
211
		}
212
213
		$can_update = current_user_can( 'edit_others_posts' );
214
215
		/**
216
		 * By default, 'edit_others_posts' is required capability.
217
		 *
218
		 * @since 2.2.3
219
		 *
220
		 * @param bool   $can_update Whether this CMB2 endpoint can be accessed.
221
		 * @param object $controller This CMB2_REST_Controller object.
222
		 */
223
		return $this->maybe_hook_callback_and_apply_filters( 'cmb2_api_update_field_value_permissions_check', $can_update );
224
	}
225
226
	/**
227
	 * Update CMB2 field value.
228
	 *
229
	 * @since 2.2.3
230
	 *
231
	 * @param  WP_REST_Request $request Full data about the request.
232
	 * @return WP_Error|WP_REST_Response
233
	 */
234
	public function update_item( $request ) {
235
		$this->initiate_rest_read_box( $request, 'field_value_update' );
236
237
		if ( ! $this->request['value'] ) {
238
			return new WP_Error( 'cmb2_rest_update_field_error', __( 'CMB2 Field value cannot be updated without the value parameter specified.', 'cmb2' ), array( 'status' => 400 ) );
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( 'status' => 400 ) );
298
		}
299
300
		if ( is_wp_error( $this->rest_box ) ) {
301
			return $this->rest_box;
302
		}
303
304
		$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...
305
			$this->field ? $this->field : $this->request->get_param( 'field_id' ),
306
			true
307
		);
308
309 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...
310
			return new WP_Error( 'cmb2_rest_no_field_by_id_error', __( 'No field found by that id.', 'cmb2' ), array( 'status' => 403 ) );
311
		}
312
313
		$this->field->args["value_{$activity}"] = (bool) 'deleted' === $activity
314
			? $this->field->remove_data()
315
			: $this->field->save_field( $this->request['value'] );
316
317
		// If options page, save the $activity options
318
		if ( 'options-page' == $this->request['object_type'] ) {
319
			$this->field->args["value_{$activity}"] = cmb2_options( $this->request['object_id'] )->set();
320
		}
321
322
		return $this->prepare_read_field( $this->field );
323
	}
324
325
	/**
326
	 * Get a response object for a specific field ID.
327
	 *
328
	 * @since 2.2.3
329
	 *
330
	 * @param  string\CMB2_Field Field id or Field object.
331
	 * @return array|WP_Error    Response array or WP_Error object.
332
	 */
333
	public function prepare_read_field( $field ) {
334
		$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...
335
336 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...
337
			return new WP_Error( 'cmb2_rest_no_field_by_id_error', __( 'No field found by that id.', 'cmb2' ), array( 'status' => 403 ) );
338
		}
339
340
		return $this->prepare_item( $this->prepare_field_response() );
341
	}
342
343
	/**
344
	 * Get a specific field response.
345
	 *
346
	 * @since 2.2.3
347
	 *
348
	 * @param  CMB2_Field Field object.
349
	 * @return array      Response array.
350
	 */
351
	public function prepare_field_response() {
352
		$field_data = $this->prepare_field_data( $this->field );
353
		$response = rest_ensure_response( $field_data );
354
355
		$response->add_links( $this->prepare_links( $this->field ) );
356
357
		return $response;
358
	}
359
360
	/**
361
	 * Prepare the field data array for JSON.
362
	 *
363
	 * @since  2.2.3
364
	 *
365
	 * @param  CMB2_Field $field field object.
366
	 *
367
	 * @return array             Array of field data.
368
	 */
369
	protected function prepare_field_data( CMB2_Field $field ) {
370
		$field_data = array();
371
		$params_to_ignore = array( 'show_in_rest', 'options' );
372
		$params_to_rename = array(
373
			'label_cb' => 'label',
374
			'options_cb' => 'options',
375
		);
376
377
		// Run this first so the js_dependencies arg is populated.
378
		$rendered = ( $cb = $field->maybe_callback( 'render_row_cb' ) )
379
			// Ok, callback is good, let's run it.
380
			? $this->get_cb_results( $cb, $field->args(), $field )
381
			: false;
382
383
		$field_args = $field->args();
384
385
		foreach ( $field_args as $key => $value ) {
386
			if ( in_array( $key, $params_to_ignore, true ) ) {
387
				continue;
388
			}
389
390
			if ( 'options_cb' === $key ) {
391
				$value = $field->options();
392
			} elseif ( in_array( $key, CMB2_Field::$callable_fields, true ) ) {
393
394
				if ( isset( $this->request['_rendered'] ) ) {
395
					$value = $key === 'render_row_cb' ? $rendered : $field->get_param_callback_result( $key );
396
				} elseif ( is_array( $value ) ) {
397
					// We need to rewrite callbacks as string as they will cause
398
					// JSON recursion errors.
399
					$class = is_string( $value[0] ) ? $value[0] : get_class( $value[0] );
400
					$value = $class . '::' . $value[1];
401
				}
402
			}
403
404
			$key = isset( $params_to_rename[ $key ] ) ? $params_to_rename[ $key ] : $key;
405
406
			if ( empty( $value ) || is_scalar( $value ) || is_array( $value ) ) {
407
				$field_data[ $key ] = $value;
408
			} else {
409
				$field_data[ $key ] = sprintf( __( 'Value Error for %s', 'cmb2' ), $key );
410
			}
411
		}
412
413
		if ( $this->request['object_id'] && $this->request['object_type'] ) {
414
			$field_data['value'] = $field->get_data();
415
		}
416
417
		return $field_data;
418
	}
419
420
	/**
421
	 * Return an array of contextual links for field/fields.
422
	 *
423
	 * @since  2.2.3
424
	 *
425
	 * @param  CMB2_Field $field Field object to build links from.
426
	 *
427
	 * @return array             Array of links
428
	 */
429
	protected function prepare_links( $field ) {
430
		$boxbase      = $this->namespace_base . '/' . $this->rest_box->cmb->cmb_id;
431
		$query_string = $this->get_query_string();
432
433
		$links = array(
434
			'self' => array(
435
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields/' . $field->_id() . $query_string ),
436
			),
437
			'collection' => array(
438
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields' . $query_string ),
439
			),
440
			'up' => array(
441
				'embeddable' => true,
442
				'href' => rest_url( $boxbase . $query_string ),
443
			),
444
		);
445
446
		return $links;
447
	}
448
449
	/**
450
	 * Checks if the CMB2 box or field has any registered callback parameters for the given filter.
451
	 *
452
	 * The registered handlers will have a property name which matches the filter, except:
453
	 * - The 'cmb2_api' prefix will be removed
454
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
455
	 *
456
	 * @since  2.2.3
457
	 *
458
	 * @param  string $filter      The filter name.
459
	 * @param  bool   $default_val The default filter value.
460
	 *
461
	 * @return bool                The possibly-modified filter value (if the _cb param is a non-callable).
462
	 */
463
	public function maybe_hook_registered_callback( $filter, $default_val ) {
464
		$default_val = parent::maybe_hook_registered_callback( $filter, $default_val );
465
466
		if ( $this->field ) {
467
468
			// Hook field specific filter callbacks.
469
			$val = $this->field->maybe_hook_parameter( $filter, $default_val );
470
			if ( null !== $val ) {
471
				$default_val = $val;
472
			}
473
		}
474
475
		return $default_val;
476
	}
477
478
	/**
479
	 * Unhooks any CMB2 box or field registered callback parameters for the given filter.
480
	 *
481
	 * @since  2.2.3
482
	 *
483
	 * @param  string $filter The filter name.
484
	 *
485
	 * @return void
486
	 */
487
	public function maybe_unhook_registered_callback( $filter ) {
488
		parent::maybe_unhook_registered_callback( $filter );
489
490
		if ( $this->field ) {
491
			// Unhook field specific filter callbacks.
492
			$this->field->maybe_hook_parameter( $filter, null, 'remove_filter' );
493
		}
494
	}
495
496
}
497