Passed
Pull Request — master (#240)
by
unknown
03:32
created

WPInv_REST_Invoice_Controller::get_post()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
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
	 * Constructor.
31
	 *
32
	 * @since 1.0.13
33
	 *
34
	 * @param string $namespace Api Namespace
35
	 */
36
	public function __construct( $namespace ) {
37
        
38
        // Set api namespace...
39
		$this->namespace = $namespace;
40
41
        // ... and the rest base
42
        $this->rest_base = 'invoices';
43
		
44
    }
45
    
46
    /**
47
	 * Checks if a given request has access to read invoices.
48
     * 
49
	 *
50
	 * @since 1.0.13
51
	 *
52
	 * @param WP_REST_Request $request Full details about the request.
53
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
54
	 */
55
	public function get_items_permissions_check( $request ) {
56
	
57
        $post_type = get_post_type_object( $this->post_type );
58
59 View Code Duplication
		if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
60
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit invoices.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
61
		}
62
63
		// Read checks will be evaluated on a per invoice basis
64
65
		return true;
66
67
    }
68
    
69
    /**
70
	 * Retrieves a collection of invoices.
71
	 *
72
	 * @since 1.0.13
73
	 *
74
	 * @param WP_REST_Request $request Full details about the request.
75
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
76
	 */
77
	public function get_items( $request ) {
78
		
79
		// Retrieve the list of registered invoice query parameters.
80
        $registered = $this->get_collection_params();
81
        
82
        $args       = array();
83
84
        foreach( array_keys( $registered ) as $key ) {
85
86
            if( isset( $request[ $key] ) ) {
87
                $args[ $key ] = $request[ $key];
88
            }
89
90
        }
91
92
		/**
93
		 * Filters the wpinv_get_invoices arguments for invoices requests.
94
		 *
95
		 *
96
		 * @since 1.0.13
97
		 *
98
		 *
99
		 * @param array           $args    Key value array of query var to query value.
100
		 * @param WP_REST_Request $request The request used.
101
		 */
102
        $args       = apply_filters( "wpinv_rest_get_invoices_arguments", $args, $request, $this );
103
		
104
		// Special args
105
		$args[ 'return' ]   = 'objects';
106
		$args[ 'paginate' ] = true;
107
108
        // Run the query.
109
		$query = wpinv_get_invoices( $args );
110
		
111
		// Prepare the retrieved invoices
112
		$invoices = array();
113
		foreach( $query->invoices as $invoice ) {
114
115
			if ( ! $this->check_read_permission( $invoice ) ) {
116
				continue;
117
			}
118
119
			$data       = $this->prepare_item_for_response( $invoice, $request );
120
			$invoices[] = $this->prepare_response_for_collection( $data );
121
122
		}
123
124
		// Prepare the response.
125
		$response = rest_ensure_response( $invoices );
126
		$response->header( 'X-WP-Total', (int) $query->total );
127
		$response->header( 'X-WP-TotalPages', (int) $query->max_num_pages );
128
129
		/**
130
		 * Filters the responses for invoices requests.
131
		 *
132
		 *
133
		 * @since 1.0.13
134
		 *
135
		 *
136
		 * @param arrWP_REST_Response $response    Response object.
137
		 * @param WP_REST_Request     $request The request used.
138
         * @param array               $args Array of args used to retrieve the invoices
139
		 */
140
        $response       = apply_filters( "rest_wpinv_invoices_response", $response, $request, $args );
141
142
        return rest_ensure_response( $response );
143
        
144
    }
145
146
    /**
147
	 * Get the post, if the ID is valid.
148
	 *
149
	 * @since 1.0.13
150
	 *
151
	 * @param int $invoice_id Supplied ID.
152
	 * @return WPInv_Invoice|WP_Error Invoice object if ID is valid, WP_Error otherwise.
153
	 */
154
	protected function get_post( $invoice_id ) {
155
		
156
		$error     = new WP_Error( 'rest_invoice_invalid_id', __( 'Invalid invoice ID.', 'invoicing' ), array( 'status' => 404 ) );
157
158
        // Ids start from 1
159
        if ( (int) $invoice_id <= 0 ) {
160
			return $error;
161
		}
162
163
		$invoice = wpinv_get_invoice( (int) $invoice_id );
164
		if ( empty( $invoice ) ) {
165
			return $error;
166
        }
167
168
        return $invoice;
169
170
    }
171
172
    /**
173
	 * Checks if a given request has access to read an invoice.
174
	 *
175
	 * @since 1.0.13
176
	 *
177
	 * @param WP_REST_Request $request Full details about the request.
178
	 * @return bool|WP_Error True if the request has read access for the invoice, WP_Error object otherwise.
179
	 */
180
	public function get_item_permissions_check( $request ) {
181
182
        // Retrieve the invoice object.
183
        $invoice = $this->get_post( $request['id'] );
184
        
185
        // Ensure it is valid.
186
		if ( is_wp_error( $invoice ) ) {
187
			return $invoice;
188
		}
189
190
		if ( $invoice ) {
191
			return $this->check_read_permission( $invoice );
192
		}
193
194
		return true;
195
    }
196
    
197
    /**
198
	 * Checks if an invoice can be read.
199
	 * 
200
	 * An invoice can be read by site admins and owners of the invoice
201
	 *
202
	 *
203
	 * @since 1.0.13
204
	 *
205
	 * @param WPInv_Invoice $invoice WPInv_Invoice object.
206
	 * @return bool Whether the post can be read.
207
	 */
