Completed
Branch develop (25b425)
by
unknown
04:54 queued 02:37
created

prepare_item_for_response()   F

Complexity

Conditions 10
Paths 512

Size

Total Lines 48
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 48
rs 3.646
cc 10
eloc 25
nc 512
nop 2

How to fix   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
/**
4
 * Access terms associated with a taxonomy
5
 */
6
class WP_REST_Terms_Controller extends WP_REST_Controller {
7
8
	protected $taxonomy;
9
10
	/**
11
	 * @param string $taxonomy
12
	 */
13
	public function __construct( $taxonomy ) {
14
		$this->taxonomy = $taxonomy;
15
		$this->namespace = 'wp/v2';
16
		$tax_obj = get_taxonomy( $taxonomy );
17
		$this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
18
	}
19
20
	/**
21
	 * Register the routes for the objects of the controller.
22
	 */
23 View Code Duplication
	public function register_routes() {
24
25
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
26
			array(
27
				'methods'             => WP_REST_Server::READABLE,
28
				'callback'            => array( $this, 'get_items' ),
29
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
30
				'args'                => $this->get_collection_params(),
31
			),
32
			array(
33
				'methods'     => WP_REST_Server::CREATABLE,
34
				'callback'    => array( $this, 'create_item' ),
35
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
36
				'args'        => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
37
			),
38
			'schema' => array( $this, 'get_public_item_schema' ),
39
		));
40
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
41
			array(
42
				'methods'             => WP_REST_Server::READABLE,
43
				'callback'            => array( $this, 'get_item' ),
44
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
45
				'args'                => array(
46
					'context'         => $this->get_context_param( array( 'default' => 'view' ) ),
47
				),
48
			),
49
			array(
50
				'methods'    => WP_REST_Server::EDITABLE,
51
				'callback'   => array( $this, 'update_item' ),
52
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
53
				'args'        => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
54
			),
55
			array(
56
				'methods'    => WP_REST_Server::DELETABLE,
57
				'callback'   => array( $this, 'delete_item' ),
58
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
59
				'args'       => array(
60
					'force'    => array(
61
						'default'     => false,
62
						'description' => __( 'Required to be true, as resource does not support trashing.' ),
63
					),
64
				),
65
			),
66
			'schema' => array( $this, 'get_public_item_schema' ),
67
		) );
68
	}
69
70
	/**
71
	 * Check if a given request has access to read the terms.
72
	 *
73
	 * @param  WP_REST_Request $request Full details about the request.
74
	 * @return WP_Error|boolean
75
	 */
76 View Code Duplication
	public function get_items_permissions_check( $request ) {
77
		$tax_obj = get_taxonomy( $this->taxonomy );
78
		if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
79
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method WP_REST_Controller::get_items_permissions_check of type WP_Error.

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...
80
		}
81
		if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
82
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
83
		}
84
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method WP_REST_Controller::get_items_permissions_check of type WP_Error.

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...
85
	}
86
87
	/**
88
	 * Get terms associated with a taxonomy
89
	 *
90
	 * @param WP_REST_Request $request Full details about the request
91
	 * @return WP_REST_Response|WP_Error
92
	 */
93
	public function get_items( $request ) {
94
		$prepared_args = array(
95
			'exclude'    => $request['exclude'],
96
			'include'    => $request['include'],
97
			'order'      => $request['order'],
98
			'orderby'    => $request['orderby'],
99
			'post'       => $request['post'],
100
			'hide_empty' => $request['hide_empty'],
101
			'number'     => $request['per_page'],
102
			'search'     => $request['search'],
103
			'slug'       => $request['slug'],
104
		);
105
106 View Code Duplication
		if ( ! empty( $request['offset'] ) ) {
107
			$prepared_args['offset'] = $request['offset'];
108
		} else {
109
			$prepared_args['offset']  = ( $request['page'] - 1 ) * $prepared_args['number'];
110
		}
111
112
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
113
114
		if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
115
			if ( 0 === $request['parent'] ) {
116
				// Only query top-level terms.
117
				$prepared_args['parent'] = 0;
118
			} else {
119
				if ( $request['parent'] ) {
120
					$prepared_args['parent'] = $request['parent'];
121
				}
122
			}
123
		}
