Completed
Push — master ( b71328...08c185 )
by Brian
20s queued 15s
created

GetPaid_REST_Posts_Controller::get_items()   C

Complexity

Conditions 10
Paths 288

Size

Total Lines 81
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 53
c 1
b 0
f 0
dl 0
loc 81
rs 5.6121
cc 10
nc 288
nop 1

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
 * GetPaid REST Posts controller class.
4
 * 
5
 * Extends the GetPaid_REST_Controller class to provide functionalities for endpoints
6
 * that store data using CPTs
7
 * 
8
 * @version 1.0.19
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * GetPaid REST Posts controller class.
15
 *
16
 * @package Invoicing
17
 */
18
class GetPaid_REST_Posts_Controller extends GetPaid_REST_Controller {
19
20
    /**
21
	 * Post type.
22
	 *
23
	 * @var string
24
	 */
25
	protected $post_type;
26
27
	/**
28
	 * Controls visibility on frontend.
29
	 *
30
	 * @var string
31
	 */
32
	public $public = false;
33
34
	/**
35
	 * Contains this controller's class name.
36
	 *
37
	 * @var string
38
	 */
39
	public $crud_class;
40
41
	/**
42
	 * Registers the routes for the objects of the controller.
43
	 *
44
	 * @since 1.0.19
45
	 *
46
	 * @see register_rest_route()
47
	 */
48
	public function register_namespace_routes( $namespace ) {
49
50
		register_rest_route(
51
			$namespace,
52
			'/' . $this->rest_base,
53
			array(
54
				array(
55
					'methods'             => WP_REST_Server::READABLE,
56
					'callback'            => array( $this, 'get_items' ),
57
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
58
					'args'                => $this->get_collection_params(),
59
				),
60
				array(
61
					'methods'             => WP_REST_Server::CREATABLE,
62
					'callback'            => array( $this, 'create_item' ),
63
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
64
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
65
				),
66
				'schema' => array( $this, 'get_public_item_schema' ),
67
			)
68
		);
69
70
		$get_item_args = array(
71
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
72
		);
73
74
		register_rest_route(
75
			$namespace,
76
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
77
			array(
78
				'args'   => array(
79
					'id' => array(
80
						'description' => __( 'Unique identifier for the object.', 'invoicing' ),
81
						'type'        => 'integer',
82
					),
83
				),
84
				array(
85
					'methods'             => WP_REST_Server::READABLE,
86
					'callback'            => array( $this, 'get_item' ),
87
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
88
					'args'                => $get_item_args,
89
				),
90
				array(
91
					'methods'             => WP_REST_Server::EDITABLE,
92
					'callback'            => array( $this, 'update_item' ),
93
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
94
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
95
				),
96
				array(
97
					'methods'             => WP_REST_Server::DELETABLE,
98
					'callback'            => array( $this, 'delete_item' ),
99
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
100
					'args'                => array(
101
						'force' => array(
102
							'type'        => 'boolean',
103
							'default'     => false,
104
							'description' => __( 'Whether to bypass Trash and force deletion.', 'invoicing' ),
105
						),
106
					),
107
				),
108
				'schema' => array( $this, 'get_public_item_schema' ),
109
			)
110
		);
111
112
		register_rest_route(
113
			$namespace,
114
			'/' . $this->rest_base . '/batch',
115
			array(
116
				array(
117
					'methods'             => WP_REST_Server::EDITABLE,
118
					'callback'            => array( $this, 'batch_items' ),
119
					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
120
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
121
				),
122
				'schema' => array( $this, 'get_public_batch_schema' ),
123
			)
124
		);
125
126
	}
127
128
	/**
129
	 * Check permissions of items on REST API.
130
	 *
131
	 * @since 1.0.19
132
	 * @param string $context   Request context.
133
	 * @param int    $object_id Post ID.
134
	 * @return bool
135
	 */
136
	public function check_post_permissions( $context = 'read', $object_id = 0 ) {
137
		return true;
138
		$contexts = array(
0 ignored issues
show
Unused Code introduced by
$contexts = array('read'...=> 'edit_others_posts') is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
139
			'read'   => 'read_private_posts',
140
			'create' => 'publish_posts',
141
			'edit'   => 'edit_post',
142
			'delete' => 'delete_post',
143
			'batch'  => 'edit_others_posts',
144
		);
145
146
		if ( 'revision' === $this->post_type ) {
147
			$permission = false;
148
		} else {
149
			$cap              = $contexts[ $context ];
150
			$post_type_object = get_post_type_object( $this->post_type );
151
			$permission       = current_user_can( $post_type_object->cap->$cap, $object_id );
152
		}
153
154
		return apply_filters( 'getpaid_rest_check_permissions', $permission, $context, $object_id, $this->post_type );
155
	}
156
157
	/**
158
	 * Check if a given request has access to read items.
159
	 *
160
	 * @param  WP_REST_Request $request Full details about the request.
161
	 * @return WP_Error|boolean
162
	 */
163
	public function get_items_permissions_check( $request ) {
164
		return $this->check_post_permissions() ? true : new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot list resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
165
	}
166
167
	/**
168
	 * Check if a given request has access to create an item.
169
	 *
170
	 * @param  WP_REST_Request $request Full details about the request.
171
	 * @return WP_Error|boolean
172
	 */
173
	public function create_item_permissions_check( $request ) {
174
		return $this->check_post_permissions( 'create' ) ? true : new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
175
	}
176
177
	/**
178
	 * Check if a given request has access to read an item.
179
	 *
180
	 * @param  WP_REST_Request $request Full details about the request.
181
	 * @return WP_Error|boolean
182
	 */
183
	public function get_item_permissions_check( $request ) {
184
		$post = get_post( (int) $request['id'] );
185
186
		if ( $post && ! $this->check_post_permissions( 'read', $post->ID ) ) {
187
			return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
188
		}
189
190
		return true;
191
	}
192
193
	/**
194
	 * Check if a given request has access to update an item.
195
	 *
196
	 * @param  WP_REST_Request $request Full details about the request.
197
	 * @return WP_Error|boolean
198
	 */
199
	public function update_item_permissions_check( $request ) {
200
		$post = get_post( (int) $request['id'] );
201
202
		if ( $post && ! $this->check_post_permissions( 'edit', $post->ID ) ) {
203
			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
204
		}
205
206
		return true;
207
	}
208
209
	/**
210
	 * Check if a given request has access to delete an item.
211
	 *
212
	 * @param  WP_REST_Request $request Full details about the request.
213
	 * @return bool|WP_Error
214
	 */
215
	public function delete_item_permissions_check( $request ) {
216
		$post = get_post( (int) $request['id'] );
217
218
		if ( $post && ! $this->check_post_permissions( 'delete', $post->ID ) ) {
219
			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
220
		}
221
222
		return true;
223
	}
224
225
	/**
226
	 * Check if a given request has access batch create, update and delete items.
227
	 *
228
	 * @param  WP_REST_Request $request Full details about the request.
229
	 *
230
	 * @return boolean|WP_Error
231
	 */
232
	public function batch_items_permissions_check( $request ) {
233
		return $this->check_post_permissions( 'batch' ) ? true : new WP_Error( 'rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
234
	}
235
236
	/**
237
	 * Saves a single object.
238
	 *
239
	 * @param GetPaid_Data $object Object to save.
240
	 * @return WP_Error|GetPaid_Data
241
	 */
242
	protected function save_object( $object ) {
243
		$object->save();
244
245
		if ( ! empty( $object->last_error ) ) {
246
			return new WP_Error( 'rest_cannot_save', $object->last_error, array( 'status' => 400 ) );
247
		}
248
249
		return new $this->crud_class( $object->get_id() );
250
	}
251
252
	/**
253
	 * Returns the item's object.
254
	 *
255
	 * Child classes must implement this method.
256
	 * @since 1.0.13
257
	 *
258
	 * @param int|WP_Post $object_id Supplied ID.
259
	 * @return GetPaid_Data|WP_Error GetPaid_Data object if ID is valid, WP_Error otherwise.
260
	 */
261
	protected function get_object( $object_id ) {
262
263
		// Do we have an object?
264
		if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) {
265
			return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) );
266
		}