208
	public function check_read_permission( $invoice ) {
209
210
		// An invoice can be read by an admin...
211
		if ( current_user_can( 'manage_options' ) ||  current_user_can( 'view_invoices' ) ) {
212
			return true;
213
		}
214
215
        // ... and the owner of the invoice
216
		if( get_current_user_id() ===(int) $invoice->get_user_id() ) {
217
			return true;
218
		}
219
220
		return false;
221
    }
222
    
223
    /**
224
	 * Retrieves a single invoice.
225
	 *
226
	 * @since 1.0.13
227
	 *
228
	 * @param WP_REST_Request $request Full details about the request.
229
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
230
	 */
231
	public function get_item( $request ) {
232
233
        // Fetch the invoice.
234
        $invoice = $this->get_post( $request['id'] );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get_post($request['id']); of type WPInv_Invoice|WP_Error adds the type WPInv_Invoice to the return on line 238 which is incompatible with the return type documented by WPInv_REST_Invoice_Controller::get_item of type WP_REST_Response|WP_Error.
Loading history...
235
        
236
        // Abort early if it does not exist
237
		if ( is_wp_error( $invoice ) ) {
238
			return $invoice;
239
		}
240
241
		// Prepare the response
242
		$response = $this->prepare_item_for_response( $invoice, $request );
243
		$response->link_header( 'alternate', esc_url( $invoice->get_view_url() ), array( 'type' => 'text/html' ) );
244
245
		/**
246
		 * Filters the responses for single invoice requests.
247
		 *
248
		 *
249
		 * @since 1.0.13
250
		 * @var WP_HTTP_Response
251
		 *
252
		 * @param WP_HTTP_Response $response Response.
253
		 * @param WP_REST_Request  $request The request used.
254
		 */
255
        $response       = apply_filters( "rest_wpinv_get_invoice_response", $response, $request );
256
257
        return rest_ensure_response( $response );
258
259
    }
260
    
261
    /**
262
	 * Checks if a given request has access to create an invoice.
263
	 *
264
	 * @since 1.0.13
265
	 *
266
	 * @param WP_REST_Request $request Full details about the request.
267
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
268
	 */
269
	public function create_item_permissions_check( $request ) {
270
	
271 View Code Duplication
		if ( ! empty( $request['id'] ) ) {
272
			return new WP_Error( 'rest_invoice_exists', __( 'Cannot create existing invoice.', 'invoicing' ), array( 'status' => 400 ) );
273
		}
274
275
		$post_type = get_post_type_object( $this->post_type );
276
277 View Code Duplication
		if ( ! current_user_can( $post_type->cap->create_posts ) ) {
278
			return new WP_Error( 
279
                'rest_cannot_create', 
280
                __( 'Sorry, you are not allowed to create invoices as this user.', 'invoicing' ), 
281
                array( 
282
                    'status' => rest_authorization_required_code(),
283
                )
284
            );
285
        }
286
287
		return true;
288
    }
289
    
290
    /**
291
	 * Creates a single invoice.
292
	 *
293
	 * @since 1.0.13
294
	 *
295
	 * @param WP_REST_Request $request Full details about the request.
296
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
297
	 */
298
	public function create_item( $request ) {
299
300 View Code Duplication
		if ( ! empty( $request['id'] ) ) {
301
			return new WP_Error( 'rest_invoice_exists', __( 'Cannot create existing invoice.', 'invoicing' ), array( 'status' => 400 ) );
302
		}
303
304
		$request->set_param( 'context', 'edit' );
305
306
		// Prepare the updated data.
307
		$invoice_data = wp_unslash( $this->prepare_item_for_database( $request ) );
308
309
		if ( is_wp_error( $invoice_data ) ) {
310
			return $invoice_data;
311
		}
312
313
		// Try creating the invoice
314
        $invoice = wpinv_insert_invoice( $invoice_data, true );
315
316
		if ( is_wp_error( $invoice ) ) {
317
            return $invoice;
318
		}
319
320
		// Prepare the response
321
		$response = $this->prepare_item_for_response( $invoice, $request );
322
323
		/**
324
		 * Fires after a single invoice is created or updated via the REST API.
325
		 *
326
		 * @since 1.0.13
327
		 *
328
		 * @param WPinv_Invoice   $invoice  Inserted or updated invoice object.
329
		 * @param WP_REST_Request $request  Request object.
330
		 * @param bool            $creating True when creating a post, false when updating.
331
		 */
332
		do_action( "wpinv_rest_insert_invoice", $invoice, $request, true );
333
334
		/**
335
		 * Filters the responses for creating single invoice requests.
336
		 *
337
		 *
338
		 * @since 1.0.13
339
		 *
340
		 *
341
		 * @param array           $invoice_data Invoice properties.
342
		 * @param WP_REST_Request $request The request used.
343
		 */
344
        $response       = apply_filters( "rest_wpinv_create_invoice_response", $response, $request );
345
346
        return rest_ensure_response( $response );
347
	}
348
349
	/**
350
	 * Checks if a given request has access to update an invoice.
351
	 *
352
	 * @since 1.0.13
353
	 *
354
	 * @param WP_REST_Request $request Full details about the request.
355
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
356
	 */
357
	public function update_item_permissions_check( $request ) {
358
359
		// Retrieve the invoice.
360
		$invoice = $this->get_post( $request['id'] );
361
		if ( is_wp_error( $invoice ) ) {
362
			return $invoice;
363
		}
364
365
		$post_type = get_post_type_object( $this->post_type );
366
367 View Code Duplication
		if ( ! current_user_can(  $post_type->cap->edit_post, $invoice->ID  ) ) {
368
			return new WP_Error( 
369
                'rest_cannot_edit', 
370
                __( 'Sorry, you are not allowed to update this invoice.', 'invoicing' ), 
371
                array( 
372
                    'status' => rest_authorization_required_code(),
373
                )
374
            );
375
        }
376
377
		return true;
378
	}
