Passed
Push — master ( 182f0c...6bcbfc )
by
unknown
05:33 queued 11s
created

WPInv_REST_Discounts_Controller::register_routes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 11
rs 10
1
<?php
2
/**
3
 * REST API Discounts 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 discounts controller class.
17
 *
18
 * @package Invoicing
19
 */
20
class WPInv_REST_Discounts_Controller extends WP_REST_Posts_Controller {
21
22
    /**
23
	 * Post type.
24
	 *
25
	 * @var string
26
	 */
27
	protected $post_type = 'wpi_discount';
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 = 'discounts';
51
		
52
    }
53
	
54
	/**
55
	 * Registers the routes for the objects of the controller.
56
	 *
57
	 * @since 1.0.13
58
	 *
59
	 * @see register_rest_route()
60
	 */
61
	public function register_routes() {
62
63
		parent::register_routes();
64
65
		register_rest_route(
66
			$this->namespace,
67
			'/' . $this->rest_base . '/discount-types',
68
			array(
69
				array(
70
					'methods'             => WP_REST_Server::READABLE,
71
					'callback'            => array( $this, 'get_discount_types' ),
72
				),
73
			)
74
		);
75
76
	}
77
78
    /**
79
	 * Checks if a given request has access to read discounts.
80
     * 
81
	 *
82
	 * @since 1.0.13
83
	 *
84
	 * @param WP_REST_Request $request Full details about the request.
85
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
86
	 */
87
	public function get_items_permissions_check( $request ) {
88
	
89
		if ( wpinv_current_user_can_manage_invoicing() ) {
90
			return true;
91
		}
92
93
		return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to view invoice discounts.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
94
95
    }
96
    
97
    /**
98
	 * Retrieves a collection of discounts.
99
	 *
100
	 * @since 1.0.13
101
	 *
102
	 * @param WP_REST_Request $request Full details about the request.
103
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
104
	 */
105
	public function get_items( $request ) {
106
		
107
		// Retrieve the list of registered item query parameters.
108
        $registered = $this->get_collection_params();
109
        
110
        $args       = array();
111
112
        foreach( array_keys( $registered ) as $key ) {
113
114
            if( isset( $request[ $key] ) ) {
115
                $args[ $key ] = $request[ $key];
116
            }
117
118
		} 
119
120
		/**
121
		 * Filters the wpinv_get_all_discounts arguments for discounts rest requests.
122
		 *
123
		 *
124
		 * @since 1.0.13
125
		 *
126
		 *
127
		 * @param array           $args    Key value array of query var to query value.
128
		 * @param WP_REST_Request $request The request used.
129
		 */
130
        $args       = apply_filters( "wpinv_rest_get_discounts_arguments", $args, $request, $this );
131
		
132
		// Special args
133
		$args[ 'return' ]   = 'objects';
134
		$args[ 'paginate' ] = true;
135
136
        // Run the query.
137
		$query = wpinv_get_all_discounts( $args );
138
		
139
		// Prepare the retrieved discounts
140
		$discounts = array();
141
		foreach( $query->discounts as $discount ) {
142
143
			$data       = $this->prepare_item_for_response( $discount, $request );
144
			$discounts[]    = $this->prepare_response_for_collection( $data );
145
146
		}
147
148
		// Prepare the response.
149
		$response = rest_ensure_response( $discounts );
150
		$response->header( 'X-WP-Total', (int) $query->total );
151
		$response->header( 'X-WP-TotalPages', (int) $query->max_num_pages );
152
153
		/**
154
		 * Filters the responses for discount requests.
155
		 *
156
		 *
157
		 * @since 1.0.13
158
		 *
159
		 *
160
		 * @param arrWP_REST_Response $response    Response object.
161
		 * @param WP_REST_Request     $request The request used.
162
         * @param array               $args Array of args used to retrieve the discounts
163
		 */
164
        $response       = apply_filters( "wpinv_rest_discounts_response", $response, $request, $args );
165
166
        return rest_ensure_response( $response );
167
        
168
    }
169
170
    /**
171
	 * Get the post, if the ID is valid.
172
	 *
173
	 * @since 1.0.13
174
	 *
175
	 * @param int $discount_id Supplied ID.
176
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
177
	 */
