Passed
Push — master ( 4371ac...570cd0 )
by Brian
05:01 queued 11s
created

get_collection_params()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 140
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 80
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 140
rs 8.4362

How to fix   Long Method   

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
 * 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 items.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
69
		}
70
71
		if ( ! is_user_logged_in() ) {
72
			return new WP_Error( 'rest_forbidden', __( 'Sorry, you must be logged in to view items.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
73
		}
74
75
		// Read checks will be evaluated on a per invoice basis
76
77
		return true;
78
79
    }
80
    
81
    /**
82
	 * Retrieves a collection of invoices.
83
	 *
84
	 * @since 1.0.13
85
	 *
86
	 * @param WP_REST_Request $request Full details about the request.
87
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
88
	 */
89
	public function get_items( $request ) {
90
		
91
		// Retrieve request query parameters.
92
		$args          = $this->get_request_collection_params( $request );
93
		
94
		// WP_Query Args.
95
		$wp_query_args = $this->get_collection_wp_query_params( $args, $request );
96
		
97
		// Get invoice ids...
98
		$query         = new WP_Query( $wp_query_args );
99
100
		// ... and map them into invoice objects.
101
		$_invoices     = array_map( array( $this, 'get_post' ), $query->posts );
102
103
		// Prepare the retrieved invoices
104
		$invoices      = array();
105
106
		foreach( $_invoices as $invoice ) {
107
			if ( $this->check_read_permission( $invoice ) ) {
108
				$invoices[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $invoice, $request ) );
109
			}
110
		}
111
112
		// Prepare the response.
113
		$response = rest_ensure_response( $invoices );
114
		$response->header( 'X-WP-Total', (int) $query->found_posts );
115
		$response->header( 'X-WP-TotalPages', (int) $query->max_num_pages );
116
117
		/**
118
		 * Filters the responses for invoices requests.
119
		 *
120
		 *
121
		 * @since 1.0.13
122
		 *
123
		 *
124
		 * @param arrWP_REST_Response $response    Response object.
125
		 * @param WP_REST_Request     $request The request used.
126
         * @param array               $args Array of args used to retrieve the invoices
127
		 */
128
        $response       = apply_filters( "wpinv_rest_invoices_response", $response, $request, $args );
129
130
        return rest_ensure_response( $response );
131
        
132
    }
133
134
    /**
135
	 * Get the post, if the ID is valid.
136
	 *
137
	 * @since 1.0.13
138
	 *
139
	 * @param int $invoice_id Supplied ID.
140
	 * @return WPInv_Invoice|WP_Error Invoice object if ID is valid, WP_Error otherwise.
141
	 */
142
	protected function get_post( $invoice_id ) {
143
		
144
		$error     = new WP_Error( 'rest_invoice_invalid_id', __( 'Invalid item ID.', 'invoicing' ), array( 'status' => 404 ) );
145
146
        // Ids start from 1
147
        if ( (int) $invoice_id <= 0 ) {
148
			return $error;
149
		}
150
151
		$invoice = wpinv_get_invoice( (int) $invoice_id );
152
		if ( empty( $invoice ) || $this->post_type !== $invoice->post_type ) {
153
			return $error;
154
        }
155
156
        return $invoice;
157
158
    }
159
160
    /**
161
	 * Checks if a given request has access to read an invoice.
162
	 *
163
	 * @since 1.0.13
164
	 *
165
	 * @param WP_REST_Request $request Full details about the request.
166
	 * @return bool|WP_Error True if the request has read access for the invoice, WP_Error object otherwise.
167
	 */
168
	public function get_item_permissions_check( $request ) {
169
170
        // Retrieve the invoice object.
171
        $invoice = $this->get_post( $request['id'] );
172
        
173
        // Ensure it is valid.
174
		if ( is_wp_error( $invoice ) ) {
175
			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...
176
		}
177
178
		if ( $invoice ) {
179
			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

179
			return $this->check_read_permission( /** @scrutinizer ignore-type */ $invoice );
Loading history...
180
		}
181
182
		return true;
183
    }
184
    
185
    /**
186
	 * Checks if an invoice can be read.
187
	 * 
188
	 * An invoice can be read by site admins and owners of the invoice
189
	 *
190
	 *
191
	 * @since 1.0.13
192
	 *
193
	 * @param WPInv_Invoice $invoice WPInv_Invoice object.
194
	 * @return bool Whether the post can be read.
195
	 */
196
	public function check_read_permission( $invoice ) {
197
		return wpinv_user_can_view_invoice( $invoice->ID );
198
    }
199
    
200
    /**
201
	 * Retrieves a single invoice.
202
	 *
203
	 * @since 1.0.13
204
	 *
205
	 * @param WP_REST_Request $request Full details about the request.
206
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
207
	 */
208
	public function get_item( $request ) {
209
210
        // Fetch the invoice.
211
        $invoice = $this->get_post( $request['id'] );
212
        
213
        // Abort early if it does not exist
214
		if ( is_wp_error( $invoice ) ) {
215
			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...
216
		}
217
218
		// Prepare the response
219
		$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

219
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $invoice, $request );
Loading history...
220
		$response->link_header( 'alternate', esc_url( $invoice->get_view_url() ), array( 'type' => 'text/html' ) );
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

220
		$response->link_header( 'alternate', esc_url( /** @scrutinizer ignore-type */ $invoice->get_view_url() ), array( 'type' => 'text/html' ) );
Loading history...
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

220
		$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...
221
222
		/**
223
		 * Filters the responses for single invoice requests.
224
		 *
225
		 *
226
		 * @since 1.0.13
227
		 * @var WP_HTTP_Response
228
		 *
229
		 * @param WP_HTTP_Response $response Response.
230
		 * @param WP_REST_Request  $request The request used.
231
		 */
232
        $response       = apply_filters( "wpinv_rest_get_invoice_response", $response, $request );
233
234
        return rest_ensure_response( $response );
235
236
    }
237
    