379
380
	/**
381
	 * Updates a single invoice.
382
	 *
383
	 * @since 1.0.13
384
	 *
385
	 * @param WP_REST_Request $request Full details about the request.
386
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
387
	 */
388
	public function update_item( $request ) {
389
		
390
		// Ensure the invoice exists.
391
        $valid_check = $this->get_post( $request['id'] );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get_post($request['id']); of type WP_Error|WPInv_Invoice adds the type WPInv_Invoice to the return on line 395 which is incompatible with the return type documented by WPInv_REST_Invoice_Controller::update_item of type WP_REST_Response|WP_Error.
Loading history...
392
        
393
        // Abort early if it does not exist
394
		if ( is_wp_error( $valid_check ) ) {
395
			return $valid_check;
396
		}
397
398
		$request->set_param( 'context', 'edit' );
399
400
		// Prepare the updated data.
401
		$data_to_update = wp_unslash( $this->prepare_item_for_database( $request ) );
402
403
		if ( is_wp_error( $data_to_update ) ) {
404
			return $data_to_update;
405
		}
406
407
		// Abort if no invoice data is provided
408
        if( empty( $data_to_update ) ) {
409
            return new WP_Error( 'missing_data', __( 'An update request cannot be empty.', 'invoicing' ) );
410
        }
411
412
		// Include the invoice ID
413
		$data_to_update['ID'] = $request['id'];
414
415
		// Update the invoice
416
		$updated_invoice = wpinv_update_invoice( $data_to_update, true );
417
418
		// Incase the update operation failed...
419
		if ( is_wp_error( $updated_invoice ) ) {
420
			return $updated_invoice;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $updated_invoice; (WP_Error|integer|WPInv_Invoice) is incompatible with the return type documented by WPInv_REST_Invoice_Controller::update_item of type WP_REST_Response|WP_Error.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
421
		}
422
423
		// Prepare the response
424
		$response = $this->prepare_item_for_response( $updated_invoice, $request );
425
426
		/** This action is documented in includes/class-wpinv-rest-invoice-controller.php */
427
		do_action( "wpinv_rest_insert_invoice", $updated_invoice, $request, false );
428
429
		/**
430
		 * Filters the responses for updating single invoice requests.
431
		 *
432
		 *
433
		 * @since 1.0.13
434
		 *
435
		 *
436
		 * @param array           $invoice_data Invoice properties.
437
		 * @param WP_REST_Request $request The request used.
438
		 */
439
        $response       = apply_filters( "wpinv_rest_update_invoice_response", $response, $request );
440
441
        return rest_ensure_response( $response );
442
	}
443
444
	/**
445
	 * Checks if a given request has access to delete an invoice.
446
	 *
447
	 * @since 1.0.13
448
	 *
449
	 * @param WP_REST_Request $request Full details about the request.
450
	 * @return true|WP_Error True if the request has access to delete the invoice, WP_Error object otherwise.
451
	 */
452
	public function delete_item_permissions_check( $request ) {
453
454
		// Retrieve the invoice.
455
		$invoice = $this->get_post( $request['id'] );
456
		if ( is_wp_error( $invoice ) ) {
457
			return $invoice;
458
		}
459
460
		// Ensure the current user can delete invoices
461
		if ( current_user_can( 'manage_options' ) ||  current_user_can( 'delete_invoices' ) ) {
462
			return new WP_Error( 
463
                'rest_cannot_delete', 
464
                __( 'Sorry, you are not allowed to delete this invoice.', 'invoicing' ), 
465
                array( 
466
                    'status' => rest_authorization_required_code(),
467
                )
468
            );
469
		}
470
471
		return true;
472
	}
473
474
	/**
475
	 * Deletes a single invoice.
476
	 *
477
	 * @since 1.0.13
478
	 *
479
	 * @param WP_REST_Request $request Full details about the request.
480
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
481
	 */
482
	public function delete_item( $request ) {
483
		
484
		// Retrieve the invoice.
485
		$invoice = $this->get_post( $request['id'] );
486
		if ( is_wp_error( $invoice ) ) {
487
			return $invoice;
488
		}
489
490
		$request->set_param( 'context', 'edit' );
491
492
		// Prepare the invoice id
493
		$id    = $invoice->ID;
494
495
		// Prepare the response
496
		$response = $this->prepare_item_for_response( $invoice, $request );
497
498
		// Check if the user wants to bypass the trash...
499
		$force_delete = (bool) $request['force'];
500
501
		// Try deleting the invoice.
502
		$deleted = wp_delete_post( $id, $force_delete );
503
504
		// Abort early if we can't delete the invoice.
505
		if ( ! $deleted ) {
506
			return new WP_Error( 'rest_cannot_delete', __( 'The invoice cannot be deleted.', 'invoicing' ), array( 'status' => 500 ) );
507
		}
508
509
		/**
510
		 * Fires immediately after a single invoice is deleted or trashed via the REST API.
511
		 *
512
		 *
513
		 * @since 1.0.13
514
		 *
515
		 * @param WPInv_Invoice    $invoice  The deleted or trashed invoice.
516
		 * @param WP_REST_Request  $request  The request sent to the API.
517
		 */
518
		do_action( "wpinv_rest_delete_invoice", $invoice, $request );
519
520
		return $response;
521
522
	}
523
    
524
    
525
    /**
526
	 * Retrieves the query params for the invoices collection.
527
	 *
528
	 * @since 1.0.13
529
	 *
530
	 * @return array Collection parameters.
531
	 */