178
	protected function get_post( $discount_id ) {
179
		
180
		$error     = new WP_Error( 'rest_item_invalid_id', __( 'Invalid discount ID.', 'invoicing' ), array( 'status' => 404 ) );
181
182
        // Ids start from 1
183
        if ( (int) $discount_id <= 0 ) {
184
			return $error;
185
		}
186
187
		$discount = wpinv_get_discount( (int) $discount_id );
188
		if ( empty( $discount ) ) {
189
			return $error;
190
        }
191
192
        return $discount;
193
194
    }
195
196
    /**
197
	 * Checks if a given request has access to read a discount.
198
	 *
199
	 * @since 1.0.13
200
	 *
201
	 * @param WP_REST_Request $request Full details about the request.
202
	 * @return bool|WP_Error True if the request has read access for the invoice item, WP_Error object otherwise.
203
	 */
204
	public function get_item_permissions_check( $request ) {
205
206
        // Retrieve the discount object.
207
        $discount = $this->get_post( $request['id'] );
208
        
209
        // Ensure it is valid.
210
		if ( is_wp_error( $discount ) ) {
211
			return $discount;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $discount also could return the type WP_Post which is incompatible with the documented return type WP_Error|boolean.
Loading history...
212
		}
213
214
		if ( ! wpinv_current_user_can_manage_invoicing() ) {
215
			return new WP_Error(
216
                'rest_cannot_view', 
217
                __( 'Sorry, you are not allowed to view this discount.', 'invoicing' ), 
218
                array( 
219
                    'status' => rest_authorization_required_code(),
220
                )
221
            );
222
        }
223
224
		return true;
225
    }
226
    
227
    /**
228
	 * Retrieves a single invoice item.
229
	 *
230
	 * @since 1.0.13
231
	 *
232
	 * @param WP_REST_Request $request Full details about the request.
233
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
234
	 */
235
	public function get_item( $request ) {
236
237
        // Fetch the discount.
238
        $discount = $this->get_post( $request['id'] );
239
        
240
        // Abort early if it does not exist
241
		if ( is_wp_error( $discount ) ) {
242
			return $discount;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $discount also could return the type WP_Post which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
243
		}
244
245
		// Prepare the response
246
		$response = $this->prepare_item_for_response( $discount, $request );
0 ignored issues
show
Bug introduced by
It seems like $discount can also be of type WP_Error; however, parameter $discount of WPInv_REST_Discounts_Con...are_item_for_response() does only seem to accept WP_Post, 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

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

322
        $discount = wp_insert_post( /** @scrutinizer ignore-type */ $discount_data, true );
Loading history...
323
324
		if ( is_wp_error( $discount ) ) {
325
            return $discount;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $discount also could return the type integer which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
326
		}
327
328
		// Prepare the response
329
		$response = $this->prepare_item_for_response( $discount, $request );
0 ignored issues
show
Bug introduced by
$discount of type WP_Error|integer is incompatible with the type WP_Post expected by parameter $discount of WPInv_REST_Discounts_Con...are_item_for_response(). ( Ignorable by Annotation )

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

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

519
		$response = $this->prepare_item_for_response( /** @scrutinizer ignore-type */ $discount, $request );
Loading history...
520
521
		// Delete the discount...
522
		wpinv_remove_discount( $id );
523
524
		/**
525
		 * Fires immediately after a single discount is deleted via the REST API.
526
		 *
527
		 *
528
		 * @since 1.0.13
529
		 *
530
		 * @param WP_POST    $discount  The deleted discount.
531
		 * @param WP_REST_Request  $request  The request sent to the API.
532
		 */
533
		do_action( "wpinv_rest_delete_discount", $discount, $request );
534
535
		return $response;
536
537
	}
538
    
539
    
540
    /**
541
	 * Retrieves the query params for the discount collection.
542
	 *
543
	 * @since 1.0.13
544
	 *
545
	 * @return array Collection parameters.
546
	 */