267
268
		// Fetch the object.
269
		$object = new $this->crud_class( $object_id );
270
		if ( ! empty( $object->last_error ) ) {
271
			return new WP_Error( 'rest_object_invalid_id', $object->last_error, array( 'status' => 404 ) );
272
		}
273
274
		return $object->get_id() ? $object : new WP_Error( 'rest_object_invalid_id', __( 'Invalid ID.', 'invoicing' ), array( 'status' => 404 ) );
275
276
	}
277
	
278
	/**
279
	 * @deprecated
280
	 */
281
	public function get_post( $object_id ) {
282
		return $this->get_object( $object_id );
283
    }
284
285
	/**
286
	 * Get a single object.
287
	 *
288
	 * @param WP_REST_Request $request Full details about the request.
289
	 * @return WP_Error|WP_REST_Response
290
	 */
291
	public function get_item( $request ) {
292
293
		// Fetch the item.
294
		$object = $this->get_object( $request['id'] );
295
296
		if ( is_wp_error( $object ) ) {
297
			return $object;
298
		}
299
300
		// Generate a response.
301
		$data     = $this->prepare_item_for_response( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type WP_Error is incompatible with the type GetPaid_Data expected by parameter $object of GetPaid_REST_Posts_Contr...are_item_for_response(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

301
		$data     = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
302
		$response = rest_ensure_response( $data );
303
304
		// (Maybe) add a link to the html pagee.
305
		if ( $this->public && ! is_wp_error( $response ) ) {
306
			$response->link_header( 'alternate', get_permalink( $object->get_id() ), array( 'type' => 'text/html' ) );
0 ignored issues
show
Bug introduced by
The method get_id() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

306
			$response->link_header( 'alternate', get_permalink( $object->/** @scrutinizer ignore-call */ get_id() ), array( 'type' => 'text/html' ) );

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...
Bug introduced by
It seems like get_permalink($object->get_id()) can also be of type false; however, parameter $link of WP_REST_Response::link_header() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

306
			$response->link_header( 'alternate', /** @scrutinizer ignore-type */ get_permalink( $object->get_id() ), array( 'type' => 'text/html' ) );
Loading history...
307
		}
308
309
		return $response;
310
	}
311
312
	/**
313
	 * Create a single object.
314
	 *
315
	 * @param WP_REST_Request $request Full details about the request.
316
	 * @return WP_Error|WP_REST_Response
317
	 */
318
	public function create_item( $request ) {
319
320
		// Can not create an existing item.
321
		if ( ! empty( $request['id'] ) ) {
322
			/* translators: %s: post type */
323
			return new WP_Error( "getpaid_rest_{$this->post_type}_exists", __( 'Cannot create existing resource.', 'invoicing' ), array( 'status' => 400 ) );
324
		}
325
326
		// Generate a GetPaid_Data object from the request.
327
		$object = $this->prepare_item_for_database( $request );
328
		if ( is_wp_error( $object ) ) {
329
			return $object;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $object also could return the type GetPaid_Data which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
330
		}
331
332
		// Save the object.
333
		$object = $this->save_object( $object );
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type WP_Error; however, parameter $object of GetPaid_REST_Posts_Controller::save_object() does only seem to accept GetPaid_Data, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

333
		$object = $this->save_object( /** @scrutinizer ignore-type */ $object );
Loading history...
334
		if ( is_wp_error( $object ) ) {
335
			return $object;
336
		}
337
338
		// Save special fields.
339
		$save_special = $this->update_additional_fields_for_object( $object, $request );
340
		if ( is_wp_error( $save_special ) ) {
341
			$object->delete( true );
0 ignored issues
show
Bug introduced by
The method delete() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

341
			$object->/** @scrutinizer ignore-call */ 
342
            delete( true );

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...
342
			return $save_special;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $save_special also could return the type boolean which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
343
		}
344
345
		/**
346
		 * Fires after a single item is created or updated via the REST API.
347
		 *
348
		 * @param WP_Post         $post      Post object.
349
		 * @param WP_REST_Request $request   Request object.
350
		 * @param boolean         $creating  True when creating item, false when updating.
351
		 */
352
		do_action( "getpaid_rest_insert_{$this->post_type}", $object, $request, true );
353
354
		$request->set_param( 'context', 'edit' );
355
		$response = $this->prepare_item_for_response( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type WP_Error is incompatible with the type GetPaid_Data expected by parameter $object of GetPaid_REST_Posts_Contr...are_item_for_response(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
356
		$response = rest_ensure_response( $response );
357
		$response->set_status( 201 );
358
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) );
359
360
		return $response;
361
	}
362
363
	/**
364
	 * Update a single object.
365
	 *
366
	 * @param WP_REST_Request $request Full details about the request.
367
	 * @return WP_Error|WP_REST_Response
368
	 */
369
	public function update_item( $request ) {
370
371
		// Fetch the item.
372
		$object = $this->get_object( $request['id'] );
373
		if ( is_wp_error( $object ) ) {
374
			return $object;
375
		}
376
377
		// Prepare the item for saving.
378
		$object = $this->prepare_item_for_database( $request );
379
		if ( is_wp_error( $object ) ) {
380
			return $object;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $object also could return the type GetPaid_Data which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
381
		}
382
383
		// Save the item.
384
		$object = $this->save_object( $object );
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type WP_Error; however, parameter $object of GetPaid_REST_Posts_Controller::save_object() does only seem to accept GetPaid_Data, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

384
		$object = $this->save_object( /** @scrutinizer ignore-type */ $object );
Loading history...
385
		if ( is_wp_error( $object ) ) {
386
			return $object;
387
		}
388
389
		// Save special fields (those added via hooks).
390
		$save_special = $this->update_additional_fields_for_object( $object, $request );
391
		if ( is_wp_error( $save_special ) ) {
392
			return $save_special;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $save_special also could return the type boolean which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
393
		}
394
395
		/**
396
		 * Fires after a single item is created or updated via the REST API.
397
		 *
398
		 * @param GetPaid_Data    $object    GetPaid_Data object.
399
		 * @param WP_REST_Request $request   Request object.
400
		 * @param boolean         $creating  True when creating item, false when updating.
401
		 */
402
		do_action( "getpaid_rest_insert_{$this->post_type}", $object, $request, false );
403
404
		$request->set_param( 'context', 'edit' );
405
		$response = $this->prepare_item_for_response( $object, $request );
0 ignored issues
show
Bug introduced by
$object of type WP_Error is incompatible with the type GetPaid_Data expected by parameter $object of GetPaid_REST_Posts_Contr...are_item_for_response(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

405
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $object, $request );
Loading history...
406
		return rest_ensure_response( $response );
407
	}
408
409
	/**
410
	 * Get a collection of objects.
411
	 *
412
	 * @param WP_REST_Request $request Full details about the request.
413
	 * @return WP_Error|WP_REST_Response
414
	 */
415
	public function get_items( $request ) {
416
		$args                         = array();
417
		$args['offset']               = $request['offset'];
418
		$args['order']                = $request['order'];
419
		$args['orderby']              = $request['orderby'];
420
		$args['paged']                = $request['page'];
421
		$args['post__in']             = $request['include'];
422
		$args['post__not_in']         = $request['exclude'];
423
		$args['posts_per_page']       = $request['per_page'];
424
		$args['name']                 = $request['slug'];
425
		$args['post_parent__in']      = $request['parent'];
426
		$args['post_parent__not_in']  = $request['parent_exclude'];
427
		$args['s']                    = $request['search'];
428
		$args['post_status']          = $request['status'] == 'any' ? 'any' : wpinv_parse_list( $request['status'] );
429
430
		$args['date_query'] = array();
431
		// Set before into date query. Date query must be specified as an array of an array.
432
		if ( isset( $request['before'] ) ) {
433
			$args['date_query'][0]['before'] = $request['before'];
434
		}
435
436
		// Set after into date query. Date query must be specified as an array of an array.
437
		if ( isset( $request['after'] ) ) {
438
			$args['date_query'][0]['after'] = $request['after'];
439
		}
440
441
		// Force the post_type argument, since it's not a user input variable.
442
		$args['post_type'] = $this->post_type;
443
444
		// Filter the query arguments for a request.
445
		$args       = apply_filters( "getpaid_rest_{$this->post_type}_query", $args, $request );
446
		$query_args = $this->prepare_items_query( $args, $request );
447
448
		$posts_query = new WP_Query();
449
		$query_result = $posts_query->query( $query_args );
450
451
		$posts = array();
452
		foreach ( $query_result as $post ) {
453
			if ( ! $this->check_post_permissions( 'read', $post->ID ) ) {
454
				continue;
455
			}
456
457
			$data    = $this->prepare_item_for_response( $this->get_object( $post->ID ), $request );
0 ignored issues
show
Bug introduced by
$this->get_object($post->ID) of type WP_Error is incompatible with the type GetPaid_Data expected by parameter $object of GetPaid_REST_Posts_Contr...are_item_for_response(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

457
			$data    = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $this->get_object( $post->ID ), $request );
Loading history...
458
			$posts[] = $this->prepare_response_for_collection( $data );
459
		}
460
461
		$page        = (int) $query_args['paged'];
462
		$total_posts = $posts_query->found_posts;
463
464
		if ( $total_posts < 1 ) {
465
			// Out-of-bounds, run the query again without LIMIT for total count.
466
			unset( $query_args['paged'] );
467
			$count_query = new WP_Query();
468
			$count_query->query( $query_args );
469
			$total_posts = $count_query->found_posts;
470
		}
471
472
		$max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
473
474
		$response = rest_ensure_response( $posts );
475
		$response->header( 'X-WP-Total', (int) $total_posts );
0 ignored issues
show
Bug introduced by
The method header() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

475
		$response->/** @scrutinizer ignore-call */ 
476
             header( 'X-WP-Total', (int) $total_posts );

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...
476
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
477
478
		$request_params = $request->get_query_params();
479
		$base = add_query_arg( $request_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
480
481
		if ( $page > 1 ) {
482
			$prev_page = $page - 1;
483
			if ( $prev_page > $max_pages ) {
484
				$prev_page = $max_pages;
485
			}
486
			$prev_link = add_query_arg( 'page', $prev_page, $base );
487
			$response->link_header( 'prev', $prev_link );
0 ignored issues
show
Bug introduced by
The method link_header() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

487
			$response->/** @scrutinizer ignore-call */ 
488
              link_header( 'prev', $prev_link );

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...
488
		}
489
		if ( $max_pages > $page ) {
490
			$next_page = $page + 1;
491
			$next_link = add_query_arg( 'page', $next_page, $base );
492
			$response->link_header( 'next', $next_link );
493
		}
494
495
		return $response;
496
	}
497
498
	/**
499
	 * Delete a single item.
500
	 *
501
	 * @param WP_REST_Request $request Full details about the request.
502
	 * @return WP_REST_Response|WP_Error
503
	 */
504
	public function delete_item( $request ) {
505
506
		// Fetch the item.
507
		$item = $this->get_object( $request['id'] );
508
		if ( is_wp_error( $item ) ) {
509
			return $item;
510
		}
511
512
		$supports_trash = EMPTY_TRASH_DAYS > 0;
513
		$force          = $supports_trash && (bool) $request['force'];
514
515
		if ( ! $this->check_post_permissions( 'delete', $item->ID ) ) {
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on WP_Error.
Loading history...
516
			return new WP_Error( "cannot_delete", __( 'Sorry, you are not allowed to delete this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
517
		}
518
519
		$request->set_param( 'context', 'edit' );
520
		$response = $this->prepare_item_for_response( $item, $request );
0 ignored issues
show
Bug introduced by
$item of type WP_Error is incompatible with the type GetPaid_Data expected by parameter $object of GetPaid_REST_Posts_Contr...are_item_for_response(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

520
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $item, $request );
Loading history...
521
522
		if ( ! wp_delete_post( $item->ID, $force ) ) {
523
			return new WP_Error( 'rest_cannot_delete', sprintf( __( 'The resource cannot be deleted.', 'invoicing' ), $this->post_type ), array( 'status' => 500 ) );
524
		}
525
526
		return $response;
527
	}
528
529
	/**
530
	 * Prepare links for the request.
531
	 *
532
	 * @param GetPaid_Data    $object GetPaid_Data object.
533
	 * @return array Links for the given object.
534
	 */
535
	protected function prepare_links( $object ) {
536
537
		$links = array(
538
			'self'       => array(
539
				'href'   => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ),
540
			),
541
			'collection' => array(
542
				'href'   => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
543
			),
544
		);
545
546
		if ( is_callable( array( $object, 'get_user_id' ) ) ) {
547
			$links['user'] = array(
548
				'href'       => rest_url( 'wp/v2/users/' . call_user_func(  array( $object, 'get_user_id' )  ) ),
549
				'embeddable' => true,
550
			);
551
		}
552
553
		return $links;
554
	}
555
556
	/**
557
	 * Determine the allowed query_vars for a get_items() response and
558
	 * prepare for WP_Query.
559
	 *
560
	 * @param array           $prepared_args Prepared arguments.
561
	 * @param WP_REST_Request $request Request object.
562
	 * @return array          $query_args
563
	 */
564
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
565
566
		$valid_vars = array_flip( $this->get_allowed_query_vars() );
567
		$query_args = array();
568
		foreach ( $valid_vars as $var => $index ) {
569
			if ( isset( $prepared_args[ $var ] ) ) {
570
				$query_args[ $var ] = apply_filters( "getpaid_rest_query_var-{$var}", $prepared_args[ $var ] );
571
			}
572
		}
573
574
		$query_args['ignore_sticky_posts'] = true;
575
576
		if ( 'include' === $query_args['orderby'] ) {
577
			$query_args['orderby'] = 'post__in';
578
		} elseif ( 'id' === $query_args['orderby'] ) {
579
			$query_args['orderby'] = 'ID'; // ID must be capitalized.
580
		} elseif ( 'slug' === $query_args['orderby'] ) {
581
			$query_args['orderby'] = 'name';
582
		}
583
584
		return apply_filters( 'getpaid_rest_prepare_items_query', $query_args, $request, $this );
585
586
	}
587
588
	/**
589
	 * Get all the WP Query vars that are allowed for the API request.
590
	 *
591
	 * @return array
592
	 */
593
	protected function get_allowed_query_vars() {
594
		global $wp;
595
596
		/**
597
		 * Filter the publicly allowed query vars.
598
		 *
599
		 * Allows adjusting of the default query vars that are made public.
600
		 *
601
		 * @param array  Array of allowed WP_Query query vars.
602
		 */
603
		$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
604
605
		$post_type_obj = get_post_type_object( $this->post_type );
606
		if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
607
			$private = apply_filters( 'getpaid_rest_private_query_vars', $wp->private_query_vars );
608
			$valid_vars = array_merge( $valid_vars, $private );
609
		}
610
611
		// Define our own in addition to WP's normal vars.
612
		$rest_valid = array(
613
			'post_status',
614
			'date_query',
615
			'ignore_sticky_posts',
616
			'offset',
617
			'post__in',
618
			'post__not_in',
619
			'post_parent',
620
			'post_parent__in',
621
			'post_parent__not_in',
622
			'posts_per_page',
623
			'meta_query',
624
			'tax_query',
625
			'meta_key',
626
			'meta_value',
627
			'meta_compare',
628
			'meta_value_num',
629
		);
630
		$valid_vars = array_merge( $valid_vars, $rest_valid );
631
632
		// Filter allowed query vars for the REST API.
633
		$valid_vars = apply_filters( 'getpaid_rest_query_vars', $valid_vars, $this );
634
635
		return $valid_vars;
636
	}
637
638
	/**
639
	 * Get the query params for collections of attachments.
640
	 *
641
	 * @return array
642
	 */
643
	public function get_collection_params() {
644
		$params = parent::get_collection_params();
645
646
		$params['context']['default'] = 'view';
647
648
		$params['status'] = array(
649
			'default'           => 'any',
650
			'description'       => __( 'Limit result set to resources assigned one or more statuses.', 'invoicing' ),
651
			'type'              => array( 'array', 'string' ),
652
			'items'             => array(
653
				'enum'          => array_merge( array( 'any' ), $this->get_post_statuses() ),
654
				'type'          => 'string',
655
			),
656
			'sanitize_callback' => 'rest_validate_request_arg',
657
		);
658
659
		$params['after'] = array(
660
			'description'        => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'invoicing' ),
661
			'type'               => 'string',
662
			'format'             => 'string',
663
			'validate_callback'  => 'rest_validate_request_arg',
664
		);
665
		$params['before'] = array(
666
			'description'        => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'invoicing' ),
667
			'type'               => 'string',
668
			'format'             => 'string',
669
			'validate_callback'  => 'rest_validate_request_arg',
670
		);
671
		$params['exclude'] = array(
672
			'description'       => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
673
			'type'              => 'array',
674
			'items'             => array(
675
				'type'          => 'integer',
676
			),
677
			'default'           => array(),
678
			'sanitize_callback' => 'wp_parse_id_list',
679
		);
680
		$params['include'] = array(
681
			'description'       => __( 'Limit result set to specific ids.', 'invoicing' ),
682
			'type'              => 'array',
683
			'items'             => array(
684
				'type'          => 'integer',
685
			),
686
			'default'           => array(),
687
			'sanitize_callback' => 'wp_parse_id_list',
688
		);
689
		$params['offset'] = array(
690
			'description'        => __( 'Offset the result set by a specific number of items.', 'invoicing' ),
691
			'type'               => 'integer',
692
			'sanitize_callback'  => 'absint',
693
			'validate_callback'  => 'rest_validate_request_arg',
694
		);
695
		$params['order'] = array(
696
			'description'        => __( 'Order sort attribute ascending or descending.', 'invoicing' ),
697
			'type'               => 'string',
698
			'default'            => 'desc',
699
			'enum'               => array( 'asc', 'desc' ),
700
			'validate_callback'  => 'rest_validate_request_arg',
701
		);
702
		$params['orderby'] = array(
703
			'description'        => __( 'Sort collection by object attribute.', 'invoicing' ),
704
			'type'               => 'string',
705
			'default'            => 'date',
706
			'enum'               => array(
707
				'date',
708
				'id',
709
				'include',
710
				'title',
711
				'slug',
712
				'modified',
713
			),
714
			'validate_callback'  => 'rest_validate_request_arg',
715
		);
716
717
		$post_type_obj = get_post_type_object( $this->post_type );
718
719
		if ( isset( $post_type_obj->hierarchical ) && $post_type_obj->hierarchical ) {
720
			$params['parent'] = array(
721
				'description'       => __( 'Limit result set to those of particular parent IDs.', 'invoicing' ),
722
				'type'              => 'array',
723
				'items'             => array(
724
					'type'          => 'integer',
725
				),
726
				'sanitize_callback' => 'wp_parse_id_list',
727
				'default'           => array(),
728
			);
729
			$params['parent_exclude'] = array(
730
				'description'       => __( 'Limit result set to all items except those of a particular parent ID.', 'invoicing' ),
731
				'type'              => 'array',
732
				'items'             => array(
733
					'type'          => 'integer',
734
				),
735
				'sanitize_callback' => 'wp_parse_id_list',
736
				'default'           => array(),
737
			);
738
		}
739
740
		return $params;
741
	}
