Passed
Branch master (50908e)
by Stiofan
07:01
created

WPInv_REST_Invoice_Controller   F

Complexity

Total Complexity 110

Size/Duplication

Total Lines 1365
Duplicated Lines 4.69 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 64
loc 1365
rs 0.8
c 0
b 0
f 0
wmc 110
lcom 1
cbo 1

23 Methods

Rating   Name   Duplication   Size   Complexity  
A get_post() 0 15 3
B get_item_schema() 0 299 2
A create_item_permissions_check() 0 19 3
F prepare_item_for_database() 0 106 30
A delete_item_permissions_check() 0 20 4
A get_items() 0 66 5
A get_schema_links() 0 36 1
A get_items_permissions_check() 0 11 3
A update_item() 0 54 5
F prepare_item_for_response() 0 109 15
A check_read_permission() 0 2 1
A __construct() 0 7 1
A get_item_permissions_check() 0 15 3
A get_collection_params() 0 78 1
A create_item() 0 49 4
A prepare_links() 0 32 2
A get_available_actions() 0 31 4
A delete_item() 0 39 3
C get_fields_for_response() 0 56 13
A sanitize_post_statuses() 0 5 1
A update_item_permissions_check() 0 21 3
A check_is_post_type_allowed() 0 2 1
A get_item() 0 27 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WPInv_REST_Invoice_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WPInv_REST_Invoice_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * REST API Invoice controller
4
 *
5
 * Handles requests to the invoices endpoint.
6
 *
7
 * @package  Invoicing
8
 * @since    1.0.13
9
 */
10
11
if ( !defined( 'WPINC' ) ) {
12
    exit;
13
}
14
15
/**
16
 * REST API invoices controller class.
17
 *
18
 * @package Invoicing
19
 */
20
class WPInv_REST_Invoice_Controller extends WP_REST_Posts_Controller {
21
22
    /**
23
	 * Post type.
24
	 *
25
	 * @var string
26
	 */
27
	protected $post_type = 'wpi_invoice';
28
	
29
	/**
30
	 * Cached results of get_item_schema.
31
	 *
32
	 * @since 1.0.13
33
	 * @var array
34
	 */
35
	protected $schema;
36
37
    /**
38
	 * Constructor.
39
	 *
40
	 * @since 1.0.13
41
	 *
42
	 * @param string $namespace Api Namespace
43
	 */
44
	public function __construct( $namespace ) {
45
        
46
        // Set api namespace...
47
		$this->namespace = $namespace;
48
49
        // ... and the rest base
50
        $this->rest_base = 'invoices';
51
		
52
    }
53
    
54
    /**
55
	 * Checks if a given request has access to read invoices.
56
     * 
57
	 *
58
	 * @since 1.0.13
59
	 *
60
	 * @param WP_REST_Request $request Full details about the request.
61
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
62
	 */
63
	public function get_items_permissions_check( $request ) {
64
	
65
        $post_type = get_post_type_object( $this->post_type );
66
67
		if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
68
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit invoices.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
69
		}
70
71
		// Read checks will be evaluated on a per invoice basis
72
73
		return true;
74
75
    }
76
    
77
    /**
78
	 * Retrieves a collection of invoices.
79
	 *
80
	 * @since 1.0.13
81
	 *
82
	 * @param WP_REST_Request $request Full details about the request.
83
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
84
	 */
85
	public function get_items( $request ) {
86
		
87
		// Retrieve the list of registered invoice query parameters.
88
        $registered = $this->get_collection_params();
89
        
90
        $args       = array();
91
92
        foreach( array_keys( $registered ) as $key ) {
93
94
            if( isset( $request[ $key] ) ) {
95
                $args[ $key ] = $request[ $key];
96
            }
97
98
        }
99
100
		/**
101
		 * Filters the wpinv_get_invoices arguments for invoices requests.
102
		 *
103
		 *
104
		 * @since 1.0.13
105
		 *
106
		 *
107
		 * @param array           $args    Key value array of query var to query value.
108
		 * @param WP_REST_Request $request The request used.
109
		 */
110
        $args       = apply_filters( "wpinv_rest_get_invoices_arguments", $args, $request, $this );
111
		
112
		// Special args
113
		$args[ 'return' ]   = 'objects';
114
		$args[ 'paginate' ] = true;
115
116
        // Run the query.
117
		$query = wpinv_get_invoices( $args );
118
		
119
		// Prepare the retrieved invoices
120
		$invoices = array();
121
		foreach( $query->invoices as $invoice ) {
122
123
			if ( ! $this->check_read_permission( $invoice ) ) {
124
				continue;
125
			}
126
127
			$data       = $this->prepare_item_for_response( $invoice, $request );
128
			$invoices[] = $this->prepare_response_for_collection( $data );
129
130
		}
131
132
		// Prepare the response.
133
		$response = rest_ensure_response( $invoices );
134
		$response->header( 'X-WP-Total', (int) $query->total );
135
		$response->header( 'X-WP-TotalPages', (int) $query->max_num_pages );
136
137
		/**
138
		 * Filters the responses for invoices requests.
139
		 *
140
		 *
141
		 * @since 1.0.13
142
		 *
143
		 *
144
		 * @param arrWP_REST_Response $response    Response object.
145
		 * @param WP_REST_Request     $request The request used.
146
         * @param array               $args Array of args used to retrieve the invoices
147
		 */
148
        $response       = apply_filters( "wpinv_rest_invoices_response", $response, $request, $args );
149
150
        return rest_ensure_response( $response );
151
        
152
    }
153
154
    /**
155
	 * Get the post, if the ID is valid.
156
	 *
157
	 * @since 1.0.13
158
	 *
159
	 * @param int $invoice_id Supplied ID.
160
	 * @return WPInv_Invoice|WP_Error Invoice object if ID is valid, WP_Error otherwise.
161
	 */
162
	protected function get_post( $invoice_id ) {
163
		
164
		$error     = new WP_Error( 'rest_invoice_invalid_id', __( 'Invalid invoice ID.', 'invoicing' ), array( 'status' => 404 ) );
165
166
        // Ids start from 1
167
        if ( (int) $invoice_id <= 0 ) {
168
			return $error;
169
		}
170
171
		$invoice = wpinv_get_invoice( (int) $invoice_id );
172
		if ( empty( $invoice ) ) {
173
			return $error;
174
        }
175
176
        return $invoice;
177
178
    }