547
	public function get_collection_params() {
548
        
549
        $query_params               = array(
550
551
            // Discount status.
552
            'status'                => array(
553
                'default'           => 'publish',
554
                'description'       => __( 'Limit result set to discounts assigned one or more statuses.', 'invoicing' ),
555
                'type'              => 'array',
556
                'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
557
            ),
558
            
559
            // Discount types
560
            'type'                  => array(
561
				'description'       => __( 'Type of discounts to fetch.', 'invoicing' ),
562
				'type'              => 'array',
563
				'default'           => array_keys( wpinv_get_discount_types() ),
564
				'items'             => array(
565
                    'enum'          => array_keys( wpinv_get_discount_types() ),
566
                    'type'          => 'string',
567
                ),
568
			),
569
			
570
			// Number of results per page
571
            'limit'                 => array(
572
				'description'       => __( 'Number of discounts to fetch.', 'invoicing' ),
573
				'type'              => 'integer',
574
				'default'           => (int) get_option( 'posts_per_page' ),
575
            ),
576
577
            // Pagination
578
            'page'     => array(
579
				'description'       => __( 'Current page to fetch.', 'invoicing' ),
580
				'type'              => 'integer',
581
				'default'           => 1,
582
            ),
583
584
            // Exclude certain items
585
            'exclude'  => array(
586
                'description' => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
587
                'type'        => 'array',
588
                'items'       => array(
589
                    'type' => 'integer',
590
                ),
591
                'default'     => array(),
592
            ),
593
594
            // Order discounts by
595
            'orderby'  => array(
596
                'description' => __( 'Sort discounts by object attribute.', 'invoicing' ),
597
                'type'        => 'string',
598
                'default'     => 'date',
599
                'enum'        => array(
600
                    'author',
601
                    'date',
602
                    'ID',
603
                    'modified',
604
					'title',
605
					'relevance',
606
					'rand'
607
                ),
608
            ),
609
610
            // How to order
611
            'order'    => array(
612
                'description' => __( 'Order sort attribute ascending or descending.', 'invoicing' ),
613
                'type'        => 'string',
614
                'default'     => 'DESC',
615
                'enum'        => array( 'ASC', 'DESC' ),
616
			),
617
			
618
			// Search term
619
            'search'                => array(
620
				'description'       => __( 'Return discounts that match the search term.', 'invoicing' ),
621
				'type'              => 'string',
622
            ),
623
        );
624
625
		/**
626
		 * Filter collection parameters for the discounts controller.
627
		 *
628
		 *
629
		 * @since 1.0.13
630
		 *
631
		 * @param array        $query_params JSON Schema-formatted collection parameters.
632
		 */
633
		return apply_filters( "wpinv_rest_discounts_collection_params", $query_params );
634
    }
635
    
636
    /**
637
	 * Checks if a given post type can be viewed or managed.
638
	 *
639
	 * @since 1.0.13
640
	 *
641
	 * @param object|string $post_type Post type name or object.
642
	 * @return bool Whether the post type is allowed in REST.
643
	 */
644
	protected function check_is_post_type_allowed( $post_type ) {
645
		return true;
646
	}
647
648
	/**
649
	 * Prepares a single item for create or update.
650
	 *
651
	 * @since 1.0.13
652
	 *
653
	 * @param WP_REST_Request $request Request object.
654
	 * @return array|WP_Error Discount Properties or WP_Error.
655
	 */
656
	protected function prepare_item_for_database( $request ) {
657
		$prepared_item 		 = new stdClass();
658
		$prepared_item->meta_input = array();
659
660
		// Post ID.
661
		if ( isset( $request['id'] ) ) {
662
			$existing_item = $this->get_post( $request['id'] );
663
			if ( is_wp_error( $existing_item ) ) {
664
				return $existing_item;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $existing_item also could return the type WP_Post which is incompatible with the documented return type WP_Error|array.
Loading history...
665
			}
666
667
			$prepared_item->ID 		  = $existing_item->ID;
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on WP_Error.
Loading history...
668
		}
669
670
		$schema = $this->get_item_schema();
671
672
		// item title.
673
		if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
674
			$prepared_item->post_title = sanitize_text_field( $request['title'] );
675
		}