238
    /**
239
	 * Checks if a given request has access to create an invoice.
240
	 *
241
	 * @since 1.0.13
242
	 *
243
	 * @param WP_REST_Request $request Full details about the request.
244
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
245
	 */
246
	public function create_item_permissions_check( $request ) {
247
	
248
		if ( ! empty( $request['id'] ) ) {
249
			return new WP_Error( 'rest_invoice_exists', __( 'Cannot create existing item.', 'invoicing' ), array( 'status' => 400 ) );
250
		}
251
252
		$post_type = get_post_type_object( $this->post_type );
253
254
		if ( ! current_user_can( $post_type->cap->create_posts ) && ! wpinv_current_user_can_manage_invoicing() ) {
255
			return new WP_Error( 
256
                'rest_cannot_create',
257
                __( 'Sorry, you are not allowed to create items as this user.', 'invoicing' ), 
258
                array( 
259
                    'status' => rest_authorization_required_code(),
260
                )
261
            );
262
        }
263
264
		return true;
265
    }
266
    
267
    /**
268
	 * Creates a single invoice.
269
	 *
270
	 * @since 1.0.13
271
	 *
272
	 * @param WP_REST_Request $request Full details about the request.
273
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
274
	 */
275
	public function create_item( $request ) {
276
277
		if ( ! empty( $request['id'] ) ) {
278
			return new WP_Error( 'rest_invoice_exists', __( 'Cannot create existing item.', 'invoicing' ), array( 'status' => 400 ) );
279
		}
280
281
		$request->set_param( 'context', 'edit' );
282
283
		// Prepare the updated data.
284
		$invoice_data = $this->prepare_item_for_database( $request );
285
286
		if ( is_wp_error( $invoice_data ) ) {
287
			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...
288
		}
289
290
		// Try creating the invoice
291
		$invoice_data['post_type']    = $this->post_type;
292
		$invoice_data['private_note'] = __( 'Created via API.', 'invoicing' );
293
        $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

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

475
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $invoice, $request );
Loading history...
476
477
		// Check if the user wants to bypass the trash...
478
		$force_delete = (bool) $request['force'];
479
480
		// Try deleting the invoice.
481
		$deleted = wp_delete_post( $id, $force_delete );
482
483
		// Abort early if we can't delete the invoice.
484
		if ( ! $deleted ) {
485
			return new WP_Error( 'rest_cannot_delete', __( 'The item cannot be deleted.', 'invoicing' ), array( 'status' => 500 ) );
486
		}
487
488
		/**
489
		 * Fires immediately after a single invoice is deleted or trashed via the REST API.
490
		 *
491
		 *
492
		 * @since 1.0.13
493
		 *
494
		 * @param WPInv_Invoice    $invoice  The deleted or trashed invoice.
495
		 * @param WP_REST_Request  $request  The request sent to the API.
496
		 */
497
		do_action( "wpinv_rest_delete_invoice", $invoice, $request );
498
499
		return $response;
500
501
	}
502
    
503
    
504
    /**
505
	 * Retrieves the query params for the invoices collection.
506
	 *
507
	 * @since 1.0.13
508
	 *
509
	 * @return array Collection parameters.
510
	 */
511
	public function get_collection_params() {
512
        
513
        $query_params               = array(
514
515
            // item status.
516
            'status'                => array(
517
                'default'           => $this->get_post_statuses(),
518
                'description'       => __( 'Limit result set to items assigned one or more statuses.', 'invoicing' ),
519
                'type'              => 'array',
520
                'items'             => array(
521
                    'enum'          => $this->get_post_statuses(),
522
                    'type'          => 'string',
523
                ),
524
                'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
525
            ),
526
527
            // User.
528
            'user'                  => array(
529
				'description'       => __( 'Limit result set to items for a specif user. Accepts a user ID, or comma-separated list of IDs', 'invoicing' ),
530
				'type'              => 'string',
531
			),
532
533
			// Exclude certain users
534
            'exclude_users'  	    => array(
535
                'description' 		=> __( 'Exclude items from specific users.', 'invoicing' ),
536
                'type'        		=> 'array',
537
                'items'       		=> array(
538
                    'type' => 'integer',
539
                ),
540
                'default'     		=> array(),
541
            ),
542
			
543
			// Items before.
544
            'before_date'           => array(
545
				'description'       => __( 'Limit result set to items created before a specific date. Accepts strtotime()-compatible string.', 'invoicing' ),
546
				'type'              => 'string',
547
			),
548
549
            'meta_key'           => array(
550
				'description'       => __( 'Filter items by custom field key.', 'invoicing' ),
551
				'type'              => 'string',
552
			),
553
554
            'meta_compare_key'           => array(
555
				'description'       => __( 'Comparison operator to test the `meta_key`.', 'invoicing' ),
556
				'type'              => 'string',
557
				'default'              => '=',
558
				'enum'        		=> array_map( 'trim', explode( ',', '=, !=, >, >=, <, <=, LIKE NOT, LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS, REGEXP, NOT REGEXP, RLIKE' ) ),
559
			),
560
561
            'meta_value'           => array(
562
				'description'       => __( 'Filter items by custom field value.', 'invoicing' ),
563
				'type'              => 'string',
564
			),
565
566
            'meta_compare'           => array(
567
				'description'       => __( 'Comparison operator to test the `meta_value`.', 'invoicing' ),
568
				'type'              => 'string',
569
				'default'              => '=',
570
				'enum'        		=> array_map( 'trim', explode( ',', '=, !=, >, >=, <, <=, LIKE NOT, LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS, REGEXP, NOT REGEXP, RLIKE' ) ),
571
			),
572
573
			'meta_value_num'           => array(
574
				'description'       => __( 'Filter items by a numeric custom field value.', 'invoicing' ),
575
				'type'              => 'integer',
576
			),
577
578
			// items after.
579
            'after_date'            => array(
580
				'description'       => __( 'Limit result set to items created after a specific date. Accepts strtotime()-compatible string.', 'invoicing' ),
581
				'type'              => 'string',
582
            ),
583
            
584
            // Number of results per page
585
            'limit'                 => array(
586
				'description'       => __( 'Number of items to fetch.', 'invoicing' ),
587
				'type'              => 'integer',
588
				'default'           => (int) get_option( 'posts_per_page' ),
589
            ),
590
591
            // Pagination
592
            'page'     => array(
593
				'description'       => __( 'Current page to fetch.', 'invoicing' ),
594
				'type'              => 'integer',
595
				'default'           => 1,
596
            ),
597
598
            // limit to certain items
599
            'include'  => array(
600
                'description' => __( 'Limit result set to specific IDs.', 'invoicing' ),
601
                'type'        => 'array',
602
                'items'       => array(
603
                    'type' => 'integer',
604
                ),
605
                'default'     => array(),
606
			),
607
			
608
			// Exclude certain items
609
            'exclude'  => array(
610
                'description' => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
611
                'type'        => 'array',
612
                'items'       => array(
613
                    'type' => 'integer',
614
                ),
615
                'default'     => array(),
616
            ),
617
618
            // Order items by
619
            'orderby'  		  => array(
620
                'description' => __( 'Sort retrieved items by parameter.', 'invoicing' ),
621
                'type'        => 'string',
622
                'default'     => 'date',
623
                'enum'        => array(
624
                    'date',
625
                    'id',
626
                    'modified',
627
					'title',
628
					'meta_value',
629
					'meta_value_num'
630
                ),
631
            ),
632
633
            // How to order
634
            'order'    => array(
635
                'description' => __( 'Designates ascending or descending order of ítems.', 'invoicing' ),
636
                'type'        => 'string',
637
                'default'     => 'DESC',
638
                'enum'        => array( 'ASC', 'DESC' ),
639
            ),
640
        );
641
642
		/**
643
		 * Filter collection parameters for the invoices controller.
644
		 *
645
		 *
646
		 * @since 1.0.13
647
		 *
648
		 * @param array        $query_params JSON Schema-formatted collection parameters.
649
		 */
650
		return apply_filters( "wpinv_rest_invoices_collection_params", $query_params );
651
	}