532
	public function get_collection_params() {
533
        
534
        $query_params               = array(
535
536
            // Invoice status.
537
            'status'                => array(
538
                'default'           => 'publish',
539
                'description'       => __( 'Limit result set to invoices assigned one or more statuses.', 'invoicing' ),
540
                'type'              => 'array',
541
                'items'             => array(
542
                    'enum'          => array_keys( wpinv_get_invoice_statuses( true, true ) ),
543
                    'type'          => 'string',
544
                ),
545
                'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
546
            ),
547
548
            // User.
549
            'user'                  => array(
550
				'description'       => __( 'Limit result set to invoices for a specif user.', 'invoicing' ),
551
				'type'              => 'integer',
552
            ),
553
            
554
            // Number of results per page
555
            'limit'                 => array(
556
				'description'       => __( 'Number of invoices to fetch.', 'invoicing' ),
557
				'type'              => 'integer',
558
				'default'           => (int) get_option( 'posts_per_page' ),
559
            ),
560
561
            // Pagination
562
            'page'     => array(
563
				'description'       => __( 'Current page to fetch.', 'invoicing' ),
564
				'type'              => 'integer',
565
				'default'           => 1,
566
            ),
567
568
            // Exclude certain invoices
569
            'exclude'  => array(
570
                'description' => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
571
                'type'        => 'array',
572
                'items'       => array(
573
                    'type' => 'integer',
574
                ),
575
                'default'     => array(),
576
            ),
577
578
            // Order invoices by
579
            'orderby'  => array(
580
                'description' => __( 'Sort invoices by object attribute.', 'invoicing' ),
581
                'type'        => 'string',
582
                'default'     => 'date',
583
                'enum'        => array(
584
                    'author',
585
                    'date',
586
                    'id',
587
                    'modified',
588
                    'title',
589
                ),
590
            ),
591
592
            // How to order
593
            'order'    => array(
594
                'description' => __( 'Order sort attribute ascending or descending.', 'invoicing' ),
595
                'type'        => 'string',
596
                'default'     => 'DESC',
597
                'enum'        => array( 'ASC', 'DESC' ),
598
            ),
599
        );
600
601
		/**
602
		 * Filter collection parameters for the invoices controller.
603
		 *
604
		 *
605
		 * @since 1.0.13
606
		 *
607
		 * @param array        $query_params JSON Schema-formatted collection parameters.
608
		 * @param WP_Post_Type $post_type    Post type object.
609
		 */
610
		return apply_filters( "rest_invoices_collection_params", $query_params, 'wpi-invoice' );
611
    }
612
    
613
    /**
614
	 * Checks if a given post type can be viewed or managed.
615
	 *
616
	 * @since 1.0.13
617
	 *
618
	 * @param object|string $post_type Post type name or object.
619
	 * @return bool Whether the post type is allowed in REST.
620
	 */
621
	protected function check_is_post_type_allowed( $post_type ) {
622
		return true;
623
	}
624
625
	/**
626
	 * Prepares a single invoice for create or update.
627
	 *
628
	 * @since 1.0.13
629
	 *
630
	 * @param WP_REST_Request $request Request object.
631
	 * @return array|WP_Error Invoice Properties or WP_Error.
632
	 */
633
	protected function prepare_item_for_database( $request ) {
634
		$prepared_invoice = new stdClass();
635
636
		// Post ID.
637
		if ( isset( $request['id'] ) ) {
638
			$existing_invoice = $this->get_post( $request['id'] );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get_post($request['id']); of type WPInv_Invoice|WP_Error adds the type WPInv_Invoice to the return on line 640 which is incompatible with the return type documented by WPInv_REST_Invoice_Contr...epare_item_for_database of type array|WP_Error.
Loading history...
639
			if ( is_wp_error( $existing_invoice ) ) {
640
				return $existing_invoice;
641
			}
642
643
			$prepared_invoice->ID 		  = $existing_invoice->ID;
644
			$prepared_invoice->invoice_id = $existing_invoice->ID;
645
		}
646
647
		$schema = $this->get_item_schema();
648
649
		// Invoice owner.
650 View Code Duplication
		if ( ! empty( $schema['properties']['user_id'] ) && isset( $request['user_id'] ) ) {
651
			$prepared_invoice->user_id = (int) $request['user_id'];
652
		}
653
654
		// Cart details.
655 View Code Duplication
		if ( ! empty( $schema['properties']['cart_details'] ) && isset( $request['cart_details'] ) ) {
656
			$prepared_invoice->cart_details = (array) $request['cart_details'];
657
		}
658
659
		// Invoice status.
660
		if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
661
662
			if ( in_array( $request['status'], array_keys( wpinv_get_invoice_statuses( true, true ) ), true ) ) {
663
				$prepared_invoice->status = $request['status'];
664
			}
665
666
		}
667
668
		// User info
669
		if ( ! empty( $schema['properties']['user_info'] ) && isset( $request['user_info'] ) ) {
670
			$prepared_invoice->user_info = array();
671
			$user_info = (array) $request['user_info'];
672
673
			foreach( $user_info as $prop => $value ) {
674
675
				if ( ! empty( $schema['properties']['user_info']['properties'][$prop] ) ) {
676
677
					$prepared_invoice->user_info[$prop] = $value;
678
		
679
				}
680
681
			}
682
683
		}
684
685
		// IP
686 View Code Duplication
		if ( ! empty( $schema['properties']['ip'] ) && isset( $request['ip'] ) ) {
687
			$prepared_invoice->ip = $request['ip'];
688
		}
689
690
		// Payment details
691
		$prepared_invoice->payment_details = array();
692
693 View Code Duplication
		if ( ! empty( $schema['properties']['gateway'] ) && isset( $request['gateway'] ) ) {
694
			$prepared_invoice->payment_details['gateway'] = $request['gateway'];
695
		}