676
677
		// item status.
678
		if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) && in_array( $request['status'], array_keys( get_post_stati( array( 'internal' => false ) ) ) ) ) {
679
			$prepared_item->post_status = sanitize_text_field( $request['status'] );
680
		}
681
682
		// Code.
683
		if ( ! empty( $schema['properties']['code'] ) && isset( $request['code'] ) ) {
684
			$prepared_item->meta_input['_wpi_discount_code'] = trim( $request['code'] );
685
		}
686
687
		// Type.
688
		if ( ! empty( $schema['properties']['type'] ) && isset( $request['type'] )  && in_array( $request['type'], array_keys( wpinv_get_discount_types() ) ) ) {
689
			$prepared_item->meta_input['_wpi_discount_type'] = trim( $request['type'] );
690
		}
691
692
		// Amount.
693
		if ( ! empty( $schema['properties']['amount'] ) && isset( $request['amount'] ) ) {
694
			$prepared_item->meta_input['_wpi_discount_amount'] = floatval( $request['amount'] );
695
		}
696
697
		// Items.
698
		if ( ! empty( $schema['properties']['items'] ) && isset( $request['items'] ) ) {
699
			$prepared_item->meta_input['_wpi_discount_items'] = wpinv_parse_list( $request['items'] );
700
		}
701
702
		// Excluded Items.
703
		if ( ! empty( $schema['properties']['exclude_items'] ) && isset( $request['exclude_items'] ) ) {
704
			$prepared_item->meta_input['_wpi_discount_excluded_items'] = wpinv_parse_list( $request['exclude_items'] );
705
		}
706
707
		// Start date.
708
		if ( ! empty( $schema['properties']['start_date'] ) && isset( $request['start_date'] ) ) {
709
			$prepared_item->meta_input['_wpi_discount_start'] = trim( $request['start_date'] );
710
		}
711
712
		// End date.
713
		if ( ! empty( $schema['properties']['end_date'] ) && isset( $request['end_date'] ) ) {
714
			$prepared_item->meta_input['_wpi_discount_expiration'] = trim( $request['end_date'] );
715
		}
716
717
		// Minimum amount.
718
		if ( ! empty( $schema['properties']['minimum_amount'] ) && isset( $request['minimum_amount'] ) ) {
719
			$prepared_item->meta_input['_wpi_discount_min_total'] = floatval( $request['minimum_amount'] );
720
		}
721
722
		// Maximum amount.
723
		if ( ! empty( $schema['properties']['maximum_amount'] ) && isset( $request['maximum_amount'] ) ) {
724
			$prepared_item->meta_input['_wpi_discount_max_total'] = floatval( $request['maximum_amount'] );
725
		}
726
727
		// Recurring.
728
		if ( ! empty( $schema['properties']['recurring'] ) && isset( $request['recurring'] ) ) {
729
			$prepared_item->meta_input['_wpi_discount_is_recurring'] = empty( (int) $request['recurring'] ) ? 0 : 1;
730
		}
731
732
		// Maximum uses.
733
		if ( ! empty( $schema['properties']['max_uses'] ) && isset( $request['max_uses'] ) ) {
734
			$prepared_item->meta_input['_wpi_discount_max_uses'] = intval( $request['max_uses'] );
735
		}
736
737
		// Single use.
738
		if ( ! empty( $schema['properties']['single_use'] ) && isset( $request['single_use'] ) ) {
739
			$prepared_item->meta_input['_wpi_discount_is_single_use'] = empty( (int) $request['single_use'] ) ? 0 : 1;
740
		}
741
742
		$discount_data = (array) wp_unslash( $prepared_item );
0 ignored issues
show
Bug introduced by
$prepared_item 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

742
		$discount_data = (array) wp_unslash( /** @scrutinizer ignore-type */ $prepared_item );
Loading history...
743
744
		/**
745
		 * Filters an item before it is inserted via the REST API.
746
		 *
747
		 * @since 1.0.13
748
		 *
749
		 * @param array        $discount_data An array of discount data
750
		 * @param WP_REST_Request $request       Request object.
751
		 */