652
	
653
	/**
654
	 * Retrieves the request query params for the invoices collection.
655
	 *
656
	 * @since 1.0.15
657
	 * @param WP_REST_Request $request Full details about the request.
658
	 * @return array Request collection parameters.
659
	 */
660
	public function get_request_collection_params( $request ) {
661
		
662
		// Retrieve the list of registered invoice query parameters.
663
		$registered = $this->get_collection_params();
664
665
		// Default args
666
		$args       = array(
667
            'status'                => $this->get_post_statuses(),
668
            'user'                  => null,
669
            'exclude_users'  	    => array(),
670
			'before_date'           => null,
671
            'meta_key'              => null,
672
            'meta_compare_key'      => '=',
673
            'meta_value'            => null,
674
            'meta_compare'          => '=',
675
			'meta_value_num'        => null,
676
            'after_date'            => null,
677
            'limit'                 => (int) get_option( 'posts_per_page' ),
678
			'page'     				=> 1,
679
			'include'				=> array(),
680
            'exclude'  				=> array(),
681
            'orderby'  		  		=> 'date',
682
            'order'    				=> 'DESC',
683
		);
684
685
		// Add any params from the requests.
686
		foreach ( array_keys( $registered ) as $key ) {
687
            if ( isset( $request[ $key] ) ) {
688
                $args[ $key ] = $request[ $key];
689
            }
690
        }
691
		
692
		/**
693
		 * Filters the requests collection parameters for the invoices controller.
694
		 *
695
		 *
696
		 * @since 1.0.15
697
		 *
698
		 * @param array           $args    Request query args.
699
		 * @param WP_REST_Request $request Full details about the request.
700
		 */
701
		return apply_filters( "wpinv_rest_invoices_collection_request_params", $args, $request );
702
	}
703
	
704
	/**
705
	 * Retrieves the WP_Query params for the invoices collection.
706
	 *
707
	 * @since 1.0.15
708
	 * @param array           $args Request args.
709
	 * @param WP_REST_Request $request Full details about the request.
710
	 * @return array WP_Query parameters.
711
	 */
712
	public function get_collection_wp_query_params( $args, $request ) {
713
		
714
		// Prepare the parameters.
715
		$wp_query_args = array(
716
			'post_type'        => $this->post_type,
717
			'post_status'      => $args['status'],
718
			'author'           => $args['user'],
719
			'author__not_in'   => $args['exclude_users'],
720
			'posts_per_page'   => $args['limit'],
721
			'paged'            => $args['page'],
722
			'meta_key'         => $args['meta_key'],
723
			'meta_compare_key' => $args['meta_compare_key'],
724
			'meta_value'       => $args['meta_value'],
725
			'meta_compare'     => $args['meta_compare'],
726
			'meta_value_num'   => $args['meta_value_num'],
727
			'post__in'         => $args['include'],
728
			'post__in'         => $args['exclude'],
729
			'date_query'       => array( array() ),
730
			'fields'           => 'ids',
731
			'orderby'          => $args['orderby'],
732
			'order'            => $args['order'],
733
		);
734
735
		// Only admins can view other user's invoices.
736
		if ( ! wpinv_current_user_can_manage_invoicing() ) {
737
			$wp_query_args['author'] = get_current_user_id();
738
		}
739
740
		// No date specific params provided.
741
		if ( empty( $args['before_date'] ) && empty( $args['after_date'] ) ) {
742
			unset( $wp_query_args['date_query'] );
743
		}
744
745
		if ( ! empty( $args['before_date'] ) ) {
746
			$wp_query_args['date_query'][0]['before'] = $args['before_date'];
747
		}
748
749
		if ( ! empty( $args['after_date'] ) ) {
750
			$wp_query_args['date_query'][0]['after'] = $args['after_date'];
751
		}
752
753
		// Remove empty variables.
754
		$wp_query_args = array_filter( $wp_query_args );
755
756
		// This can be zero.
757
		if ( ! is_null( $args['meta_value_num'] ) ) {
758
			$wp_query_args['meta_value_num'] = $args['meta_value_num'];
759
		}
760
		
761
		/**
762
		 * Filters the invoices collection WP_Query parameters for the invoices controller.
763
		 *
764
		 *
765
		 * @since 1.0.15
766
		 *
767
		 * @param array           $args          Request args.
768
		 * @param array           $wp_query_args Generated WP_Query args args.
769
		 * @param WP_REST_Request $request       Full details about the request.
770
		 */
771
		return apply_filters( "wpinv_rest_invoices_collection_wp_query_params", $wp_query_args, $args, $request );
772
    }
