Completed
Push — master ( 1e5530...3e2977 )
by
unknown
10:33 queued 14s
created

Router   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 458
Duplicated Lines 4.37 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 20
loc 458
rs 8.72
c 0
b 0
f 0
wmc 46
lcom 2
cbo 2

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A boot() 0 3 1
A set_routes() 0 3 1
A get_routes() 0 3 1
A set_version() 0 3 1
A get_version() 0 3 1
A set_vendor() 0 3 1
A get_vendor() 0 3 1
A allow_access() 0 3 1
A register_routes() 0 5 2
A register_route() 0 8 2
A options_accessor() 0 9 2
A options_permission() 0 3 1
A get_all_field_values() 0 18 5
A get_post_meta() 0 4 1
A get_user_meta() 0 4 1
A get_term_meta() 0 4 1
A get_comment_meta() 0 4 1
A get_association_data() 0 24 4
A get_attachment_data() 0 6 1
A get_options() 0 4 1
A set_options() 0 17 4
B block_renderer_permission() 20 31 6
A block_renderer_args_schema() 0 18 1
A block_renderer() 0 31 4

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 Router 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 Router, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Carbon_Fields\REST_API;
4
5
use Carbon_Fields\Helper\Helper;
6
use Carbon_Fields\Container\Repository as ContainerRepository;
7
8
/**
9
* Register custom routes for REST API
10
*/
11
class Router {
12
13
	/**
14
	 * Carbon Fields routes
15
	 *
16
	 * @var array
17
	 */
18
	protected $routes = array(
19
		'post_meta' => array(
20
			'path'                => '/posts/(?P<id>\d+)',
21
			'callback'            => 'get_post_meta',
22
			'permission_callback' => 'allow_access',
23
			'methods'             => 'GET',
24
		),
25
		'term_meta' => array(
26
			'path'                => '/terms/(?P<id>\d+)',
27
			'callback'            => 'get_term_meta',
28
			'permission_callback' => 'allow_access',
29
			'methods'             => 'GET',
30
		),
31
		'user_meta' => array(
32
			'path'                => '/users/(?P<id>\d+)',
33
			'callback'            => 'get_user_meta',
34
			'permission_callback' => 'allow_access',
35
			'methods'             => 'GET',
36
		),
37
		'comment_meta' => array(
38
			'path'                => '/comments/(?P<id>\d+)',
39
			'callback'            => 'get_comment_meta',
40
			'permission_callback' => 'allow_access',
41
			'methods'             => 'GET',
42
		),
43
		'theme_options' => array(
44
			'path'                => '/options/',
45
			'callback'            => 'options_accessor',
46
			'permission_callback' => 'options_permission',
47
			'methods'             => array( 'GET', 'POST' ),
48
		),
49
		'association_data' => array(
50
			'path'                => '/association',
51
			'callback'            => 'get_association_data',
52
			'permission_callback' => 'allow_access',
53
			'methods'             => 'GET',
54
		),
55
		'attachment_data' => array(
56
			'path'                => '/attachment',
57
			'callback'            => 'get_attachment_data',
58
			'permission_callback' => 'allow_access',
59
			'methods'             => 'GET',
60
		),
61
		'block_renderer' => array(
62
			'path'                => '/block-renderer',
63
			'callback'            => 'block_renderer',
64
			'permission_callback' => 'block_renderer_permission',
65
			'methods'             => 'POST',
66
			'args'                => 'block_renderer_args_schema',
67
		)
68
	);
69
70
	/**
71
	 * Version of the API
72
	 *
73
	 * @see set_version()
74
	 * @see get_version()
75
	 * @var string
76
	 */
77
	protected $version = '1';
78
79
	/**
80
	 * Vendor slug for the API
81
	 *
82
	 * @see set_vendor()
83
	 * @see get_vendor()
84
	 * @var string
85
	 */
86
	protected $vendor = 'carbon-fields';
87
88
	/**
89
	 * ContainerRepository instance
90
	 *
91
	 * @var ContainerRepository
92
	 */
93
	protected $container_repository;
94
95
	/**
96
	 * @param ContainerRepository $container_repository
97
	 */
98
	public function __construct( ContainerRepository $container_repository ) {
99
		$this->container_repository = $container_repository;
100
	}
101
102
	/**
103
	 * Boot up functionality
104
	 */
105
	public function boot() {
106
		add_action( 'rest_api_init', array( $this, 'register_routes' ), 15 );
107
	}
108
109
	/**
110
	 * Set routes
111
	 */
112
	public function set_routes( $routes ) {
113
		$this->routes = $routes;
114
	}
115
116
	/**
117
	 * Return routes
118
	 *
119
	 * @return array
120
	 */
121
	public function get_routes() {
122
		return $this->routes;
123
	}
124
125
	/**
126
	 * Set version
127
	 */
128
	public function set_version( $version ) {
129
		$this->version = $version;
130
	}
131
132
	/**
133
	 * Return version
134
	 *
135
	 * @return string
136
	 */
137
	public function get_version() {
138
		return $this->version;
139
	}
140
141
	/**
142
	 * Set vendor
143
	 */
144
	public function set_vendor( $vendor ) {
145
		$this->vendor = $vendor;
146
	}
147
148
	/**
149
	 * Return vendor
150
	 *
151
	 * @return string
152
	 */
153
	public function get_vendor() {
154
		return $this->vendor;
155
	}
156
157
	/**
158
	 * Allow access to an endpoint
159
	 *
160
	 * @return bool
161
	 */
162
	public function allow_access() {
163
		return true;
164
	}
165
166
	/**
167
	 * Register custom routes
168
	 *
169
	 * @see  register_route()
170
	 */
171
	public function register_routes() {
172
		foreach ( $this->routes as $route ) {
173
			$this->register_route( $route );
174
		}
175
	}
176
177
	/**
178
	 * Register a custom REST route
179
	 *
180
	 * @param  array $route
181
	 */
182
	protected function register_route( $route ) {
183
		register_rest_route( $this->get_vendor() . '/v' . $this->get_version(), $route['path'], array(
184
			'methods'             => $route['methods'],
185
			'permission_callback' => array( $this, $route['permission_callback'] ),
186
			'callback'            => array( $this, $route['callback'] ),
187
			'args'                => isset( $route['args'] ) ? call_user_func( array( $this, $route['args'] ) ) : array(),
188
		) );
189
	}
190
191
	/**
192
	 * Proxy method for handling get/set for theme options
193
	 *
194
	 * @param  WP_REST_Request $request
195
	 * @return array|WP_REST_Response
196
	 */
197
	public function options_accessor( $request ) {
198
		$request_type = $request->get_method();
199
200
		if ( $request_type === 'POST' ) {
201
			return $this->set_options( $request );
202
		}
203
204
		return $this->get_options();
205
	}
206
207
	/**
208
	 * Proxy method for handling theme options permissions
209
	 *
210
	 * @param  WP_REST_Request $request
211
	 * @return bool
212
	 */
213
	public function options_permission( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
214
		return current_user_can( 'manage_options' );
215
	}
216
217
	/**
218
	 * Wrapper method used for retrieving data from Data_Manager
219
	 *
220
	 * @param  string $container_type
221
	 * @param  string $object_id
222
	 * @return array
223
	 */
224
	protected function get_all_field_values( $container_type, $object_id = null ) {
225
		$object_id = ( $object_id !== '' ) ? $object_id : null;
226
227
		$containers = $this->container_repository->get_containers( $container_type );
228
		$fields = array();
229
		foreach ( $containers as $container ) {
230
			$fields = array_merge( $fields, $container->get_fields() );
231
		}
232
233
		$values = array();
234
		foreach ( $fields as $field ) {
235
			if ( ! $field->get_visible_in_rest_api() ) {
236
				continue;
237
			}
238
			$values[ $field->get_base_name() ] = Helper::get_value( $object_id, $container_type, '', $field->get_base_name() );
239
		}
240
		return $values;
241
	}
242
243
	/**
244
	 * Get Carbon Fields post meta values
245
	 *
246
	 * @param  array $data
247
	 * @return array
248
	 */
249
	public function get_post_meta( $data ) {
250
		$carbon_data = $this->get_all_field_values( 'post_meta', $data['id'] );
251
		return array( 'carbon_fields' => $carbon_data );
252
	}
253
254
	/**
255
	 * Get Carbon Fields user meta values
256
	 *
257
	 * @param  array $data
258
	 * @return array
259
	 */
260
	public function get_user_meta( $data ) {
261
		$carbon_data = $this->get_all_field_values( 'user_meta', $data['id'] );
262
		return array( 'carbon_fields' => $carbon_data );
263
	}
264
265
	/**
266
	 * Get Carbon Fields term meta values
267
	 *
268
	 * @param  array $data
269
	 * @return array
270
	 */
271
	public function get_term_meta( $data ) {
272
		$carbon_data = $this->get_all_field_values( 'term_meta', $data['id'] );
273
		return array( 'carbon_fields' => $carbon_data );
274
	}
275
276
	/**
277
	 * Get Carbon Fields comment meta values
278
	 *
279
	 * @param  array $data
280
	 * @return array
281
	 */
282
	public function get_comment_meta( $data ) {
283
		$carbon_data = $this->get_all_field_values( 'comment_meta', $data['id'] );
284
		return array( 'carbon_fields' => $carbon_data );
285
	}
286
287
	/**
288
	 * Get Carbon Fields association options data.
289
	 *
290
	 * @return array
291
	 */
292
	public function get_association_data() {
293
		$container_id = $_GET['container_id'];
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
294
		$field_id     = $_GET['field_id'];
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
295
		$options      = isset( $_GET['options'] ) ? $_GET['options'] : array();
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
296
		$return_value = array();
297
298
		$field = Helper::get_field( null, $container_id, $field_id );
299
300
		foreach ( $options as $entry ) {
301
			$item = array(
302
				'type'       => $entry['type'],
303
				'subtype'    => $entry['subtype'],
304
				'thumbnail'  => $field->get_thumbnail_by_type( $entry['id'], $entry['type'], $entry['subtype'] ),
0 ignored issues
show
Bug introduced by
The method get_thumbnail_by_type cannot be called on $field (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
305
				'id'         => intval( $entry['id'] ),
306
				'title'      => $field->get_title_by_type( $entry['id'], $entry['type'], $entry['subtype'] ),
0 ignored issues
show
Bug introduced by
The method get_title_by_type cannot be called on $field (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
307
				'label'      => $field->get_item_label( $entry['id'], $entry['type'], $entry['subtype'] ),
0 ignored issues
show
Bug introduced by
The method get_item_label cannot be called on $field (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
308
				'is_trashed' => ( $entry['type'] == 'post' && get_post_status( $entry['id'] ) === 'trash' ),
309
			);
310
311
			$return_value[] = $item;
312
		}
313
314
		return $return_value;
315
	}
316
317
	/**
318
	 * Get attachment data by given ID or URL.
319
	 *
320
	 * @return array
321
	 */
322
	public function get_attachment_data() {
323
		$type  = sanitize_text_field( $_GET['type'] );
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
324
		$value = sanitize_text_field( $_GET['value'] );
0 ignored issues
show
introduced by
Detected access of super global var $_GET, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-validated input variable: $_GET
Loading history...
325
326
		return Helper::get_attachment_metadata( $value, $type );
327
	}
328
329
	/**
330
	 * Retrieve Carbon theme options
331
	 *
332
	 * @return array
333
	 */
334
	protected function get_options() {
335
		$carbon_data = $this->get_all_field_values( 'theme_options' );
336
		return array( 'carbon_fields' => $carbon_data );
337
	}
338
339
	/**
340
	 * Set Carbon theme options
341
	 *
342
	 * @param WP_REST_Request $request Full data about the request.
343
	 * @return WP_Error|WP_REST_Response
344
	 */
345
	protected function set_options( $request ) {
346
		$options = $request->get_params();
347
348
		if ( empty( $options ) ) {
349
			return new \WP_REST_Response( __( 'No option names provided', 'carbon-fields' ) );
350
		}
351
352
		foreach ( $options as $key => $value ) {
353
			try {
354
				Helper::set_value( null, 'Theme_Options', '', $key, $value );
355
			} catch ( \Exception $e ) {
356
				return new \WP_REST_Response( wp_strip_all_tags( $e->getMessage() ) );
357
			}
358
		}
359
360
		return new \WP_REST_Response( __( 'Theme Options updated.', 'carbon-fields' ), 200 );
361
	}
362
363
	/**
364
	 * Checks if a given request has access to read blocks.
365
	 *
366
	 * @see https://github.com/WordPress/WordPress/blob/master/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php#L78-L116
367
	 *
368
	 * @param  WP_REST_Request
369
	 * @return true|WP_Error
370
	 */
371
	public function block_renderer_permission( $request ) {
372
		global $post;
373
374
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
375
376
		if ( 0 < $post_id ) {
377
			$post = get_post( $post_id );
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
378
379 View Code Duplication
			if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
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...
380
				return new \WP_Error(
381
					'block_cannot_read',
382
					__( 'Sorry, you are not allowed to read blocks of this post.', 'carbon-fields' ),
383
					array(
384
						'status' => rest_authorization_required_code(),
385
					)
386
				);
387
			}
388 View Code Duplication
		} else {
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...
389
			if ( ! current_user_can( 'edit_posts' ) ) {
390
				return new \WP_Error(
391
					'block_cannot_read',
392
					__( 'Sorry, you are not allowed to read blocks as this user.', 'carbon-fields' ),
393
					array(
394
						'status' => rest_authorization_required_code(),
395
					)
396
				);
397
			}
398
		}
399
400
		return true;
401
	}
402
403
	/**
404
	 * Returns the schema of the accepted arguments.
405
	 *
406
	 * @see https://github.com/WordPress/WordPress/blob/master/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php#L56-L71
407
	 *
408
	 * @return array
409
	 */
410
	public function block_renderer_args_schema() {
411
		return array(
412
			'name'       => array(
413
				'type'        => 'string',
414
				'required'    => true,
415
				'description' => __( 'The name of the block.', 'carbon-fields' ),
416
			),
417
			'content'    => array(
418
				'type'        => 'string',
419
				'required'    => true,
420
				'description' => __( 'The content of the block.', 'carbon-fields' ),
421
			),
422
			'post_id'    => array(
423
				'type'        => 'integer',
424
				'description' => __( 'ID of the post context.', 'carbon-fields' ),
425
			),
426
		);
427
	}
428
429
	/**
430
	 * Returns block output from block's registered render_callback.
431
	 *
432
	 * @see https://github.com/WordPress/WordPress/blob/master/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php#L118-L154
433
	 *
434
	 * @param  WP_REST_Request $request
435
	 * @return WP_REST_Response|WP_Error
436
	 */
437
	public function block_renderer( $request ) {
438
		global $post;
439
440
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
441
442
		if ( 0 < $post_id ) {
443
			$post = get_post( $post_id );
0 ignored issues
show
introduced by
Overridding WordPress globals is prohibited
Loading history...
444
445
			// Set up postdata since this will be needed if post_id was set.
446
			setup_postdata( $post );
447
		}
448
449
		$registry = \WP_Block_Type_Registry::get_instance();
450
		$block    = $registry->get_registered( $request['name'] );
451
452
		if ( null === $block ) {
453
			return new \WP_Error(
454
				'block_invalid',
455
				__( 'Invalid block.' ),
456
				array(
457
					'status' => 404,
458
				)
459
			);
460
		}
461
462
		$data = array(
463
			'rendered' => do_blocks( $request['content'] ),
464
		);
465
466
		return rest_ensure_response( $data );
467
	}
468
}
469