696
697 View Code Duplication
		if ( ! empty( $schema['properties']['gateway_title'] ) && isset( $request['gateway_title'] ) ) {
698
			$prepared_invoice->payment_details['gateway_title'] = $request['gateway_title'];
699
		}
700
701 View Code Duplication
		if ( ! empty( $schema['properties']['currency'] ) && isset( $request['currency'] ) ) {
702
			$prepared_invoice->payment_details['currency'] = $request['currency'];
703
		}
704
705 View Code Duplication
		if ( ! empty( $schema['properties']['transaction_id'] ) && isset( $request['transaction_id'] ) ) {
706
			$prepared_invoice->payment_details['transaction_id'] = $request['transaction_id'];
707
		}
708
709
		// Dates
710 View Code Duplication
		if ( ! empty( $schema['properties']['date'] ) && isset( $request['date'] ) ) {
711
			$post_date = rest_get_date_with_gmt( $request['date'] );
712
713
			if ( ! empty( $post_date ) ) {
714
				$prepared_invoice->post_date = $post_date[0];
715
			}
716
			
717
		}
718
719 View Code Duplication
		if ( ! empty( $schema['properties']['due_date'] ) && isset( $request['due_date'] ) ) {
720
			$due_date = rest_get_date_with_gmt( $request['due_date'] );
721
722
			if ( ! empty( $due_date ) ) {
723
				$prepared_invoice->due_date = $due_date[0];
724
			}
725
726
		}
727
728
		$invoice_data = (array) $prepared_invoice;
729
730
		/**
731
		 * Filters an invoice before it is inserted via the REST API.
732
		 *
733
		 * @since 1.0.13
734
		 *
735
		 * @param array        $invoice_data An array of invoice data
736
		 * @param WP_REST_Request $request       Request object.
737
		 */
738
		return apply_filters( "wpinv_rest_pre_insert_invoice", $invoice_data, $request );
739
740
	}
741
742
	/**
743
	 * Prepares a single invoice output for response.
744
	 *
745
	 * @since 1.0.13
746
	 *
747
	 * @param WPInv_Invoice   $invoice    Invoice object.
748
	 * @param WP_REST_Request $request Request object.
749
	 * @return WP_REST_Response Response object.
750
	 */
751
	public function prepare_item_for_response( $invoice, $request ) {
752
753
		$GLOBALS['post'] = get_post( $invoice->ID );
754
755
		setup_postdata( $invoice->ID );
756
757
		// Fetch the fields to include in this response.
758
		$fields = $this->get_fields_for_response( $request );
759
760
		// Base fields for every invoice.
761
		$data = array();
762
763
		// Set up ID
764
		if ( rest_is_field_included( 'id', $fields ) ) {
765
			$data['id'] = $invoice->ID;
766
		}
767
768
769
		// Basic properties
770
		$invoice_properties = array(
771
			'title', 'email', 'ip', 
772
			'key', 'number', 'transaction_id', 'mode',
773
			'gateway', 'gateway_title',
774
			'total', 'discount', 'discount_code', 
775
			'tax', 'fees_total', 'subtotal', 'currency',
776
			'status', 'status_nicename', 'post_type'
777
		);
778
779
		foreach( $invoice_properties as $property ) {
780
781
			if ( rest_is_field_included( $property, $fields ) ) {
782
				$data[$property] = $invoice->get( $property );
783
			}
784
785
		}
786
787
		// Cart details
788
		if ( rest_is_field_included( 'cart_details', $fields ) ) {
789
			$data['cart_details'] = $invoice->get( 'cart_details' );
790
		}
791
792
		//Dates
793
		$invoice_properties = array( 'date', 'due_date', 'completed_date' );
794
795
		foreach( $invoice_properties as $property ) {
796
797
			if ( rest_is_field_included( $property, $fields ) ) {
798
				$data[$property] = $this->prepare_date_response( '0000-00-00 00:00:00', $invoice->get( $property ) );
799
			}
800
801
		}
802
803
		// User id
804
		if ( rest_is_field_included( 'user_id', $fields ) ) {
805
			$data['user_id'] = (int) $invoice->get( 'user_id' );
806
		}
807
808
		// User info
809
		$user_info = array( 'first_name', 'last_name', 'company', 'vat_number', 'vat_rate', 'address', 'city', 'country', 'state', 'zip', 'phone' );
810
811
		foreach( $user_info as $property ) {
812
813
			if ( rest_is_field_included( "user_info.$property", $fields ) ) {
814
				$data['user_info'][$property] = $invoice->get( $property );
815
			}
816
817
		}
818
819
		// Slug
820
		if ( rest_is_field_included( 'slug', $fields ) ) {
821
			$data['slug'] = $invoice->get( 'post_name' );
822
		}
823
824
		// View invoice link
825
		if ( rest_is_field_included( 'link', $fields ) ) {
826
			$data['link'] = esc_url( $invoice->get_view_url() );
827
		}
828
829
830
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
831
		$data    = $this->add_additional_fields_to_object( $data, $request );
832
		$data    = $this->filter_response_by_context( $data, $context );
833
834
		// Wrap the data in a response object.
835
		$response = rest_ensure_response( $data );
836
837
		$links = $this->prepare_links( $invoice );
838
		$response->add_links( $links );
839
840
		if ( ! empty( $links['self']['href'] ) ) {
841
			$actions = $this->get_available_actions( $invoice, $request );
842
843
			$self = $links['self']['href'];
844
845
			foreach ( $actions as $rel ) {
846
				$response->add_link( $rel, $self );
847
			}
848
		}
849
850
		/**
851
		 * Filters the invoice data for a response.
852
		 *
853
		 * @since 1.0.13
854
		 *
855
		 * @param WP_REST_Response $response The response object.
856
		 * @param WPInv_Invoice    $invoice  The invoice object.
857
		 * @param WP_REST_Request  $request  Request object.
858
		 */
859
		return apply_filters( "wpinv_rest_prepare_invoice", $response, $invoice, $request );
860
	}
