Completed
Pull Request — trunk (#541)
by Justin
06:28
created

CMB2_REST_Controller_Fields::update_field_value()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 31
Code Lines 16

Duplication

Lines 3
Ratio 9.68 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 16
nc 6
nop 1
dl 3
loc 31
rs 6.7272
c 1
b 0
f 1
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 View Code Duplication
		if ( ! $this->request['object_id'] && ! $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...
124
			return $this->prepare_item( array( 'error' => __( 'CMB2 Field value cannot be updated without the object_id and object_type parameters specified.', 'cmb2' ) ) );
125
		}
126
127
		if ( ! $this->request['value'] ) {
128
			return $this->prepare_item( array( 'error' => __( 'CMB2 Field value cannot be updated without the value parameter specified.', 'cmb2' ) ) );
129
		}
130
131
		if ( is_wp_error( $this->rest_box ) ) {
132
			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...
133
		}
134
135
		$field = $this->rest_box->field_can_write( $this->request->get_param( 'field_id' ), true );
136
137
		if ( ! $field ) {
138
			return new WP_Error( 'cmb2_rest_error', __( 'No field found by that id.', 'cmb2' ) );
139
		}
140
141
		$field->args['value_updated'] = (bool) $field->save_field( $this->request['value'] );
142
143
		$field_data = $this->get_rest_field( $field );
144
145
		if ( is_wp_error( $field_data ) ) {
146
			return $this->prepare_item( array( 'error' => $field_data->get_error_message() ) );
147
		}
148
149
		return $this->prepare_item( $field_data );
150
	}
151
152
	/**
153
	 * Update CMB2 field value.
154
	 *
155
	 * @since 2.2.4
156
	 *
157
	 * @param  WP_REST_Request $request Full data about the request.
158
	 * @return WP_Error|WP_REST_Response
159
	 */
160
	public function delete_field_value( $request ) {
161
		$this->initiate_rest_read_box( $request, 'field_value_delete' );
162
163 View Code Duplication
		if ( ! $this->request['object_id'] && ! $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...
164
			return $this->prepare_item( array( 'error' => __( 'CMB2 Field value cannot be deleted without the object_id and object_type parameters specified.', 'cmb2' ) ) );
165
		}
166
167
		if ( is_wp_error( $this->rest_box ) ) {
168
			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...
169
		}
170
171
		$field = $this->rest_box->field_can_write( $this->request->get_param( 'field_id' ), true );
172
173
		if ( ! $field ) {
174
			return new WP_Error( 'cmb2_rest_error', __( 'No field found by that id.', 'cmb2' ) );
175
		}
176
177
		$field->args['value_deleted'] = (bool) $field->remove_data();
178
179
		$field_data = $this->get_rest_field( $field );
180
181
		if ( is_wp_error( $field_data ) ) {
182
			return $this->prepare_item( array( 'error' => $field_data->get_error_message() ) );
183
		}
184
185
		return $this->prepare_item( $field_data );
186
	}
187
188
	/**
189
	 * Get a specific field
190
	 *
191
	 * @since 2.2.4
192
	 *
193
	 * @param  string Field id
194
	 * @return array|WP_Error
195
	 */
196
	public function get_rest_field( $field_id ) {
197
		$field = $field_id instanceof CMB2_Field ? $field_id : $this->rest_box->field_can_read( $field_id, true );
198
199
		if ( ! $field ) {
200
			return new WP_Error( 'cmb2_rest_error', __( 'No field found by that id.', 'cmb2' ) );
201
		}
202
203
		$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 197 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...
204
		$response = rest_ensure_response( $field_data );
205
206
		$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 197 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...
207
208
		return $response;
209
	}
210
211
	/**
212
	 * Prepare the field data array for JSON.
213
	 *
214
	 * @since  2.2.4
215
	 *
216
	 * @param  CMB2_Field $field field object.
217
	 *
218
	 * @return array             Array of field data.
219
	 */
220
	protected function prepare_field_data( CMB2_Field $field ) {
221
		$field_data = array();
222
		$params_to_ignore = array( 'show_in_rest', 'options' );
223
		$params_to_rename = array(
224
			'label_cb' => 'label',
225
			'options_cb' => 'options',
226
		);
227
228
		// Run this first so the js_dependencies arg is populated.
229
		$rendered = ( $cb = $field->maybe_callback( 'render_row_cb' ) )
230
			// Ok, callback is good, let's run it.
231
			? $this->get_cb_results( $cb, $field->args(), $field )
232
			: false;
233
234
		$field_args = $field->args();
235
236
		foreach ( $field_args as $key => $value ) {
237
			if ( in_array( $key, $params_to_ignore, true ) ) {
238
				continue;
239
			}
240
241
			if ( 'options_cb' === $key ) {
242
				$value = $field->options();
243
			} elseif ( in_array( $key, CMB2_Field::$callable_fields, true ) ) {
244
245
				if ( isset( $this->request['_rendered'] ) ) {
246
					$value = $key === 'render_row_cb' ? $rendered : $field->get_param_callback_result( $key );
247
				} elseif ( is_array( $value ) ) {
248
					// We need to rewrite callbacks as string as they will cause
249
					// JSON recursion errors.
250
					$class = is_string( $value[0] ) ? $value[0] : get_class( $value[0] );
251
					$value = $class . '::' . $value[1];
252
				}
253
			}
254
255
			$key = isset( $params_to_rename[ $key ] ) ? $params_to_rename[ $key ] : $key;
256
257
			if ( empty( $value ) || is_scalar( $value ) || is_array( $value ) ) {
258
				$field_data[ $key ] = $value;
259
			} else {
260
				$field_data[ $key ] = sprintf( __( 'Value Error for %s', 'cmb2' ), $key );
261
			}
262
		}
263
264
		if ( $this->request['object_id'] && $this->request['object_type'] ) {
265
			$field_data['value'] = $field->get_data();
266
		}
267
268
		return $field_data;
269
	}
270
271
	/**
272
	 * Return an array of contextual links for field/fields.
273
	 *
274
	 * @since  2.2.4
275
	 *
276
	 * @param  CMB2_Field $field Field object to build links from.
277
	 *
278
	 * @return array             Array of links
279
	 */
280
	protected function prepare_links( $field ) {
281
		$boxbase      = $this->namespace_base . '/' . $this->rest_box->cmb->cmb_id;
282
		$query_string = $this->get_query_string();
283
284
		$links = array(
285
			'self' => array(
286
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields/' . $field->_id() . $query_string ),
287
			),
288
			'collection' => array(
289
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields' . $query_string ),
290
			),
291
			'up' => array(
292
				'href' => rest_url( $boxbase . $query_string ),
293
			),
294
		);
295
296
		// Don't embed boxes when looking at boxes route.
297
		if ( '/cmb2/v1/boxes' !== CMB2_REST_Controller::get_intial_route() ) {
298
			$links['up']['embeddable'] = true;
299
		}
300
301
		return $links;
302
	}
303
304
}
305