Completed
Pull Request — trunk (#541)
by Justin
04:46
created

CMB2_REST_Controller_Fields::get_items()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 13
c 3
b 0
f 0
nc 4
nop 1
dl 0
loc 21
rs 9.0534
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.4
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.4
23
	 */
24
	public function register_routes() {
25
26
		// Returns specific box's fields.
27
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<cmb_id>[\w-]+)/fields/', array(
28
			array(
29
				'methods'  => WP_REST_Server::READABLE,
30
				'callback' => array( $this, 'get_items' ),
31
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
32
			),
33
			'schema' => array( $this, 'get_item_schema' ),
34
		) );
35
36
		// Returns specific field data.
37
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<cmb_id>[\w-]+)/fields/(?P<field_id>[\w-]+)', array(
38
			array(
39
				'methods'  => WP_REST_Server::READABLE,
40
				'callback' => array( $this, 'get_item' ),
41
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
42
			),
43
			array(
44
				'methods'  => WP_REST_Server::EDITABLE,
45
				'callback' => array( $this, 'update_field_value' ),
46
				'args'     => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
47
				'permission_callback' => array( $this, 'update_field_value_permissions_check' ),
48
			),
49
			array(
50
				'methods'  => WP_REST_Server::DELETABLE,
51
				'callback' => array( $this, 'delete_field_value' ),
52
				'permission_callback' => array( $this, 'delete_field_value_permissions_check' ),
53
			),
54
			'schema' => array( $this, 'get_item_schema' ),
55
		) );
56
	}
57
58
	/**
59
	 * Get all public CMB2 box fields.
60
	 *
61
	 * @since 2.2.4
62
	 *
63
	 * @param  WP_REST_Request $request Full data about the request.
64
	 * @return WP_Error|WP_REST_Response
65
	 */
66
	public function get_items( $request ) {
67
		$this->initiate_rest_read_box( $request, 'fields_read' );
68
69
		if ( is_wp_error( $this->rest_box ) ) {
70
			return $this->prepare_item( array( 'error' => $this->rest_box->get_error_message() ) );
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<CMB2_REST>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
71
		}
72
73
		$fields = array();
74
		foreach ( $this->rest_box->cmb->prop( 'fields', array() ) as $field ) {
75
			$field_id = $field['id'];
76
			$rest_field = $this->get_rest_field( $field_id );
77
78
			if ( ! is_wp_error( $rest_field ) ) {
79
				$fields[ $field_id ] = $this->server->response_to_data( $rest_field, isset( $this->request['_embed'] ) );
80
			} else {
81
				$fields[ $field_id ] = array( 'error' => $rest_field->get_error_message() );
82
			}
83
		}
84
85
		return $this->prepare_item( $fields );
86
	}
87
88
	/**
89
	 * Get one CMB2 field from the collection.
90
	 *
91
	 * @since 2.2.4
92
	 *
93
	 * @param  WP_REST_Request $request Full data about the request.
94
	 * @return WP_Error|WP_REST_Response
95
	 */
96
	public function get_item( $request ) {
97
		$this->initiate_rest_read_box( $request, 'field_read' );
98
99
		if ( is_wp_error( $this->rest_box ) ) {
100
			return $this->prepare_item( array( 'error' => $this->rest_box->get_error_message() ) );
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<CMB2_REST>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
101
		}
102
103
		$field = $this->get_rest_field( $this->request->get_param( 'field_id' ) );
104
105
		if ( is_wp_error( $field ) ) {
106
			return $this->prepare_item( array( 'error' => $field->get_error_message() ) );
107
		}
108
109
		return $this->prepare_item( $field );
110
	}
111
112
	/**
113
	 * Update CMB2 field value.
114
	 *
115
	 * @since 2.2.4
116
	 *
117
	 * @param  WP_REST_Request $request Full data about the request.
118
	 * @return WP_Error|WP_REST_Response
119
	 */