752
		return apply_filters( "wpinv_rest_pre_insert_discount", $discount_data, $request );
753
754
	}
755
756
	/**
757
	 * Prepares a single discount output for response.
758
	 *
759
	 * @since 1.0.13
760
	 *
761
	 * @param WP_Post   $discount    WP_Post object.
762
	 * @param WP_REST_Request $request Request object.
763
	 * @return WP_REST_Response Response object.
764
	 */
765
	public function prepare_item_for_response( $discount, $request ) {
766
767
		$GLOBALS['post'] = get_post( $discount->ID );
768
769
		setup_postdata( $discount->ID );
770
771
		// Fetch the fields to include in this response.
772
		$fields = $this->get_fields_for_response( $request );
773
774
		// Base fields for every discount.
775
		$data = array();
776
777
		// Set up ID.
778
		if ( rest_is_field_included( 'id', $fields ) ) {
779
			$data['id'] = $discount->ID;
780
		}
781
782
		// Title.
783
		if ( rest_is_field_included( 'title', $fields ) ) {
784
			$data['title'] = get_the_title( $discount->ID );
785
		}
786
787
		// Code.
788
		if ( rest_is_field_included( 'code', $fields ) ) {
789
			$data['code'] = wpinv_get_discount_code( $discount->ID );
790
		}
791
792
		// Type.
793
		if ( rest_is_field_included( 'type', $fields ) ) {
794
			$data['type'] = wpinv_get_discount_type( $discount->ID );
795
		}
796
797
		// Amount.
798
		if ( rest_is_field_included( 'amount', $fields ) ) {
799
			$data['amount'] = wpinv_get_discount_amount( $discount->ID );
800
		}
801
802
		// Status.
803
		if ( rest_is_field_included( 'status', $fields ) ) {
804
			$data['status'] = get_post_status( $discount->ID );
805
		}
806
807
		// Items.
808
		if ( rest_is_field_included( 'items', $fields ) ) {
809
			$data['items'] = wpinv_get_discount_item_reqs( $discount->ID );
810
		}
811
812
		// Excluded Items.
813
		if ( rest_is_field_included( 'exclude_items', $fields ) ) {
814
			$data['exclude_items'] = wpinv_get_discount_excluded_items( $discount->ID );
815
		}
816
817
		// Start date.
818
		if ( rest_is_field_included( 'start_date', $fields ) ) {
819
			$data['start_date'] = wpinv_get_discount_start_date( $discount->ID );
820
		}
821
822
		// End date.
823
		if ( rest_is_field_included( 'end_date', $fields ) ) {
824
			$data['end_date'] = wpinv_get_discount_expiration( $discount->ID );
825
		}
826
827
		// Minimum amount.
828
		if ( rest_is_field_included( 'minimum_amount', $fields ) ) {
829
			$data['minimum_amount'] = wpinv_get_discount_min_total( $discount->ID );
830
		}
831
832
		// Maximum amount.
833
		if ( rest_is_field_included( 'maximum_amount', $fields ) ) {
834
			$data['maximum_amount'] = wpinv_get_discount_max_total( $discount->ID );
835
		}
836
837
		// Recurring.
838
		if ( rest_is_field_included( 'recurring', $fields ) ) {
839
			$data['recurring'] = wpinv_discount_is_recurring( $discount->ID );
840
		}
841
842
		// Maximum uses.
843
		if ( rest_is_field_included( 'max_uses', $fields ) ) {
844
			$data['max_uses'] = wpinv_get_discount_max_uses( $discount->ID );
845
		}
846
847
		// Single use.
848
		if ( rest_is_field_included( 'single_use', $fields ) ) {
849
			$data['single_use'] = wpinv_discount_is_single_use( $discount->ID );
850
		}
851
852
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
853
		$data    = $this->add_additional_fields_to_object( $data, $request );
854
		$data    = $this->filter_response_by_context( $data, $context );
855
856
		// Wrap the data in a response object.
857
		$response = rest_ensure_response( $data );
858
859
		$links = $this->prepare_links( $discount );
860
		$response->add_links( $links );
861
862
		if ( ! empty( $links['self']['href'] ) ) {
863
			$actions = $this->get_available_actions( $discount, $request );
864
865
			$self = $links['self']['href'];
866
867
			foreach ( $actions as $rel ) {
868
				$response->add_link( $rel, $self );
869
			}
870
		}
871
872
		/**
873
		 * Filters the discount data for a response.
874
		 *
875
		 * @since 1.0.13
876
		 *
877
		 * @param WP_REST_Response $response The response object.
878
		 * @param WP_Post    $discount  The discount post object.
879
		 * @param WP_REST_Request  $request  Request object.
880
		 */
881
		return apply_filters( "wpinv_rest_prepare_discount", $response, $discount, $request );
0 ignored issues
show
Bug Best Practice introduced by
The expression return apply_filters('wp...e, $discount, $request) also could return the type array which is incompatible with the documented return type WP_REST_Response.
Loading history...
882
	}
883
884
	/**
885
	 * Gets an array of fields to be included on the response.
886
	 *
887
	 * Included fields are based on item schema and `_fields=` request argument.
888
	 *
889
	 * @since 1.0.13
890
	 *
891
	 * @param WP_REST_Request $request Full details about the request.
892
	 * @return array Fields to be included in the response.
893
	 */
894
	public function get_fields_for_response( $request ) {
895
		$schema     = $this->get_item_schema();
896
		$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
897
898
		$additional_fields = $this->get_additional_fields();
899
		foreach ( $additional_fields as $field_name => $field_options ) {
900
			// For back-compat, include any field with an empty schema
901
			// because it won't be present in $this->get_item_schema().
902
			if ( is_null( $field_options['schema'] ) ) {
903
				$properties[ $field_name ] = $field_options;
904
			}
905
		}
906
907
		// Exclude fields that specify a different context than the request context.
908
		$context = $request['context'];
909
		if ( $context ) {
910
			foreach ( $properties as $name => $options ) {
911
				if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
912
					unset( $properties[ $name ] );
913
				}
914
			}
915
		}
916
917
		$fields = array_keys( $properties );
918
919
		if ( ! isset( $request['_fields'] ) ) {
920
			return $fields;
921
		}
922
		$requested_fields = wpinv_parse_list( $request['_fields'] );
923
		if ( 0 === count( $requested_fields ) ) {
924
			return $fields;
925
		}
926
		// Trim off outside whitespace from the comma delimited list.
927
		$requested_fields = array_map( 'trim', $requested_fields );
928
		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
929
		if ( in_array( 'id', $fields, true ) ) {
930
			$requested_fields[] = 'id';
931
		}
932
		// Return the list of all requested fields which appear in the schema.
933
		return array_reduce(
934
			$requested_fields,
935
			function( $response_fields, $field ) use ( $fields ) {
936
				if ( in_array( $field, $fields, true ) ) {
937
					$response_fields[] = $field;
938
					return $response_fields;
939
				}
940
				// Check for nested fields if $field is not a direct match.
941
				$nested_fields = explode( '.', $field );
942
				// A nested field is included so long as its top-level property is
943
				// present in the schema.
944
				if ( in_array( $nested_fields[0], $fields, true ) ) {
945
					$response_fields[] = $field;
946
				}
947
				return $response_fields;
948
			},
949
			array()
950
		);
951
	}