179
180
    /**
181
	 * Checks if a given request has access to read an invoice.
182
	 *
183
	 * @since 1.0.13
184
	 *
185
	 * @param WP_REST_Request $request Full details about the request.
186
	 * @return bool|WP_Error True if the request has read access for the invoice, WP_Error object otherwise.
187
	 */
188
	public function get_item_permissions_check( $request ) {
189
190
        // Retrieve the invoice object.
191
        $invoice = $this->get_post( $request['id'] );
192
        
193
        // Ensure it is valid.
194
		if ( is_wp_error( $invoice ) ) {
195
			return $invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|boolean.
Loading history...
196
		}
197
198
		if ( $invoice ) {
199
			return $this->check_read_permission( $invoice );
0 ignored issues
show
Bug introduced by
It seems like $invoice can also be of type WP_Error; however, parameter $invoice of WPInv_REST_Invoice_Contr...check_read_permission() does only seem to accept WPInv_Invoice, 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

199
			return $this->check_read_permission( /** @scrutinizer ignore-type */ $invoice );
Loading history...
200
		}
201
202
		return true;
203
    }
204
    
205
    /**
206
	 * Checks if an invoice can be read.
207
	 * 
208
	 * An invoice can be read by site admins and owners of the invoice
209
	 *
210
	 *
211
	 * @since 1.0.13
212
	 *
213
	 * @param WPInv_Invoice $invoice WPInv_Invoice object.
214
	 * @return bool Whether the post can be read.
215
	 */
216
	public function check_read_permission( $invoice ) {
217
		return wpinv_user_can_view_invoice( $invoice->ID );
218
    }
219
    
220
    /**
221
	 * Retrieves a single invoice.
222
	 *
223
	 * @since 1.0.13
224
	 *
225
	 * @param WP_REST_Request $request Full details about the request.
226
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
227
	 */
228
	public function get_item( $request ) {
229
230
        // Fetch the invoice.
231
        $invoice = $this->get_post( $request['id'] );
232
        
233
        // Abort early if it does not exist
234
		if ( is_wp_error( $invoice ) ) {
235
			return $invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
236
		}
237
238
		// Prepare the response
239
		$response = $this->prepare_item_for_response( $invoice, $request );
0 ignored issues
show
Bug introduced by
It seems like $invoice can also be of type WP_Error; however, parameter $invoice of WPInv_REST_Invoice_Contr...are_item_for_response() does only seem to accept WPInv_Invoice, 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

239
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $invoice, $request );
Loading history...
240
		$response->link_header( 'alternate', esc_url( $invoice->get_view_url() ), array( 'type' => 'text/html' ) );
0 ignored issues
show
Bug introduced by
The method get_view_url() 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

240
		$response->link_header( 'alternate', esc_url( $invoice->/** @scrutinizer ignore-call */ get_view_url() ), 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 $invoice->get_view_url() can also be of type false; however, parameter $url of esc_url() 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

240
		$response->link_header( 'alternate', esc_url( /** @scrutinizer ignore-type */ $invoice->get_view_url() ), array( 'type' => 'text/html' ) );
Loading history...
241
242
		/**
243
		 * Filters the responses for single invoice requests.
244
		 *
245
		 *
246
		 * @since 1.0.13
247
		 * @var WP_HTTP_Response
248
		 *
249
		 * @param WP_HTTP_Response $response Response.
250
		 * @param WP_REST_Request  $request The request used.
251
		 */
252
        $response       = apply_filters( "wpinv_rest_get_invoice_response", $response, $request );
253
254
        return rest_ensure_response( $response );
255
256
    }
257
    
258
    /**
259
	 * Checks if a given request has access to create an invoice.
260
	 *
261
	 * @since 1.0.13
262
	 *
263
	 * @param WP_REST_Request $request Full details about the request.
264
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
265
	 */
266
	public function create_item_permissions_check( $request ) {
267
	
268
		if ( ! empty( $request['id'] ) ) {
269
			return new WP_Error( 'rest_invoice_exists', __( 'Cannot create existing invoice.', 'invoicing' ), array( 'status' => 400 ) );
270
		}
271
272
		$post_type = get_post_type_object( $this->post_type );
273
274
		if ( ! current_user_can( $post_type->cap->create_posts ) ) {
275
			return new WP_Error( 
276
                'rest_cannot_create', 
277
                __( 'Sorry, you are not allowed to create invoices as this user.', 'invoicing' ), 
278
                array( 
279
                    'status' => rest_authorization_required_code(),
280
                )
281
            );
282
        }
283
284
		return true;
285
    }
286
    
287
    /**
288
	 * Creates a single invoice.
289
	 *
290
	 * @since 1.0.13
291
	 *
292
	 * @param WP_REST_Request $request Full details about the request.
293
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
294
	 */
295
	public function create_item( $request ) {
296
297
		if ( ! empty( $request['id'] ) ) {
298
			return new WP_Error( 'rest_invoice_exists', __( 'Cannot create existing invoice.', 'invoicing' ), array( 'status' => 400 ) );
299
		}
300
301
		$request->set_param( 'context', 'edit' );
302
303
		// Prepare the updated data.
304
		$invoice_data = $this->prepare_item_for_database( $request );
305
306
		if ( is_wp_error( $invoice_data ) ) {
307
			return $invoice_data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice_data also could return the type array which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
308
		}
309
310
		// Try creating the invoice
311
        $invoice = wpinv_insert_invoice( $invoice_data, true );
0 ignored issues
show
Bug introduced by
It seems like $invoice_data can also be of type WP_Error; however, parameter $invoice_data of wpinv_insert_invoice() does only seem to accept array, 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

311
        $invoice = wpinv_insert_invoice( /** @scrutinizer ignore-type */ $invoice_data, true );
Loading history...
312
313
		if ( is_wp_error( $invoice ) ) {
314
            return $invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice also could return the type WPInv_Invoice|false|integer which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
315
		}
316
317
		// Prepare the response
318
		$response = $this->prepare_item_for_response( $invoice, $request );
319
320
		/**
321
		 * Fires after a single invoice is created or updated via the REST API.
322
		 *
323
		 * @since 1.0.13
324
		 *
325
		 * @param WPinv_Invoice   $invoice  Inserted or updated invoice object.
326
		 * @param WP_REST_Request $request  Request object.
327
		 * @param bool            $creating True when creating a post, false when updating.
328
		 */
329
		do_action( "wpinv_rest_insert_invoice", $invoice, $request, true );
330
331
		/**
332
		 * Filters the responses for creating single invoice requests.
333
		 *
334
		 *
335
		 * @since 1.0.13
336
		 *
337
		 *
338
		 * @param array           $invoice_data Invoice properties.
339
		 * @param WP_REST_Request $request The request used.
340
		 */
341
        $response       = apply_filters( "wpinv_rest_create_invoice_response", $response, $request );
342
343
        return rest_ensure_response( $response );
344
	}