120
	public function update_field_value( $request ) {
121
		$this->initiate_rest_read_box( $request, 'field_value_update' );
122
123
		if ( ! $this->request['value'] ) {
124
			return $this->prepare_item( array( 'error' => __( 'CMB2 Field value cannot be updated without the value parameter specified.', 'cmb2' ) ) );
125
		}
126
127
		$field = $this->rest_box->field_can_write( $this->request->get_param( 'field_id' ), true );
128
129
		return $this->modify_field_value( 'updated', $field );
0 ignored issues
show
Bug introduced by
It seems like $field defined by $this->rest_box->field_c...aram('field_id'), true) on line 127 can also be of type boolean; however, CMB2_REST_Controller_Fields::modify_field_value() does only seem to accept object<CMB2_Field>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
130
	}
131
132
	/**
133
	 * Delete CMB2 field value.
134
	 *
135
	 * @since 2.2.4
136
	 *
137
	 * @param  WP_REST_Request $request Full data about the request.
138
	 * @return WP_Error|WP_REST_Response
139
	 */
140
	public function delete_field_value( $request ) {
141
		$this->initiate_rest_read_box( $request, 'field_value_delete' );
142
143
		$field = $this->rest_box->field_can_write( $this->request->get_param( 'field_id' ), true );
144
145
		return $this->modify_field_value( 'deleted', $field );
0 ignored issues
show
Bug introduced by
It seems like $field defined by $this->rest_box->field_c...aram('field_id'), true) on line 143 can also be of type boolean; however, CMB2_REST_Controller_Fields::modify_field_value() does only seem to accept object<CMB2_Field>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
146
	}
147
148
	/**
149
	 * Modify CMB2 field value.
150
	 *
151
	 * @since 2.2.4
152
	 *
153
	 * @param  string     $activity The modification activity (updated or deleted).
154
	 * @param  CMB2_Field $field    The field object.
155
	 * @return WP_Error|WP_REST_Response
156
	 */
157
	public function modify_field_value( $activity, $field ) {
158
159
		if ( ! $this->request['object_id'] && ! $this->request['object_type'] ) {
160
			return $this->prepare_item( array( 'error' => __( 'CMB2 Field value cannot be modified without the object_id and object_type parameters specified.', 'cmb2' ) ) );
161
		}
162
163
		if ( is_wp_error( $this->rest_box ) ) {
164
			return $this->prepare_item( array( 'error' => $this->rest_box->get_error_message() ) );
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<CMB2_REST>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
165
		}
166
167
		if ( ! $field ) {
168
			return new WP_Error( 'cmb2_rest_error', __( 'No field found by that id.', 'cmb2' ) );
169
		}
170
171
		$field->args["value_{$activity}"] = (bool) 'deleted' === $activity
172
			? $field->remove_data()
173
			: $field->save_field( $this->request['value'] );
174
175
		// If options page, save the $activity options
176
		if ( 'options-page' == $this->request['object_type'] ) {
177
			$field->args["value_{$activity}"] = cmb2_options( $this->request['object_id'] )->set();
178
		}
179
180
		$field_data = $this->get_rest_field( $field );
181
182
		if ( is_wp_error( $field_data ) ) {
183
			return $this->prepare_item( array( 'error' => $field_data->get_error_message() ) );
184
		}
185
186
		return $this->prepare_item( $field_data );
187
	}
188
189
	/**
190
	 * Get a specific field
191
	 *
192
	 * @since 2.2.4
193
	 *
194
	 * @param  string Field id
195
	 * @return array|WP_Error
196
	 */