124
125
		/**
126
		 * Filter the query arguments, before passing them to `get_terms()`.
127
		 *
128
		 * Enables adding extra arguments or setting defaults for a terms
129
		 * collection request.
130
		 *
131
		 * @see https://developer.wordpress.org/reference/functions/get_terms/
132
		 *
133
		 * @param array           $prepared_args Array of arguments to be
134
		 *                                       passed to get_terms.
135
		 * @param WP_REST_Request $request       The current request.
136
		 */
137
		$prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );
138
139
		// Can we use the cached call?
140
		$use_cache = ! empty( $prepared_args['post'] )
0 ignored issues
show
Unused Code introduced by
$use_cache is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
141
			&& empty( $prepared_args['include'] )
142
			&& empty( $prepared_args['exclude'] )
143
			&& empty( $prepared_args['hide_empty'] )
144
			&& empty( $prepared_args['search'] )
145
			&& empty( $prepared_args['slug'] );
146
147
		if ( ! empty( $prepared_args['post'] )  ) {
148
			$query_result = $this->get_terms_for_post( $prepared_args );
149
			$total_terms = $this->total_terms;
0 ignored issues
show
Bug introduced by
The property total_terms does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
150
		} else {
151
			$query_result = get_terms( $this->taxonomy, $prepared_args );
152
153
			$count_args = $prepared_args;
154
			unset( $count_args['number'] );
155
			unset( $count_args['offset'] );
156
			$total_terms = wp_count_terms( $this->taxonomy, $count_args );
157
158
			// Ensure we don't return results when offset is out of bounds
159
			// see https://core.trac.wordpress.org/ticket/35935
160
			if ( $prepared_args['offset'] >= $total_terms ) {
161
				$query_result = array();
162
			}
163
164
			// wp_count_terms can return a falsy value when the term has no children
165
			if ( ! $total_terms ) {
166
				$total_terms = 0;
167
			}
168
		}
169
		$response = array();
170
		foreach ( $query_result as $term ) {
171
			$data = $this->prepare_item_for_response( $term, $request );
172
			$response[] = $this->prepare_response_for_collection( $data );
173
		}
174
175
		$response = rest_ensure_response( $response );
176
177
		// Store pagation values for headers then unset for count query.
178
		$per_page = (int) $prepared_args['number'];
179
		$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
180
181
		$response->header( 'X-WP-Total', (int) $total_terms );
182
		$max_pages = ceil( $total_terms / $per_page );
183
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
184
185
		$base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $this->rest_base ) );
186 View Code Duplication
		if ( $page > 1 ) {
187
			$prev_page = $page - 1;
188
			if ( $prev_page > $max_pages ) {
189
				$prev_page = $max_pages;
190
			}
191
			$prev_link = add_query_arg( 'page', $prev_page, $base );
192
			$response->link_header( 'prev', $prev_link );
193
		}
194 View Code Duplication
		if ( $max_pages > $page ) {
195
			$next_page = $page + 1;
196
			$next_link = add_query_arg( 'page', $next_page, $base );
197
			$response->link_header( 'next', $next_link );
198
		}
199
200
		return $response;
201
	}
202
203
	/**
204
	 * Get the terms attached to a post.
205
	 *
206
	 * This is an alternative to `get_terms()` that uses `get_the_terms()`
207
	 * instead, which hits the object cache. There are a few things not
208
	 * supported, notably `include`, `exclude`. In `self::get_items()` these
209
	 * are instead treated as a full query.
210
	 *
211
	 * @param array $prepared_args Arguments for `get_terms()`
212
	 * @return array List of term objects. (Total count in `$this->total_terms`)
213
	 */