952
953
	/**
954
	 * Retrieves the discount's schema, conforming to JSON Schema.
955
	 *
956
	 * @since 1.0.13
957
	 *
958
	 * @return array Discount schema data.
959
	 */
960
	public function get_item_schema() {
961
962
		// Maybe retrieve the schema from cache.
963
		if (  empty( $this->schema ) ) {
964
			return $this->add_additional_fields_schema( $this->schema );
965
		}
966
967
		$schema = array(
968
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
969
			'title'      => $this->post_type,
970
			'type'       => 'object',
971
972
			// Base properties for every Item.
973
			'properties' 		  => array(
974
975
				'id'           => array(
976
					'description' => __( 'Unique identifier for the discount.', 'invoicing' ),
977
					'type'        => 'integer',
978
					'context'     => array( 'view', 'edit', 'embed' ),
979
					'readonly'    => true,
980
				),
981
982
				'title'			  => array(
983
					'description' => __( 'The title for the discount.', 'invoicing' ),
984
					'type'        => 'string',
985
					'context'     => array( 'view', 'edit' ),
986
				),
987
988
				'code'        => array(
989
					'description' => __( 'The discount code.', 'invoicing' ),
990
					'type'        => 'string',
991
					'context'     => array( 'view', 'edit', 'embed' ),
992
					'required'	  => true,
993
				),
994
995
				'type'        => array(
996
					'description' => __( 'The type of discount.', 'invoicing' ),
997
					'type'        => 'string',
998
					'enum'        => array_keys( wpinv_get_discount_types() ),
999
					'context'     => array( 'view', 'edit', 'embed' ),
1000
					'default'	  => 'percentage',
1001
				),
1002
1003
				'amount'        => array(
1004
					'description' => __( 'The discount value.', 'invoicing' ),
1005
					'type'        => 'number',
1006
					'context'     => array( 'view', 'edit', 'embed' ),
1007
					'required'	  => true,
1008
				),
1009
1010
				'status'       => array(
1011
					'description' => __( 'A named status for the discount.', 'invoicing' ),
1012
					'type'        => 'string',
1013
					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
1014
					'context'     => array( 'view', 'edit' ),
1015
				),
1016
1017
				'items'       => array(
1018
					'description' => __( 'Items which need to be in the cart to use this discount or, for "Item Discounts", which items are discounted. If left blank, this discount will be used on any item.', 'invoicing' ),
1019
					'type'        => 'array',
1020
					'context'     => array( 'view', 'edit' ),
1021
				),
1022
1023
				'exclude_items'   => array(
1024
					'description' => __( 'Items which are NOT allowed to use this discount.', 'invoicing' ),
1025
					'type'        => 'array',
1026
					'context'     => array( 'view', 'edit' ),
1027
				),
1028
1029
				'start_date'       => array(
1030
					'description' => __( 'The start date for the discount in the format of yyyy-mm-dd hh:mm:ss  . If provided, the discount can only be used after or on this date.', 'invoicing' ),
1031
					'type'        => 'string',
1032
					'context'     => array( 'view', 'edit' ),
1033
				),
1034
1035
				'end_date'        => array(
1036
					'description' => __( 'The expiration date for the discount.', 'invoicing' ),
1037
					'type'        => 'string',
1038
					'context'     => array( 'view', 'edit', 'embed' ),
1039
				),
1040
				
1041
				'minimum_amount'       => array(
1042
					'description' => __( 'Minimum amount needed to use this invoice.', 'invoicing' ),
1043
					'type'        => 'number',
1044
					'context'     => array( 'view', 'edit', 'embed' ),
1045
				),
1046
1047
				'maximum_amount'       => array(
1048
					'description' => __( 'Maximum amount needed to use this invoice.', 'invoicing' ),
1049
					'type'        => 'number',
1050
					'context'     => array( 'view', 'edit', 'embed' ),
1051
				),
1052
1053
				'recurring'       => array(
1054
					'description' => __( 'Whether the discount is applied to all recurring payments or only the first recurring payment.', 'invoicing' ),
1055
					'type'        => 'integer',
1056
					'context'     => array( 'view', 'edit', 'embed' ),
1057
				),
1058
1059
				'max_uses'        => array(
1060
					'description' => __( 'The maximum number of times this discount code can be used.', 'invoicing' ),
1061
					'type'        => 'number',
1062
					'context'     => array( 'view', 'edit', 'embed' ),
1063
				),
1064
1065
				'single_use'       => array(
1066
					'description' => __( 'Whether or not this discount can only be used once per user.', 'invoicing' ),
1067
					'type'        => 'integer',
1068
					'context'     => array( 'view', 'edit', 'embed' ),
1069
				)
1070
1071
			),
1072
		);
1073
1074
		// Add helpful links to the discount schem.
1075
		$schema['links'] = $this->get_schema_links();
1076
1077
		/**
1078
		 * Filters the discount schema for the REST API.
1079
		 *
1080
		 * Enables adding extra properties to discounts.
1081
		 *
1082
		 * @since 1.0.13
1083
		 *
1084
		 * @param array   $schema    The discount schema.
1085
		 */
1086
        $schema = apply_filters( "wpinv_rest_discount_schema", $schema );
1087
1088
		//  Cache the discount schema.
1089
		$this->schema = $schema;
1090
		
1091
		return $this->add_additional_fields_schema( $this->schema );
1092
	}