742
743
	/**
744
	 * Retrieves the items's schema, conforming to JSON Schema.
745
	 *
746
	 * @since 1.0.19
747
	 *
748
	 * @return array Item schema data.
749
	 */
750
	public function get_item_schema() {
751
752
		// Maybe retrieve the schema from cache.
753
		if ( $this->schema ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->schema of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
754
			return $this->add_additional_fields_schema( $this->schema );
755
		}
756
757
		$type   = str_replace( 'wpi_', '', $this->post_type );
758
		$schema = array(
759
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
760
			'title'      => $this->post_type,
761
			'type'       => 'object',
762
			'properties' => wpinv_get_data( "$type-schema" ),
763
		);
764
765
		// Filters the invoice schema for the REST API.
766
        $schema = apply_filters( "wpinv_rest_{$type}_schema", $schema );
767
768
		// Cache the invoice schema.
769
		$this->schema = $schema;
770
771
		return $this->add_additional_fields_schema( $this->schema );
772
	}
773
774
	/**
775
	 * Only return writable props from schema.
776
	 *
777
	 * @param  array $schema Schema.
778
	 * @return bool
779
	 */
780
	public function filter_writable_props( $schema ) {
781
		return empty( $schema['readonly'] );
782
	}
783
784
	/**
785
	 * Sanitizes and validates the list of post statuses.
786
	 *
787
	 * @since 1.0.13
788
	 *
789
	 * @param string|array    $statuses  One or more post statuses.
790
	 * @param WP_REST_Request $request   Full details about the request.
791
	 * @param string          $parameter Additional parameter to pass to validation.
792
	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
793
	 */