214
	protected function get_terms_for_post( $prepared_args ) {
215
		$query_result = get_the_terms( $prepared_args['post'], $this->taxonomy );
216
		if ( empty( $query_result ) ) {
217
			$this->total_terms = 0;
218
			return array();
219
		}
220
221
		// get_items() verifies that we don't have `include` set, and default
222
		// ordering is by `name`
223
		if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ) ) ) {
224
			switch ( $prepared_args['orderby'] ) {
225
				case 'id':
226
					$this->sort_column = 'term_id';
0 ignored issues
show
Bug introduced by
The property sort_column does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
227
					break;
228
229
				case 'slug':
230
				case 'term_group':
231
				case 'description':
232
				case 'count':
233
					$this->sort_column = $prepared_args['orderby'];
234
					break;
235
			}
236
			usort( $query_result, array( $this, 'compare_terms' ) );
237
		}
238
		if ( strtolower( $prepared_args['order'] ) !== 'asc' ) {
239
			$query_result = array_reverse( $query_result );
240
		}
241
242
		// Pagination
243
		$this->total_terms = count( $query_result );
244
		$query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] );
245
246
		return $query_result;
247
	}
248
249
	/**
250
	 * Comparison function for sorting terms by a column.
251
	 *
252
	 * Uses `$this->sort_column` to determine field to sort by.
253
	 *
254
	 * @param stdClass $left Term object.
255
	 * @param stdClass $right Term object.
256
	 * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left.
257
	 */
258
	protected function compare_terms( $left, $right ) {
259
		$col = $this->sort_column;
260
		$left_val = $left->$col;
261
		$right_val = $right->$col;
262
263
		if ( is_int( $left_val ) && is_int( $right_val ) ) {
264
			return $left_val - $right_val;
265
		}
266
267
		return strcmp( $left_val, $right_val );
268
	}
269
270
	/**
271
	 * Check if a given request has access to read a term.
272
	 *
273
	 * @param  WP_REST_Request $request Full details about the request.
274
	 * @return WP_Error|boolean
275
	 */
276 View Code Duplication
	public function get_item_permissions_check( $request ) {
277
		$tax_obj = get_taxonomy( $this->taxonomy );
278
		if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
279
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method WP_REST_Controller::get_item_permissions_check of type WP_Error.

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...
280
		}
281
		if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
282
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
283
		}
284
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method WP_REST_Controller::get_item_permissions_check of type WP_Error.

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...
285
	}
286
287
	/**
288
	 * Get a single term from a taxonomy
289
	 *
290
	 * @param WP_REST_Request $request Full details about the request
291
	 * @return WP_REST_Request|WP_Error
292
	 */
293
	public function get_item( $request ) {
294
295
		$term = get_term( (int) $request['id'], $this->taxonomy );
296
		if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
297
			return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
298
		}
299
		if ( is_wp_error( $term ) ) {
300
			return $term;
301
		}
302
303
		$response = $this->prepare_item_for_response( $term, $request );
304
305
		return rest_ensure_response( $response );
306
	}
307
308
	/**
309
	 * Check if a given request has access to create a term
310
	 *
311
	 * @param  WP_REST_Request $request Full details about the request.
312
	 * @return WP_Error|boolean
313
	 */
314
	public function create_item_permissions_check( $request ) {
315
316
		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
317
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method WP_REST_Controller::create_item_permissions_check of type WP_Error.

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...
318
		}
319
320
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
321
		if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
322
			return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new resource.' ), array( 'status' => rest_authorization_required_code() ) );
323
		}
324
325
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method WP_REST_Controller::create_item_permissions_check of type WP_Error.

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...
326
	}
327
328
	/**
329
	 * Create a single term for a taxonomy
330
	 *
331
	 * @param WP_REST_Request $request Full details about the request
332
	 * @return WP_REST_Request|WP_Error
333
	 */
334
	public function create_item( $request ) {
335 View Code Duplication
		if ( isset( $request['parent'] ) ) {
336
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
337
				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
338
			}
339
340
			$parent = get_term( (int) $request['parent'], $this->taxonomy );
341
342
			if ( ! $parent ) {
343
				return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
344
			}
345
		}
346
347
		$prepared_term = $this->prepare_item_for_database( $request );
348
349
		$term = wp_insert_term( $prepared_term->name, $this->taxonomy, $prepared_term );
350
		if ( is_wp_error( $term ) ) {
351
352
			// If we're going to inform the client that the term exists, give them the identifier
353
			// they can actually use.
354
355
			if ( ( $term_id = $term->get_error_data( 'term_exists' ) ) ) {
356
				$existing_term = get_term( $term_id, $this->taxonomy );
357
				$term->add_data( $existing_term->term_id, 'term_exists' );
358
			}
359
360
			return $term;
361
		}