197
	public function get_rest_field( $field_id ) {
198
		$field = $field_id instanceof CMB2_Field ? $field_id : $this->rest_box->field_can_read( $field_id, true );
199
200
		if ( ! $field ) {
201
			return new WP_Error( 'cmb2_rest_error', __( 'No field found by that id.', 'cmb2' ) );
202
		}
203
204
		$field_data = $this->prepare_field_data( $field );
0 ignored issues
show
Bug introduced by
It seems like $field defined by $field_id instanceof \CM...n_read($field_id, true) on line 198 can also be of type boolean; however, CMB2_REST_Controller_Fields::prepare_field_data() does only seem to accept object<CMB2_Field>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
205
		$response = rest_ensure_response( $field_data );
206
207
		$response->add_links( $this->prepare_links( $field ) );
0 ignored issues
show
Bug introduced by
It seems like $field defined by $field_id instanceof \CM...n_read($field_id, true) on line 198 can also be of type boolean; however, CMB2_REST_Controller_Fields::prepare_links() does only seem to accept object<CMB2_Field>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
208
209
		return $response;
210
	}
211
212
	/**
213
	 * Prepare the field data array for JSON.
214
	 *
215
	 * @since  2.2.4
216
	 *
217
	 * @param  CMB2_Field $field field object.
218
	 *
219
	 * @return array             Array of field data.
220
	 */
221
	protected function prepare_field_data( CMB2_Field $field ) {
222
		$field_data = array();
223
		$params_to_ignore = array( 'show_in_rest', 'options' );
224
		$params_to_rename = array(
225
			'label_cb' => 'label',
226
			'options_cb' => 'options',
227
		);
228
229
		// Run this first so the js_dependencies arg is populated.
230
		$rendered = ( $cb = $field->maybe_callback( 'render_row_cb' ) )
231
			// Ok, callback is good, let's run it.
232
			? $this->get_cb_results( $cb, $field->args(), $field )
233
			: false;
234
235
		$field_args = $field->args();
236
237
		foreach ( $field_args as $key => $value ) {
238
			if ( in_array( $key, $params_to_ignore, true ) ) {
239
				continue;
240
			}
241
242
			if ( 'options_cb' === $key ) {
243
				$value = $field->options();
244
			} elseif ( in_array( $key, CMB2_Field::$callable_fields, true ) ) {
245
246
				if ( isset( $this->request['_rendered'] ) ) {
247
					$value = $key === 'render_row_cb' ? $rendered : $field->get_param_callback_result( $key );
248
				} elseif ( is_array( $value ) ) {
249
					// We need to rewrite callbacks as string as they will cause
250
					// JSON recursion errors.
251
					$class = is_string( $value[0] ) ? $value[0] : get_class( $value[0] );
252
					$value = $class . '::' . $value[1];
253
				}
254
			}
255
256
			$key = isset( $params_to_rename[ $key ] ) ? $params_to_rename[ $key ] : $key;
257
258
			if ( empty( $value ) || is_scalar( $value ) || is_array( $value ) ) {
259
				$field_data[ $key ] = $value;
260
			} else {
261
				$field_data[ $key ] = sprintf( __( 'Value Error for %s', 'cmb2' ), $key );
262
			}
263
		}
264
265
		if ( $this->request['object_id'] && $this->request['object_type'] ) {
266
			$field_data['value'] = $field->get_data();
267
		}
268
269
		return $field_data;
270
	}
271
272
	/**
273
	 * Return an array of contextual links for field/fields.
274
	 *
275
	 * @since  2.2.4
276
	 *
277
	 * @param  CMB2_Field $field Field object to build links from.
278
	 *
279
	 * @return array             Array of links
280
	 */
281
	protected function prepare_links( $field ) {
282
		$boxbase      = $this->namespace_base . '/' . $this->rest_box->cmb->cmb_id;
283
		$query_string = $this->get_query_string();
284
285
		$links = array(
286
			'self' => array(
287
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields/' . $field->_id() . $query_string ),
288
			),
289
			'collection' => array(
290
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields' . $query_string ),
291
			),
292
			'up' => array(
293
				'href' => rest_url( $boxbase . $query_string ),
294
			),
295
		);
296
297
		// Don't embed boxes when looking at boxes route.
298
		if ( '/cmb2/v1/boxes' !== CMB2_REST_Controller::get_intial_route() ) {
299
			$links['up']['embeddable'] = true;
300
		}
301
302
		return $links;
303
	}
304
305
}
306