794
	public function sanitize_post_statuses( $statuses, $request, $parameter ) {
795
		return array_intersect( wp_parse_slug_list( $statuses ), $this->get_post_statuses() );
796
	}
797
798
	/**
799
	 * Retrieves a valid list of post statuses.
800
	 *
801
	 * @since 1.0.19
802
	 *
803
	 * @return array A list of registered item statuses.
804
	 */
805
	public function get_post_statuses() {
806
		return get_post_stati();
807
	}
808
809
	/**
810
	 * Prepare a single object for create or update.
811
	 *
812
	 * @since 1.0.19
813
	 * @param  WP_REST_Request $request Request object.
814
	 * @return GetPaid_Data|WP_Error Data object or WP_Error.
815
	 */
816
	protected function prepare_item_for_database( $request ) {
817
818
		// Do we have an object?
819
		if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) {
820
			return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) );
821
		}
822
823
		// Prepare the object.
824
		$id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
825
		$object    = new $this->crud_class( $id );
826
827
		// Abort if an error exists.
828
		if ( ! empty( $object->last_error ) ) {
829
			return new WP_Error( 'invalid_item', $object->last_error );
830
		}
831
832
		$schema    = $this->get_item_schema();
833
		$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
834
835
		// Handle all writable props.
836
		foreach ( $data_keys as $key ) {
837
			$value = $request[ $key ];
838
839
			if ( ! is_null( $value ) ) {
840
				switch ( $key ) {
841
842
					case 'meta_data':
843
						if ( is_array( $value ) ) {
844
							foreach ( $value as $meta ) {
845
								$object->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
846
							}
847
						}
848
						break;
849
850
					default:
851
						if ( is_callable( array( $object, "set_{$key}" ) ) ) {
852
							$object->{"set_{$key}"}( $value );
853
						}
854
						break;
855
				}
856
			}
857
858
		}