345
346
	/**
347
	 * Checks if a given request has access to update an invoice.
348
	 *
349
	 * @since 1.0.13
350
	 *
351
	 * @param WP_REST_Request $request Full details about the request.
352
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
353
	 */
354
	public function update_item_permissions_check( $request ) {
355
356
		// Retrieve the invoice.
357
		$invoice = $this->get_post( $request['id'] );
358
		if ( is_wp_error( $invoice ) ) {
359
			return $invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|true.
Loading history...
360
		}
361
362
		$post_type = get_post_type_object( $this->post_type );
363
364
		if ( ! current_user_can(  $post_type->cap->edit_post, $invoice->ID  ) ) {
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on WP_Error.
Loading history...
365
			return new WP_Error( 
366
                'rest_cannot_edit', 
367
                __( 'Sorry, you are not allowed to update this invoice.', 'invoicing' ), 
368
                array( 
369
                    'status' => rest_authorization_required_code(),
370
                )
371
            );
372
        }
373
374
		return true;
375
	}
376
377
	/**
378
	 * Updates a single invoice.
379
	 *
380
	 * @since 1.0.13
381
	 *
382
	 * @param WP_REST_Request $request Full details about the request.
383
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
384
	 */
385
	public function update_item( $request ) {
386
		
387
		// Ensure the invoice exists.
388
        $valid_check = $this->get_post( $request['id'] );
389
        
390
        // Abort early if it does not exist
391
		if ( is_wp_error( $valid_check ) ) {
392
			return $valid_check;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $valid_check also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
393
		}
394
395
		$request->set_param( 'context', 'edit' );
396
397
		// Prepare the updated data.
398
		$data_to_update = $this->prepare_item_for_database( $request );
399
400
		if ( is_wp_error( $data_to_update ) ) {
401
			return $data_to_update;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data_to_update also could return the type array which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
402
		}
403
404
		// Abort if no invoice data is provided
405
        if( empty( $data_to_update ) ) {
406
            return new WP_Error( 'missing_data', __( 'An update request cannot be empty.', 'invoicing' ) );
407
        }
408
409
		// Include the invoice ID
410
		$data_to_update['ID'] = $request['id'];
411
412
		// Update the invoice
413
		$updated_invoice = wpinv_update_invoice( $data_to_update, true );
414
415
		// Incase the update operation failed...
416
		if ( is_wp_error( $updated_invoice ) ) {
417
			return $updated_invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $updated_invoice also could return the type WPInv_Invoice|integer which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
418
		}
419
420
		// Prepare the response
421
		$response = $this->prepare_item_for_response( $updated_invoice, $request );
422
423
		/** This action is documented in includes/class-wpinv-rest-invoice-controller.php */
424
		do_action( "wpinv_rest_insert_invoice", $updated_invoice, $request, false );
425
426
		/**
427
		 * Filters the responses for updating single invoice requests.
428
		 *
429
		 *
430
		 * @since 1.0.13
431
		 *
432
		 *
433
		 * @param array           $invoice_data Invoice properties.
434
		 * @param WP_REST_Request $request The request used.
435
		 */
436
        $response       = apply_filters( "wpinv_rest_update_invoice_response", $response, $request );
437
438
        return rest_ensure_response( $response );
439
	}
440
441
	/**
442
	 * Checks if a given request has access to delete an invoice.
443
	 *
444
	 * @since 1.0.13
445
	 *
446
	 * @param WP_REST_Request $request Full details about the request.
447
	 * @return true|WP_Error True if the request has access to delete the invoice, WP_Error object otherwise.
448
	 */
449
	public function delete_item_permissions_check( $request ) {
450
451
		// Retrieve the invoice.
452
		$invoice = $this->get_post( $request['id'] );
453
		if ( is_wp_error( $invoice ) ) {
454
			return $invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|true.
Loading history...
455
		}
456
457
		// Ensure the current user can delete invoices
458
		if ( wpinv_current_user_can_manage_invoicing() ||  current_user_can( 'delete_invoices', $request['id'] ) ) {
459
			return new WP_Error( 
460
                'rest_cannot_delete', 
461
                __( 'Sorry, you are not allowed to delete this invoice.', 'invoicing' ), 
462
                array( 
463
                    'status' => rest_authorization_required_code(),
464
                )
465
            );
466
		}
467
468
		return true;
469
	}
470
471
	/**
472
	 * Deletes a single invoice.
473
	 *
474
	 * @since 1.0.13
475
	 *
476
	 * @param WP_REST_Request $request Full details about the request.
477
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
478
	 */
479
	public function delete_item( $request ) {
480
		
481
		// Retrieve the invoice.
482
		$invoice = $this->get_post( $request['id'] );
483
		if ( is_wp_error( $invoice ) ) {
484
			return $invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
485
		}
486
487
		$request->set_param( 'context', 'edit' );
488
489
		// Prepare the invoice id
490
		$id    = $invoice->ID;
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on WP_Error.
Loading history...
491
492
		// Prepare the response
493
		$response = $this->prepare_item_for_response( $invoice, $request );
0 ignored issues
show
Bug introduced by
It seems like $invoice can also be of type WP_Error; however, parameter $invoice of WPInv_REST_Invoice_Contr...are_item_for_response() does only seem to accept WPInv_Invoice, 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

493
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $invoice, $request );
Loading history...
494
495
		// Check if the user wants to bypass the trash...
496
		$force_delete = (bool) $request['force'];
497
498
		// Try deleting the invoice.
499
		$deleted = wp_delete_post( $id, $force_delete );
500
501
		// Abort early if we can't delete the invoice.
502
		if ( ! $deleted ) {
503
			return new WP_Error( 'rest_cannot_delete', __( 'The invoice cannot be deleted.', 'invoicing' ), array( 'status' => 500 ) );
504
		}