861
862
	/**
863
	 * Retrieves the invoice's schema, conforming to JSON Schema.
864
	 *
865
	 * @since 1.0.13
866
	 *
867
	 * @return array Invoice schema data.
868
	 */
869
	public function get_item_schema() {
870
871
		// Maybe retrieve the schema from cache.
872
		if ( $this->schema ) {
873
			return $this->add_additional_fields_schema( $this->schema );
874
		}
875
876
		$schema = array(
877
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
878
			'title'      => $this->post_type,
879
			'type'       => 'object',
880
881
			// Base properties for every Invoice.
882
			'properties' 		  => array(
883
884
				'title'			  => array(
885
					'description' => __( 'The title for the invoice.', 'invoicing' ),
886
					'type'        => 'string',
887
					'context'     => array( 'view', 'edit', 'embed' ),
888
					'readonly'    => true,
889
				),
890
891
				'user_id'		  => array(
892
					'description' => __( 'The ID of the owner of the invoice.', 'invoicing' ),
893
					'type'        => 'integer',
894
					'context'     => array( 'view', 'edit', 'embed' ),
895
				),
896
897
				'email'		  	  => array(
898
					'description' => __( 'The email of the owner of the invoice.', 'invoicing' ),
899
					'type'        => 'string',
900
					'context'     => array( 'view', 'edit', 'embed' ),
901
					'readonly'    => true,
902
				),
903
904
				'ip'			  => array(
905
					'description' => __( 'The IP of the owner of the invoice.', 'invoicing' ),
906
					'type'        => 'string',
907
					'context'     => array( 'view', 'edit', 'embed' ),
908
				),
909
910
				'user_info'       => array(
911
					'description' => __( 'Information about the owner of the invoice.', 'invoicing' ),
912
					'type'        => 'object',
913
					'context'     => array( 'view', 'edit', 'embed' ),
914
					'properties'  => array(
915
916
						'first_name'      => array(
917
							'description' => __( 'The first name of the owner of the invoice.', 'invoicing' ),
918
							'type'        => 'string',
919
							'context'     => array( 'view', 'edit', 'embed' ),
920
						),
921
922
						'last_name'       => array(
923
							'description' => __( 'The last name of the owner of the invoice.', 'invoicing' ),
924
							'type'        => 'string',
925
							'context'     => array( 'view', 'edit', 'embed' ),
926
						),
927
928
						'company'         => array(
929
							'description' => __( 'The company of the owner of the invoice.', 'invoicing' ),
930
							'type'        => 'string',
931
							'context'     => array( 'view', 'edit', 'embed' ),
932
						),
933
934
						'vat_number'      => array(
935
							'description' => __( 'The VAT number of the owner of the invoice.', 'invoicing' ),
936
							'type'        => 'string',
937
							'context'     => array( 'view', 'edit', 'embed' ),
938
						),
939
940
						'vat_rate'        => array(
941
							'description' => __( 'The VAT rate applied on the invoice.', 'invoicing' ),
942
							'type'        => 'string',
943
							'context'     => array( 'view', 'edit', 'embed' ),
944
						),
945
946
						'address'        => array(
947
							'description' => __( 'The address of the invoice owner.', 'invoicing' ),
948
							'type'        => 'string',
949
							'context'     => array( 'view', 'edit', 'embed' ),
950
						),
951
952
						'city'            => array(
953
							'description' => __( 'The city of the invoice owner.', 'invoicing' ),
954
							'type'        => 'string',
955
							'context'     => array( 'view', 'edit', 'embed' ),
956
						),
957
958
						'country'         => array(
959
							'description' => __( 'The country of the invoice owner.', 'invoicing' ),
960
							'type'        => 'string',
961
							'context'     => array( 'view', 'edit', 'embed' ),
962
						),
963
964
						'state'           => array(
965
							'description' => __( 'The state of the invoice owner.', 'invoicing' ),
966
							'type'        => 'string',
967
							'context'     => array( 'view', 'edit', 'embed' ),
968
						),
969
970
						'zip'             => array(
971
							'description' => __( 'The zip code of the invoice owner.', 'invoicing' ),
972
							'type'        => 'string',
973
							'context'     => array( 'view', 'edit', 'embed' ),
974
						),
975
976
						'phone'             => array(
977
							'description' => __( 'The phone number of the invoice owner.', 'invoicing' ),
978
							'type'        => 'string',
979
							'context'     => array( 'view', 'edit', 'embed' ),
980
						),
981
					),
982
				),
983
984
				'id'           => array(
985
					'description' => __( 'Unique identifier for the invoice.', 'invoicing' ),
986
					'type'        => 'integer',
987
					'context'     => array( 'view', 'edit', 'embed' ),
988
					'readonly'    => true,
989
				),
990
991
				'key'			  => array(
992
					'description' => __( 'A unique key for the invoice.', 'invoicing' ),
993
					'type'        => 'string',
994
					'context'     => array( 'view', 'edit', 'embed' ),
995
					'readonly'    => true,
996
				),
997
998
				'number'		  => array(
999
					'description' => __( 'The invoice number.', 'invoicing' ),
1000
					'type'        => 'string',
1001
					'context'     => array( 'view', 'edit', 'embed' ),
1002
					'readonly'    => true,
1003
				),
1004
1005
				'transaction_id'  => array(
1006
					'description' => __( 'The transaction id of the invoice.', 'invoicing' ),
1007
					'type'        => 'string',
1008
					'context'     => array( 'view', 'edit', 'embed' ),
1009
				),
1010
1011
				'gateway'		  => array(
1012
					'description' => __( 'The gateway used to process the invoice.', 'invoicing' ),
1013
					'type'        => 'string',
1014
					'context'     => array( 'view', 'edit', 'embed' ),
1015
				),
1016
1017
				'gateway_title'	  => array(
1018
					'description' => __( 'The title of the gateway used to process the invoice.', 'invoicing' ),
1019
					'type'        => 'string',
1020
					'context'     => array( 'view', 'edit', 'embed' ),
1021
				),
1022
1023
				'total'	  		  => array(
1024
					'description' => __( 'The total amount of the invoice.', 'invoicing' ),
1025
					'type'        => 'number',
1026
					'context'     => array( 'view', 'edit', 'embed' ),
1027
					'readonly'    => true,
1028
				),
1029
1030
				'discount'		  => array(
1031
					'description' => __( 'The discount applied to the invoice.', 'invoicing' ),
1032
					'type'        => 'number',
1033
					'context'     => array( 'view', 'edit', 'embed' ),
1034
					'readonly'    => true,
1035
				),
1036
1037
				'discount_code'	  => array(
1038
					'description' => __( 'The discount code applied to the invoice.', 'invoicing' ),
1039
					'type'        => 'string',
1040
					'context'     => array( 'view', 'edit', 'embed' ),
1041
					'readonly'    => true,
1042
				),
1043
1044
				'tax'	  		  => array(
1045
					'description' => __( 'The tax applied to the invoice.', 'invoicing' ),
1046
					'type'        => 'number',
1047
					'context'     => array( 'view', 'edit', 'embed' ),
1048
					'readonly'    => true,
1049
				),
1050
1051
				'fees_total'	  => array(
1052
					'description' => __( 'The total fees applied to the invoice.', 'invoicing' ),
1053
					'type'        => 'number',
1054
					'context'     => array( 'view', 'edit', 'embed' ),
1055
					'readonly'    => true,
1056
				),
1057
1058
				'subtotal'	  	  => array(
1059
					'description' => __( 'The sub-total for the invoice.', 'invoicing' ),
1060
					'type'        => 'number',
1061
					'context'     => array( 'view', 'edit', 'embed' ),
1062
					'readonly'    => true,
1063
				),
1064
1065
				'currency'	  	  => array(
1066
					'description' => __( 'The currency used to process the invoice.', 'invoicing' ),
1067
					'type'        => 'string',
1068
					'context'     => array( 'view', 'edit', 'embed' ),
1069
				),
1070
1071
				'cart_details'	  => array(
1072
					'description' => __( 'The cart details for invoice.', 'invoicing' ),
1073
					'type'        => 'array',
1074
					'context'     => array( 'view', 'edit', 'embed' ),
1075
					'required'	  => true,
1076
				),
1077
1078
				'date'         => array(
1079
					'description' => __( "The date the invoice was published, in the site's timezone.", 'invoicing' ),
1080
					'type'        => array( 'string', 'null' ),
1081
					'format'      => 'date-time',
1082
					'context'     => array( 'view', 'edit', 'embed' ),
1083
				),
1084
1085
				'due_date'     => array(
1086
					'description' => __( 'The due date for the invoice.', 'invoicing' ),
1087
					'type'        => array( 'string', 'null' ),
1088
					'format'      => 'date-time',
1089
					'context'     => array( 'view', 'edit', 'embed' ),
1090
				),
1091
1092
				'completed_date'  => array(
1093
					'description' => __( 'The completed date for the invoice.', 'invoicing' ),
1094
					'type'        => array( 'string', 'null' ),
1095
					'format'      => 'date-time',
1096
					'context'     => array( 'view', 'edit', 'embed' ),
1097
					'readonly'    => true,
1098
				),
1099
				
1100
				'link'         => array(
1101
					'description' => __( 'URL to the invoice.', 'invoicing' ),
1102
					'type'        => 'string',
1103
					'format'      => 'uri',
1104
					'context'     => array( 'view', 'edit', 'embed' ),
1105
					'readonly'    => true,
1106
				),
1107
1108
				'mode'       	  => array(
1109
					'description' => __( 'The mode used to process the invoice.', 'invoicing' ),
1110
					'type'        => 'string',
1111
					'enum'        => array( 'live', 'test' ),
1112
					'context'     => array( 'view', 'edit', 'embed' ),
1113
					'readonly'    => true,
1114
				),
1115
1116
				'slug'       	  => array(
1117
					'description' => __( 'An alphanumeric identifier for the invoice.', 'invoicing' ),
1118
					'type'        => 'string',
1119
					'context'     => array( 'view', 'edit', 'embed' ),
1120
					'arg_options' => array(
1121
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
1122
					),
1123
					'readonly'    => true,
1124
				),
1125
1126
				'status'       	  => array(
1127
					'description' => __( 'A named status for the invoice.', 'invoicing' ),
1128
					'type'        => 'string',
1129
					'enum'        => array_keys( wpinv_get_invoice_statuses( true, true ) ),
1130
					'context'     => array( 'view', 'edit' ),
1131
					'default'	  => 'wpi-pending',
1132
				),
1133
1134
				'status_nicename' => array(
1135
					'description' => __( 'A human-readable status name for the invoice.', 'invoicing' ),
1136
					'type'        => 'string',
1137
					'context'     => array( 'view', 'embed' ),
1138
					'readonly'    => true,
1139
				),
1140
1141
				'post_type'       => array(
1142
					'description' => __( 'The post type for the invoice.', 'invoicing' ),
1143
					'type'        => 'string',
1144
					'context'     => array( 'view' ),
1145
					'readonly'    => true,
1146
				),
1147
			),
1148
		);
1149
1150
		// Add helpful links to the invoice schem.
1151
		$schema['links'] = $this->get_schema_links();
1152
1153
		/**
1154
		 * Filters the invoice schema for the REST API.
1155
		 *
1156
		 * Enables adding extra properties to invoices.
1157
		 *
1158
		 * @since 1.0.13
1159
		 *
1160
		 * @param array   $schema    The invoice schema.
1161
		 */
1162
        $schema = apply_filters( "wpinv_rest_invoice_schema", $schema );
1163
1164
		// Cache the invoice schema.
1165
		$this->schema = $schema;
1166
		
1167
		return $this->add_additional_fields_schema( $this->schema );
1168
	}
