Passed
Push — add/43 ( b57d71...424871 )
by Chris
14:00 queued 07:35
created

CMB2_REST_Controller_Fields::prepare_field_data()   F

Complexity

Conditions 17
Paths 240

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
eloc 30
c 1
b 0
f 0
nc 240
nop 1
dl 0
loc 53
rs 3.8833

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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    CMB2 team
14
 * @license   GPL-2.0+
15
 * @link      https://cmb2.io
16
 */
17
class CMB2_REST_Controller_Fields extends CMB2_REST_Controller_Boxes {
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;
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 );
126
127
			// And make sure current user can view this box.
128
			if ( $this->field && $this->get_item_permissions_check_filter() ) {
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 );
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' ) );
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
	public function update_item_permissions_check( $request ) {
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 );
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' );
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
	public function delete_item_permissions_check( $request ) {
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 );
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' );
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(
307
			$this->field ? $this->field : $this->request->get_param( 'field_id' ),
308
			true
309
		);
310
311
		if ( ! $this->field ) {
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 );
339
340
		if ( ! $this->field ) {
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 ( $field->args( 'has_supporting_data' ) ) {
420
			$field_data = $this->get_supporting_data( $field_data, $field );
421
		}
422
423
		if ( $this->request['object_id'] && $this->request['object_type'] ) {
424
			$field_data['value'] = $field->get_rest_value();
425
		}
426
427
		return $field_data;
428
	}
429
430
	/**
431
	 * Gets field supporting data (field id and value).
432
	 *
433
	 * @since  2.7.0
434
	 *
435
	 * @param  CMB2_Field $field      Field object.
436
	 * @param  array      $field_data Array of field data.
437
	 *
438
	 * @return array                  Array of field data.
439
	 */
440
	public function get_supporting_data( $field_data, $field ) {
441
442
		// Reset placement of this property.
443
		unset( $field_data['has_supporting_data'] );
444
		$field_data['has_supporting_data'] = true;
445
446
		$field = $field->get_supporting_field();
447
		$field_data['supporting_data'] = array(
448
			'id' => $field->_id( '', false ),
449
		);
450
451
		if ( $this->request['object_id'] && $this->request['object_type'] ) {
452
			$field_data['supporting_data']['value'] = $field->get_rest_value();
453
		}
454
455
		return $field_data;
456
	}
457
458
	/**
459
	 * Return an array of contextual links for field/fields.
460
	 *
461
	 * @since  2.2.3
462
	 *
463
	 * @param  CMB2_Field $field Field object to build links from.
464
	 *
465
	 * @return array             Array of links
466
	 */
467
	protected function prepare_links( $field ) {
468
		$boxbase      = $this->namespace_base . '/' . $this->rest_box->cmb->cmb_id;
469
		$query_string = $this->get_query_string();
470
471
		$links = array(
472
			'self' => array(
473
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields/' . $field->_id( '', false ) . $query_string ),
474
			),
475
			'collection' => array(
476
				'href' => rest_url( trailingslashit( $boxbase ) . 'fields' . $query_string ),
477
			),
478
			'up' => array(
479
				'embeddable' => true,
480
				'href' => rest_url( $boxbase . $query_string ),
481
			),
482
		);
483
484
		return $links;
485
	}
486
487
	/**
488
	 * Checks if the CMB2 box or field has any registered callback parameters for the given filter.
489
	 *
490
	 * The registered handlers will have a property name which matches the filter, except:
491
	 * - The 'cmb2_api' prefix will be removed
492
	 * - A '_cb' suffix will be added (to stay inline with other '*_cb' parameters).
493
	 *
494
	 * @since  2.2.3
495
	 *
496
	 * @param  string $filter      The filter name.
497
	 * @param  bool   $default_val The default filter value.
498
	 *
499
	 * @return bool                The possibly-modified filter value (if the _cb param is a non-callable).
500
	 */
501
	public function maybe_hook_registered_callback( $filter, $default_val ) {
502
		$default_val = parent::maybe_hook_registered_callback( $filter, $default_val );
503
504
		if ( $this->field ) {
505
506
			// Hook field specific filter callbacks.
507
			$val = $this->field->maybe_hook_parameter( $filter, $default_val );
508
			if ( null !== $val ) {
509
				$default_val = $val;
510
			}
511
		}
512
513
		return $default_val;
514
	}
515
516
	/**
517
	 * Unhooks any CMB2 box or field registered callback parameters for the given filter.
518
	 *
519
	 * @since  2.2.3
520
	 *
521
	 * @param  string $filter The filter name.
522
	 *
523
	 * @return void
524
	 */
525
	public function maybe_unhook_registered_callback( $filter ) {
526
		parent::maybe_unhook_registered_callback( $filter );
527
528
		if ( $this->field ) {
529
			// Unhook field specific filter callbacks.
530
			$this->field->maybe_hook_parameter( $filter, null, 'remove_filter' );
531
		}
532
	}
533
534
}
535