505
506
		/**
507
		 * Fires immediately after a single invoice is deleted or trashed via the REST API.
508
		 *
509
		 *
510
		 * @since 1.0.13
511
		 *
512
		 * @param WPInv_Invoice    $invoice  The deleted or trashed invoice.
513
		 * @param WP_REST_Request  $request  The request sent to the API.
514
		 */
515
		do_action( "wpinv_rest_delete_invoice", $invoice, $request );
516
517
		return $response;
518
519
	}
520
    
521
    
522
    /**
523
	 * Retrieves the query params for the invoices collection.
524
	 *
525
	 * @since 1.0.13
526
	 *
527
	 * @return array Collection parameters.
528
	 */
529
	public function get_collection_params() {
530
        
531
        $query_params               = array(
532
533
            // Invoice status.
534
            'status'                => array(
535
                'default'           => 'publish',
536
                'description'       => __( 'Limit result set to invoices assigned one or more statuses.', 'invoicing' ),
537
                'type'              => 'array',
538
                'items'             => array(
539
                    'enum'          => array_keys( wpinv_get_invoice_statuses( true, true ) ),
540
                    'type'          => 'string',
541
                ),
542
                'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
543
            ),
544
545
            // User.
546
            'user'                  => array(
547
				'description'       => __( 'Limit result set to invoices for a specif user.', 'invoicing' ),
548
				'type'              => 'integer',
549
            ),
550
            
551
            // Number of results per page
552
            'limit'                 => array(
553
				'description'       => __( 'Number of invoices to fetch.', 'invoicing' ),
554
				'type'              => 'integer',
555
				'default'           => (int) get_option( 'posts_per_page' ),
556
            ),
557
558
            // Pagination
559
            'page'     => array(
560
				'description'       => __( 'Current page to fetch.', 'invoicing' ),
561
				'type'              => 'integer',
562
				'default'           => 1,
563
            ),
564
565
            // Exclude certain invoices
566
            'exclude'  => array(
567
                'description' => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
568
                'type'        => 'array',
569
                'items'       => array(
570
                    'type' => 'integer',
571
                ),
572
                'default'     => array(),
573
            ),
574
575
            // Order invoices by
576
            'orderby'  => array(
577
                'description' => __( 'Sort invoices by object attribute.', 'invoicing' ),
578
                'type'        => 'string',
579
                'default'     => 'date',
580
                'enum'        => array(
581
                    'author',
582
                    'date',
583
                    'id',
584
                    'modified',
585
                    'title'
586
                ),
587
            ),
588
589
            // How to order
590
            'order'    => array(
591
                'description' => __( 'Order sort attribute ascending or descending.', 'invoicing' ),
592
                'type'        => 'string',
593
                'default'     => 'DESC',
594
                'enum'        => array( 'ASC', 'DESC' ),
595
            ),
596
        );
597
598
		/**
599
		 * Filter collection parameters for the invoices controller.
600
		 *
601
		 *
602
		 * @since 1.0.13
603
		 *
604
		 * @param array        $query_params JSON Schema-formatted collection parameters.
605
		 */
606
		return apply_filters( "wpinv_rest_invoices_collection_params", $query_params );
607
    }
608
    
609
    /**
610
	 * Checks if a given post type can be viewed or managed.
611
	 *
612
	 * @since 1.0.13
613
	 *
614
	 * @param object|string $post_type Post type name or object.
615
	 * @return bool Whether the post type is allowed in REST.
616
	 */
617
	protected function check_is_post_type_allowed( $post_type ) {
618
		return true;
619
	}
620
621
	/**
622
	 * Prepares a single invoice for create or update.
623
	 *
624
	 * @since 1.0.13
625
	 *
626
	 * @param WP_REST_Request $request Request object.
627
	 * @return array|WP_Error Invoice Properties or WP_Error.
628
	 */
629
	protected function prepare_item_for_database( $request ) {
630
		$prepared_invoice = new stdClass();
631
632
		// Post ID.
633
		if ( isset( $request['id'] ) ) {
634
			$existing_invoice = $this->get_post( $request['id'] );
635
			if ( is_wp_error( $existing_invoice ) ) {
636
				return $existing_invoice;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $existing_invoice also could return the type WPInv_Invoice which is incompatible with the documented return type WP_Error|array.
Loading history...
637
			}
638
639
			$prepared_invoice->ID 		  = $existing_invoice->ID;
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on WP_Error.
Loading history...
640
			$prepared_invoice->invoice_id = $existing_invoice->ID;
641
		}
642
643
		$schema = $this->get_item_schema();
644
645
		// Invoice owner.
646
		if ( ! empty( $schema['properties']['user_id'] ) && isset( $request['user_id'] ) ) {
647
			$prepared_invoice->user_id = (int) $request['user_id'];
648
		}
649
650
		// Cart details.
651
		if ( ! empty( $schema['properties']['cart_details'] ) && isset( $request['cart_details'] ) ) {
652
			$prepared_invoice->cart_details = (array) $request['cart_details'];
653
		}
654
655
		// Invoice status.
656
		if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
657
658
			if ( in_array( $request['status'], array_keys( wpinv_get_invoice_statuses( true, true ) ), true ) ) {
659
				$prepared_invoice->status = $request['status'];
660
			}
661
662
		}
663
664
		// User info
665
		if ( ! empty( $schema['properties']['user_info'] ) && isset( $request['user_info'] ) ) {
666
			$prepared_invoice->user_info = array();
667
			$user_info = (array) $request['user_info'];
668
669
			foreach( $user_info as $prop => $value ) {
670
671
				if ( ! empty( $schema['properties']['user_info']['properties'][$prop] ) ) {
672
673
					$prepared_invoice->user_info[$prop] = $value;
674
		
675
				}
676
677
			}
678
679
		}
680
681
		// IP
682
		if ( ! empty( $schema['properties']['ip'] ) && isset( $request['ip'] ) ) {
683
			$prepared_invoice->ip = $request['ip'];
684
		}
685
686
		// Payment details
687
		$prepared_invoice->payment_details = array();
688
689
		if ( ! empty( $schema['properties']['gateway'] ) && isset( $request['gateway'] ) ) {
690
			$prepared_invoice->payment_details['gateway'] = $request['gateway'];
691
		}
692
693
		if ( ! empty( $schema['properties']['gateway_title'] ) && isset( $request['gateway_title'] ) ) {
694
			$prepared_invoice->payment_details['gateway_title'] = $request['gateway_title'];
695
		}