773
    
774
    /**
775
	 * Checks if a given post type can be viewed or managed.
776
	 *
777
	 * @since 1.0.13
778
	 *
779
	 * @param object|string $post_type Post type name or object.
780
	 * @return bool Whether the post type is allowed in REST.
781
	 */
782
	protected function check_is_post_type_allowed( $post_type ) {
783
		return true;
784
	}
785
786
	/**
787
	 * Prepares a single invoice for create or update.
788
	 *
789
	 * @since 1.0.13
790
	 *
791
	 * @param WP_REST_Request $request Request object.
792
	 * @return array|WP_Error Invoice Properties or WP_Error.
793
	 */
794
	protected function prepare_item_for_database( $request ) {
795
		$prepared_invoice = new stdClass();
796
797
		// Post ID.
798
		if ( isset( $request['id'] ) ) {
799
			$existing_invoice = $this->get_post( $request['id'] );
800
			if ( is_wp_error( $existing_invoice ) ) {
801
				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...
802
			}
803
804
			$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...
805
			$prepared_invoice->invoice_id = $existing_invoice->ID;
806
		}
807
808
		$schema = $this->get_item_schema();
809
810
		// Invoice owner.
811
		if ( ! empty( $schema['properties']['user_id'] ) && isset( $request['user_id'] ) ) {
812
			$prepared_invoice->user_id = (int) $request['user_id'];
813
		}
814
815
		// Cart details.
816
		if ( ! empty( $schema['properties']['cart_details'] ) && isset( $request['cart_details'] ) ) {
817
			$prepared_invoice->cart_details = (array) $request['cart_details'];
818
		}
819
820
		// Invoice status.
821
		if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
822
823
			if ( in_array( $request['status'], $this->get_post_statuses(), true ) ) {
824
				$prepared_invoice->status = $request['status'];
825
			}
826
827
		}
828
829
		// User info
830
		if ( ! empty( $schema['properties']['user_info'] ) && isset( $request['user_info'] ) ) {
831
			$prepared_invoice->user_info = array();
832
			$user_info = (array) $request['user_info'];
833
834
			foreach( $user_info as $prop => $value ) {
835
836
				if ( ! empty( $schema['properties']['user_info']['properties'][$prop] ) ) {
837
838
					$prepared_invoice->user_info[$prop] = $value;
839
		
840
				}
841
842
			}
843
844
		}
845
846
		// IP
847
		if ( ! empty( $schema['properties']['ip'] ) && isset( $request['ip'] ) ) {
848
			$prepared_invoice->ip = $request['ip'];
849
		}
850
851
		// Payment details
852
		$prepared_invoice->payment_details = array();
853
854
		if ( ! empty( $schema['properties']['gateway'] ) && isset( $request['gateway'] ) ) {
855
			$prepared_invoice->payment_details['gateway'] = $request['gateway'];
856
		}
857
858
		if ( ! empty( $schema['properties']['gateway_title'] ) && isset( $request['gateway_title'] ) ) {
859
			$prepared_invoice->payment_details['gateway_title'] = $request['gateway_title'];
860
		}
861
862
		if ( ! empty( $schema['properties']['currency'] ) && isset( $request['currency'] ) ) {
863
			$prepared_invoice->payment_details['currency'] = $request['currency'];
864
		}
865
866
		if ( ! empty( $schema['properties']['transaction_id'] ) && isset( $request['transaction_id'] ) ) {
867
			$prepared_invoice->payment_details['transaction_id'] = $request['transaction_id'];
868
		}
869
870
		// Dates
871
		if ( ! empty( $schema['properties']['date'] ) && isset( $request['date'] ) ) {
872
			$post_date = rest_get_date_with_gmt( $request['date'] );
873
874
			if ( ! empty( $post_date ) ) {
875
				$prepared_invoice->post_date = $post_date[0];
876
			}
877
			
878
		}
879
880
		if ( ! empty( $schema['properties']['due_date'] ) && isset( $request['due_date'] ) ) {
881
			$due_date = rest_get_date_with_gmt( $request['due_date'] );
882
883
			if ( ! empty( $due_date ) ) {
884
				$prepared_invoice->due_date = $due_date[0];
885
			}
886
887
		}
888
889
		if ( ! empty( $schema['properties']['valid_until'] ) && isset( $request['valid_until'] ) ) {
890
891
			if ( ! empty( $request['valid_until'] ) ) {
892
				$prepared_invoice->valid_until = gmdate( 'Y-m-d', strtotime( $request['valid_until'] ) );
893
			} else {
894
				$prepared_invoice->valid_until = '';
895
			}
896
897
		}
898
899
		$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

899
		$invoice_data = (array) wp_unslash( /** @scrutinizer ignore-type */ $prepared_invoice );
Loading history...
900
901
		/**
902
		 * Filters an invoice before it is inserted via the REST API.
903
		 *
904
		 * @since 1.0.13
905
		 *
906
		 * @param array        $invoice_data An array of invoice data
907
		 * @param WP_REST_Request $request       Request object.
908
		 */
909
		return apply_filters( "wpinv_rest_pre_insert_invoice", $invoice_data, $request );
910
911
	}
912
913
	/**
914
	 * Prepares a single invoice output for response.
915
	 *
916
	 * @since 1.0.13
917
	 *
918
	 * @param WPInv_Invoice   $invoice    Invoice object.
919
	 * @param WP_REST_Request $request Request object.
920
	 * @return WP_REST_Response Response object.
921
	 */