362
363
		$term = get_term( $term['term_id'], $this->taxonomy );
364
365
		/**
366
		 * Fires after a single term is created or updated via the REST API.
367
		 *
368
		 * @param WP_Term         $term     Inserted Term object.
369
		 * @param WP_REST_Request $request   Request object.
370
		 * @param boolean         $creating  True when creating term, false when updating.
371
		 */
372
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
373
374
		$this->update_additional_fields_for_object( $term, $request );
375
		$request->set_param( 'context', 'view' );
376
		$response = $this->prepare_item_for_response( $term, $request );
377
		$response = rest_ensure_response( $response );
378
		$response->set_status( 201 );
379
		$response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
380
		return $response;
381
	}
382
383
	/**
384
	 * Check if a given request has access to update a term
385
	 *
386
	 * @param  WP_REST_Request $request Full details about the request.
387
	 * @return WP_Error|boolean
388
	 */
389 View Code Duplication
	public function update_item_permissions_check( $request ) {
390
391
		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
392
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method WP_REST_Controller::update_item_permissions_check of type WP_Error.

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...
393
		}
394
395
		$term = get_term( (int) $request['id'], $this->taxonomy );
396
		if ( ! $term ) {
397
			return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
398
		}
399
400
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
401
		if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
402
			return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update resource.' ), array( 'status' => rest_authorization_required_code() ) );
403
		}
404
405
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method WP_REST_Controller::update_item_permissions_check of type WP_Error.

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...
406
	}
407
408
	/**
409
	 * Update a single term from a taxonomy
410
	 *
411
	 * @param WP_REST_Request $request Full details about the request
412
	 * @return WP_REST_Request|WP_Error
413
	 */
414
	public function update_item( $request ) {
415 View Code Duplication
		if ( isset( $request['parent'] ) ) {
416
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
417
				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
418
			}
419
420
			$parent = get_term( (int) $request['parent'], $this->taxonomy );
421
422
			if ( ! $parent ) {
423
				return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
424
			}
425
		}
426
427
		$prepared_term = $this->prepare_item_for_database( $request );
428
429
		$term = get_term( (int) $request['id'], $this->taxonomy );
430
431
		// Only update the term if we haz something to update.
432
		if ( ! empty( $prepared_term ) ) {
433
			$update = wp_update_term( $term->term_id, $term->taxonomy, (array) $prepared_term );
434
			if ( is_wp_error( $update ) ) {
435
				return $update;
436
			}
437
		}
438
439
		$term = get_term( (int) $request['id'], $this->taxonomy );
440
441
		/* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */
442
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
443
444
		$this->update_additional_fields_for_object( $term, $request );
445
		$request->set_param( 'context', 'view' );
446
		$response = $this->prepare_item_for_response( $term, $request );
447
		return rest_ensure_response( $response );
448
	}
449
450
	/**
451
	 * Check if a given request has access to delete a term
452
	 *
453
	 * @param  WP_REST_Request $request Full details about the request.
454
	 * @return WP_Error|boolean
455
	 */
456 View Code Duplication
	public function delete_item_permissions_check( $request ) {
457
		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
458
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method WP_REST_Controller::delete_item_permissions_check of type WP_Error.

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...
459
		}
460
		$term = get_term( (int) $request['id'], $this->taxonomy );
461
		if ( ! $term ) {
462
			return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
463
		}
464
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
465
		if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
466
			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete resource.' ), array( 'status' => rest_authorization_required_code() ) );
467
		}
468
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method WP_REST_Controller::delete_item_permissions_check of type WP_Error.

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...
469
	}
470
471
	/**
472
	 * Delete a single term from a taxonomy
473
	 *
474
	 * @param WP_REST_Request $request Full details about the request
475
	 * @return WP_REST_Response|WP_Error
476
	 */