696
697
		if ( ! empty( $schema['properties']['currency'] ) && isset( $request['currency'] ) ) {
698
			$prepared_invoice->payment_details['currency'] = $request['currency'];
699
		}
700
701
		if ( ! empty( $schema['properties']['transaction_id'] ) && isset( $request['transaction_id'] ) ) {
702
			$prepared_invoice->payment_details['transaction_id'] = $request['transaction_id'];
703
		}
704
705
		// Dates
706
		if ( ! empty( $schema['properties']['date'] ) && isset( $request['date'] ) ) {
707
			$post_date = rest_get_date_with_gmt( $request['date'] );
708
709
			if ( ! empty( $post_date ) ) {
710
				$prepared_invoice->post_date = $post_date[0];
711
			}
712
			
713
		}
714
715
		if ( ! empty( $schema['properties']['due_date'] ) && isset( $request['due_date'] ) ) {
716
			$due_date = rest_get_date_with_gmt( $request['due_date'] );
717
718
			if ( ! empty( $due_date ) ) {
719
				$prepared_invoice->due_date = $due_date[0];
720
			}
721
722
		}
723
724
		$invoice_data = (array) wp_unslash( $prepared_invoice );
0 ignored issues
show
Bug introduced by
$prepared_invoice of type stdClass is incompatible with the type array|string expected by parameter $value of wp_unslash(). ( Ignorable by Annotation )

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

724
		$invoice_data = (array) wp_unslash( /** @scrutinizer ignore-type */ $prepared_invoice );
Loading history...
725
726
		/**
727
		 * Filters an invoice before it is inserted via the REST API.
728
		 *
729
		 * @since 1.0.13
730
		 *
731
		 * @param array        $invoice_data An array of invoice data
732
		 * @param WP_REST_Request $request       Request object.
733
		 */
734
		return apply_filters( "wpinv_rest_pre_insert_invoice", $invoice_data, $request );
735
736
	}
737
738
	/**
739
	 * Prepares a single invoice output for response.
740
	 *
741
	 * @since 1.0.13
742
	 *
743
	 * @param WPInv_Invoice   $invoice    Invoice object.
744
	 * @param WP_REST_Request $request Request object.
745
	 * @return WP_REST_Response Response object.
746
	 */
747
	public function prepare_item_for_response( $invoice, $request ) {
748
749
		$GLOBALS['post'] = get_post( $invoice->ID );
750
751
		setup_postdata( $invoice->ID );
752
753
		// Fetch the fields to include in this response.
754
		$fields = $this->get_fields_for_response( $request );
755
756
		// Base fields for every invoice.
757
		$data = array();
758
759
		// Set up ID
760
		if ( rest_is_field_included( 'id', $fields ) ) {
761
			$data['id'] = $invoice->ID;
762
		}
763
764
765
		// Basic properties
766
		$invoice_properties = array(
767
			'title', 'email', 'ip', 
768
			'key', 'number', 'transaction_id', 'mode',
769
			'gateway', 'gateway_title',
770
			'total', 'discount', 'discount_code', 
771
			'tax', 'fees_total', 'subtotal', 'currency',
772
			'status', 'status_nicename', 'post_type'
773
		);
774
775
		foreach( $invoice_properties as $property ) {
776
777
			if ( rest_is_field_included( $property, $fields ) ) {
778
				$data[$property] = $invoice->get( $property );
779
			}
780
781
		}
782
783
		// Cart details
784
		if ( rest_is_field_included( 'cart_details', $fields ) ) {
785
			$data['cart_details'] = $invoice->get( 'cart_details' );
786
		}
787
788
		//Dates
789
		$invoice_properties = array( 'date', 'due_date', 'completed_date' );
790
791
		foreach( $invoice_properties as $property ) {
792
793
			if ( rest_is_field_included( $property, $fields ) ) {
794
				$data[$property] = $this->prepare_date_response( '0000-00-00 00:00:00', $invoice->get( $property ) );
795
			}
796
797
		}
798
799
		// User id
800
		if ( rest_is_field_included( 'user_id', $fields ) ) {
801
			$data['user_id'] = (int) $invoice->get( 'user_id' );
802
		}
803
804
		// User info
805
		$user_info = array( 'first_name', 'last_name', 'company', 'vat_number', 'vat_rate', 'address', 'city', 'country', 'state', 'zip', 'phone' );
806
807
		foreach( $user_info as $property ) {
808
809
			if ( rest_is_field_included( "user_info.$property", $fields ) ) {
810
				$data['user_info'][$property] = $invoice->get( $property );
811
			}
812
813
		}
814
815
		// Slug
816
		if ( rest_is_field_included( 'slug', $fields ) ) {
817
			$data['slug'] = $invoice->get( 'post_name' );
818
		}
819
820
		// View invoice link
821
		if ( rest_is_field_included( 'link', $fields ) ) {
822
			$data['link'] = esc_url( $invoice->get_view_url() );
0 ignored issues
show
Bug introduced by
It seems like $invoice->get_view_url() can also be of type false; however, parameter $url of esc_url() 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

822
			$data['link'] = esc_url( /** @scrutinizer ignore-type */ $invoice->get_view_url() );
Loading history...
823
		}
824
825
826
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
827
		$data    = $this->add_additional_fields_to_object( $data, $request );
828
		$data    = $this->filter_response_by_context( $data, $context );
829
830
		// Wrap the data in a response object.
831
		$response = rest_ensure_response( $data );
832
833
		$links = $this->prepare_links( $invoice );
834
		$response->add_links( $links );
835
836
		if ( ! empty( $links['self']['href'] ) ) {
837
			$actions = $this->get_available_actions( $invoice, $request );
838
839
			$self = $links['self']['href'];
840
841
			foreach ( $actions as $rel ) {
842
				$response->add_link( $rel, $self );
843
			}
844
		}
845
846
		/**
847
		 * Filters the invoice data for a response.
848
		 *
849
		 * @since 1.0.13
850
		 *
851
		 * @param WP_REST_Response $response The response object.
852
		 * @param WPInv_Invoice    $invoice  The invoice object.
853
		 * @param WP_REST_Request  $request  Request object.
854
		 */
855
		return apply_filters( "wpinv_rest_prepare_invoice", $response, $invoice, $request );
0 ignored issues
show
Bug Best Practice introduced by
The expression return apply_filters('wp...se, $invoice, $request) also could return the type array which is incompatible with the documented return type WP_REST_Response.
Loading history...
856
	}
857
858
	/**
859
	 * Gets an array of fields to be included on the response.
860
	 *
861
	 * Included fields are based on item schema and `_fields=` request argument.
862
	 *
863
	 * @since 1.0.13
864
	 *
865
	 * @param WP_REST_Request $request Full details about the request.
866
	 * @return array Fields to be included in the response.
867
	 */
868
	public function get_fields_for_response( $request ) {
869
		$schema     = $this->get_item_schema();
870
		$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
871
872
		$additional_fields = $this->get_additional_fields();
873
		foreach ( $additional_fields as $field_name => $field_options ) {
874
			// For back-compat, include any field with an empty schema
875
			// because it won't be present in $this->get_item_schema().
876
			if ( is_null( $field_options['schema'] ) ) {
877
				$properties[ $field_name ] = $field_options;
878
			}
879
		}
880
881
		// Exclude fields that specify a different context than the request context.
882
		$context = $request['context'];
883
		if ( $context ) {
884
			foreach ( $properties as $name => $options ) {
885
				if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
886
					unset( $properties[ $name ] );
887
				}
888
			}
889
		}
890
891
		$fields = array_keys( $properties );
892
893
		if ( ! isset( $request['_fields'] ) ) {
894
			return $fields;
895
		}
896
		$requested_fields = wpinv_parse_list( $request['_fields'] );
897
		if ( 0 === count( $requested_fields ) ) {
898
			return $fields;
899
		}
900
		// Trim off outside whitespace from the comma delimited list.
901
		$requested_fields = array_map( 'trim', $requested_fields );
902
		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
903
		if ( in_array( 'id', $fields, true ) ) {
904
			$requested_fields[] = 'id';
905
		}
906
		// Return the list of all requested fields which appear in the schema.
907
		return array_reduce(
908
			$requested_fields,
909
			function( $response_fields, $field ) use ( $fields ) {
910
				if ( in_array( $field, $fields, true ) ) {
911
					$response_fields[] = $field;
912
					return $response_fields;
913
				}
914
				// Check for nested fields if $field is not a direct match.
915
				$nested_fields = explode( '.', $field );
916
				// A nested field is included so long as its top-level property is
917
				// present in the schema.
918
				if ( in_array( $nested_fields[0], $fields, true ) ) {
919
					$response_fields[] = $field;
920
				}
921
				return $response_fields;
922
			},
923
			array()
924
		);
925
	}