1093
1094
	/**
1095
	 * Retrieve Link Description Objects that should be added to the Schema for the discounts collection.
1096
	 *
1097
	 * @since 1.0.13
1098
	 *
1099
	 * @return array
1100
	 */
1101
	protected function get_schema_links() {
1102
1103
		$href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
1104
1105
		$links = array();
1106
1107
		$links[] = array(
1108
			'rel'          => 'https://api.w.org/action-publish',
1109
			'title'        => __( 'The current user can publish this discount.', 'invoicing' ),
1110
			'href'         => $href,
1111
			'targetSchema' => array(
1112
				'type'       => 'object',
1113
				'properties' => array(
1114
					'status' => array(
1115
						'type' => 'string',
1116
						'enum' => array( 'publish', 'future' ),
1117
					),
1118
				),
1119
			),
1120
		);
1121
1122
		return $links;
1123
	}
1124
1125
	/**
1126
	 * Prepares links for the request.
1127
	 *
1128
	 * @since 1.0.13
1129
	 *
1130
	 * @param WP_Post $discount Post Object.
1131
	 * @return array Links for the given discount.
1132
	 */
1133
	protected function prepare_links( $discount ) {
1134
1135
		// Prepare the base REST API endpoint for discounts.
1136
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
1137
1138
		// Entity meta.
1139
		$links = array(
1140
			'self'       => array(
1141
				'href' => rest_url( trailingslashit( $base ) . $discount->ID ),
1142
			),
1143
			'collection' => array(
1144
				'href' => rest_url( $base ),
1145
			),
1146
		);
1147
1148
		/**
1149
		 * Filters the returned discount links for the REST API.
1150
		 *
1151
		 * Enables adding extra links to discount API responses.
1152
		 *
1153
		 * @since 1.0.13
1154
		 *
1155
		 * @param array   $links    Rest links.
1156
		 */
1157
		return apply_filters( "wpinv_rest_discount_links", $links );
1158
1159
	}