859
860
		// Filters an object before it is inserted via the REST API..
861
		return apply_filters( "getpaid_rest_pre_insert_{$this->post_type}_object", $object, $request );
862
	}
863
864
	/**
865
	 * Retrieves data from a GetPaid class.
866
	 *
867
	 * @since  1.0.19
868
	 * @param  GetPaid_Meta_Data[]    $meta_data  meta data objects.
869
	 * @return array
870
	 */
871
	protected function prepare_object_meta_data( $meta_data ) {
872
		$meta = array();
873
874
		foreach( $meta_data as $object ) {
875
			$meta[] = $object->get_data();
876
		}
877
878
		return $meta;
879
	}
880
881
	/**
882
	 * Retrieves invoice items.
883
	 *
884
	 * @since  1.0.19
885
	 * @param  WPInv_Invoice $invoice  Invoice items.
886
	 * @return array
887
	 */
888
	protected function prepare_invoice_items( $invoice ) {
889
		$items = array();
890
891
		foreach( $invoice->get_items() as $item ) {
892
			$item_data = $item->prepare_data_for_saving();
893
894
			if ( 'amount' == $invoice->get_template() ) {
895
				$item_data['quantity'] = 1;
896
			}
897
898
			$items[] = $item_data;
899
		}
900
901
		return $items;
902
	}