922
	public function prepare_item_for_response( $invoice, $request ) {
923
924
		$GLOBALS['post'] = get_post( $invoice->ID );
925
926
		setup_postdata( $invoice->ID );
927
928
		// Fetch the fields to include in this response.
929
		$fields = $this->get_fields_for_response( $request );
930
931
		// Base fields for every invoice.
932
		$data = array();
933
934
		// Set up ID
935
		if ( rest_is_field_included( 'id', $fields ) ) {
936
			$data['id'] = $invoice->ID;
937
		}
938
939
940
		// Basic properties
941
		$invoice_properties = array(
942
			'title', 'email', 'ip', 
943
			'key', 'number', 'transaction_id', 'mode',
944
			'gateway', 'gateway_title',
945
			'total', 'discount', 'discount_code', 
946
			'tax', 'fees_total', 'subtotal', 'currency',
947
			'status', 'status_nicename', 'post_type'
948
		);
949
950
		foreach( $invoice_properties as $property ) {
951
952
			if ( rest_is_field_included( $property, $fields ) ) {
953
				$data[$property] = $invoice->get( $property );
954
			}
955
956
		}
957
958
		// Valid until
959
		if ( rest_is_field_included( 'valid_until', $fields ) && $this->post_type === 'wpi_quote' ) {
960
			$data['valid_until'] = get_post_meta( $invoice->ID, 'wpinv_quote_valid_until', true );
961
		}
962
963
		// Cart details
964
		if ( rest_is_field_included( 'cart_details', $fields ) ) {
965
			$data['cart_details'] = $invoice->get( 'cart_details' );
966
		}
967
968
		//Dates
969
		$invoice_properties = array( 'date', 'due_date', 'completed_date' );
970
971
		foreach( $invoice_properties as $property ) {
972
973
			if ( rest_is_field_included( $property, $fields ) ) {
974
				$data[$property] = $this->prepare_date_response( '0000-00-00 00:00:00', $invoice->get( $property ) );
975
			}
976
977
		}
978
979
		// User id
980
		if ( rest_is_field_included( 'user_id', $fields ) ) {
981
			$data['user_id'] = (int) $invoice->get( 'user_id' );
982
		}
983
984
		// User info
985
		$user_info = array( 'first_name', 'last_name', 'company', 'vat_number', 'vat_rate', 'address', 'city', 'country', 'state', 'zip', 'phone' );
986
987
		foreach( $user_info as $property ) {
988
989
			if ( rest_is_field_included( "user_info.$property", $fields ) ) {
990
				$data['user_info'][$property] = $invoice->get( $property );
991
			}
992
993
		}
994
995
		// Slug
996
		if ( rest_is_field_included( 'slug', $fields ) ) {
997
			$data['slug'] = $invoice->get( 'post_name' );
998
		}
999
1000
		// View invoice link
1001
		if ( rest_is_field_included( 'link', $fields ) ) {
1002
			$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

1002
			$data['link'] = esc_url( /** @scrutinizer ignore-type */ $invoice->get_view_url() );
Loading history...
1003
		}
1004
1005
1006
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1007
		$data    = $this->add_additional_fields_to_object( $data, $request );
1008
		$data    = $this->filter_response_by_context( $data, $context );
1009
1010
		// Wrap the data in a response object.
1011
		$response = rest_ensure_response( $data );
1012
1013
		$links = $this->prepare_links( $invoice );
1014
		$response->add_links( $links );
1015
1016
		if ( ! empty( $links['self']['href'] ) ) {
1017
			$actions = $this->get_available_actions( $invoice, $request );
1018
1019
			$self = $links['self']['href'];
1020
1021
			foreach ( $actions as $rel ) {
1022
				$response->add_link( $rel, $self );
1023
			}
1024
		}
1025
1026
		/**
1027
		 * Filters the invoice data for a response.
1028
		 *
1029
		 * @since 1.0.13
1030
		 *
1031
		 * @param WP_REST_Response $response The response object.
1032
		 * @param WPInv_Invoice    $invoice  The invoice object.
1033
		 * @param WP_REST_Request  $request  Request object.
1034
		 */
1035
		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...
1036
	}
1037
1038
	/**
1039
	 * Gets an array of fields to be included on the response.
1040
	 *
1041
	 * Included fields are based on item schema and `_fields=` request argument.
1042
	 *
1043
	 * @since 1.0.13
1044
	 *
1045
	 * @param WP_REST_Request $request Full details about the request.
1046
	 * @return array Fields to be included in the response.
1047
	 */
1048
	public function get_fields_for_response( $request ) {
1049
		$schema     = $this->get_item_schema();
1050
		$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
1051
1052
		$additional_fields = $this->get_additional_fields();
1053
		foreach ( $additional_fields as $field_name => $field_options ) {
1054
			// For back-compat, include any field with an empty schema
1055
			// because it won't be present in $this->get_item_schema().
1056
			if ( is_null( $field_options['schema'] ) ) {
1057
				$properties[ $field_name ] = $field_options;
1058
			}
1059
		}
1060
1061
		// Exclude fields that specify a different context than the request context.
1062
		$context = $request['context'];
1063
		if ( $context ) {
1064
			foreach ( $properties as $name => $options ) {
1065
				if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
1066
					unset( $properties[ $name ] );
1067
				}
1068
			}
1069
		}
1070
1071
		$fields = array_keys( $properties );
1072
1073
		if ( ! isset( $request['_fields'] ) ) {
1074
			return $fields;
1075
		}
1076
		$requested_fields = wpinv_parse_list( $request['_fields'] );
1077
		if ( 0 === count( $requested_fields ) ) {
1078
			return $fields;
1079
		}
1080
		// Trim off outside whitespace from the comma delimited list.
1081
		$requested_fields = array_map( 'trim', $requested_fields );
1082
		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
1083
		if ( in_array( 'id', $fields, true ) ) {
1084
			$requested_fields[] = 'id';
1085
		}