477
	public function delete_item( $request ) {
478
479
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
480
481
		// We don't support trashing for this type, error out
482
		if ( ! $force ) {
483
			return new WP_Error( 'rest_trash_not_supported', __( 'Resource does not support trashing.' ), array( 'status' => 501 ) );
484
		}
485
486
		$term = get_term( (int) $request['id'], $this->taxonomy );
487
		$request->set_param( 'context', 'view' );
488
		$response = $this->prepare_item_for_response( $term, $request );
489
490
		$retval = wp_delete_term( $term->term_id, $term->taxonomy );
491
		if ( ! $retval ) {
492
			return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) );
493
		}
494
495
		/**
496
		 * Fires after a single term is deleted via the REST API.
497
		 *
498
		 * @param WP_Term          $term     The deleted term.
499
		 * @param WP_REST_Response $response The response data.
500
		 * @param WP_REST_Request  $request  The request sent to the API.
501
		 */
502
		do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
503
504
		return $response;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $response; (WP_REST_Response) is incompatible with the return type of the parent method WP_REST_Controller::delete_item of type WP_Error.

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...
505
	}
506
507
	/**
508
	 * Prepare a single term for create or update
509
	 *
510
	 * @param WP_REST_Request $request Request object.
511
	 * @return object $prepared_term Term object.
512
	 */
513
	public function prepare_item_for_database( $request ) {
514
		$prepared_term = new stdClass;
515
516
		$schema = $this->get_item_schema();
517 View Code Duplication
		if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
518
			$prepared_term->name = $request['name'];
519
		}
520
521 View Code Duplication
		if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
522
			$prepared_term->slug = $request['slug'];
523
		}
524
525
		if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) {
526
			$prepared_term->taxonomy = $request['taxonomy'];
527
		}
528
529
		if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
530
			$prepared_term->description = $request['description'];
531
		}
532
533
		if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) {
534
			$parent_term_id = 0;
535
			$parent_term = get_term( (int) $request['parent'], $this->taxonomy );
536
537
			if ( $parent_term ) {
538
				$parent_term_id = $parent_term->term_id;
539
			}
540
541
			$prepared_term->parent = $parent_term_id;
542
		}
543
544
		/**
545
		 * Filter term data before inserting term via the REST API.
546
		 *
547
		 * @param object          $prepared_term Term object.
548
		 * @param WP_REST_Request $request       Request object.
549
		 */
550
		return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request );
551
	}
552
553
	/**
554
	 * Prepare a single term output for response
555
	 *
556
	 * @param obj $item Term object
557
	 * @param WP_REST_Request $request
558
	 * @return WP_REST_Response $response
559
	 */
560
	public function prepare_item_for_response( $item, $request ) {
561
562
		$schema = $this->get_item_schema();
563
		$data = array();
564
		if ( ! empty( $schema['properties']['id'] ) ) {
565
			$data['id'] = (int) $item->term_id;
566
		}
567
		if ( ! empty( $schema['properties']['count'] ) ) {
568
			$data['count'] = (int) $item->count;
569
		}
570
		if ( ! empty( $schema['properties']['description'] ) ) {
571
			$data['description'] = $item->description;
572
		}
573
		if ( ! empty( $schema['properties']['link'] ) ) {
574
			$data['link'] = get_term_link( $item );
575
		}
576
		if ( ! empty( $schema['properties']['name'] ) ) {
577
			$data['name'] = $item->name;
578
		}
579
		if ( ! empty( $schema['properties']['slug'] ) ) {
580
			$data['slug'] = $item->slug;
581
		}
582
		if ( ! empty( $schema['properties']['taxonomy'] ) ) {
583
			$data['taxonomy'] = $item->taxonomy;
584
		}
585
		if ( ! empty( $schema['properties']['parent'] ) ) {
586
			$data['parent'] = (int) $item->parent;
587
		}
588
589
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
590
		$data = $this->add_additional_fields_to_object( $data, $request );
591
		$data = $this->filter_response_by_context( $data, $context );
592
593
		$response = rest_ensure_response( $data );
594
595
		$response->add_links( $this->prepare_links( $item ) );
596
597
		/**
598
		 * Filter a term item returned from the API.
599
		 *
600
		 * Allows modification of the term data right before it is returned.
601
		 *
602
		 * @param WP_REST_Response  $response  The response object.
603
		 * @param object            $item      The original term object.
604
		 * @param WP_REST_Request   $request   Request used to generate the response.
605
		 */
606
		return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
607
	}