903
904
	/**
905
	 * Retrieves data from a GetPaid class.
906
	 *
907
	 * @since  1.0.19
908
	 * @param  GetPaid_Data    $object  Data object.
909
	 * @param array            $fields Fields to include.
910
	 * @param string           $context either view or edit.
911
	 * @return array
912
	 */
913
	protected function prepare_object_data( $object, $fields, $context = 'view' ) {
914
915
		$data      = array();
916
		$schema    = $this->get_item_schema();
917
		$data_keys = array_keys( $schema['properties'] );
918
919
		// Handle all writable props.
920
		foreach ( $data_keys as $key ) {
921
922
			// Abort if it is not included.
923
			if ( ! empty( $fields ) && ! $this->is_field_included( $key, $fields ) ) {
924
				continue;
925
			}
926
927
			// Handle meta data.
928
			if ( $key == 'meta_data' ) {
929
				$data['meta_data'] = $this->prepare_object_meta_data( $object->get_meta_data() );
930
				continue;
931
			}
932
933
			// Handle items.
934
			if ( $key == 'items' && is_a( $object, 'WPInv_Invoice' )  ) {
935
				$data['items'] = $this->prepare_invoice_items( $object );
936
				continue;
937
			}
938
939
			// Booleans.
940
			if ( is_callable( array( $object, $key ) ) ) {
941
				$data[ $key ] = $object->$key( $context );
942
				continue;
943
			}
944
945
			// Get object value.
946
			if ( is_callable( array( $object, "get_{$key}" ) ) ) {
947
				$value = $object->{"get_{$key}"}( $context );
948
949
				// If the value is an instance of GetPaid_Data...
950
				if ( is_a( $value, 'GetPaid_Data' ) ) {
951
					$value = $value->get_data( $context );
0 ignored issues
show
Unused Code introduced by
The call to GetPaid_Data::get_data() has too many arguments starting with $context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

951
					/** @scrutinizer ignore-call */ 
952
     $value = $value->get_data( $context );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
952
				}
953
954
				// For objects, retrieves it's properties.
955
				$data[ $key ] = is_object( $value ) ? get_object_vars( $value ) :  $value ;
956
				continue;
957
			}
958
959
			// The value does not exist on an object.
960
			$data[ $key ]     = apply_filters( "getpaid_{$this->post_type}_{$key}_object_data", null, $object );
961
962
		}
963
964
		return $data;
965
	}