1086
		// Return the list of all requested fields which appear in the schema.
1087
		return array_reduce(
1088
			$requested_fields,
1089
			function( $response_fields, $field ) use ( $fields ) {
1090
				if ( in_array( $field, $fields, true ) ) {
1091
					$response_fields[] = $field;
1092
					return $response_fields;
1093
				}
1094
				// Check for nested fields if $field is not a direct match.
1095
				$nested_fields = explode( '.', $field );
1096
				// A nested field is included so long as its top-level property is
1097
				// present in the schema.
1098
				if ( in_array( $nested_fields[0], $fields, true ) ) {
1099
					$response_fields[] = $field;
1100
				}
1101
				return $response_fields;
1102
			},
1103
			array()
1104
		);
1105
	}
1106
1107
	/**
1108
	 * Retrieves the invoice's schema, conforming to JSON Schema.
1109
	 *
1110
	 * @since 1.0.13
1111
	 *
1112
	 * @return array Invoice schema data.
1113
	 */
1114
	public function get_item_schema() {
1115
1116
		// Maybe retrieve the schema from cache.
1117
		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...
1118
			return $this->add_additional_fields_schema( $this->schema );
1119
		}
1120
1121
		$schema = array(
1122
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
1123
			'title'      => $this->post_type,
1124
			'type'       => 'object',
1125
1126
			// Base properties for every Invoice.
1127
			'properties' 		  => array(
1128
1129
				'title'			  => array(
1130
					'description' => __( 'The title for the invoice.', 'invoicing' ),
1131
					'type'        => 'string',
1132
					'context'     => array( 'view', 'edit', 'embed' ),
1133
					'readonly'    => true,
1134
				),
1135
1136
				'user_id'		  => array(
1137
					'description' => __( 'The ID of the owner of the invoice.', 'invoicing' ),
1138
					'type'        => 'integer',
1139
					'context'     => array( 'view', 'edit', 'embed' ),
1140
				),
1141
1142
				'email'		  	  => array(
1143
					'description' => __( 'The email of the owner of the invoice.', 'invoicing' ),
1144
					'type'        => 'string',
1145
					'context'     => array( 'view', 'edit', 'embed' ),
1146
					'readonly'    => true,
1147
				),
1148
1149
				'ip'			  => array(
1150
					'description' => __( 'The IP of the owner of the invoice.', 'invoicing' ),
1151
					'type'        => 'string',
1152
					'context'     => array( 'view', 'edit', 'embed' ),
1153
				),
1154
1155
				'user_info'       => array(
1156
					'description' => __( 'Information about the owner of the invoice.', 'invoicing' ),
1157
					'type'        => 'object',
1158
					'context'     => array( 'view', 'edit', 'embed' ),
1159
					'properties'  => array(
1160
1161
						'first_name'      => array(
1162
							'description' => __( 'The first name of the owner of the invoice.', 'invoicing' ),
1163
							'type'        => 'string',
1164
							'context'     => array( 'view', 'edit', 'embed' ),
1165
						),
1166
1167
						'last_name'       => array(
1168
							'description' => __( 'The last name of the owner of the invoice.', 'invoicing' ),
1169
							'type'        => 'string',
1170
							'context'     => array( 'view', 'edit', 'embed' ),
1171
						),
1172
1173
						'company'         => array(
1174
							'description' => __( 'The company of the owner of the invoice.', 'invoicing' ),
1175
							'type'        => 'string',
1176
							'context'     => array( 'view', 'edit', 'embed' ),
1177
						),
1178
1179
						'vat_number'      => array(
1180
							'description' => __( 'The VAT number of the owner of the invoice.', 'invoicing' ),
1181
							'type'        => 'string',
1182
							'context'     => array( 'view', 'edit', 'embed' ),
1183
						),
1184
1185
						'vat_rate'        => array(
1186
							'description' => __( 'The VAT rate applied on the invoice.', 'invoicing' ),
1187
							'type'        => 'string',
1188
							'context'     => array( 'view', 'edit', 'embed' ),
1189
						),
1190
1191
						'address'        => array(
1192
							'description' => __( 'The address of the invoice owner.', 'invoicing' ),
1193
							'type'        => 'string',
1194
							'context'     => array( 'view', 'edit', 'embed' ),
1195
						),
1196
1197
						'city'            => array(
1198
							'description' => __( 'The city of the invoice owner.', 'invoicing' ),
1199
							'type'        => 'string',
1200
							'context'     => array( 'view', 'edit', 'embed' ),
1201
						),
1202
1203
						'country'         => array(
1204
							'description' => __( 'The country of the invoice owner.', 'invoicing' ),
1205
							'type'        => 'string',
1206
							'context'     => array( 'view', 'edit', 'embed' ),
1207
						),
1208
1209
						'state'           => array(
1210
							'description' => __( 'The state of the invoice owner.', 'invoicing' ),
1211
							'type'        => 'string',
1212
							'context'     => array( 'view', 'edit', 'embed' ),
1213
						),
1214
1215
						'zip'             => array(
1216
							'description' => __( 'The zip code of the invoice owner.', 'invoicing' ),
1217
							'type'        => 'string',
1218
							'context'     => array( 'view', 'edit', 'embed' ),
1219
						),
1220
1221
						'phone'             => array(
1222
							'description' => __( 'The phone number of the invoice owner.', 'invoicing' ),
1223
							'type'        => 'string',
1224
							'context'     => array( 'view', 'edit', 'embed' ),
1225
						),
1226
					),
1227
				),
1228
1229
				'id'           => array(
1230
					'description' => __( 'Unique identifier for the invoice.', 'invoicing' ),
1231
					'type'        => 'integer',
1232
					'context'     => array( 'view', 'edit', 'embed' ),
1233
					'readonly'    => true,
1234
				),
1235
1236
				'key'			  => array(
1237
					'description' => __( 'A unique key for the invoice.', 'invoicing' ),
1238
					'type'        => 'string',
1239
					'context'     => array( 'view', 'edit', 'embed' ),
1240
					'readonly'    => true,
1241
				),
1242
1243
				'number'		  => array(
1244
					'description' => __( 'The invoice number.', 'invoicing' ),
1245
					'type'        => 'string',
1246
					'context'     => array( 'view', 'edit', 'embed' ),
1247
					'readonly'    => true,
1248
				),
1249
1250
				'transaction_id'  => array(
1251
					'description' => __( 'The transaction id of the invoice.', 'invoicing' ),
1252
					'type'        => 'string',
1253
					'context'     => array( 'view', 'edit', 'embed' ),
1254
				),
1255
1256
				'gateway'		  => array(
1257
					'description' => __( 'The gateway used to process the invoice.', 'invoicing' ),
1258
					'type'        => 'string',
1259
					'context'     => array( 'view', 'edit', 'embed' ),
1260
				),
1261
1262
				'gateway_title'	  => array(
1263
					'description' => __( 'The title of the gateway used to process the invoice.', 'invoicing' ),
1264
					'type'        => 'string',
1265
					'context'     => array( 'view', 'edit', 'embed' ),
1266
				),
1267
1268
				'total'	  		  => array(
1269
					'description' => __( 'The total amount of the invoice.', 'invoicing' ),
1270
					'type'        => 'number',
1271
					'context'     => array( 'view', 'edit', 'embed' ),
1272
					'readonly'    => true,
1273
				),
1274
1275
				'discount'		  => array(
1276
					'description' => __( 'The discount applied to the invoice.', 'invoicing' ),
1277
					'type'        => 'number',
1278
					'context'     => array( 'view', 'edit', 'embed' ),
1279
					'readonly'    => true,
1280
				),
1281
1282
				'discount_code'	  => array(
1283
					'description' => __( 'The discount code applied to the invoice.', 'invoicing' ),
1284
					'type'        => 'string',
1285
					'context'     => array( 'view', 'edit', 'embed' ),
1286
					'readonly'    => true,
1287
				),
1288
1289
				'tax'	  		  => array(
1290
					'description' => __( 'The tax applied to the invoice.', 'invoicing' ),
1291
					'type'        => 'number',
1292
					'context'     => array( 'view', 'edit', 'embed' ),
1293
					'readonly'    => true,
1294
				),
1295
1296
				'fees_total'	  => array(
1297
					'description' => __( 'The total fees applied to the invoice.', 'invoicing' ),
1298
					'type'        => 'number',
1299
					'context'     => array( 'view', 'edit', 'embed' ),
1300
					'readonly'    => true,
1301
				),
1302
1303
				'subtotal'	  	  => array(
1304
					'description' => __( 'The sub-total for the invoice.', 'invoicing' ),
1305
					'type'        => 'number',
1306
					'context'     => array( 'view', 'edit', 'embed' ),
1307
					'readonly'    => true,
1308
				),
1309
1310
				'currency'	  	  => array(
1311
					'description' => __( 'The currency used to process the invoice.', 'invoicing' ),
1312
					'type'        => 'string',
1313
					'context'     => array( 'view', 'edit', 'embed' ),
1314
				),
1315
1316
				'cart_details'	  => array(
1317
					'description' => __( 'The cart details for invoice.', 'invoicing' ),
1318
					'type'        => 'array',
1319
					'context'     => array( 'view', 'edit', 'embed' ),
1320
					'required'	  => true,
1321
				),
1322
1323
				'date'         => array(
1324
					'description' => __( "The date the invoice was published, in the site's timezone.", 'invoicing' ),
1325
					'type'        => array( 'string', 'null' ),
1326
					'format'      => 'date-time',
1327
					'context'     => array( 'view', 'edit', 'embed' ),
1328
				),
1329
1330
				'due_date'     => array(
1331
					'description' => __( 'The due date for the invoice.', 'invoicing' ),
1332
					'type'        => array( 'string', 'null' ),
1333
					'format'      => 'date-time',
1334
					'context'     => array( 'view', 'edit', 'embed' ),
1335
				),
1336
1337
				'completed_date'  => array(
1338
					'description' => __( 'The completed date for the invoice.', 'invoicing' ),
1339
					'type'        => array( 'string', 'null' ),
1340
					'format'      => 'date-time',
1341
					'context'     => array( 'view', 'edit', 'embed' ),
1342
					'readonly'    => true,
1343
				),
1344
				
1345
				'link'         => array(
1346
					'description' => __( 'URL to the invoice.', 'invoicing' ),
1347
					'type'        => 'string',
1348
					'format'      => 'uri',
1349
					'context'     => array( 'view', 'edit', 'embed' ),
1350
					'readonly'    => true,
1351
				),
1352
1353
				'mode'       	  => array(
1354
					'description' => __( 'The mode used to process the invoice.', 'invoicing' ),
1355
					'type'        => 'string',
1356
					'enum'        => array( 'live', 'test' ),
1357
					'context'     => array( 'view', 'edit', 'embed' ),
1358
					'readonly'    => true,
1359
				),
1360
1361
				'slug'       	  => array(
1362
					'description' => __( 'An alphanumeric identifier for the invoice.', 'invoicing' ),
1363
					'type'        => 'string',
1364
					'context'     => array( 'view', 'edit', 'embed' ),
1365
					'arg_options' => array(
1366
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
1367
					),
1368
					'readonly'    => true,
1369
				),
1370
1371
				'status'       	  => array(
1372
					'description' => __( 'A named status for the invoice.', 'invoicing' ),
1373
					'type'        => 'string',
1374
					'enum'        => $this->get_post_statuses(),
1375
					'context'     => array( 'view', 'edit' ),
1376
					'default'	  => 'wpi-pending',
1377
				),
1378
1379
				'status_nicename' => array(
1380
					'description' => __( 'A human-readable status name for the invoice.', 'invoicing' ),
1381
					'type'        => 'string',
1382
					'context'     => array( 'view', 'embed' ),
1383
					'readonly'    => true,
1384
				),
1385
1386
				'post_type'       => array(
1387
					'description' => __( 'The post type for the invoice.', 'invoicing' ),
1388
					'type'        => 'string',
1389
					'context'     => array( 'view' ),
1390
					'readonly'    => true,
1391
				),
1392
			),
1393
		);