1169
1170
	/**
1171
	 * Retrieve Link Description Objects that should be added to the Schema for the invoices collection.
1172
	 *
1173
	 * @since 1.0.13
1174
	 *
1175
	 * @return array
1176
	 */
1177
	protected function get_schema_links() {
1178
1179
		$href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
1180
1181
		$links = array();
1182
1183
		$links[] = array(
1184
			'rel'          => 'https://api.w.org/action-publish',
1185
			'title'        => __( 'The current user can mark this invoice as completed.', 'invoicing' ),
1186
			'href'         => $href,
1187
			'targetSchema' => array(
1188
				'type'       => 'object',
1189
				'properties' => array(
1190
					'status' => array(
1191
						'type' => 'string',
1192
						'enum' => array( 'publish', 'wpi-renewal' ),
1193
					),
1194
				),
1195
			),
1196
		);
1197
1198
		$links[] = array(
1199
			'rel'          => 'https://api.w.org/action-assign-author',
1200
			'title'        => __( 'The current user can change the owner of this invoice.', 'invoicing' ),
1201
			'href'         => $href,
1202
			'targetSchema' => array(
1203
				'type'       => 'object',
1204
				'properties'   => array(
1205
					'user_id'  => array(
1206
						'type' => 'integer',
1207
					),
1208
				),
1209
			),
1210
		);
1211
1212
		return $links;
1213
	}