926
927
	/**
928
	 * Retrieves the invoice's schema, conforming to JSON Schema.
929
	 *
930
	 * @since 1.0.13
931
	 *
932
	 * @return array Invoice schema data.
933
	 */
934
	public function get_item_schema() {
935
936
		// Maybe retrieve the schema from cache.
937
		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...
938
			return $this->add_additional_fields_schema( $this->schema );
939
		}
940
941
		$schema = array(
942
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
943
			'title'      => $this->post_type,
944
			'type'       => 'object',
945
946
			// Base properties for every Invoice.
947
			'properties' 		  => array(
948
949
				'title'			  => array(
950
					'description' => __( 'The title for the invoice.', 'invoicing' ),
951
					'type'        => 'string',
952
					'context'     => array( 'view', 'edit', 'embed' ),
953
					'readonly'    => true,
954
				),
955
956
				'user_id'		  => array(
957
					'description' => __( 'The ID of the owner of the invoice.', 'invoicing' ),
958
					'type'        => 'integer',
959
					'context'     => array( 'view', 'edit', 'embed' ),
960
				),
961
962
				'email'		  	  => array(
963
					'description' => __( 'The email of the owner of the invoice.', 'invoicing' ),
964
					'type'        => 'string',
965
					'context'     => array( 'view', 'edit', 'embed' ),
966
					'readonly'    => true,
967
				),
968
969
				'ip'			  => array(
970
					'description' => __( 'The IP of the owner of the invoice.', 'invoicing' ),
971
					'type'        => 'string',
972
					'context'     => array( 'view', 'edit', 'embed' ),
973
				),
974
975
				'user_info'       => array(
976
					'description' => __( 'Information about the owner of the invoice.', 'invoicing' ),
977
					'type'        => 'object',
978
					'context'     => array( 'view', 'edit', 'embed' ),
979
					'properties'  => array(
980
981
						'first_name'      => array(
982
							'description' => __( 'The first name of the owner of the invoice.', 'invoicing' ),
983
							'type'        => 'string',
984
							'context'     => array( 'view', 'edit', 'embed' ),
985
						),
986
987
						'last_name'       => array(
988
							'description' => __( 'The last name of the owner of the invoice.', 'invoicing' ),
989
							'type'        => 'string',
990
							'context'     => array( 'view', 'edit', 'embed' ),
991
						),
992
993
						'company'         => array(
994
							'description' => __( 'The company of the owner of the invoice.', 'invoicing' ),
995
							'type'        => 'string',
996
							'context'     => array( 'view', 'edit', 'embed' ),
997
						),
998
999
						'vat_number'      => array(
1000
							'description' => __( 'The VAT number of the owner of the invoice.', 'invoicing' ),
1001
							'type'        => 'string',
1002
							'context'     => array( 'view', 'edit', 'embed' ),
1003
						),
1004
1005
						'vat_rate'        => array(
1006
							'description' => __( 'The VAT rate applied on the invoice.', 'invoicing' ),
1007
							'type'        => 'string',
1008
							'context'     => array( 'view', 'edit', 'embed' ),
1009
						),
1010
1011
						'address'        => array(
1012
							'description' => __( 'The address of the invoice owner.', 'invoicing' ),
1013
							'type'        => 'string',
1014
							'context'     => array( 'view', 'edit', 'embed' ),
1015
						),
1016
1017
						'city'            => array(
1018
							'description' => __( 'The city of the invoice owner.', 'invoicing' ),
1019
							'type'        => 'string',
1020
							'context'     => array( 'view', 'edit', 'embed' ),
1021
						),
1022
1023
						'country'         => array(
1024
							'description' => __( 'The country of the invoice owner.', 'invoicing' ),
1025
							'type'        => 'string',
1026
							'context'     => array( 'view', 'edit', 'embed' ),
1027
						),
1028
1029
						'state'           => array(
1030
							'description' => __( 'The state of the invoice owner.', 'invoicing' ),
1031
							'type'        => 'string',
1032
							'context'     => array( 'view', 'edit', 'embed' ),
1033
						),
1034
1035
						'zip'             => array(
1036
							'description' => __( 'The zip code of the invoice owner.', 'invoicing' ),
1037
							'type'        => 'string',
1038
							'context'     => array( 'view', 'edit', 'embed' ),
1039
						),
1040
1041
						'phone'             => array(
1042
							'description' => __( 'The phone number of the invoice owner.', 'invoicing' ),
1043
							'type'        => 'string',
1044
							'context'     => array( 'view', 'edit', 'embed' ),
1045
						),
1046
					),
1047
				),
1048
1049
				'id'           => array(
1050
					'description' => __( 'Unique identifier for the invoice.', 'invoicing' ),
1051
					'type'        => 'integer',
1052
					'context'     => array( 'view', 'edit', 'embed' ),
1053
					'readonly'    => true,
1054
				),
1055
1056
				'key'			  => array(
1057
					'description' => __( 'A unique key for the invoice.', 'invoicing' ),
1058
					'type'        => 'string',
1059
					'context'     => array( 'view', 'edit', 'embed' ),
1060
					'readonly'    => true,
1061
				),
1062
1063
				'number'		  => array(
1064
					'description' => __( 'The invoice number.', 'invoicing' ),
1065
					'type'        => 'string',
1066
					'context'     => array( 'view', 'edit', 'embed' ),
1067
					'readonly'    => true,
1068
				),
1069
1070
				'transaction_id'  => array(
1071
					'description' => __( 'The transaction id of the invoice.', 'invoicing' ),
1072
					'type'        => 'string',
1073
					'context'     => array( 'view', 'edit', 'embed' ),
1074
				),
1075
1076
				'gateway'		  => array(
1077
					'description' => __( 'The gateway used to process the invoice.', 'invoicing' ),
1078
					'type'        => 'string',
1079
					'context'     => array( 'view', 'edit', 'embed' ),
1080
				),
1081
1082
				'gateway_title'	  => array(
1083
					'description' => __( 'The title of the gateway used to process the invoice.', 'invoicing' ),
1084
					'type'        => 'string',
1085
					'context'     => array( 'view', 'edit', 'embed' ),
1086
				),
1087
1088
				'total'	  		  => array(
1089
					'description' => __( 'The total amount of the invoice.', 'invoicing' ),
1090
					'type'        => 'number',
1091
					'context'     => array( 'view', 'edit', 'embed' ),
1092
					'readonly'    => true,
1093
				),
1094
1095
				'discount'		  => array(
1096
					'description' => __( 'The discount applied to the invoice.', 'invoicing' ),
1097
					'type'        => 'number',
1098
					'context'     => array( 'view', 'edit', 'embed' ),
1099
					'readonly'    => true,
1100
				),
1101
1102
				'discount_code'	  => array(
1103
					'description' => __( 'The discount code applied to the invoice.', 'invoicing' ),
1104
					'type'        => 'string',
1105
					'context'     => array( 'view', 'edit', 'embed' ),
1106
					'readonly'    => true,
1107
				),
1108
1109
				'tax'	  		  => array(
1110
					'description' => __( 'The tax applied to the invoice.', 'invoicing' ),
1111
					'type'        => 'number',
1112
					'context'     => array( 'view', 'edit', 'embed' ),
1113
					'readonly'    => true,
1114
				),
1115
1116
				'fees_total'	  => array(
1117
					'description' => __( 'The total fees applied to the invoice.', 'invoicing' ),
1118
					'type'        => 'number',
1119
					'context'     => array( 'view', 'edit', 'embed' ),
1120
					'readonly'    => true,
1121
				),
1122
1123
				'subtotal'	  	  => array(
1124
					'description' => __( 'The sub-total for the invoice.', 'invoicing' ),
1125
					'type'        => 'number',
1126
					'context'     => array( 'view', 'edit', 'embed' ),
1127
					'readonly'    => true,
1128
				),
1129
1130
				'currency'	  	  => array(
1131
					'description' => __( 'The currency used to process the invoice.', 'invoicing' ),
1132
					'type'        => 'string',
1133
					'context'     => array( 'view', 'edit', 'embed' ),
1134
				),
1135
1136
				'cart_details'	  => array(
1137
					'description' => __( 'The cart details for invoice.', 'invoicing' ),
1138
					'type'        => 'array',
1139
					'context'     => array( 'view', 'edit', 'embed' ),
1140
					'required'	  => true,
1141
				),
1142
1143
				'date'         => array(
1144
					'description' => __( "The date the invoice was published, in the site's timezone.", 'invoicing' ),
1145
					'type'        => array( 'string', 'null' ),
1146
					'format'      => 'date-time',
1147
					'context'     => array( 'view', 'edit', 'embed' ),
1148
				),
1149
1150
				'due_date'     => array(
1151
					'description' => __( 'The due date for the invoice.', 'invoicing' ),
1152
					'type'        => array( 'string', 'null' ),
1153
					'format'      => 'date-time',
1154
					'context'     => array( 'view', 'edit', 'embed' ),
1155
				),
1156
1157
				'completed_date'  => array(
1158
					'description' => __( 'The completed date for the invoice.', 'invoicing' ),
1159
					'type'        => array( 'string', 'null' ),
1160
					'format'      => 'date-time',
1161
					'context'     => array( 'view', 'edit', 'embed' ),
1162
					'readonly'    => true,
1163
				),
1164
				
1165
				'link'         => array(
1166
					'description' => __( 'URL to the invoice.', 'invoicing' ),
1167
					'type'        => 'string',
1168
					'format'      => 'uri',
1169
					'context'     => array( 'view', 'edit', 'embed' ),
1170
					'readonly'    => true,
1171
				),
1172
1173
				'mode'       	  => array(
1174
					'description' => __( 'The mode used to process the invoice.', 'invoicing' ),
1175
					'type'        => 'string',
1176
					'enum'        => array( 'live', 'test' ),
1177
					'context'     => array( 'view', 'edit', 'embed' ),
1178
					'readonly'    => true,
1179
				),
1180
1181
				'slug'       	  => array(
1182
					'description' => __( 'An alphanumeric identifier for the invoice.', 'invoicing' ),
1183
					'type'        => 'string',
1184
					'context'     => array( 'view', 'edit', 'embed' ),
1185
					'arg_options' => array(
1186
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
1187
					),
1188
					'readonly'    => true,
1189
				),
1190
1191
				'status'       	  => array(
1192
					'description' => __( 'A named status for the invoice.', 'invoicing' ),
1193
					'type'        => 'string',
1194
					'enum'        => array_keys( wpinv_get_invoice_statuses( true, true ) ),
1195
					'context'     => array( 'view', 'edit' ),
1196
					'default'	  => 'wpi-pending',
1197
				),
1198
1199
				'status_nicename' => array(
1200
					'description' => __( 'A human-readable status name for the invoice.', 'invoicing' ),
1201
					'type'        => 'string',
1202
					'context'     => array( 'view', 'embed' ),
1203
					'readonly'    => true,
1204
				),
1205
1206
				'post_type'       => array(
1207
					'description' => __( 'The post type for the invoice.', 'invoicing' ),
1208
					'type'        => 'string',
1209
					'context'     => array( 'view' ),
1210
					'readonly'    => true,
1211
				),
1212
			),
1213
		);