608
609
	/**
610
	 * Prepare links for the request.
611
	 *
612
	 * @param object $term Term object.
613
	 * @return array Links for the given term.
614
	 */
615
	protected function prepare_links( $term ) {
616
		$base = '/' . $this->namespace . '/' . $this->rest_base;
617
		$links = array(
618
			'self'       => array(
619
				'href'       => rest_url( trailingslashit( $base ) . $term->term_id ),
620
			),
621
			'collection' => array(
622
				'href'       => rest_url( $base ),
623
			),
624
			'about'      => array(
625
				'href'       => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
626
			),
627
		);
628
629
		if ( $term->parent ) {
630
			$parent_term = get_term( (int) $term->parent, $term->taxonomy );
631
			if ( $parent_term ) {
632
				$links['up'] = array(
633
					'href'       => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
634
					'embeddable' => true,
635
				);
636
			}
637
		}
638
639
		$taxonomy_obj = get_taxonomy( $term->taxonomy );
640
		if ( empty( $taxonomy_obj->object_type ) ) {
641
			return $links;
642
		}
643
644
		$post_type_links = array();
645
		foreach ( $taxonomy_obj->object_type as $type ) {
646
			$post_type_object = get_post_type_object( $type );
647
			if ( empty( $post_type_object->show_in_rest ) ) {
648
				continue;
649
			}
650
			$rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
651
			$post_type_links[] = array(
652
				'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ),
653
			);
654
		}
655
		if ( ! empty( $post_type_links ) ) {
656
			$links['https://api.w.org/post_type'] = $post_type_links;
657
		}
658
659
		return $links;
660
	}
661
662
	/**
663
	 * Get the Term's schema, conforming to JSON Schema
664
	 *
665
	 * @return array
666
	 */
667
	public function get_item_schema() {
668
		$schema = array(
669
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
670
			'title'                => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
671
			'type'                 => 'object',
672
			'properties'           => array(
673
				'id'               => array(
674
					'description'  => __( 'Unique identifier for the resource.' ),
675
					'type'         => 'integer',
676
					'context'      => array( 'view', 'embed', 'edit' ),
677
					'readonly'     => true,
678
				),
679
				'count'            => array(
680
					'description'  => __( 'Number of published posts for the resource.' ),
681
					'type'         => 'integer',
682
					'context'      => array( 'view', 'edit' ),
683
					'readonly'     => true,
684
				),
685
				'description'      => array(
686
					'description'  => __( 'HTML description of the resource.' ),
687
					'type'         => 'string',
688
					'context'      => array( 'view', 'edit' ),
689
					'arg_options'  => array(
690
						'sanitize_callback' => 'wp_filter_post_kses',
691
					),
692
				),
693
				'link'             => array(
694
					'description'  => __( 'URL to the resource.' ),
695
					'type'         => 'string',
696
					'format'       => 'uri',
697
					'context'      => array( 'view', 'embed', 'edit' ),
698
					'readonly'     => true,
699
				),
700
				'name'             => array(
701
					'description'  => __( 'HTML title for the resource.' ),
702
					'type'         => 'string',
703
					'context'      => array( 'view', 'embed', 'edit' ),
704
					'arg_options'  => array(
705
						'sanitize_callback' => 'sanitize_text_field',
706
					),
707
					'required'     => true,
708
				),
709
				'slug'             => array(
710
					'description'  => __( 'An alphanumeric identifier for the resource unique to its type.' ),
711
					'type'         => 'string',
712
					'context'      => array( 'view', 'embed', 'edit' ),
713
					'arg_options'  => array(
714
						'sanitize_callback' => 'sanitize_title',
715
					),
716
				),
717
				'taxonomy'         => array(
718
					'description'  => __( 'Type attribution for the resource.' ),
719
					'type'         => 'string',
720
					'enum'         => array_keys( get_taxonomies() ),
721
					'context'      => array( 'view', 'embed', 'edit' ),
722
					'readonly'     => true,
723
				),
724
			),
725
		);
726
		$taxonomy = get_taxonomy( $this->taxonomy );
727 View Code Duplication
		if ( $taxonomy->hierarchical ) {
728
			$schema['properties']['parent'] = array(
729
				'description'  => __( 'The id for the parent of the resource.' ),
730
				'type'         => 'integer',
731
				'context'      => array( 'view', 'edit' ),
732
			);
733
		}
734
		return $this->add_additional_fields_schema( $schema );
735
	}