1214
1215
	/**
1216
	 * Prepares links for the request.
1217
	 *
1218
	 * @since 1.0.13
1219
	 *
1220
	 * @param WPInv_Invoice $invoice Invoice Object.
1221
	 * @return array Links for the given invoice.
1222
	 */
1223
	protected function prepare_links( $invoice ) {
1224
1225
		// Prepare the base REST API endpoint for invoices.
1226
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
1227
1228
		// Entity meta.
1229
		$links = array(
1230
			'self'       => array(
1231
				'href' => rest_url( trailingslashit( $base ) . $invoice->ID ),
1232
			),
1233
			'collection' => array(
1234
				'href' => rest_url( $base ),
1235
			),
1236
		);
1237
1238
		if ( ! empty( $invoice->user_id ) ) {
1239
			$links['user'] = array(
1240
				'href'       => rest_url( 'wp/v2/users/' . $invoice->user_id ),
1241
				'embeddable' => true,
1242
			);
1243
		}
1244
1245
		/**
1246
		 * Filters the returned invoice links for the REST API.
1247
		 *
1248
		 * Enables adding extra links to invoice API responses.
1249
		 *
1250
		 * @since 1.0.13
1251
		 *
1252
		 * @param array   $links    Rest links.
1253
		 */
1254
		return apply_filters( "wpinv_invoice_rest_links", $links );
1255
1256
	}
1257
1258
	/**
1259
	 * Get the link relations available for the post and current user.
1260
	 *
1261
	 * @since 1.0.13
1262
	 *
1263
	 * @param WPInv_Invoice   $invoice    Invoice object.
1264
	 * @param WP_REST_Request $request Request object.
1265
	 * @return array List of link relations.
1266
	 */
1267
	protected function get_available_actions( $invoice, $request ) {
1268
1269
		if ( 'edit' !== $request['context'] ) {
1270
			return array();
1271
		}
1272
1273
		$rels = array();
1274
1275
		// Retrieve the post type object.
1276
		$post_type = get_post_type_object( $invoice->post_type );
1277
1278
		// Mark invoice as completed.
1279
		if ( current_user_can( $post_type->cap->publish_posts ) ) {
1280
			$rels[] = 'https://api.w.org/action-publish';
1281
		}
1282
1283
		// Change the owner of the invoice.
1284
		if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
1285
			$rels[] = 'https://api.w.org/action-assign-author';
1286
		}
1287
1288
		/**
1289
		 * Filters the available invoice link relations for the REST API.
1290
		 *
1291
		 * Enables adding extra link relation for the current user and request to invoice responses.
1292
		 *
1293
		 * @since 1.0.13
1294
		 *
1295
		 * @param array   $rels    Available link relations.
1296
		 */
1297
		return apply_filters( "wpinv_invoice_rest_link_relations", $rels );
1298
	}
1299
1300
	/**
1301
	 * Sanitizes and validates the list of post statuses.
1302
	 *
1303
	 * @since 1.0.13
1304
	 *
1305
	 * @param string|array    $statuses  One or more post statuses.
1306
	 * @param WP_REST_Request $request   Full details about the request.
1307
	 * @param string          $parameter Additional parameter to pass to validation.
1308
	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
1309
	 */
1310
	public function sanitize_post_statuses( $statuses, $request, $parameter ) {
1311
1312
		$statuses 	  = wp_parse_slug_list( $statuses );
1313
		$valid_statuses = array_keys( wpinv_get_invoice_statuses( true, true ) );
1314
		return array_intersect( $statuses, $valid_statuses );
1315
		
1316
	}
1317
    
1318
}