1214
1215
		// Add helpful links to the invoice schem.
1216
		$schema['links'] = $this->get_schema_links();
1217
1218
		/**
1219
		 * Filters the invoice schema for the REST API.
1220
		 *
1221
		 * Enables adding extra properties to invoices.
1222
		 *
1223
		 * @since 1.0.13
1224
		 *
1225
		 * @param array   $schema    The invoice schema.
1226
		 */
1227
        $schema = apply_filters( "wpinv_rest_invoice_schema", $schema );
1228
1229
		// Cache the invoice schema.
1230
		$this->schema = $schema;
1231
		
1232
		return $this->add_additional_fields_schema( $this->schema );
1233
	}
1234
1235
	/**
1236
	 * Retrieve Link Description Objects that should be added to the Schema for the invoices collection.
1237
	 *
1238
	 * @since 1.0.13
1239
	 *
1240
	 * @return array
1241
	 */
1242
	protected function get_schema_links() {
1243
1244
		$href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
1245
1246
		$links = array();
1247
1248
		$links[] = array(
1249
			'rel'          => 'https://api.w.org/action-publish',
1250
			'title'        => __( 'The current user can mark this invoice as completed.', 'invoicing' ),
1251
			'href'         => $href,
1252
			'targetSchema' => array(
1253
				'type'       => 'object',
1254
				'properties' => array(
1255
					'status' => array(
1256
						'type' => 'string',
1257
						'enum' => array( 'publish', 'wpi-renewal' ),
1258
					),
1259
				),
1260
			),
1261
		);
1262
1263
		$links[] = array(
1264
			'rel'          => 'https://api.w.org/action-assign-author',
1265
			'title'        => __( 'The current user can change the owner of this invoice.', 'invoicing' ),
1266
			'href'         => $href,
1267
			'targetSchema' => array(
1268
				'type'       => 'object',
1269
				'properties'   => array(
1270
					'user_id'  => array(
1271
						'type' => 'integer',
1272
					),
1273
				),
1274
			),
1275
		);
1276
1277
		return $links;
1278
	}