736
737
	/**
738
	 * Get the query params for collections
739
	 *
740
	 * @return array
741
	 */
742
	public function get_collection_params() {
743
		$query_params = parent::get_collection_params();
744
		$taxonomy = get_taxonomy( $this->taxonomy );
745
746
		$query_params['context']['default'] = 'view';
747
748
		$query_params['exclude'] = array(
749
			'description'        => __( 'Ensure result set excludes specific ids.' ),
750
			'type'               => 'array',
751
			'default'            => array(),
752
			'sanitize_callback'  => 'wp_parse_id_list',
753
		);
754
		$query_params['include'] = array(
755
			'description'        => __( 'Limit result set to specific ids.' ),
756
			'type'               => 'array',
757
			'default'            => array(),
758
			'sanitize_callback'  => 'wp_parse_id_list',
759
		);
760 View Code Duplication
		if ( ! $taxonomy->hierarchical ) {
761
			$query_params['offset'] = array(
762
				'description'        => __( 'Offset the result set by a specific number of items.' ),
763
				'type'               => 'integer',
764
				'sanitize_callback'  => 'absint',
765
				'validate_callback'  => 'rest_validate_request_arg',
766
			);
767
		}
768
		$query_params['order']      = array(
769
			'description'           => __( 'Order sort attribute ascending or descending.' ),
770
			'type'                  => 'string',
771
			'sanitize_callback'     => 'sanitize_key',
772
			'default'               => 'asc',
773
			'enum'                  => array(
774
				'asc',
775
				'desc',
776
			),
777
			'validate_callback'     => 'rest_validate_request_arg',
778
		);
779
		$query_params['orderby']    = array(
780
			'description'           => __( 'Sort collection by resource attribute.' ),
781
			'type'                  => 'string',
782
			'sanitize_callback'     => 'sanitize_key',
783
			'default'               => 'name',
784
			'enum'                  => array(
785
				'id',
786
				'include',
787
				'name',
788
				'slug',
789
				'term_group',
790
				'description',
791
				'count',
792
			),
793
			'validate_callback'     => 'rest_validate_request_arg',
794
		);
795
		$query_params['hide_empty'] = array(
796
			'description'           => __( 'Whether to hide resources not assigned to any posts.' ),
797
			'type'                  => 'boolean',
798
			'default'               => false,
799
			'validate_callback'     => 'rest_validate_request_arg',
800
		);
801 View Code Duplication
		if ( $taxonomy->hierarchical ) {
802
			$query_params['parent'] = array(
803
				'description'        => __( 'Limit result set to resources assigned to a specific parent.' ),
804
				'type'               => 'integer',
805
				'sanitize_callback'  => 'absint',
806
				'validate_callback'  => 'rest_validate_request_arg',
807
			);
808
		}
809
		$query_params['post'] = array(
810
			'description'           => __( 'Limit result set to resources assigned to a specific post.' ),
811
			'type'                  => 'integer',
812
			'default'               => null,
813
			'validate_callback'     => 'rest_validate_request_arg',
814
		);
815
		$query_params['slug']    = array(
816
			'description'        => __( 'Limit result set to resources with a specific slug.' ),
817
			'type'               => 'string',
818
			'validate_callback'  => 'rest_validate_request_arg',
819
		);
820
		return $query_params;
821
	}
822
823
	/**
824
	 * Check that the taxonomy is valid
825
	 *
826
	 * @param string
827
	 * @return WP_Error|boolean
828
	 */
829
	protected function check_is_taxonomy_allowed( $taxonomy ) {
830
		$taxonomy_obj = get_taxonomy( $taxonomy );
831
		if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) {
832
			return true;
833
		}
834
		return false;
835
	}
836
}
837