1394
1395
		// Add helpful links to the invoice schem.
1396
		$schema['links'] = $this->get_schema_links();
1397
1398
		/**
1399
		 * Filters the invoice schema for the REST API.
1400
		 *
1401
		 * Enables adding extra properties to invoices.
1402
		 *
1403
		 * @since 1.0.13
1404
		 *
1405
		 * @param array   $schema    The invoice schema.
1406
		 */
1407
        $schema = apply_filters( "wpinv_rest_invoice_schema", $schema );
1408
1409
		// Cache the invoice schema.
1410
		$this->schema = $schema;
1411
		
1412
		return $this->add_additional_fields_schema( $this->schema );
1413
	}
1414
1415
	/**
1416
	 * Retrieve Link Description Objects that should be added to the Schema for the invoices collection.
1417
	 *
1418
	 * @since 1.0.13
1419
	 *
1420
	 * @return array
1421
	 */
1422
	protected function get_schema_links() {
1423
1424
		$href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
1425
1426
		$links = array();
1427
1428
		$links[] = array(
1429
			'rel'          => 'https://api.w.org/action-publish',
1430
			'title'        => __( 'The current user can mark this invoice as completed.', 'invoicing' ),
1431
			'href'         => $href,
1432
			'targetSchema' => array(
1433
				'type'       => 'object',
1434
				'properties' => array(
1435
					'status' => array(
1436
						'type' => 'string',
1437
						'enum' => array( 'publish', 'wpi-renewal' ),
1438
					),
1439
				),
1440
			),
1441
		);
1442
1443
		$links[] = array(
1444
			'rel'          => 'https://api.w.org/action-assign-author',
1445
			'title'        => __( 'The current user can change the owner of this invoice.', 'invoicing' ),
1446
			'href'         => $href,
1447
			'targetSchema' => array(
1448
				'type'       => 'object',
1449
				'properties'   => array(
1450
					'user_id'  => array(
1451
						'type' => 'integer',
1452
					),
1453
				),
1454
			),
1455
		);
1456
1457
		return $links;
1458
	}