1160
1161
	/**
1162
	 * Get the link relations available for the post and current user.
1163
	 *
1164
	 * @since 1.0.13
1165
	 *
1166
	 * @param WP_Post   $discount    WP_Post object.
1167
	 * @param WP_REST_Request $request Request object.
1168
	 * @return array List of link relations.
1169
	 */
1170
	protected function get_available_actions( $discount, $request ) {
1171
1172
		if ( 'edit' !== $request['context'] ) {
1173
			return array();
1174
		}
1175
1176
		$rels = array();
1177
1178
		// Retrieve the post type object.
1179
		$post_type = get_post_type_object( $discount->post_type );
1180
1181
		// Mark discount as published.
1182
		if ( current_user_can( $post_type->cap->publish_posts ) ) {
1183
			$rels[] = 'https://api.w.org/action-publish';
1184
		}
1185
1186
		/**
1187
		 * Filters the available discount link relations for the REST API.
1188
		 *
1189
		 * Enables adding extra link relation for the current user and request to discount responses.
1190
		 *
1191
		 * @since 1.0.13
1192
		 *
1193
		 * @param array   $rels    Available link relations.
1194
		 */
1195
		return apply_filters( "wpinv_rest_discount_link_relations", $rels );
1196
	}
1197
1198
	/**
1199
	 * Handles rest requests for discount types.
1200
	 *
1201
	 * @since 1.0.13
1202
	 * 
1203
	 * 
1204
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
1205
	 */
1206
	public function get_discount_types() {
1207
		return rest_ensure_response( wpinv_get_discount_types() );
0 ignored issues
show
Bug Best Practice introduced by
The expression return rest_ensure_respo...v_get_discount_types()) also could return the type array<string,string> which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
1208
	}
1209
    
1210
}