1279
1280
	/**
1281
	 * Prepares links for the request.
1282
	 *
1283
	 * @since 1.0.13
1284
	 *
1285
	 * @param WPInv_Invoice $invoice Invoice Object.
1286
	 * @return array Links for the given invoice.
1287
	 */
1288
	protected function prepare_links( $invoice ) {
1289
1290
		// Prepare the base REST API endpoint for invoices.
1291
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
1292
1293
		// Entity meta.
1294
		$links = array(
1295
			'self'       => array(
1296
				'href' => rest_url( trailingslashit( $base ) . $invoice->ID ),
1297
			),
1298
			'collection' => array(
1299
				'href' => rest_url( $base ),
1300
			),
1301
		);
1302
1303
		if ( ! empty( $invoice->user_id ) ) {
1304
			$links['user'] = array(
1305
				'href'       => rest_url( 'wp/v2/users/' . $invoice->user_id ),
1306
				'embeddable' => true,
1307
			);
1308
		}
1309
1310
		/**
1311
		 * Filters the returned invoice links for the REST API.
1312
		 *
1313
		 * Enables adding extra links to invoice API responses.
1314
		 *
1315
		 * @since 1.0.13
1316
		 *
1317
		 * @param array   $links    Rest links.
1318
		 */
1319
		return apply_filters( "wpinv_rest_invoice_links", $links );
1320
1321
	}
1322
1323
	/**
1324
	 * Get the link relations available for the post and current user.
1325
	 *
1326
	 * @since 1.0.13
1327
	 *
1328
	 * @param WPInv_Invoice   $invoice    Invoice object.
1329
	 * @param WP_REST_Request $request Request object.
1330
	 * @return array List of link relations.
1331
	 */
1332
	protected function get_available_actions( $invoice, $request ) {
1333
1334
		if ( 'edit' !== $request['context'] ) {
1335
			return array();
1336
		}
1337
1338
		$rels = array();
1339
1340
		// Retrieve the post type object.
1341
		$post_type = get_post_type_object( $invoice->post_type );
1342
1343
		// Mark invoice as completed.
1344
		if ( current_user_can( $post_type->cap->publish_posts ) ) {
1345
			$rels[] = 'https://api.w.org/action-publish';
1346
		}
1347
1348
		// Change the owner of the invoice.
1349
		if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
1350
			$rels[] = 'https://api.w.org/action-assign-author';
1351
		}
1352
1353
		/**
1354
		 * Filters the available invoice link relations for the REST API.
1355
		 *
1356
		 * Enables adding extra link relation for the current user and request to invoice responses.
1357
		 *
1358
		 * @since 1.0.13
1359
		 *
1360
		 * @param array   $rels    Available link relations.
1361
		 */
1362
		return apply_filters( "wpinv_rest_invoice_link_relations", $rels );
1363
	}
1364
1365
	/**
1366
	 * Sanitizes and validates the list of post statuses.
1367
	 *
1368
	 * @since 1.0.13
1369
	 *
1370
	 * @param string|array    $statuses  One or more post statuses.
1371
	 * @param WP_REST_Request $request   Full details about the request.
1372
	 * @param string          $parameter Additional parameter to pass to validation.
1373
	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
1374
	 */
1375
	public function sanitize_post_statuses( $statuses, $request, $parameter ) {
1376
1377
		$statuses 	  = wp_parse_slug_list( $statuses );
1378
		$valid_statuses = array_keys( wpinv_get_invoice_statuses( true, true ) );
1379
		return array_intersect( $statuses, $valid_statuses );
1380
		
1381
	}
1382
    
1383
}