1459
1460
	/**
1461
	 * Prepares links for the request.
1462
	 *
1463
	 * @since 1.0.13
1464
	 *
1465
	 * @param WPInv_Invoice $invoice Invoice Object.
1466
	 * @return array Links for the given invoice.
1467
	 */
1468
	protected function prepare_links( $invoice ) {
1469
1470
		// Prepare the base REST API endpoint for invoices.
1471
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
1472
1473
		// Entity meta.
1474
		$links = array(
1475
			'self'       => array(
1476
				'href' => rest_url( trailingslashit( $base ) . $invoice->ID ),
1477
			),
1478
			'collection' => array(
1479
				'href' => rest_url( $base ),
1480
			),
1481
		);
1482
1483
		if ( ! empty( $invoice->user_id ) ) {
1484
			$links['user'] = array(
1485
				'href'       => rest_url( 'wp/v2/users/' . $invoice->user_id ),
1486
				'embeddable' => true,
1487
			);
1488
		}
1489
1490
		/**
1491
		 * Filters the returned invoice links for the REST API.
1492
		 *
1493
		 * Enables adding extra links to invoice API responses.
1494
		 *
1495
		 * @since 1.0.13
1496
		 *
1497
		 * @param array   $links    Rest links.
1498
		 */
1499
		return apply_filters( "wpinv_rest_invoice_links", $links );
1500
1501
	}
1502
1503
	/**
1504
	 * Get the link relations available for the post and current user.
1505
	 *
1506
	 * @since 1.0.13
1507
	 *
1508
	 * @param WPInv_Invoice   $invoice    Invoice object.
1509
	 * @param WP_REST_Request $request Request object.
1510
	 * @return array List of link relations.
1511
	 */
1512
	protected function get_available_actions( $invoice, $request ) {
1513
1514
		if ( 'edit' !== $request['context'] ) {
1515
			return array();
1516
		}
1517
1518
		$rels = array();
1519
1520
		// Retrieve the post type object.
1521
		$post_type = get_post_type_object( $invoice->post_type );
1522
1523
		// Mark invoice as completed.
1524
		if ( current_user_can( $post_type->cap->publish_posts ) ) {
1525
			$rels[] = 'https://api.w.org/action-publish';
1526
		}
1527
1528
		// Change the owner of the invoice.
1529
		if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
1530
			$rels[] = 'https://api.w.org/action-assign-author';
1531
		}
1532
1533
		/**
1534
		 * Filters the available invoice link relations for the REST API.
1535
		 *
1536
		 * Enables adding extra link relation for the current user and request to invoice responses.
1537
		 *
1538
		 * @since 1.0.13
1539
		 *
1540
		 * @param array   $rels    Available link relations.
1541
		 */
1542
		return apply_filters( "wpinv_rest_invoice_link_relations", $rels );
1543
	}
1544
1545
	/**
1546
	 * Sanitizes and validates the list of post statuses.
1547
	 *
1548
	 * @since 1.0.13
1549
	 *
1550
	 * @param string|array    $statuses  One or more post statuses.
1551
	 * @param WP_REST_Request $request   Full details about the request.
1552
	 * @param string          $parameter Additional parameter to pass to validation.
1553
	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
1554
	 */
1555
	public function sanitize_post_statuses( $statuses, $request, $parameter ) {
1556
		return array_intersect( wp_parse_slug_list( $statuses ), $this->get_post_statuses() );
1557
	}
1558
1559
	/**
1560
	 * Retrieves a valid list of post statuses.
1561
	 *
1562
	 * @since 1.0.15
1563
	 *
1564
	 * @return array A list of registered item statuses.
1565
	 */
1566
	public function get_post_statuses() {
1567
		return array_keys( wpinv_get_invoice_statuses( true, true ) );
1568
	}
1569
    
1570
}