966
967
	/**
968
	 * Prepare a single object output for response.
969
	 *
970
	 * @since  1.0.19
971
	 * @param  GetPaid_Data    $object  Data object.
972
	 * @param  WP_REST_Request $request Request object.
973
	 * @return WP_REST_Response
974
	 */
975
	public function prepare_item_for_response( $object, $request ) {
976
977
		// Fetch the fields to include in this response.
978
		$fields = $this->get_fields_for_response( $request );
979
980
		// Prepare object data.
981
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
982
		$data    = $this->prepare_object_data( $object, $fields, $context );
983
		$data    = $this->add_additional_fields_to_object( $data, $request );
984
		$data    = $this->filter_response_by_context( $data, $context );
985
986
		// Prepare the response.
987
		$response = rest_ensure_response( $data );
988
		$response->add_links( $this->prepare_links( $object, $request ) );
0 ignored issues
show
Unused Code introduced by
The call to GetPaid_REST_Posts_Controller::prepare_links() has too many arguments starting with $request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

988
		$response->add_links( $this->/** @scrutinizer ignore-call */ prepare_links( $object, $request ) );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
989
990
		// Filter item response.
991
		return apply_filters( "getpaid_rest_prepare_{$this->post_type}_object", $response, $object, $request );
992
	}
993
994
}
995