Passed
Pull Request — master (#399)
by Brian
04:31
created
includes/admin/subscriptions.php 1 patch
Indentation   +81 added lines, -81 removed lines patch added patch discarded remove patch
@@ -8,23 +8,23 @@  discard block
 block discarded – undo
8 8
  */
9 9
 function wpinv_subscriptions_page() {
10 10
 
11
-	if ( ! empty( $_GET['id'] ) ) {
11
+    if ( ! empty( $_GET['id'] ) ) {
12 12
 
13 13
         wpinv_recurring_subscription_details();
14 14
 
15
-		return;
15
+        return;
16 16
 
17
-	}
18
-	?>
17
+    }
18
+    ?>
19 19
 	<div class="wrap">
20 20
 
21 21
 		<h1>
22 22
 			<?php _e( 'Subscriptions', 'invoicing' ); ?>
23 23
 		</h1>
24 24
 		<?php
25
-		$subscribers_table = new WPInv_Subscription_Reports_Table();
26
-		$subscribers_table->prepare_items();
27
-		?>
25
+        $subscribers_table = new WPInv_Subscription_Reports_Table();
26
+        $subscribers_table->prepare_items();
27
+        ?>
28 28
 
29 29
 		<form id="subscribers-filter" method="get">
30 30
 
@@ -47,25 +47,25 @@  discard block
 block discarded – undo
47 47
  */
48 48
 function wpinv_recurring_subscription_details() {
49 49
 
50
-	$render = true;
50
+    $render = true;
51 51
 
52
-	if ( ! current_user_can( 'manage_invoicing' ) ) {
53
-		die( __( 'You are not permitted to view this data.', 'invoicing' ) );
54
-	}
52
+    if ( ! current_user_can( 'manage_invoicing' ) ) {
53
+        die( __( 'You are not permitted to view this data.', 'invoicing' ) );
54
+    }
55 55
 
56
-	if ( ! isset( $_GET['id'] ) || ! is_numeric( $_GET['id'] ) ) {
56
+    if ( ! isset( $_GET['id'] ) || ! is_numeric( $_GET['id'] ) ) {
57 57
         die( __( 'Invalid subscription ID Provided.', 'invoicing' ) );
58
-	}
58
+    }
59 59
 
60
-	$sub_id  = (int) $_GET['id'];
61
-	$sub     = new WPInv_Subscription( $sub_id );
62
-	$parent  = new WPInv_Invoice( $sub->parent_payment_id );
60
+    $sub_id  = (int) $_GET['id'];
61
+    $sub     = new WPInv_Subscription( $sub_id );
62
+    $parent  = new WPInv_Invoice( $sub->parent_payment_id );
63 63
 
64
-	if ( empty( $sub ) ) {
65
-		die( __( 'Invalid subscription ID Provided.', 'invoicing' ) );
66
-	}
64
+    if ( empty( $sub ) ) {
65
+        die( __( 'Invalid subscription ID Provided.', 'invoicing' ) );
66
+    }
67 67
 
68
-	?>
68
+    ?>
69 69
 	<div class="wrap">
70 70
 		<h2><?php _e( 'Subscription Details', 'invoicing' ); ?></h2>
71 71
 
@@ -89,11 +89,11 @@  discard block
 block discarded – undo
89 89
 										</td>
90 90
 										<td>
91 91
 											<?php
92
-											$frequency = WPInv_Subscriptions::wpinv_get_pretty_subscription_frequency( $sub->period, $sub->frequency );
93
-											$billing   = wpinv_price( wpinv_format_amount( $sub->recurring_amount ), $parent->get_currency() ) . ' / ' . $frequency;
94
-											$initial   = wpinv_price( wpinv_format_amount( $sub->initial_amount ), $parent->get_currency() );
95
-											printf( _x( '%s then %s', 'Initial subscription amount then billing cycle and amount', 'invoicing' ), $initial, $billing );
96
-											?>
92
+                                            $frequency = WPInv_Subscriptions::wpinv_get_pretty_subscription_frequency( $sub->period, $sub->frequency );
93
+                                            $billing   = wpinv_price( wpinv_format_amount( $sub->recurring_amount ), $parent->get_currency() ) . ' / ' . $frequency;
94
+                                            $initial   = wpinv_price( wpinv_format_amount( $sub->initial_amount ), $parent->get_currency() );
95
+                                            printf( _x( '%s then %s', 'Initial subscription amount then billing cycle and amount', 'invoicing' ), $initial, $billing );
96
+                                            ?>
97 97
 										</td>
98 98
 									</tr>
99 99
 									<tr>
@@ -135,9 +135,9 @@  discard block
 block discarded – undo
135 135
 
136 136
                                             ?>
137 137
 											<a href="<?php echo esc_url( add_query_arg( array(
138
-													'post'   => $sub->product_id,
139
-													'action' => 'edit'
140
-												), admin_url( 'post.php' ) ) ); ?>" target="_blank"><?php _e( 'View Item', 'invoicing' ) ; ?></a>
138
+                                                    'post'   => $sub->product_id,
139
+                                                    'action' => 'edit'
140
+                                                ), admin_url( 'post.php' ) ) ); ?>" target="_blank"><?php _e( 'View Item', 'invoicing' ) ; ?></a>
141 141
 										</td>
142 142
 									</tr>
143 143
 									<tr>
@@ -300,56 +300,56 @@  discard block
 block discarded – undo
300 300
  */
301 301
 function wpinv_recurring_process_subscription_update() {
302 302
 
303
-	if( empty( $_POST['sub_id'] ) ) {
304
-		return;
305
-	}
303
+    if( empty( $_POST['sub_id'] ) ) {
304
+        return;
305
+    }
306 306
 
307
-	if( empty( $_POST['wpinv_update_subscription'] ) ) {
308
-		return;
309
-	}
307
+    if( empty( $_POST['wpinv_update_subscription'] ) ) {
308
+        return;
309
+    }
310 310
 
311
-	if( ! current_user_can( 'manage_invoicing') ) {
312
-		return;
313
-	}
311
+    if( ! current_user_can( 'manage_invoicing') ) {
312
+        return;
313
+    }
314 314
 
315
-	if( ! wp_verify_nonce( $_POST['wpinv-recurring-update-nonce'], 'wpinv-recurring-update' ) ) {
316
-		wp_die( __( 'Nonce verification failed', 'invoicing' ), __( 'Error', 'invoicing' ), array( 'response' => 403 ) );
317
-	}
315
+    if( ! wp_verify_nonce( $_POST['wpinv-recurring-update-nonce'], 'wpinv-recurring-update' ) ) {
316
+        wp_die( __( 'Nonce verification failed', 'invoicing' ), __( 'Error', 'invoicing' ), array( 'response' => 403 ) );
317
+    }
318 318
 
319
-	$profile_id      = sanitize_text_field( $_POST['profile_id'] );
320
-	$transaction_id  = sanitize_text_field( $_POST['transaction_id'] );
321
-	$product_id      = absint( $_POST['product_id'] );
322
-	$subscription    = new WPInv_Subscription( absint( $_POST['sub_id'] ) );
323
-	$subscription->update( array(
324
-		'status'         => sanitize_text_field( $_POST['status'] ),
325
-		'profile_id'     => $profile_id,
326
-		'product_id'     => $product_id,
327
-		'transaction_id' => $transaction_id,
328
-	) );
319
+    $profile_id      = sanitize_text_field( $_POST['profile_id'] );
320
+    $transaction_id  = sanitize_text_field( $_POST['transaction_id'] );
321
+    $product_id      = absint( $_POST['product_id'] );
322
+    $subscription    = new WPInv_Subscription( absint( $_POST['sub_id'] ) );
323
+    $subscription->update( array(
324
+        'status'         => sanitize_text_field( $_POST['status'] ),
325
+        'profile_id'     => $profile_id,
326
+        'product_id'     => $product_id,
327
+        'transaction_id' => $transaction_id,
328
+    ) );
329 329
 
330
-	$status = sanitize_text_field( $_POST['status'] );
330
+    $status = sanitize_text_field( $_POST['status'] );
331 331
 
332
-	switch( $status ) {
332
+    switch( $status ) {
333 333
 
334
-		case 'cancelled' :
334
+        case 'cancelled' :
335 335
 
336
-			$subscription->cancel();
337
-			break;
336
+            $subscription->cancel();
337
+            break;
338 338
 
339
-		case 'expired' :
339
+        case 'expired' :
340 340
 
341
-			$subscription->expire();
342
-			break;
341
+            $subscription->expire();
342
+            break;
343 343
 
344
-		case 'completed' :
344
+        case 'completed' :
345 345
 
346
-			$subscription->complete();
347
-			break;
346
+            $subscription->complete();
347
+            break;
348 348
 
349
-	}
349
+    }
350 350
 
351
-	wp_redirect( admin_url( 'admin.php?page=wpinv-subscriptions&wpinv-message=updated&id=' . $subscription->id ) );
352
-	exit;
351
+    wp_redirect( admin_url( 'admin.php?page=wpinv-subscriptions&wpinv-message=updated&id=' . $subscription->id ) );
352
+    exit;
353 353
 
354 354
 }
355 355
 add_action( 'admin_init', 'wpinv_recurring_process_subscription_update', 1 );
@@ -363,30 +363,30 @@  discard block
 block discarded – undo
363 363
  */
364 364
 function wpinv_recurring_process_subscription_deletion() {
365 365
 
366
-	if( empty( $_POST['sub_id'] ) ) {
367
-		return;
368
-	}
366
+    if( empty( $_POST['sub_id'] ) ) {
367
+        return;
368
+    }
369 369
 
370
-	if( empty( $_POST['wpinv_delete_subscription'] ) ) {
371
-		return;
372
-	}
370
+    if( empty( $_POST['wpinv_delete_subscription'] ) ) {
371
+        return;
372
+    }
373 373
 
374
-	if( ! current_user_can( 'manage_invoicing') ) {
375
-		return;
376
-	}
374
+    if( ! current_user_can( 'manage_invoicing') ) {
375
+        return;
376
+    }
377 377
 
378
-	if( ! wp_verify_nonce( $_POST['wpinv-recurring-update-nonce'], 'wpinv-recurring-update' ) ) {
379
-		wp_die( __( 'Nonce verification failed', 'invoicing' ), __( 'Error', 'invoicing' ), array( 'response' => 403 ) );
380
-	}
378
+    if( ! wp_verify_nonce( $_POST['wpinv-recurring-update-nonce'], 'wpinv-recurring-update' ) ) {
379
+        wp_die( __( 'Nonce verification failed', 'invoicing' ), __( 'Error', 'invoicing' ), array( 'response' => 403 ) );
380
+    }
381 381
 
382
-	$subscription = new WPInv_Subscription( absint( $_POST['sub_id'] ) );
382
+    $subscription = new WPInv_Subscription( absint( $_POST['sub_id'] ) );
383 383
 
384
-	delete_post_meta( $subscription->parent_payment_id, '_wpinv_subscription_payment' );
384
+    delete_post_meta( $subscription->parent_payment_id, '_wpinv_subscription_payment' );
385 385
 
386
-	$subscription->delete();
386
+    $subscription->delete();
387 387
 
388
-	wp_redirect( admin_url( 'admin.php?page=wpinv-subscriptions&wpinv-message=deleted' ) );
389
-	exit;
388
+    wp_redirect( admin_url( 'admin.php?page=wpinv-subscriptions&wpinv-message=deleted' ) );
389
+    exit;
390 390
 
391 391
 }
392 392
 add_action( 'admin_init', 'wpinv_recurring_process_subscription_deletion', 2 );
Please login to merge, or discard this patch.
includes/class-wpinv-ajax.php 1 patch
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -14,70 +14,70 @@  discard block
 block discarded – undo
14 14
 class WPInv_Ajax {
15 15
 
16 16
     /**
17
-	 * Hook in ajax handlers.
18
-	 */
19
-	public static function init() {
20
-		add_action( 'init', array( __CLASS__, 'define_ajax' ), 0 );
21
-		add_action( 'template_redirect', array( __CLASS__, 'do_wpinv_ajax' ), 0 );
22
-		self::add_ajax_events();
17
+     * Hook in ajax handlers.
18
+     */
19
+    public static function init() {
20
+        add_action( 'init', array( __CLASS__, 'define_ajax' ), 0 );
21
+        add_action( 'template_redirect', array( __CLASS__, 'do_wpinv_ajax' ), 0 );
22
+        self::add_ajax_events();
23 23
     }
24 24
 
25 25
     /**
26
-	 * Set GetPaid AJAX constant and headers.
27
-	 */
28
-	public static function define_ajax() {
29
-
30
-		if ( ! empty( $_GET['wpinv-ajax'] ) ) {
31
-			getpaid_maybe_define_constant( 'DOING_AJAX', true );
32
-			getpaid_maybe_define_constant( 'WPInv_DOING_AJAX', true );
33
-			if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
34
-				/** @scrutinizer ignore-unhandled */ @ini_set( 'display_errors', 0 );
35
-			}
36
-			$GLOBALS['wpdb']->hide_errors();
37
-		}
26
+     * Set GetPaid AJAX constant and headers.
27
+     */
28
+    public static function define_ajax() {
29
+
30
+        if ( ! empty( $_GET['wpinv-ajax'] ) ) {
31
+            getpaid_maybe_define_constant( 'DOING_AJAX', true );
32
+            getpaid_maybe_define_constant( 'WPInv_DOING_AJAX', true );
33
+            if ( ! WP_DEBUG || ( WP_DEBUG && ! WP_DEBUG_DISPLAY ) ) {
34
+                /** @scrutinizer ignore-unhandled */ @ini_set( 'display_errors', 0 );
35
+            }
36
+            $GLOBALS['wpdb']->hide_errors();
37
+        }
38 38
 
39 39
     }
40 40
     
41 41
     /**
42
-	 * Send headers for GetPaid Ajax Requests.
43
-	 *
44
-	 * @since 1.0.18
45
-	 */
46
-	private static function wpinv_ajax_headers() {
47
-		if ( ! headers_sent() ) {
48
-			send_origin_headers();
49
-			send_nosniff_header();
50
-			nocache_headers();
51
-			header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
52
-			header( 'X-Robots-Tag: noindex' );
53
-			status_header( 200 );
54
-		}
42
+     * Send headers for GetPaid Ajax Requests.
43
+     *
44
+     * @since 1.0.18
45
+     */
46
+    private static function wpinv_ajax_headers() {
47
+        if ( ! headers_sent() ) {
48
+            send_origin_headers();
49
+            send_nosniff_header();
50
+            nocache_headers();
51
+            header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
52
+            header( 'X-Robots-Tag: noindex' );
53
+            status_header( 200 );
54
+        }
55 55
     }
56 56
     
57 57
     /**
58
-	 * Check for GetPaid Ajax request and fire action.
59
-	 */
60
-	public static function do_wpinv_ajax() {
61
-		global $wp_query;
58
+     * Check for GetPaid Ajax request and fire action.
59
+     */
60
+    public static function do_wpinv_ajax() {
61
+        global $wp_query;
62 62
 
63
-		if ( ! empty( $_GET['wpinv-ajax'] ) ) {
64
-			$wp_query->set( 'wpinv-ajax', sanitize_text_field( wp_unslash( $_GET['wpinv-ajax'] ) ) );
65
-		}
63
+        if ( ! empty( $_GET['wpinv-ajax'] ) ) {
64
+            $wp_query->set( 'wpinv-ajax', sanitize_text_field( wp_unslash( $_GET['wpinv-ajax'] ) ) );
65
+        }
66 66
 
67
-		$action = $wp_query->get( 'wpinv-ajax' );
67
+        $action = $wp_query->get( 'wpinv-ajax' );
68 68
 
69
-		if ( $action ) {
70
-			self::wpinv_ajax_headers();
71
-			$action = sanitize_text_field( $action );
72
-			do_action( 'wpinv_ajax_' . $action );
73
-			wp_die();
74
-		}
69
+        if ( $action ) {
70
+            self::wpinv_ajax_headers();
71
+            $action = sanitize_text_field( $action );
72
+            do_action( 'wpinv_ajax_' . $action );
73
+            wp_die();
74
+        }
75 75
 
76 76
     }
77 77
 
78 78
     /**
79
-	 * Hook in ajax methods.
80
-	 */
79
+     * Hook in ajax methods.
80
+     */
81 81
     public static function add_ajax_events() {
82 82
 
83 83
         // array( 'event' => is_frontend )
@@ -260,24 +260,24 @@  discard block
 block discarded – undo
260 260
         }
261 261
 
262 262
         // Is the request set up correctly?
263
-		if ( empty( $_GET['form'] ) && empty( $_GET['item'] ) ) {
264
-			echo aui()->alert(
265
-				array(
266
-					'type'    => 'warning',
267
-					'content' => __( 'No payment form or item provided', 'invoicing' ),
268
-				)
263
+        if ( empty( $_GET['form'] ) && empty( $_GET['item'] ) ) {
264
+            echo aui()->alert(
265
+                array(
266
+                    'type'    => 'warning',
267
+                    'content' => __( 'No payment form or item provided', 'invoicing' ),
268
+                )
269 269
             );
270 270
             exit;
271 271
         }
272 272
 
273 273
         // Payment form or button?
274
-		if ( ! empty( $_GET['form'] ) ) {
274
+        if ( ! empty( $_GET['form'] ) ) {
275 275
             getpaid_display_payment_form( $_GET['form'] );
276
-		} else if( $_GET['invoice'] ) {
277
-		    echo getpaid_display_invoice_payment_form( $_GET['invoice'] );
276
+        } else if( $_GET['invoice'] ) {
277
+            echo getpaid_display_invoice_payment_form( $_GET['invoice'] );
278 278
         } else {
279
-			$items = getpaid_convert_items_to_array( $_GET['item'] );
280
-		    getpaid_display_item_payment_form( $items );
279
+            $items = getpaid_convert_items_to_array( $_GET['item'] );
280
+            getpaid_display_item_payment_form( $items );
281 281
         }
282 282
 
283 283
         exit;
Please login to merge, or discard this patch.
includes/api/class-getpaid-rest-controller.php 1 patch
Indentation   +542 added lines, -542 removed lines patch added patch discarded remove patch
@@ -21,570 +21,570 @@
 block discarded – undo
21 21
  */
22 22
 class GetPaid_REST_Controller extends WP_REST_Controller {
23 23
 
24
-	/**
24
+    /**
25 25
      * The namespaces of this controller's route.
26 26
      *
27 27
      * @since 1.0.19
28 28
      * @var array
29 29
      */
30
-	protected $namespaces;
30
+    protected $namespaces;
31 31
 
32
-	/**
32
+    /**
33 33
      * The official namespace of this controller's route.
34 34
      *
35 35
      * @since 1.0.19
36 36
      * @var string
37 37
      */
38
-	protected $namespace = 'getpaid/v1';
38
+    protected $namespace = 'getpaid/v1';
39 39
 
40
-	/**
40
+    /**
41 41
      * Cached results of get_item_schema.
42 42
      *
43 43
      * @since 1.0.19
44 44
      * @var array
45 45
      */
46
-	protected $schema;
46
+    protected $schema;
47 47
 
48 48
     /**
49
-	 * Constructor.
50
-	 *
51
-	 * @since 1.0.19
52
-	 *
53
-	 */
54
-	public function __construct() {
55
-
56
-		// Offer several namespaces for backwards compatibility.
57
-		$this->namespaces = apply_filters(
58
-			'getpaid_rest_api_namespaces',
59
-			array(
60
-				'getpaid/v1',
61
-				'invoicing/v1',
62
-				'wpi/v1'
63
-			)
64
-		);
65
-
66
-		// Register REST routes.
49
+     * Constructor.
50
+     *
51
+     * @since 1.0.19
52
+     *
53
+     */
54
+    public function __construct() {
55
+
56
+        // Offer several namespaces for backwards compatibility.
57
+        $this->namespaces = apply_filters(
58
+            'getpaid_rest_api_namespaces',
59
+            array(
60
+                'getpaid/v1',
61
+                'invoicing/v1',
62
+                'wpi/v1'
63
+            )
64
+        );
65
+
66
+        // Register REST routes.
67 67
         add_action( 'rest_api_init', array( $this, 'register_routes' ) );
68 68
 
69
-	}
70
-
71
-	/**
72
-	 * Registers routes for each namespace.
73
-	 *
74
-	 * @since 1.0.19
75
-	 *
76
-	 */
77
-	public function register_routes() {
78
-
79
-		foreach ( $this->namespaces as $namespace ) {
80
-			$this->register_namespace_routes( $namespace );
81
-		}
82
-
83
-	}
84
-
85
-	/**
86
-	 * Registers routes for a namespace.
87
-	 *
88
-	 * @since 1.0.19
89
-	 *
90
-	 * @param string $namespace
91
-	 */
92
-	public function register_namespace_routes( /** @scrutinizer ignore-unused */ $namespace ) {
93
-
94
-		getpaid_doing_it_wrong(
95
-			__CLASS__ . '::' .__METHOD__,
96
-			/* translators: %s: register_namespace_routes() */
97
-			sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ),
98
-			'1.0.19'
99
-		);
100
-
101
-	}
102
-
103
-	/**
104
-	 * Get normalized rest base.
105
-	 *
106
-	 * @return string
107
-	 */
108
-	protected function get_normalized_rest_base() {
109
-		return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
110
-	}
111
-
112
-	/**
113
-	 * Fill batches.
114
-	 *
115
-	 * @param array array of request items.
116
-	 * @return array
117
-	 */
118
-	protected function fill_batch_keys( $items ) {
119
-
120
-		$items['create'] = empty( $items['create'] ) ? array() : $items['create'];
121
-		$items['update'] = empty( $items['update'] ) ? array() : $items['update'];
122
-		$items['delete'] = empty( $items['delete'] ) ? array() : wp_parse_id_list( $items['delete'] );
123
-		return $items;
124
-
125
-	}
126
-
127
-	/**
128
-	 * Check batch limit.
129
-	 *
130
-	 * @param array $items Request items.
131
-	 * @return bool|WP_Error
132
-	 */
133
-	protected function check_batch_limit( $items ) {
134
-		$limit = apply_filters( 'getpaid_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
135
-		$total = count( $items['create'] ) + count( $items['update'] ) + count( $items['delete'] );
136
-
137
-		if ( $total > $limit ) {
138
-			/* translators: %s: items limit */
139
-			return new WP_Error( 'getpaid_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'invoicing' ), $limit ), array( 'status' => 413 ) );
140
-		}
141
-
142
-		return true;
143
-	}
144
-
145
-	/**
146
-	 * Bulk create items.
147
-	 *
148
-	 * @param array $items Array of items to create.
149
-	 * @param WP_REST_Request $request Full details about the request.
150
-	 * @param WP_REST_Server $wp_rest_server
151
-	 * @return array()
152
-	 */
153
-	protected function batch_create_items( $items, $request, $wp_rest_server ) {
154
-
155
-		$query  = $request->get_query_params();
156
-		$create = array();
157
-
158
-		foreach ( $items as $item ) {
159
-			$_item = new WP_REST_Request( 'POST' );
160
-
161
-			// Default parameters.
162
-			$defaults = array();
163
-			$schema   = $this->get_public_item_schema();
164
-			foreach ( $schema['properties'] as $arg => $options ) {
165
-				if ( isset( $options['default'] ) ) {
166
-					$defaults[ $arg ] = $options['default'];
167
-				}
168
-			}
169
-			$_item->set_default_params( $defaults );
170
-
171
-			// Set request parameters.
172
-			$_item->set_body_params( $item );
173
-
174
-			// Set query (GET) parameters.
175
-			$_item->set_query_params( $query );
176
-
177
-			// Create the item.
178
-			$_response = $this->create_item( $_item );
179
-
180
-			// If an error occured...
181
-			if ( is_wp_error( $_response ) ) {
182
-
183
-				$create[]   = array(
184
-					'id'    => 0,
185
-					'error' => array(
186
-						'code'    => $_response->get_error_code(),
187
-						'message' => $_response->get_error_message(),
188
-						'data'    => $_response->get_error_data(),
189
-					),
190
-				);
191
-
192
-				continue;
193
-			}
194
-
195
-			$create[] = $wp_rest_server->response_to_data( /** @scrutinizer ignore-type */ $_response, false );
196
-
197
-		}
198
-
199
-		return $create;
69
+    }
70
+
71
+    /**
72
+     * Registers routes for each namespace.
73
+     *
74
+     * @since 1.0.19
75
+     *
76
+     */
77
+    public function register_routes() {
78
+
79
+        foreach ( $this->namespaces as $namespace ) {
80
+            $this->register_namespace_routes( $namespace );
81
+        }
82
+
83
+    }
84
+
85
+    /**
86
+     * Registers routes for a namespace.
87
+     *
88
+     * @since 1.0.19
89
+     *
90
+     * @param string $namespace
91
+     */
92
+    public function register_namespace_routes( /** @scrutinizer ignore-unused */ $namespace ) {
93
+
94
+        getpaid_doing_it_wrong(
95
+            __CLASS__ . '::' .__METHOD__,
96
+            /* translators: %s: register_namespace_routes() */
97
+            sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ),
98
+            '1.0.19'
99
+        );
100
+
101
+    }
102
+
103
+    /**
104
+     * Get normalized rest base.
105
+     *
106
+     * @return string
107
+     */
108
+    protected function get_normalized_rest_base() {
109
+        return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
110
+    }
111
+
112
+    /**
113
+     * Fill batches.
114
+     *
115
+     * @param array array of request items.
116
+     * @return array
117
+     */
118
+    protected function fill_batch_keys( $items ) {
119
+
120
+        $items['create'] = empty( $items['create'] ) ? array() : $items['create'];
121
+        $items['update'] = empty( $items['update'] ) ? array() : $items['update'];
122
+        $items['delete'] = empty( $items['delete'] ) ? array() : wp_parse_id_list( $items['delete'] );
123
+        return $items;
124
+
125
+    }
126
+
127
+    /**
128
+     * Check batch limit.
129
+     *
130
+     * @param array $items Request items.
131
+     * @return bool|WP_Error
132
+     */
133
+    protected function check_batch_limit( $items ) {
134
+        $limit = apply_filters( 'getpaid_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
135
+        $total = count( $items['create'] ) + count( $items['update'] ) + count( $items['delete'] );
136
+
137
+        if ( $total > $limit ) {
138
+            /* translators: %s: items limit */
139
+            return new WP_Error( 'getpaid_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'invoicing' ), $limit ), array( 'status' => 413 ) );
140
+        }
141
+
142
+        return true;
143
+    }
144
+
145
+    /**
146
+     * Bulk create items.
147
+     *
148
+     * @param array $items Array of items to create.
149
+     * @param WP_REST_Request $request Full details about the request.
150
+     * @param WP_REST_Server $wp_rest_server
151
+     * @return array()
152
+     */
153
+    protected function batch_create_items( $items, $request, $wp_rest_server ) {
154
+
155
+        $query  = $request->get_query_params();
156
+        $create = array();
157
+
158
+        foreach ( $items as $item ) {
159
+            $_item = new WP_REST_Request( 'POST' );
160
+
161
+            // Default parameters.
162
+            $defaults = array();
163
+            $schema   = $this->get_public_item_schema();
164
+            foreach ( $schema['properties'] as $arg => $options ) {
165
+                if ( isset( $options['default'] ) ) {
166
+                    $defaults[ $arg ] = $options['default'];
167
+                }
168
+            }
169
+            $_item->set_default_params( $defaults );
170
+
171
+            // Set request parameters.
172
+            $_item->set_body_params( $item );
173
+
174
+            // Set query (GET) parameters.
175
+            $_item->set_query_params( $query );
176
+
177
+            // Create the item.
178
+            $_response = $this->create_item( $_item );
179
+
180
+            // If an error occured...
181
+            if ( is_wp_error( $_response ) ) {
182
+
183
+                $create[]   = array(
184
+                    'id'    => 0,
185
+                    'error' => array(
186
+                        'code'    => $_response->get_error_code(),
187
+                        'message' => $_response->get_error_message(),
188
+                        'data'    => $_response->get_error_data(),
189
+                    ),
190
+                );
191
+
192
+                continue;
193
+            }
194
+
195
+            $create[] = $wp_rest_server->response_to_data( /** @scrutinizer ignore-type */ $_response, false );
196
+
197
+        }
198
+
199
+        return $create;
200
+
201
+    }
202
+
203
+    /**
204
+     * Bulk update items.
205
+     *
206
+     * @param array $items Array of items to update.
207
+     * @param WP_REST_Request $request Full details about the request.
208
+     * @param WP_REST_Server $wp_rest_server
209
+     * @return array()
210
+     */
211
+    protected function batch_update_items( $items, $request, $wp_rest_server ) {
212
+
213
+        $query  = $request->get_query_params();
214
+        $update = array();
215
+
216
+        foreach ( $items as $item ) {
217
+
218
+            // Create a dummy request.
219
+            $_item = new WP_REST_Request( 'PUT' );
220
+
221
+            // Add body params.
222
+            $_item->set_body_params( $item );
223
+
224
+            // Set query (GET) parameters.
225
+            $_item->set_query_params( $query );
226
+
227
+            // Update the item.
228
+            $_response = $this->update_item( $_item );
229
+
230
+            // If an error occured...
231
+            if ( is_wp_error( $_response ) ) {
232
+
233
+                $update[] = array(
234
+                    'id'    => $item['id'],
235
+                    'error' => array(
236
+                        'code'    => $_response->get_error_code(),
237
+                        'message' => $_response->get_error_message(),
238
+                        'data'    => $_response->get_error_data(),
239
+                    ),
240
+                );
241
+
242
+                continue;
243
+
244
+            }
245
+
246
+            $update[] = $wp_rest_server->response_to_data( /** @scrutinizer ignore-type */ $_response, false );
247
+
248
+        }
249
+
250
+        return $update;
251
+
252
+    }
253
+
254
+    /**
255
+     * Bulk delete items.
256
+     *
257
+     * @param array $items Array of items to delete.
258
+     * @param WP_REST_Server $wp_rest_server
259
+     * @return array()
260
+     */
261
+    protected function batch_delete_items( $items, $wp_rest_server ) {
262
+
263
+        $delete = array();
264
+
265
+        foreach ( array_filter( $items ) as $id ) {
266
+
267
+            // Prepare the request.
268
+            $_item = new WP_REST_Request( 'DELETE' );
269
+            $_item->set_query_params(
270
+                array(
271
+                    'id'    => $id,
272
+                    'force' => true,
273
+                )
274
+            );
275
+
276
+            // Delete the item.
277
+            $_response = $this->delete_item( $_item );
278
+
279
+            if ( is_wp_error( $_response ) ) {
280
+
281
+                $delete[] = array(
282
+                    'id'    => $id,
283
+                    'error' => array(
284
+                        'code'    => $_response->get_error_code(),
285
+                        'message' => $_response->get_error_message(),
286
+                        'data'    => $_response->get_error_data(),
287
+                    ),
288
+                );
289
+
290
+                continue;
291
+            }
200 292
 
201
-	}
202
-
203
-	/**
204
-	 * Bulk update items.
205
-	 *
206
-	 * @param array $items Array of items to update.
207
-	 * @param WP_REST_Request $request Full details about the request.
208
-	 * @param WP_REST_Server $wp_rest_server
209
-	 * @return array()
210
-	 */
211
-	protected function batch_update_items( $items, $request, $wp_rest_server ) {
212
-
213
-		$query  = $request->get_query_params();
214
-		$update = array();
215
-
216
-		foreach ( $items as $item ) {
217
-
218
-			// Create a dummy request.
219
-			$_item = new WP_REST_Request( 'PUT' );
220
-
221
-			// Add body params.
222
-			$_item->set_body_params( $item );
223
-
224
-			// Set query (GET) parameters.
225
-			$_item->set_query_params( $query );
226
-
227
-			// Update the item.
228
-			$_response = $this->update_item( $_item );
229
-
230
-			// If an error occured...
231
-			if ( is_wp_error( $_response ) ) {
232
-
233
-				$update[] = array(
234
-					'id'    => $item['id'],
235
-					'error' => array(
236
-						'code'    => $_response->get_error_code(),
237
-						'message' => $_response->get_error_message(),
238
-						'data'    => $_response->get_error_data(),
239
-					),
240
-				);
241
-
242
-				continue;
243
-
244
-			}
245
-
246
-			$update[] = $wp_rest_server->response_to_data( /** @scrutinizer ignore-type */ $_response, false );
247
-
248
-		}
249
-
250
-		return $update;
251
-
252
-	}
293
+            $delete[] = $wp_rest_server->response_to_data( /** @scrutinizer ignore-type */ $_response, false );
253 294
 
254
-	/**
255
-	 * Bulk delete items.
256
-	 *
257
-	 * @param array $items Array of items to delete.
258
-	 * @param WP_REST_Server $wp_rest_server
259
-	 * @return array()
260
-	 */
261
-	protected function batch_delete_items( $items, $wp_rest_server ) {
262
-
263
-		$delete = array();
264
-
265
-		foreach ( array_filter( $items ) as $id ) {
266
-
267
-			// Prepare the request.
268
-			$_item = new WP_REST_Request( 'DELETE' );
269
-			$_item->set_query_params(
270
-				array(
271
-					'id'    => $id,
272
-					'force' => true,
273
-				)
274
-			);
275
-
276
-			// Delete the item.
277
-			$_response = $this->delete_item( $_item );
278
-
279
-			if ( is_wp_error( $_response ) ) {
280
-
281
-				$delete[] = array(
282
-					'id'    => $id,
283
-					'error' => array(
284
-						'code'    => $_response->get_error_code(),
285
-						'message' => $_response->get_error_message(),
286
-						'data'    => $_response->get_error_data(),
287
-					),
288
-				);
289
-
290
-				continue;
291
-			}
292
-
293
-			$delete[] = $wp_rest_server->response_to_data( /** @scrutinizer ignore-type */ $_response, false );
294
-
295
-		}
296
-
297
-		return $delete;
298
-
299
-	}
300
-
301
-	/**
302
-	 * Bulk create, update and delete items.
303
-	 *
304
-	 * @param WP_REST_Request $request Full details about the request.
305
-	 * @return WP_Error|array.
306
-	 */
307
-	public function batch_items( $request ) {
308
-		global $wp_rest_server;
309
-
310
-		// Prepare the batch items.
311
-		$items = $this->fill_batch_keys( array_filter( $request->get_params() ) );
312
-
313
-		// Ensure that the batch has not exceeded the limit to prevent abuse.
314
-		$limit = $this->check_batch_limit( $items );
315
-		if ( is_wp_error( $limit ) ) {
316
-			return $limit;
317
-		}
318
-
319
-		// Process the items.
320
-		return array(
321
-			'create' => $this->batch_create_items( $items['create'], $request, $wp_rest_server ),
322
-			'update' => $this->batch_update_items( $items['update'], $request, $wp_rest_server ),
323
-			'delete' => $this->batch_delete_items( $items['delete'], $wp_rest_server ),
324
-		);
325
-
326
-	}
327
-
328
-	/**
329
-	 * Add meta query.
330
-	 *
331
-	 * @since 1.0.19
332
-	 * @param array $args       Query args.
333
-	 * @param array $meta_query Meta query.
334
-	 * @return array
335
-	 */
336
-	protected function add_meta_query( $args, $meta_query ) {
337
-		if ( empty( $args['meta_query'] ) ) {
338
-			$args['meta_query'] = array();
339
-		}
340
-
341
-		$args['meta_query'][] = $meta_query;
342
-
343
-		return $args['meta_query'];
344
-	}
345
-
346
-	/**
347
-	 * Get the batch schema, conforming to JSON Schema.
348
-	 *
349
-	 * @return array
350
-	 */
351
-	public function get_public_batch_schema() {
352
-
353
-		return array(
354
-			'$schema'    => 'http://json-schema.org/draft-04/schema#',
355
-			'title'      => 'batch',
356
-			'type'       => 'object',
357
-			'properties' => array(
358
-				'create' => array(
359
-					'description' => __( 'List of created resources.', 'invoicing' ),
360
-					'type'        => 'array',
361
-					'context'     => array( 'view', 'edit' ),
362
-					'items'       => array(
363
-						'type'    => 'object',
364
-					),
365
-				),
366
-				'update' => array(
367
-					'description' => __( 'List of updated resources.', 'invoicing' ),
368
-					'type'        => 'array',
369
-					'context'     => array( 'view', 'edit' ),
370
-					'items'       => array(
371
-						'type'    => 'object',
372
-					),
373
-				),
374
-				'delete' => array(
375
-					'description' => __( 'List of deleted resources.', 'invoicing' ),
376
-					'type'        => 'array',
377
-					'context'     => array( 'view', 'edit' ),
378
-					'items'       => array(
379
-						'type'    => 'integer',
380
-					),
381
-				),
382
-			),
383
-		);
384
-
385
-	}
386
-
387
-	/**
388
-	 * Returns the value of schema['properties']
389
-	 * 
390
-	 * i.e Schema fields.
391
-	 *
392
-	 * @since 1.0.19
393
-	 * @return array
394
-	 */
395
-	protected function get_schema_properties() {
396
-
397
-		$schema     = $this->get_item_schema();
398
-		$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
399
-
400
-		// For back-compat, include any field with an empty schema
401
-		// because it won't be present in $this->get_item_schema().
402
-		foreach ( $this->get_additional_fields() as $field_name => $field_options ) {
403
-			if ( is_null( $field_options['schema'] ) ) {
404
-				$properties[ $field_name ] = $field_options;
405
-			}
406
-		}
407
-
408
-		return $properties;
409
-	}
410
-
411
-	/**
412
-	 * Filters fields by context.
413
-	 *
414
-	 * @param array $fields Array of fields
415
-	 * @param string|null context view, edit or embed
416
-	 * @since 1.0.19
417
-	 * @return array
418
-	 */
419
-	protected function filter_response_fields_by_context( $fields, $context ) {
420
-
421
-		if ( empty( $context ) ) {
422
-			return $fields;
423
-		}
424
-
425
-		foreach ( $fields as $name => $options ) {
426
-			if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
427
-				unset( $fields[ $name ] );
428
-			}
429
-		}
430
-
431
-		return $fields;
432
-
433
-	}
434
-
435
-	/**
436
-	 * Filters fields by an array of requested fields.
437
-	 *
438
-	 * @param array $fields Array of available fields
439
-	 * @param array $requested array of requested fields.
440
-	 * @since 1.0.19
441
-	 * @return array
442
-	 */
443
-	protected function filter_response_fields_by_array( $fields, $requested ) {
444
-
445
-		// Trim off any whitespace from the list array.
446
-		$requested = array_map( 'trim', $requested );
447
-
448
-		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
449
-		if ( in_array( 'id', $fields, true ) ) {
450
-			$requested[] = 'id';
451
-		}
452
-
453
-		// Get rid of duplicate fields.
454
-		$requested = array_unique( $requested );
455
-
456
-		// Return the list of all included fields which are available.
457
-		return array_reduce(
458
-			$requested,
459
-			function( $response_fields, $field ) use ( $fields ) {
460
-
461
-				if ( in_array( $field, $fields, true ) ) {
462
-					$response_fields[] = $field;
463
-					return $response_fields;
464
-				}
465
-
466
-				// Check for nested fields if $field is not a direct match.
467
-				$nested_fields = explode( '.', $field );
468
-
469
-				// A nested field is included so long as its top-level property is
470
-				// present in the schema.
471
-				if ( in_array( $nested_fields[0], $fields, true ) ) {
472
-					$response_fields[] = $field;
473
-				}
474
-
475
-				return $response_fields;
476
-			},
477
-			array()
478
-		);
479
-
480
-	}
481
-
482
-	/**
483
-	 * Gets an array of fields to be included on the response.
484
-	 *
485
-	 * Included fields are based on item schema and `_fields=` request argument.
486
-	 * Copied from WordPress 5.3 to support old versions.
487
-	 *
488
-	 * @since 1.0.19
489
-	 * @param WP_REST_Request $request Full details about the request.
490
-	 * @return array Fields to be included in the response.
491
-	 */
492
-	public function get_fields_for_response( $request ) {
493
-
494
-		// Retrieve fields in the schema.
495
-		$properties = $this->get_schema_properties();
496
-
497
-		// Exclude fields that specify a different context than the request context.
498
-		$properties = $this->filter_response_fields_by_context( $properties, $request['context'] );
499
-
500
-		// We only need the field keys.
501
-		$fields = array_keys( $properties );
502
-
503
-		// Is the user filtering the response fields??
504
-		if ( empty( $request['_fields'] ) ) {
505
-			return $fields;
506
-		}
507
-
508
-		return $this->filter_response_fields_by_array( $fields, wpinv_parse_list( $request['_fields'] ) );
509
-
510
-	}
511
-
512
-	/**
513
-	 * Limits an object to the requested fields.
514
-	 *
515
-	 * Included fields are based on the `_fields` request argument.
516
-	 *
517
-	 * @since 1.0.19
518
-	 * @param array $data Fields to include in the response.
519
-	 * @param array $fields Requested fields.
520
-	 * @return array Fields to be included in the response.
521
-	 */
522
-	public function limit_object_to_requested_fields( $data, $fields, $prefix = '' ) {
523
-
524
-		// Is the user filtering the response fields??
525
-		if ( empty( $fields ) ) {
526
-			return $data;
527
-		}
528
-
529
-		foreach ( $data as $key => $value ) {
530
-
531
-			// Numeric arrays.
532
-			if ( is_numeric( $key ) && is_array( $value ) ) {
533
-				$data[ $key ] = $this->limit_object_to_requested_fields( $value, $fields, $prefix );
534
-				continue;
535
-			}
536
-
537
-			// Generate a new prefix.
538
-			$new_prefix = empty( $prefix ) ? $key : "$prefix.$key";
539
-
540
-			// Check if it was requested.
541
-			if ( ! empty( $key ) && ! $this->is_field_included( $new_prefix, $fields ) ) {
542
-				unset( $data[ $key ] );
543
-				continue;
544
-			}
545
-
546
-			if ( $key != 'meta_data' && is_array( $value ) ) {
547
-				$data[ $key ] = $this->limit_object_to_requested_fields( $value, $fields, $new_prefix );
548
-			}
549
-
550
-		}
551
-
552
-		return $data;
553
-	}
554
-
555
-	/**
556
-	 * Given an array of fields to include in a response, some of which may be
557
-	 * `nested.fields`, determine whether the provided field should be included
558
-	 * in the response body.
559
-	 *
560
-	 * Copied from WordPress 5.3 to support old versions.
561
-	 *
562
-	 * @since 1.0.19
563
-	 *
564
-	 * @param string $field  A field to test for inclusion in the response body.
565
-	 * @param array  $fields An array of string fields supported by the endpoint.
566
-	 * @return bool Whether to include the field or not.
567
-	 * @see rest_is_field_included()
568
-	 */
569
-	public function is_field_included( $field, $fields ) {
570
-		if ( in_array( $field, $fields, true ) ) {
571
-			return true;
572
-		}
573
-
574
-		foreach ( $fields as $accepted_field ) {
575
-			// Check to see if $field is the parent of any item in $fields.
576
-			// A field "parent" should be accepted if "parent.child" is accepted.
577
-			if ( strpos( $accepted_field, "$field." ) === 0 ) {
578
-				return true;
579
-			}
580
-			// Conversely, if "parent" is accepted, all "parent.child" fields
581
-			// should also be accepted.
582
-			if ( strpos( $field, "$accepted_field." ) === 0 ) {
583
-				return true;
584
-			}
585
-		}
586
-
587
-		return false;
588
-	}
295
+        }
296
+
297
+        return $delete;
298
+
299
+    }
300
+
301
+    /**
302
+     * Bulk create, update and delete items.
303
+     *
304
+     * @param WP_REST_Request $request Full details about the request.
305
+     * @return WP_Error|array.
306
+     */
307
+    public function batch_items( $request ) {
308
+        global $wp_rest_server;
309
+
310
+        // Prepare the batch items.
311
+        $items = $this->fill_batch_keys( array_filter( $request->get_params() ) );
312
+
313
+        // Ensure that the batch has not exceeded the limit to prevent abuse.
314
+        $limit = $this->check_batch_limit( $items );
315
+        if ( is_wp_error( $limit ) ) {
316
+            return $limit;
317
+        }
318
+
319
+        // Process the items.
320
+        return array(
321
+            'create' => $this->batch_create_items( $items['create'], $request, $wp_rest_server ),
322
+            'update' => $this->batch_update_items( $items['update'], $request, $wp_rest_server ),
323
+            'delete' => $this->batch_delete_items( $items['delete'], $wp_rest_server ),
324
+        );
325
+
326
+    }
327
+
328
+    /**
329
+     * Add meta query.
330
+     *
331
+     * @since 1.0.19
332
+     * @param array $args       Query args.
333
+     * @param array $meta_query Meta query.
334
+     * @return array
335
+     */
336
+    protected function add_meta_query( $args, $meta_query ) {
337
+        if ( empty( $args['meta_query'] ) ) {
338
+            $args['meta_query'] = array();
339
+        }
340
+
341
+        $args['meta_query'][] = $meta_query;
342
+
343
+        return $args['meta_query'];
344
+    }
345
+
346
+    /**
347
+     * Get the batch schema, conforming to JSON Schema.
348
+     *
349
+     * @return array
350
+     */
351
+    public function get_public_batch_schema() {
352
+
353
+        return array(
354
+            '$schema'    => 'http://json-schema.org/draft-04/schema#',
355
+            'title'      => 'batch',
356
+            'type'       => 'object',
357
+            'properties' => array(
358
+                'create' => array(
359
+                    'description' => __( 'List of created resources.', 'invoicing' ),
360
+                    'type'        => 'array',
361
+                    'context'     => array( 'view', 'edit' ),
362
+                    'items'       => array(
363
+                        'type'    => 'object',
364
+                    ),
365
+                ),
366
+                'update' => array(
367
+                    'description' => __( 'List of updated resources.', 'invoicing' ),
368
+                    'type'        => 'array',
369
+                    'context'     => array( 'view', 'edit' ),
370
+                    'items'       => array(
371
+                        'type'    => 'object',
372
+                    ),
373
+                ),
374
+                'delete' => array(
375
+                    'description' => __( 'List of deleted resources.', 'invoicing' ),
376
+                    'type'        => 'array',
377
+                    'context'     => array( 'view', 'edit' ),
378
+                    'items'       => array(
379
+                        'type'    => 'integer',
380
+                    ),
381
+                ),
382
+            ),
383
+        );
384
+
385
+    }
386
+
387
+    /**
388
+     * Returns the value of schema['properties']
389
+     * 
390
+     * i.e Schema fields.
391
+     *
392
+     * @since 1.0.19
393
+     * @return array
394
+     */
395
+    protected function get_schema_properties() {
396
+
397
+        $schema     = $this->get_item_schema();
398
+        $properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
399
+
400
+        // For back-compat, include any field with an empty schema
401
+        // because it won't be present in $this->get_item_schema().
402
+        foreach ( $this->get_additional_fields() as $field_name => $field_options ) {
403
+            if ( is_null( $field_options['schema'] ) ) {
404
+                $properties[ $field_name ] = $field_options;
405
+            }
406
+        }
407
+
408
+        return $properties;
409
+    }
410
+
411
+    /**
412
+     * Filters fields by context.
413
+     *
414
+     * @param array $fields Array of fields
415
+     * @param string|null context view, edit or embed
416
+     * @since 1.0.19
417
+     * @return array
418
+     */
419
+    protected function filter_response_fields_by_context( $fields, $context ) {
420
+
421
+        if ( empty( $context ) ) {
422
+            return $fields;
423
+        }
424
+
425
+        foreach ( $fields as $name => $options ) {
426
+            if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
427
+                unset( $fields[ $name ] );
428
+            }
429
+        }
430
+
431
+        return $fields;
432
+
433
+    }
434
+
435
+    /**
436
+     * Filters fields by an array of requested fields.
437
+     *
438
+     * @param array $fields Array of available fields
439
+     * @param array $requested array of requested fields.
440
+     * @since 1.0.19
441
+     * @return array
442
+     */
443
+    protected function filter_response_fields_by_array( $fields, $requested ) {
444
+
445
+        // Trim off any whitespace from the list array.
446
+        $requested = array_map( 'trim', $requested );
447
+
448
+        // Always persist 'id', because it can be needed for add_additional_fields_to_object().
449
+        if ( in_array( 'id', $fields, true ) ) {
450
+            $requested[] = 'id';
451
+        }
452
+
453
+        // Get rid of duplicate fields.
454
+        $requested = array_unique( $requested );
455
+
456
+        // Return the list of all included fields which are available.
457
+        return array_reduce(
458
+            $requested,
459
+            function( $response_fields, $field ) use ( $fields ) {
460
+
461
+                if ( in_array( $field, $fields, true ) ) {
462
+                    $response_fields[] = $field;
463
+                    return $response_fields;
464
+                }
465
+
466
+                // Check for nested fields if $field is not a direct match.
467
+                $nested_fields = explode( '.', $field );
468
+
469
+                // A nested field is included so long as its top-level property is
470
+                // present in the schema.
471
+                if ( in_array( $nested_fields[0], $fields, true ) ) {
472
+                    $response_fields[] = $field;
473
+                }
474
+
475
+                return $response_fields;
476
+            },
477
+            array()
478
+        );
479
+
480
+    }
481
+
482
+    /**
483
+     * Gets an array of fields to be included on the response.
484
+     *
485
+     * Included fields are based on item schema and `_fields=` request argument.
486
+     * Copied from WordPress 5.3 to support old versions.
487
+     *
488
+     * @since 1.0.19
489
+     * @param WP_REST_Request $request Full details about the request.
490
+     * @return array Fields to be included in the response.
491
+     */
492
+    public function get_fields_for_response( $request ) {
493
+
494
+        // Retrieve fields in the schema.
495
+        $properties = $this->get_schema_properties();
496
+
497
+        // Exclude fields that specify a different context than the request context.
498
+        $properties = $this->filter_response_fields_by_context( $properties, $request['context'] );
499
+
500
+        // We only need the field keys.
501
+        $fields = array_keys( $properties );
502
+
503
+        // Is the user filtering the response fields??
504
+        if ( empty( $request['_fields'] ) ) {
505
+            return $fields;
506
+        }
507
+
508
+        return $this->filter_response_fields_by_array( $fields, wpinv_parse_list( $request['_fields'] ) );
509
+
510
+    }
511
+
512
+    /**
513
+     * Limits an object to the requested fields.
514
+     *
515
+     * Included fields are based on the `_fields` request argument.
516
+     *
517
+     * @since 1.0.19
518
+     * @param array $data Fields to include in the response.
519
+     * @param array $fields Requested fields.
520
+     * @return array Fields to be included in the response.
521
+     */
522
+    public function limit_object_to_requested_fields( $data, $fields, $prefix = '' ) {
523
+
524
+        // Is the user filtering the response fields??
525
+        if ( empty( $fields ) ) {
526
+            return $data;
527
+        }
528
+
529
+        foreach ( $data as $key => $value ) {
530
+
531
+            // Numeric arrays.
532
+            if ( is_numeric( $key ) && is_array( $value ) ) {
533
+                $data[ $key ] = $this->limit_object_to_requested_fields( $value, $fields, $prefix );
534
+                continue;
535
+            }
536
+
537
+            // Generate a new prefix.
538
+            $new_prefix = empty( $prefix ) ? $key : "$prefix.$key";
539
+
540
+            // Check if it was requested.
541
+            if ( ! empty( $key ) && ! $this->is_field_included( $new_prefix, $fields ) ) {
542
+                unset( $data[ $key ] );
543
+                continue;
544
+            }
545
+
546
+            if ( $key != 'meta_data' && is_array( $value ) ) {
547
+                $data[ $key ] = $this->limit_object_to_requested_fields( $value, $fields, $new_prefix );
548
+            }
549
+
550
+        }
551
+
552
+        return $data;
553
+    }
554
+
555
+    /**
556
+     * Given an array of fields to include in a response, some of which may be
557
+     * `nested.fields`, determine whether the provided field should be included
558
+     * in the response body.
559
+     *
560
+     * Copied from WordPress 5.3 to support old versions.
561
+     *
562
+     * @since 1.0.19
563
+     *
564
+     * @param string $field  A field to test for inclusion in the response body.
565
+     * @param array  $fields An array of string fields supported by the endpoint.
566
+     * @return bool Whether to include the field or not.
567
+     * @see rest_is_field_included()
568
+     */
569
+    public function is_field_included( $field, $fields ) {
570
+        if ( in_array( $field, $fields, true ) ) {
571
+            return true;
572
+        }
573
+
574
+        foreach ( $fields as $accepted_field ) {
575
+            // Check to see if $field is the parent of any item in $fields.
576
+            // A field "parent" should be accepted if "parent.child" is accepted.
577
+            if ( strpos( $accepted_field, "$field." ) === 0 ) {
578
+                return true;
579
+            }
580
+            // Conversely, if "parent" is accepted, all "parent.child" fields
581
+            // should also be accepted.
582
+            if ( strpos( $field, "$accepted_field." ) === 0 ) {
583
+                return true;
584
+            }
585
+        }
586
+
587
+        return false;
588
+    }
589 589
 
590 590
 }
Please login to merge, or discard this patch.
includes/api/class-getpaid-rest-posts-controller.php 1 patch
Indentation   +1009 added lines, -1009 removed lines patch added patch discarded remove patch
@@ -18,1018 +18,1018 @@
 block discarded – undo
18 18
 class GetPaid_REST_Posts_Controller extends GetPaid_REST_Controller {
19 19
 
20 20
     /**
21
-	 * Post type.
22
-	 *
23
-	 * @var string
24
-	 */
25
-	protected $post_type;
26
-
27
-	/**
28
-	 * Controls visibility on frontend.
29
-	 *
30
-	 * @var string
31
-	 */
32
-	public $public = false;
33
-
34
-	/**
35
-	 * Contains this controller's class name.
36
-	 *
37
-	 * @var string
38
-	 */
39
-	public $crud_class;
40
-
41
-	/**
42
-	 * Registers the routes for the objects of the controller.
43
-	 *
44
-	 * @since 1.0.19
45
-	 *
46
-	 * @see register_rest_route()
47
-	 */
48
-	public function register_namespace_routes( $namespace ) {
49
-
50
-		register_rest_route(
51
-			$namespace,
52
-			'/' . $this->rest_base,
53
-			array(
54
-				array(
55
-					'methods'             => WP_REST_Server::READABLE,
56
-					'callback'            => array( $this, 'get_items' ),
57
-					'permission_callback' => array( $this, 'get_items_permissions_check' ),
58
-					'args'                => $this->get_collection_params(),
59
-				),
60
-				array(
61
-					'methods'             => WP_REST_Server::CREATABLE,
62
-					'callback'            => array( $this, 'create_item' ),
63
-					'permission_callback' => array( $this, 'create_item_permissions_check' ),
64
-					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
65
-				),
66
-				'schema' => array( $this, 'get_public_item_schema' ),
67
-			)
68
-		);
69
-
70
-		$get_item_args = array(
71
-			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
72
-		);
73
-
74
-		register_rest_route(
75
-			$namespace,
76
-			'/' . $this->rest_base . '/(?P<id>[\d]+)',
77
-			array(
78
-				'args'   => array(
79
-					'id' => array(
80
-						'description' => __( 'Unique identifier for the object.', 'invoicing' ),
81
-						'type'        => 'integer',
82
-					),
83
-				),
84
-				array(
85
-					'methods'             => WP_REST_Server::READABLE,
86
-					'callback'            => array( $this, 'get_item' ),
87
-					'permission_callback' => array( $this, 'get_item_permissions_check' ),
88
-					'args'                => $get_item_args,
89
-				),
90
-				array(
91
-					'methods'             => WP_REST_Server::EDITABLE,
92
-					'callback'            => array( $this, 'update_item' ),
93
-					'permission_callback' => array( $this, 'update_item_permissions_check' ),
94
-					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
95
-				),
96
-				array(
97
-					'methods'             => WP_REST_Server::DELETABLE,
98
-					'callback'            => array( $this, 'delete_item' ),
99
-					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
100
-					'args'                => array(
101
-						'force' => array(
102
-							'type'        => 'boolean',
103
-							'default'     => false,
104
-							'description' => __( 'Whether to bypass Trash and force deletion.', 'invoicing' ),
105
-						),
106
-					),
107
-				),
108
-				'schema' => array( $this, 'get_public_item_schema' ),
109
-			)
110
-		);
111
-
112
-		register_rest_route(
113
-			$namespace,
114
-			'/' . $this->rest_base . '/batch',
115
-			array(
116
-				array(
117
-					'methods'             => WP_REST_Server::EDITABLE,
118
-					'callback'            => array( $this, 'batch_items' ),
119
-					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
120
-					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
121
-				),
122
-				'schema' => array( $this, 'get_public_batch_schema' ),
123
-			)
124
-		);
125
-
126
-	}
127
-
128
-	/**
129
-	 * Check permissions of items on REST API.
130
-	 *
131
-	 * @since 1.0.19
132
-	 * @param string $context   Request context.
133
-	 * @param int    $object_id Post ID.
134
-	 * @return bool
135
-	 */
136
-	public function check_post_permissions( $context = 'read', $object_id = 0 ) {
137
-
138
-		$contexts = array(
139
-			'read'   => 'read_private_posts',
140
-			'create' => 'publish_posts',
141
-			'edit'   => 'edit_post',
142
-			'delete' => 'delete_post',
143
-			'batch'  => 'edit_others_posts',
144
-		);
145
-
146
-		if ( 'revision' === $this->post_type ) {
147
-			$permission = false;
148
-		} else {
149
-			$cap              = $contexts[ $context ];
150
-			$post_type_object = get_post_type_object( $this->post_type );
151
-			$permission       = current_user_can( $post_type_object->cap->$cap, $object_id );
152
-		}
153
-
154
-		return apply_filters( 'getpaid_rest_check_permissions', $permission, $context, $object_id, $this->post_type );
155
-	}
156
-
157
-	/**
158
-	 * Check if a given request has access to read items.
159
-	 *
160
-	 * @param  WP_REST_Request $request Full details about the request.
161
-	 * @return WP_Error|boolean
162
-	 */
163
-	public function get_items_permissions_check( $request ) {
164
-		return $this->check_post_permissions() ? true : new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot list resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
165
-	}
166
-
167
-	/**
168
-	 * Check if a given request has access to create an item.
169
-	 *
170
-	 * @param  WP_REST_Request $request Full details about the request.
171
-	 * @return WP_Error|boolean
172
-	 */
173
-	public function create_item_permissions_check( $request ) {
174
-		return $this->check_post_permissions( 'create' ) ? true : new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
175
-	}
176
-
177
-	/**
178
-	 * Check if a given request has access to read an item.
179
-	 *
180
-	 * @param  WP_REST_Request $request Full details about the request.
181
-	 * @return WP_Error|boolean
182
-	 */
183
-	public function get_item_permissions_check( $request ) {
184
-		$post = get_post( (int) $request['id'] );
185
-
186
-		if ( $post && ! $this->check_post_permissions( 'read', $post->ID ) ) {
187
-			return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
188
-		}
189
-
190
-		return true;
191
-	}
192
-
193
-	/**
194
-	 * Check if a given request has access to update an item.
195
-	 *
196
-	 * @param  WP_REST_Request $request Full details about the request.
197
-	 * @return WP_Error|boolean
198
-	 */
199
-	public function update_item_permissions_check( $request ) {
200
-		$post = get_post( (int) $request['id'] );
201
-
202
-		if ( $post && ! $this->check_post_permissions( 'edit', $post->ID ) ) {
203
-			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
204
-		}
205
-
206
-		return true;
207
-	}
208
-
209
-	/**
210
-	 * Check if a given request has access to delete an item.
211
-	 *
212
-	 * @param  WP_REST_Request $request Full details about the request.
213
-	 * @return bool|WP_Error
214
-	 */
215
-	public function delete_item_permissions_check( $request ) {
216
-		$post = get_post( (int) $request['id'] );
217
-
218
-		if ( $post && ! $this->check_post_permissions( 'delete', $post->ID ) ) {
219
-			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
220
-		}
221
-
222
-		return true;
223
-	}
224
-
225
-	/**
226
-	 * Check if a given request has access batch create, update and delete items.
227
-	 *
228
-	 * @param  WP_REST_Request $request Full details about the request.
229
-	 *
230
-	 * @return boolean|WP_Error
231
-	 */
232
-	public function batch_items_permissions_check( $request ) {
233
-		return $this->check_post_permissions( 'batch' ) ? true : new WP_Error( 'rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
234
-	}
235
-
236
-	/**
237
-	 * Saves a single object.
238
-	 *
239
-	 * @param GetPaid_Data $object Object to save.
240
-	 * @return WP_Error|GetPaid_Data
241
-	 */
242
-	protected function save_object( $object ) {
243
-		$object->save();
244
-
245
-		if ( ! empty( $object->last_error ) ) {
246
-			return new WP_Error( 'rest_cannot_save', $object->last_error, array( 'status' => 400 ) );
247
-		}
248
-
249
-		return new $this->crud_class( $object->get_id() );
250
-	}
251
-
252
-	/**
253
-	 * Returns the item's object.
254
-	 *
255
-	 * Child classes must implement this method.
256
-	 * @since 1.0.13
257
-	 *
258
-	 * @param int|WP_Post $object_id Supplied ID.
259
-	 * @return GetPaid_Data|WP_Error GetPaid_Data object if ID is valid, WP_Error otherwise.
260
-	 */
261
-	protected function get_object( $object_id ) {
262
-
263
-		// Do we have an object?
264
-		if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) {
265
-			return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) );
266
-		}
267
-
268
-		// Fetch the object.
269
-		$object = new $this->crud_class( $object_id );
270
-		if ( ! empty( $object->last_error ) ) {
271
-			return new WP_Error( 'rest_object_invalid_id', $object->last_error, array( 'status' => 404 ) );
272
-		}
273
-
274
-		return $object->get_id() ? $object : new WP_Error( 'rest_object_invalid_id', __( 'Invalid ID.', 'invoicing' ), array( 'status' => 404 ) );
275
-
276
-	}
277
-
278
-	/**
279
-	 * @deprecated
280
-	 */
281
-	public function get_post( $object_id ) {
282
-		return $this->get_object( $object_id );
21
+     * Post type.
22
+     *
23
+     * @var string
24
+     */
25
+    protected $post_type;
26
+
27
+    /**
28
+     * Controls visibility on frontend.
29
+     *
30
+     * @var string
31
+     */
32
+    public $public = false;
33
+
34
+    /**
35
+     * Contains this controller's class name.
36
+     *
37
+     * @var string
38
+     */
39
+    public $crud_class;
40
+
41
+    /**
42
+     * Registers the routes for the objects of the controller.
43
+     *
44
+     * @since 1.0.19
45
+     *
46
+     * @see register_rest_route()
47
+     */
48
+    public function register_namespace_routes( $namespace ) {
49
+
50
+        register_rest_route(
51
+            $namespace,
52
+            '/' . $this->rest_base,
53
+            array(
54
+                array(
55
+                    'methods'             => WP_REST_Server::READABLE,
56
+                    'callback'            => array( $this, 'get_items' ),
57
+                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
58
+                    'args'                => $this->get_collection_params(),
59
+                ),
60
+                array(
61
+                    'methods'             => WP_REST_Server::CREATABLE,
62
+                    'callback'            => array( $this, 'create_item' ),
63
+                    'permission_callback' => array( $this, 'create_item_permissions_check' ),
64
+                    'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
65
+                ),
66
+                'schema' => array( $this, 'get_public_item_schema' ),
67
+            )
68
+        );
69
+
70
+        $get_item_args = array(
71
+            'context' => $this->get_context_param( array( 'default' => 'view' ) ),
72
+        );
73
+
74
+        register_rest_route(
75
+            $namespace,
76
+            '/' . $this->rest_base . '/(?P<id>[\d]+)',
77
+            array(
78
+                'args'   => array(
79
+                    'id' => array(
80
+                        'description' => __( 'Unique identifier for the object.', 'invoicing' ),
81
+                        'type'        => 'integer',
82
+                    ),
83
+                ),
84
+                array(
85
+                    'methods'             => WP_REST_Server::READABLE,
86
+                    'callback'            => array( $this, 'get_item' ),
87
+                    'permission_callback' => array( $this, 'get_item_permissions_check' ),
88
+                    'args'                => $get_item_args,
89
+                ),
90
+                array(
91
+                    'methods'             => WP_REST_Server::EDITABLE,
92
+                    'callback'            => array( $this, 'update_item' ),
93
+                    'permission_callback' => array( $this, 'update_item_permissions_check' ),
94
+                    'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
95
+                ),
96
+                array(
97
+                    'methods'             => WP_REST_Server::DELETABLE,
98
+                    'callback'            => array( $this, 'delete_item' ),
99
+                    'permission_callback' => array( $this, 'delete_item_permissions_check' ),
100
+                    'args'                => array(
101
+                        'force' => array(
102
+                            'type'        => 'boolean',
103
+                            'default'     => false,
104
+                            'description' => __( 'Whether to bypass Trash and force deletion.', 'invoicing' ),
105
+                        ),
106
+                    ),
107
+                ),
108
+                'schema' => array( $this, 'get_public_item_schema' ),
109
+            )
110
+        );
111
+
112
+        register_rest_route(
113
+            $namespace,
114
+            '/' . $this->rest_base . '/batch',
115
+            array(
116
+                array(
117
+                    'methods'             => WP_REST_Server::EDITABLE,
118
+                    'callback'            => array( $this, 'batch_items' ),
119
+                    'permission_callback' => array( $this, 'batch_items_permissions_check' ),
120
+                    'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
121
+                ),
122
+                'schema' => array( $this, 'get_public_batch_schema' ),
123
+            )
124
+        );
125
+
126
+    }
127
+
128
+    /**
129
+     * Check permissions of items on REST API.
130
+     *
131
+     * @since 1.0.19
132
+     * @param string $context   Request context.
133
+     * @param int    $object_id Post ID.
134
+     * @return bool
135
+     */
136
+    public function check_post_permissions( $context = 'read', $object_id = 0 ) {
137
+
138
+        $contexts = array(
139
+            'read'   => 'read_private_posts',
140
+            'create' => 'publish_posts',
141
+            'edit'   => 'edit_post',
142
+            'delete' => 'delete_post',
143
+            'batch'  => 'edit_others_posts',
144
+        );
145
+
146
+        if ( 'revision' === $this->post_type ) {
147
+            $permission = false;
148
+        } else {
149
+            $cap              = $contexts[ $context ];
150
+            $post_type_object = get_post_type_object( $this->post_type );
151
+            $permission       = current_user_can( $post_type_object->cap->$cap, $object_id );
152
+        }
153
+
154
+        return apply_filters( 'getpaid_rest_check_permissions', $permission, $context, $object_id, $this->post_type );
155
+    }
156
+
157
+    /**
158
+     * Check if a given request has access to read items.
159
+     *
160
+     * @param  WP_REST_Request $request Full details about the request.
161
+     * @return WP_Error|boolean
162
+     */
163
+    public function get_items_permissions_check( $request ) {
164
+        return $this->check_post_permissions() ? true : new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot list resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
283 165
     }
284 166
 
285
-	/**
286
-	 * Get a single object.
287
-	 *
288
-	 * @param WP_REST_Request $request Full details about the request.
289
-	 * @return WP_Error|WP_REST_Response
290
-	 */
291
-	public function get_item( $request ) {
292
-		remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
293
-
294
-		// Fetch the item.
295
-		$object = $this->get_object( $request['id'] );
296
-
297
-		if ( is_wp_error( $object ) ) {
298
-			return $object;
299
-		}
300
-
301
-		// Generate a response.
302
-		$data     = $this->prepare_item_for_response( $object, $request );
303
-		$response = rest_ensure_response( $data );
304
-
305
-		// (Maybe) add a link to the html pagee.
306
-		if ( $this->public && ! is_wp_error( $response ) ) {
307
-			$response->link_header( 'alternate', get_permalink( $object->get_id() ), array( 'type' => 'text/html' ) );
308
-		}
309
-
310
-		return $response;
311
-	}
312
-
313
-	/**
314
-	 * Create a single object.
315
-	 *
316
-	 * @param WP_REST_Request $request Full details about the request.
317
-	 * @return WP_Error|WP_REST_Response
318
-	 */
319
-	public function create_item( $request ) {
320
-		remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
321
-
322
-		// Can not create an existing item.
323
-		if ( ! empty( $request['id'] ) ) {
324
-			/* translators: %s: post type */
325
-			return new WP_Error( "getpaid_rest_{$this->post_type}_exists", __( 'Cannot create existing resource.', 'invoicing' ), array( 'status' => 400 ) );
326
-		}
327
-
328
-		// Generate a GetPaid_Data object from the request.
329
-		$object = $this->prepare_item_for_database( $request );
330
-		if ( is_wp_error( $object ) ) {
331
-			return $object;
332
-		}
333
-
334
-		// Save the object.
335
-		$object = $this->save_object( $object );
336
-		if ( is_wp_error( $object ) ) {
337
-			return $object;
338
-		}
339
-
340
-		// Save special fields.
341
-		$save_special = $this->update_additional_fields_for_object( $object, $request );
342
-		if ( is_wp_error( $save_special ) ) {
343
-			$object->delete( true );
344
-			return $save_special;
345
-		}
346
-
347
-		/**
348
-		 * Fires after a single item is created or updated via the REST API.
349
-		 *
350
-		 * @param WP_Post         $post      Post object.
351
-		 * @param WP_REST_Request $request   Request object.
352
-		 * @param boolean         $creating  True when creating item, false when updating.
353
-		 */
354
-		do_action( "getpaid_rest_insert_{$this->post_type}", $object, $request, true );
355
-
356
-		$request->set_param( 'context', 'edit' );
357
-		$response = $this->prepare_item_for_response( $object, $request );
358
-		$response = rest_ensure_response( $response );
359
-		$response->set_status( 201 );
360
-		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) );
361
-
362
-		return $response;
363
-	}
364
-
365
-	/**
366
-	 * Update a single object.
367
-	 *
368
-	 * @param WP_REST_Request $request Full details about the request.
369
-	 * @return WP_Error|WP_REST_Response
370
-	 */
371
-	public function update_item( $request ) {
372
-		remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
373
-
374
-		// Fetch the item.
375
-		$object = $this->get_object( $request['id'] );
376
-		if ( is_wp_error( $object ) ) {
377
-			return $object;
378
-		}
379
-
380
-		// Prepare the item for saving.
381
-		$object = $this->prepare_item_for_database( $request );
382
-		if ( is_wp_error( $object ) ) {
383
-			return $object;
384
-		}
385
-
386
-		// Save the item.
387
-		$object = $this->save_object( $object );
388
-		if ( is_wp_error( $object ) ) {
389
-			return $object;
390
-		}
391
-
392
-		// Save special fields (those added via hooks).
393
-		$save_special = $this->update_additional_fields_for_object( $object, $request );
394
-		if ( is_wp_error( $save_special ) ) {
395
-			return $save_special;
396
-		}
397
-
398
-		/**
399
-		 * Fires after a single item is created or updated via the REST API.
400
-		 *
401
-		 * @param GetPaid_Data    $object    GetPaid_Data object.
402
-		 * @param WP_REST_Request $request   Request object.
403
-		 * @param boolean         $creating  True when creating item, false when updating.
404
-		 */
405
-		do_action( "getpaid_rest_insert_{$this->post_type}", $object, $request, false );
406
-
407
-		$request->set_param( 'context', 'edit' );
408
-		$response = $this->prepare_item_for_response( $object, $request );
409
-		return rest_ensure_response( $response );
410
-	}
411
-
412
-	/**
413
-	 * Get a collection of objects.
414
-	 *
415
-	 * @param WP_REST_Request $request Full details about the request.
416
-	 * @return WP_Error|WP_REST_Response
417
-	 */
418
-	public function get_items( $request ) {
419
-		remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
420
-
421
-		$args                         = array();
422
-		$args['offset']               = $request['offset'];
423
-		$args['order']                = $request['order'];
424
-		$args['orderby']              = $request['orderby'];
425
-		$args['paged']                = $request['page'];
426
-		$args['post__in']             = $request['include'];
427
-		$args['post__not_in']         = $request['exclude'];
428
-		$args['posts_per_page']       = $request['per_page'];
429
-		$args['name']                 = $request['slug'];
430
-		$args['post_parent__in']      = $request['parent'];
431
-		$args['post_parent__not_in']  = $request['parent_exclude'];
432
-		$args['s']                    = $request['search'];
433
-		$args['post_status']          = wpinv_parse_list( $request['status'] );
434
-
435
-		$args['date_query'] = array();
436
-		// Set before into date query. Date query must be specified as an array of an array.
437
-		if ( isset( $request['before'] ) ) {
438
-			$args['date_query'][0]['before'] = $request['before'];
439
-		}
440
-
441
-		// Set after into date query. Date query must be specified as an array of an array.
442
-		if ( isset( $request['after'] ) ) {
443
-			$args['date_query'][0]['after'] = $request['after'];
444
-		}
445
-
446
-		// Force the post_type & fields arguments, since they're not a user input variable.
447
-		$args['post_type'] = $this->post_type;
448
-		$args['fields']    = 'ids';
449
-
450
-		// Filter the query arguments for a request.
451
-		$args       = apply_filters( "getpaid_rest_{$this->post_type}_query", $args, $request );
452
-		$query_args = $this->prepare_items_query( $args, $request );
453
-
454
-		$posts_query = new WP_Query();
455
-		$query_result = $posts_query->query( $query_args );
456
-
457
-		$posts = array();
458
-		foreach ( $query_result as $post_id ) {
459
-			if ( ! $this->check_post_permissions( 'read', $post_id ) ) {
460
-				continue;
461
-			}
462
-
463
-			$data    = $this->prepare_item_for_response( $this->get_object( $post_id ), $request );
464
-			$posts[] = $this->prepare_response_for_collection( $data );
465
-		}
466
-
467
-		$page        = (int) $query_args['paged'];
468
-		$total_posts = $posts_query->found_posts;
469
-
470
-		if ( $total_posts < 1 ) {
471
-			// Out-of-bounds, run the query again without LIMIT for total count.
472
-			unset( $query_args['paged'] );
473
-			$count_query = new WP_Query();
474
-			$count_query->query( $query_args );
475
-			$total_posts = $count_query->found_posts;
476
-		}
477
-
478
-		$max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
479
-
480
-		$response = rest_ensure_response( $posts );
481
-		$response->header( 'X-WP-Total', (int) $total_posts );
482
-		$response->header( 'X-WP-TotalPages', (int) $max_pages );
483
-
484
-		$request_params = $request->get_query_params();
485
-		$base = add_query_arg( $request_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
486
-
487
-		if ( $page > 1 ) {
488
-			$prev_page = $page - 1;
489
-			if ( $prev_page > $max_pages ) {
490
-				$prev_page = $max_pages;
491
-			}
492
-			$prev_link = add_query_arg( 'page', $prev_page, $base );
493
-			$response->link_header( 'prev', $prev_link );
494
-		}
495
-		if ( $max_pages > $page ) {
496
-			$next_page = $page + 1;
497
-			$next_link = add_query_arg( 'page', $next_page, $base );
498
-			$response->link_header( 'next', $next_link );
499
-		}
500
-
501
-		return $response;
502
-	}
503
-
504
-	/**
505
-	 * Delete a single item.
506
-	 *
507
-	 * @param WP_REST_Request $request Full details about the request.
508
-	 * @return WP_REST_Response|WP_Error
509
-	 */
510
-	public function delete_item( $request ) {
511
-		remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
512
-
513
-		// Fetch the item.
514
-		$item = $this->get_object( $request['id'] );
515
-		if ( is_wp_error( $item ) ) {
516
-			return $item;
517
-		}
518
-
519
-		$supports_trash = EMPTY_TRASH_DAYS > 0;
520
-		$force          = $supports_trash && (bool) $request['force'];
521
-
522
-		if ( ! $this->check_post_permissions( 'delete', $item->ID ) ) {
523
-			return new WP_Error( "cannot_delete", __( 'Sorry, you are not allowed to delete this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
524
-		}
525
-
526
-		$request->set_param( 'context', 'edit' );
527
-		$response = $this->prepare_item_for_response( $item, $request );
528
-
529
-		if ( ! wp_delete_post( $item->ID, $force ) ) {
530
-			return new WP_Error( 'rest_cannot_delete', sprintf( __( 'The resource cannot be deleted.', 'invoicing' ), $this->post_type ), array( 'status' => 500 ) );
531
-		}
532
-
533
-		return $response;
534
-	}
535
-
536
-	/**
537
-	 * Prepare links for the request.
538
-	 *
539
-	 * @param GetPaid_Data    $object GetPaid_Data object.
540
-	 * @return array Links for the given object.
541
-	 */
542
-	protected function prepare_links( $object ) {
543
-
544
-		$links = array(
545
-			'self'       => array(
546
-				'href'   => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ),
547
-			),
548
-			'collection' => array(
549
-				'href'   => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
550
-			),
551
-		);
552
-
553
-		if ( is_callable( array( $object, 'get_user_id' ) ) ) {
554
-			$links['user'] = array(
555
-				'href'       => rest_url( 'wp/v2/users/' . call_user_func(  array( $object, 'get_user_id' )  ) ),
556
-				'embeddable' => true,
557
-			);
558
-		}
559
-
560
-		if ( is_callable( array( $object, 'get_owner' ) ) ) {
561
-			$links['owner']  = array(
562
-				'href'       => rest_url( 'wp/v2/users/' . call_user_func(  array( $object, 'get_owner' )  ) ),
563
-				'embeddable' => true,
564
-			);
565
-		}
566
-
567
-		if ( is_callable( array( $object, 'get_parent_id' ) ) && call_user_func(  array( $object, 'get_parent_id' )  ) ) {
568
-			$links['parent']  = array(
569
-				'href'       => rest_url( "$this->namespace/$this->rest_base/" . call_user_func(  array( $object, 'get_parent_id' )  ) ),
570
-				'embeddable' => true,
571
-			);
572
-		}
573
-
574
-		return $links;
575
-	}
576
-
577
-	/**
578
-	 * Determine the allowed query_vars for a get_items() response and
579
-	 * prepare for WP_Query.
580
-	 *
581
-	 * @param array           $prepared_args Prepared arguments.
582
-	 * @param WP_REST_Request $request Request object.
583
-	 * @return array          $query_args
584
-	 */
585
-	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
586
-
587
-		$valid_vars = array_flip( $this->get_allowed_query_vars() );
588
-		$query_args = array();
589
-		foreach ( $valid_vars as $var => $index ) {
590
-			if ( isset( $prepared_args[ $var ] ) ) {
591
-				$query_args[ $var ] = apply_filters( "getpaid_rest_query_var-{$var}", $prepared_args[ $var ] );
592
-			}
593
-		}
594
-
595
-		$query_args['ignore_sticky_posts'] = true;
596
-
597
-		if ( 'include' === $query_args['orderby'] ) {
598
-			$query_args['orderby'] = 'post__in';
599
-		} elseif ( 'id' === $query_args['orderby'] ) {
600
-			$query_args['orderby'] = 'ID'; // ID must be capitalized.
601
-		} elseif ( 'slug' === $query_args['orderby'] ) {
602
-			$query_args['orderby'] = 'name';
603
-		}
604
-
605
-		return apply_filters( 'getpaid_rest_prepare_items_query', $query_args, $request, $this );
606
-
607
-	}
608
-
609
-	/**
610
-	 * Get all the WP Query vars that are allowed for the API request.
611
-	 *
612
-	 * @return array
613
-	 */
614
-	protected function get_allowed_query_vars() {
615
-		global $wp;
616
-
617
-		/**
618
-		 * Filter the publicly allowed query vars.
619
-		 *
620
-		 * Allows adjusting of the default query vars that are made public.
621
-		 *
622
-		 * @param array  Array of allowed WP_Query query vars.
623
-		 */
624
-		$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
625
-
626
-		$post_type_obj = get_post_type_object( $this->post_type );
627
-		if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
628
-			$private = apply_filters( 'getpaid_rest_private_query_vars', $wp->private_query_vars );
629
-			$valid_vars = array_merge( $valid_vars, $private );
630
-		}
631
-
632
-		// Define our own in addition to WP's normal vars.
633
-		$rest_valid = array(
634
-			'post_status',
635
-			'date_query',
636
-			'ignore_sticky_posts',
637
-			'offset',
638
-			'post__in',
639
-			'post__not_in',
640
-			'post_parent',
641
-			'post_parent__in',
642
-			'post_parent__not_in',
643
-			'posts_per_page',
644
-			'meta_query',
645
-			'tax_query',
646
-			'meta_key',
647
-			'meta_value',
648
-			'meta_compare',
649
-			'meta_value_num',
650
-		);
651
-		$valid_vars = array_merge( $valid_vars, $rest_valid );
652
-
653
-		// Filter allowed query vars for the REST API.
654
-		$valid_vars = apply_filters( 'getpaid_rest_query_vars', $valid_vars, $this );
655
-
656
-		return $valid_vars;
657
-	}
658
-
659
-	/**
660
-	 * Get the query params for collections of attachments.
661
-	 *
662
-	 * @return array
663
-	 */
664
-	public function get_collection_params() {
665
-		$params = parent::get_collection_params();
666
-
667
-		$params['context']['default'] = 'view';
668
-
669
-		$params['status'] = array(
670
-			'default'           => $this->get_post_statuses(),
671
-			'description'       => __( 'Limit result set to resources assigned one or more statuses.', 'invoicing' ),
672
-			'type'              => array( 'array', 'string' ),
673
-			'items'             => array(
674
-				'enum'          => $this->get_post_statuses(),
675
-				'type'          => 'string',
676
-			),
677
-			'validate_callback' => 'rest_validate_request_arg',
678
-			'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
679
-		);
680
-
681
-		$params['after'] = array(
682
-			'description'        => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'invoicing' ),
683
-			'type'               => 'string',
684
-			'format'             => 'string',
685
-			'validate_callback'  => 'rest_validate_request_arg',
686
-			'sanitize_callback'  => 'sanitize_text_field',
687
-		);
688
-		$params['before'] = array(
689
-			'description'        => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'invoicing' ),
690
-			'type'               => 'string',
691
-			'format'             => 'string',
692
-			'validate_callback'  => 'rest_validate_request_arg',
693
-			'sanitize_callback'  => 'sanitize_text_field',
694
-		);
695
-		$params['exclude'] = array(
696
-			'description'       => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
697
-			'type'              => 'array',
698
-			'items'             => array(
699
-				'type'          => 'integer',
700
-			),
701
-			'default'           => array(),
702
-			'sanitize_callback' => 'wp_parse_id_list',
703
-			'validate_callback' => 'rest_validate_request_arg',
704
-		);
705
-		$params['include'] = array(
706
-			'description'       => __( 'Limit result set to specific ids.', 'invoicing' ),
707
-			'type'              => 'array',
708
-			'items'             => array(
709
-				'type'          => 'integer',
710
-			),
711
-			'default'           => array(),
712
-			'sanitize_callback' => 'wp_parse_id_list',
713
-			'validate_callback' => 'rest_validate_request_arg',
714
-		);
715
-		$params['offset'] = array(
716
-			'description'        => __( 'Offset the result set by a specific number of items.', 'invoicing' ),
717
-			'type'               => 'integer',
718
-			'sanitize_callback'  => 'absint',
719
-			'validate_callback'  => 'rest_validate_request_arg',
720
-		);
721
-		$params['order'] = array(
722
-			'description'        => __( 'Order sort attribute ascending or descending.', 'invoicing' ),
723
-			'type'               => 'string',
724
-			'default'            => 'desc',
725
-			'enum'               => array( 'asc', 'desc' ),
726
-			'validate_callback'  => 'rest_validate_request_arg',
727
-		);
728
-		$params['orderby'] = array(
729
-			'description'        => __( 'Sort collection by object attribute.', 'invoicing' ),
730
-			'type'               => 'string',
731
-			'default'            => 'date',
732
-			'enum'               => array(
733
-				'date',
734
-				'id',
735
-				'include',
736
-				'title',
737
-				'slug',
738
-				'modified',
739
-			),
740
-			'validate_callback'  => 'rest_validate_request_arg',
741
-		);
742
-
743
-		$post_type_obj = get_post_type_object( $this->post_type );
744
-
745
-		if ( isset( $post_type_obj->hierarchical ) && $post_type_obj->hierarchical ) {
746
-			$params['parent'] = array(
747
-				'description'       => __( 'Limit result set to those of particular parent IDs.', 'invoicing' ),
748
-				'type'              => 'array',
749
-				'items'             => array(
750
-					'type'          => 'integer',
751
-				),
752
-				'sanitize_callback' => 'wp_parse_id_list',
753
-				'default'           => array(),
754
-			);
755
-			$params['parent_exclude'] = array(
756
-				'description'       => __( 'Limit result set to all items except those of a particular parent ID.', 'invoicing' ),
757
-				'type'              => 'array',
758
-				'items'             => array(
759
-					'type'          => 'integer',
760
-				),
761
-				'sanitize_callback' => 'wp_parse_id_list',
762
-				'default'           => array(),
763
-			);
764
-		}
765
-
766
-		return $params;
767
-	}
768
-
769
-	/**
770
-	 * Retrieves the items's schema, conforming to JSON Schema.
771
-	 *
772
-	 * @since 1.0.19
773
-	 *
774
-	 * @return array Item schema data.
775
-	 */
776
-	public function get_item_schema() {
777
-
778
-		// Maybe retrieve the schema from cache.
779
-		if ( $this->schema ) {
780
-			return $this->add_additional_fields_schema( $this->schema );
781
-		}
782
-
783
-		$type   = str_replace( 'wpi_', '', $this->post_type );
784
-		$schema = array(
785
-			'$schema'    => 'http://json-schema.org/draft-04/schema#',
786
-			'title'      => $this->post_type,
787
-			'type'       => 'object',
788
-			'properties' => wpinv_get_data( "$type-schema" ),
789
-		);
790
-
791
-		// Filters the invoice schema for the REST API.
167
+    /**
168
+     * Check if a given request has access to create an item.
169
+     *
170
+     * @param  WP_REST_Request $request Full details about the request.
171
+     * @return WP_Error|boolean
172
+     */
173
+    public function create_item_permissions_check( $request ) {
174
+        return $this->check_post_permissions( 'create' ) ? true : new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
175
+    }
176
+
177
+    /**
178
+     * Check if a given request has access to read an item.
179
+     *
180
+     * @param  WP_REST_Request $request Full details about the request.
181
+     * @return WP_Error|boolean
182
+     */
183
+    public function get_item_permissions_check( $request ) {
184
+        $post = get_post( (int) $request['id'] );
185
+
186
+        if ( $post && ! $this->check_post_permissions( 'read', $post->ID ) ) {
187
+            return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
188
+        }
189
+
190
+        return true;
191
+    }
192
+
193
+    /**
194
+     * Check if a given request has access to update an item.
195
+     *
196
+     * @param  WP_REST_Request $request Full details about the request.
197
+     * @return WP_Error|boolean
198
+     */
199
+    public function update_item_permissions_check( $request ) {
200
+        $post = get_post( (int) $request['id'] );
201
+
202
+        if ( $post && ! $this->check_post_permissions( 'edit', $post->ID ) ) {
203
+            return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
204
+        }
205
+
206
+        return true;
207
+    }
208
+
209
+    /**
210
+     * Check if a given request has access to delete an item.
211
+     *
212
+     * @param  WP_REST_Request $request Full details about the request.
213
+     * @return bool|WP_Error
214
+     */
215
+    public function delete_item_permissions_check( $request ) {
216
+        $post = get_post( (int) $request['id'] );
217
+
218
+        if ( $post && ! $this->check_post_permissions( 'delete', $post->ID ) ) {
219
+            return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
220
+        }
221
+
222
+        return true;
223
+    }
224
+
225
+    /**
226
+     * Check if a given request has access batch create, update and delete items.
227
+     *
228
+     * @param  WP_REST_Request $request Full details about the request.
229
+     *
230
+     * @return boolean|WP_Error
231
+     */
232
+    public function batch_items_permissions_check( $request ) {
233
+        return $this->check_post_permissions( 'batch' ) ? true : new WP_Error( 'rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
234
+    }
235
+
236
+    /**
237
+     * Saves a single object.
238
+     *
239
+     * @param GetPaid_Data $object Object to save.
240
+     * @return WP_Error|GetPaid_Data
241
+     */
242
+    protected function save_object( $object ) {
243
+        $object->save();
244
+
245
+        if ( ! empty( $object->last_error ) ) {
246
+            return new WP_Error( 'rest_cannot_save', $object->last_error, array( 'status' => 400 ) );
247
+        }
248
+
249
+        return new $this->crud_class( $object->get_id() );
250
+    }
251
+
252
+    /**
253
+     * Returns the item's object.
254
+     *
255
+     * Child classes must implement this method.
256
+     * @since 1.0.13
257
+     *
258
+     * @param int|WP_Post $object_id Supplied ID.
259
+     * @return GetPaid_Data|WP_Error GetPaid_Data object if ID is valid, WP_Error otherwise.
260
+     */
261
+    protected function get_object( $object_id ) {
262
+
263
+        // Do we have an object?
264
+        if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) {
265
+            return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) );
266
+        }
267
+
268
+        // Fetch the object.
269
+        $object = new $this->crud_class( $object_id );
270
+        if ( ! empty( $object->last_error ) ) {
271
+            return new WP_Error( 'rest_object_invalid_id', $object->last_error, array( 'status' => 404 ) );
272
+        }
273
+
274
+        return $object->get_id() ? $object : new WP_Error( 'rest_object_invalid_id', __( 'Invalid ID.', 'invoicing' ), array( 'status' => 404 ) );
275
+
276
+    }
277
+
278
+    /**
279
+     * @deprecated
280
+     */
281
+    public function get_post( $object_id ) {
282
+        return $this->get_object( $object_id );
283
+    }
284
+
285
+    /**
286
+     * Get a single object.
287
+     *
288
+     * @param WP_REST_Request $request Full details about the request.
289
+     * @return WP_Error|WP_REST_Response
290
+     */
291
+    public function get_item( $request ) {
292
+        remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
293
+
294
+        // Fetch the item.
295
+        $object = $this->get_object( $request['id'] );
296
+
297
+        if ( is_wp_error( $object ) ) {
298
+            return $object;
299
+        }
300
+
301
+        // Generate a response.
302
+        $data     = $this->prepare_item_for_response( $object, $request );
303
+        $response = rest_ensure_response( $data );
304
+
305
+        // (Maybe) add a link to the html pagee.
306
+        if ( $this->public && ! is_wp_error( $response ) ) {
307
+            $response->link_header( 'alternate', get_permalink( $object->get_id() ), array( 'type' => 'text/html' ) );
308
+        }
309
+
310
+        return $response;
311
+    }
312
+
313
+    /**
314
+     * Create a single object.
315
+     *
316
+     * @param WP_REST_Request $request Full details about the request.
317
+     * @return WP_Error|WP_REST_Response
318
+     */
319
+    public function create_item( $request ) {
320
+        remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
321
+
322
+        // Can not create an existing item.
323
+        if ( ! empty( $request['id'] ) ) {
324
+            /* translators: %s: post type */
325
+            return new WP_Error( "getpaid_rest_{$this->post_type}_exists", __( 'Cannot create existing resource.', 'invoicing' ), array( 'status' => 400 ) );
326
+        }
327
+
328
+        // Generate a GetPaid_Data object from the request.
329
+        $object = $this->prepare_item_for_database( $request );
330
+        if ( is_wp_error( $object ) ) {
331
+            return $object;
332
+        }
333
+
334
+        // Save the object.
335
+        $object = $this->save_object( $object );
336
+        if ( is_wp_error( $object ) ) {
337
+            return $object;
338
+        }
339
+
340
+        // Save special fields.
341
+        $save_special = $this->update_additional_fields_for_object( $object, $request );
342
+        if ( is_wp_error( $save_special ) ) {
343
+            $object->delete( true );
344
+            return $save_special;
345
+        }
346
+
347
+        /**
348
+         * Fires after a single item is created or updated via the REST API.
349
+         *
350
+         * @param WP_Post         $post      Post object.
351
+         * @param WP_REST_Request $request   Request object.
352
+         * @param boolean         $creating  True when creating item, false when updating.
353
+         */
354
+        do_action( "getpaid_rest_insert_{$this->post_type}", $object, $request, true );
355
+
356
+        $request->set_param( 'context', 'edit' );
357
+        $response = $this->prepare_item_for_response( $object, $request );
358
+        $response = rest_ensure_response( $response );
359
+        $response->set_status( 201 );
360
+        $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) );
361
+
362
+        return $response;
363
+    }
364
+
365
+    /**
366
+     * Update a single object.
367
+     *
368
+     * @param WP_REST_Request $request Full details about the request.
369
+     * @return WP_Error|WP_REST_Response
370
+     */
371
+    public function update_item( $request ) {
372
+        remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
373
+
374
+        // Fetch the item.
375
+        $object = $this->get_object( $request['id'] );
376
+        if ( is_wp_error( $object ) ) {
377
+            return $object;
378
+        }
379
+
380
+        // Prepare the item for saving.
381
+        $object = $this->prepare_item_for_database( $request );
382
+        if ( is_wp_error( $object ) ) {
383
+            return $object;
384
+        }
385
+
386
+        // Save the item.
387
+        $object = $this->save_object( $object );
388
+        if ( is_wp_error( $object ) ) {
389
+            return $object;
390
+        }
391
+
392
+        // Save special fields (those added via hooks).
393
+        $save_special = $this->update_additional_fields_for_object( $object, $request );
394
+        if ( is_wp_error( $save_special ) ) {
395
+            return $save_special;
396
+        }
397
+
398
+        /**
399
+         * Fires after a single item is created or updated via the REST API.
400
+         *
401
+         * @param GetPaid_Data    $object    GetPaid_Data object.
402
+         * @param WP_REST_Request $request   Request object.
403
+         * @param boolean         $creating  True when creating item, false when updating.
404
+         */
405
+        do_action( "getpaid_rest_insert_{$this->post_type}", $object, $request, false );
406
+
407
+        $request->set_param( 'context', 'edit' );
408
+        $response = $this->prepare_item_for_response( $object, $request );
409
+        return rest_ensure_response( $response );
410
+    }
411
+
412
+    /**
413
+     * Get a collection of objects.
414
+     *
415
+     * @param WP_REST_Request $request Full details about the request.
416
+     * @return WP_Error|WP_REST_Response
417
+     */
418
+    public function get_items( $request ) {
419
+        remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
420
+
421
+        $args                         = array();
422
+        $args['offset']               = $request['offset'];
423
+        $args['order']                = $request['order'];
424
+        $args['orderby']              = $request['orderby'];
425
+        $args['paged']                = $request['page'];
426
+        $args['post__in']             = $request['include'];
427
+        $args['post__not_in']         = $request['exclude'];
428
+        $args['posts_per_page']       = $request['per_page'];
429
+        $args['name']                 = $request['slug'];
430
+        $args['post_parent__in']      = $request['parent'];
431
+        $args['post_parent__not_in']  = $request['parent_exclude'];
432
+        $args['s']                    = $request['search'];
433
+        $args['post_status']          = wpinv_parse_list( $request['status'] );
434
+
435
+        $args['date_query'] = array();
436
+        // Set before into date query. Date query must be specified as an array of an array.
437
+        if ( isset( $request['before'] ) ) {
438
+            $args['date_query'][0]['before'] = $request['before'];
439
+        }
440
+
441
+        // Set after into date query. Date query must be specified as an array of an array.
442
+        if ( isset( $request['after'] ) ) {
443
+            $args['date_query'][0]['after'] = $request['after'];
444
+        }
445
+
446
+        // Force the post_type & fields arguments, since they're not a user input variable.
447
+        $args['post_type'] = $this->post_type;
448
+        $args['fields']    = 'ids';
449
+
450
+        // Filter the query arguments for a request.
451
+        $args       = apply_filters( "getpaid_rest_{$this->post_type}_query", $args, $request );
452
+        $query_args = $this->prepare_items_query( $args, $request );
453
+
454
+        $posts_query = new WP_Query();
455
+        $query_result = $posts_query->query( $query_args );
456
+
457
+        $posts = array();
458
+        foreach ( $query_result as $post_id ) {
459
+            if ( ! $this->check_post_permissions( 'read', $post_id ) ) {
460
+                continue;
461
+            }
462
+
463
+            $data    = $this->prepare_item_for_response( $this->get_object( $post_id ), $request );
464
+            $posts[] = $this->prepare_response_for_collection( $data );
465
+        }
466
+
467
+        $page        = (int) $query_args['paged'];
468
+        $total_posts = $posts_query->found_posts;
469
+
470
+        if ( $total_posts < 1 ) {
471
+            // Out-of-bounds, run the query again without LIMIT for total count.
472
+            unset( $query_args['paged'] );
473
+            $count_query = new WP_Query();
474
+            $count_query->query( $query_args );
475
+            $total_posts = $count_query->found_posts;
476
+        }
477
+
478
+        $max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
479
+
480
+        $response = rest_ensure_response( $posts );
481
+        $response->header( 'X-WP-Total', (int) $total_posts );
482
+        $response->header( 'X-WP-TotalPages', (int) $max_pages );
483
+
484
+        $request_params = $request->get_query_params();
485
+        $base = add_query_arg( $request_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
486
+
487
+        if ( $page > 1 ) {
488
+            $prev_page = $page - 1;
489
+            if ( $prev_page > $max_pages ) {
490
+                $prev_page = $max_pages;
491
+            }
492
+            $prev_link = add_query_arg( 'page', $prev_page, $base );
493
+            $response->link_header( 'prev', $prev_link );
494
+        }
495
+        if ( $max_pages > $page ) {
496
+            $next_page = $page + 1;
497
+            $next_link = add_query_arg( 'page', $next_page, $base );
498
+            $response->link_header( 'next', $next_link );
499
+        }
500
+
501
+        return $response;
502
+    }
503
+
504
+    /**
505
+     * Delete a single item.
506
+     *
507
+     * @param WP_REST_Request $request Full details about the request.
508
+     * @return WP_REST_Response|WP_Error
509
+     */
510
+    public function delete_item( $request ) {
511
+        remove_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
512
+
513
+        // Fetch the item.
514
+        $item = $this->get_object( $request['id'] );
515
+        if ( is_wp_error( $item ) ) {
516
+            return $item;
517
+        }
518
+
519
+        $supports_trash = EMPTY_TRASH_DAYS > 0;
520
+        $force          = $supports_trash && (bool) $request['force'];
521
+
522
+        if ( ! $this->check_post_permissions( 'delete', $item->ID ) ) {
523
+            return new WP_Error( "cannot_delete", __( 'Sorry, you are not allowed to delete this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
524
+        }
525
+
526
+        $request->set_param( 'context', 'edit' );
527
+        $response = $this->prepare_item_for_response( $item, $request );
528
+
529
+        if ( ! wp_delete_post( $item->ID, $force ) ) {
530
+            return new WP_Error( 'rest_cannot_delete', sprintf( __( 'The resource cannot be deleted.', 'invoicing' ), $this->post_type ), array( 'status' => 500 ) );
531
+        }
532
+
533
+        return $response;
534
+    }
535
+
536
+    /**
537
+     * Prepare links for the request.
538
+     *
539
+     * @param GetPaid_Data    $object GetPaid_Data object.
540
+     * @return array Links for the given object.
541
+     */
542
+    protected function prepare_links( $object ) {
543
+
544
+        $links = array(
545
+            'self'       => array(
546
+                'href'   => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ),
547
+            ),
548
+            'collection' => array(
549
+                'href'   => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
550
+            ),
551
+        );
552
+
553
+        if ( is_callable( array( $object, 'get_user_id' ) ) ) {
554
+            $links['user'] = array(
555
+                'href'       => rest_url( 'wp/v2/users/' . call_user_func(  array( $object, 'get_user_id' )  ) ),
556
+                'embeddable' => true,
557
+            );
558
+        }
559
+
560
+        if ( is_callable( array( $object, 'get_owner' ) ) ) {
561
+            $links['owner']  = array(
562
+                'href'       => rest_url( 'wp/v2/users/' . call_user_func(  array( $object, 'get_owner' )  ) ),
563
+                'embeddable' => true,
564
+            );
565
+        }
566
+
567
+        if ( is_callable( array( $object, 'get_parent_id' ) ) && call_user_func(  array( $object, 'get_parent_id' )  ) ) {
568
+            $links['parent']  = array(
569
+                'href'       => rest_url( "$this->namespace/$this->rest_base/" . call_user_func(  array( $object, 'get_parent_id' )  ) ),
570
+                'embeddable' => true,
571
+            );
572
+        }
573
+
574
+        return $links;
575
+    }
576
+
577
+    /**
578
+     * Determine the allowed query_vars for a get_items() response and
579
+     * prepare for WP_Query.
580
+     *
581
+     * @param array           $prepared_args Prepared arguments.
582
+     * @param WP_REST_Request $request Request object.
583
+     * @return array          $query_args
584
+     */
585
+    protected function prepare_items_query( $prepared_args = array(), $request = null ) {
586
+
587
+        $valid_vars = array_flip( $this->get_allowed_query_vars() );
588
+        $query_args = array();
589
+        foreach ( $valid_vars as $var => $index ) {
590
+            if ( isset( $prepared_args[ $var ] ) ) {
591
+                $query_args[ $var ] = apply_filters( "getpaid_rest_query_var-{$var}", $prepared_args[ $var ] );
592
+            }
593
+        }
594
+
595
+        $query_args['ignore_sticky_posts'] = true;
596
+
597
+        if ( 'include' === $query_args['orderby'] ) {
598
+            $query_args['orderby'] = 'post__in';
599
+        } elseif ( 'id' === $query_args['orderby'] ) {
600
+            $query_args['orderby'] = 'ID'; // ID must be capitalized.
601
+        } elseif ( 'slug' === $query_args['orderby'] ) {
602
+            $query_args['orderby'] = 'name';
603
+        }
604
+
605
+        return apply_filters( 'getpaid_rest_prepare_items_query', $query_args, $request, $this );
606
+
607
+    }
608
+
609
+    /**
610
+     * Get all the WP Query vars that are allowed for the API request.
611
+     *
612
+     * @return array
613
+     */
614
+    protected function get_allowed_query_vars() {
615
+        global $wp;
616
+
617
+        /**
618
+         * Filter the publicly allowed query vars.
619
+         *
620
+         * Allows adjusting of the default query vars that are made public.
621
+         *
622
+         * @param array  Array of allowed WP_Query query vars.
623
+         */
624
+        $valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
625
+
626
+        $post_type_obj = get_post_type_object( $this->post_type );
627
+        if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
628
+            $private = apply_filters( 'getpaid_rest_private_query_vars', $wp->private_query_vars );
629
+            $valid_vars = array_merge( $valid_vars, $private );
630
+        }
631
+
632
+        // Define our own in addition to WP's normal vars.
633
+        $rest_valid = array(
634
+            'post_status',
635
+            'date_query',
636
+            'ignore_sticky_posts',
637
+            'offset',
638
+            'post__in',
639
+            'post__not_in',
640
+            'post_parent',
641
+            'post_parent__in',
642
+            'post_parent__not_in',
643
+            'posts_per_page',
644
+            'meta_query',
645
+            'tax_query',
646
+            'meta_key',
647
+            'meta_value',
648
+            'meta_compare',
649
+            'meta_value_num',
650
+        );
651
+        $valid_vars = array_merge( $valid_vars, $rest_valid );
652
+
653
+        // Filter allowed query vars for the REST API.
654
+        $valid_vars = apply_filters( 'getpaid_rest_query_vars', $valid_vars, $this );
655
+
656
+        return $valid_vars;
657
+    }
658
+
659
+    /**
660
+     * Get the query params for collections of attachments.
661
+     *
662
+     * @return array
663
+     */
664
+    public function get_collection_params() {
665
+        $params = parent::get_collection_params();
666
+
667
+        $params['context']['default'] = 'view';
668
+
669
+        $params['status'] = array(
670
+            'default'           => $this->get_post_statuses(),
671
+            'description'       => __( 'Limit result set to resources assigned one or more statuses.', 'invoicing' ),
672
+            'type'              => array( 'array', 'string' ),
673
+            'items'             => array(
674
+                'enum'          => $this->get_post_statuses(),
675
+                'type'          => 'string',
676
+            ),
677
+            'validate_callback' => 'rest_validate_request_arg',
678
+            'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
679
+        );
680
+
681
+        $params['after'] = array(
682
+            'description'        => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'invoicing' ),
683
+            'type'               => 'string',
684
+            'format'             => 'string',
685
+            'validate_callback'  => 'rest_validate_request_arg',
686
+            'sanitize_callback'  => 'sanitize_text_field',
687
+        );
688
+        $params['before'] = array(
689
+            'description'        => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'invoicing' ),
690
+            'type'               => 'string',
691
+            'format'             => 'string',
692
+            'validate_callback'  => 'rest_validate_request_arg',
693
+            'sanitize_callback'  => 'sanitize_text_field',
694
+        );
695
+        $params['exclude'] = array(
696
+            'description'       => __( 'Ensure result set excludes specific IDs.', 'invoicing' ),
697
+            'type'              => 'array',
698
+            'items'             => array(
699
+                'type'          => 'integer',
700
+            ),
701
+            'default'           => array(),
702
+            'sanitize_callback' => 'wp_parse_id_list',
703
+            'validate_callback' => 'rest_validate_request_arg',
704
+        );
705
+        $params['include'] = array(
706
+            'description'       => __( 'Limit result set to specific ids.', 'invoicing' ),
707
+            'type'              => 'array',
708
+            'items'             => array(
709
+                'type'          => 'integer',
710
+            ),
711
+            'default'           => array(),
712
+            'sanitize_callback' => 'wp_parse_id_list',
713
+            'validate_callback' => 'rest_validate_request_arg',
714
+        );
715
+        $params['offset'] = array(
716
+            'description'        => __( 'Offset the result set by a specific number of items.', 'invoicing' ),
717
+            'type'               => 'integer',
718
+            'sanitize_callback'  => 'absint',
719
+            'validate_callback'  => 'rest_validate_request_arg',
720
+        );
721
+        $params['order'] = array(
722
+            'description'        => __( 'Order sort attribute ascending or descending.', 'invoicing' ),
723
+            'type'               => 'string',
724
+            'default'            => 'desc',
725
+            'enum'               => array( 'asc', 'desc' ),
726
+            'validate_callback'  => 'rest_validate_request_arg',
727
+        );
728
+        $params['orderby'] = array(
729
+            'description'        => __( 'Sort collection by object attribute.', 'invoicing' ),
730
+            'type'               => 'string',
731
+            'default'            => 'date',
732
+            'enum'               => array(
733
+                'date',
734
+                'id',
735
+                'include',
736
+                'title',
737
+                'slug',
738
+                'modified',
739
+            ),
740
+            'validate_callback'  => 'rest_validate_request_arg',
741
+        );
742
+
743
+        $post_type_obj = get_post_type_object( $this->post_type );
744
+
745
+        if ( isset( $post_type_obj->hierarchical ) && $post_type_obj->hierarchical ) {
746
+            $params['parent'] = array(
747
+                'description'       => __( 'Limit result set to those of particular parent IDs.', 'invoicing' ),
748
+                'type'              => 'array',
749
+                'items'             => array(
750
+                    'type'          => 'integer',
751
+                ),
752
+                'sanitize_callback' => 'wp_parse_id_list',
753
+                'default'           => array(),
754
+            );
755
+            $params['parent_exclude'] = array(
756
+                'description'       => __( 'Limit result set to all items except those of a particular parent ID.', 'invoicing' ),
757
+                'type'              => 'array',
758
+                'items'             => array(
759
+                    'type'          => 'integer',
760
+                ),
761
+                'sanitize_callback' => 'wp_parse_id_list',
762
+                'default'           => array(),
763
+            );
764
+        }
765
+
766
+        return $params;
767
+    }
768
+
769
+    /**
770
+     * Retrieves the items's schema, conforming to JSON Schema.
771
+     *
772
+     * @since 1.0.19
773
+     *
774
+     * @return array Item schema data.
775
+     */
776
+    public function get_item_schema() {
777
+
778
+        // Maybe retrieve the schema from cache.
779
+        if ( $this->schema ) {
780
+            return $this->add_additional_fields_schema( $this->schema );
781
+        }
782
+
783
+        $type   = str_replace( 'wpi_', '', $this->post_type );
784
+        $schema = array(
785
+            '$schema'    => 'http://json-schema.org/draft-04/schema#',
786
+            'title'      => $this->post_type,
787
+            'type'       => 'object',
788
+            'properties' => wpinv_get_data( "$type-schema" ),
789
+        );
790
+
791
+        // Filters the invoice schema for the REST API.
792 792
         $schema = apply_filters( "wpinv_rest_{$type}_schema", $schema );
793 793
 
794
-		// Cache the invoice schema.
795
-		$this->schema = $schema;
796
-
797
-		return $this->add_additional_fields_schema( $this->schema );
798
-	}
799
-
800
-	/**
801
-	 * Only return writable props from schema.
802
-	 *
803
-	 * @param  array $schema Schema.
804
-	 * @return bool
805
-	 */
806
-	public function filter_writable_props( $schema ) {
807
-		return empty( $schema['readonly'] );
808
-	}
809
-
810
-	/**
811
-	 * Sanitizes and validates the list of post statuses.
812
-	 *
813
-	 * @since 1.0.13
814
-	 *
815
-	 * @param string|array    $statuses  One or more post statuses.
816
-	 * @param WP_REST_Request $request   Full details about the request.
817
-	 * @param string          $parameter Additional parameter to pass to validation.
818
-	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
819
-	 */
820
-	public function sanitize_post_statuses( $statuses, $request, $parameter ) {
821
-		return array_intersect( wp_parse_slug_list( $statuses ), $this->get_post_statuses() );
822
-	}
823
-
824
-	/**
825
-	 * Retrieves a valid list of post statuses.
826
-	 *
827
-	 * @since 1.0.19
828
-	 *
829
-	 * @return array A list of registered item statuses.
830
-	 */
831
-	public function get_post_statuses() {
832
-		return get_post_stati();
833
-	}
834
-
835
-	/**
836
-	 * Prepare a single object for create or update.
837
-	 *
838
-	 * @since 1.0.19
839
-	 * @param  WP_REST_Request $request Request object.
840
-	 * @return GetPaid_Data|WP_Error Data object or WP_Error.
841
-	 */
842
-	protected function prepare_item_for_database( $request ) {
843
-
844
-		// Do we have an object?
845
-		if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) {
846
-			return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) );
847
-		}
848
-
849
-		// Prepare the object.
850
-		$id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
851
-		$object    = new $this->crud_class( $id );
852
-
853
-		// Abort if an error exists.
854
-		if ( ! empty( $object->last_error ) ) {
855
-			return new WP_Error( 'invalid_item', $object->last_error );
856
-		}
857
-
858
-		$schema    = $this->get_item_schema();
859
-		$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
860
-
861
-		// Handle all writable props.
862
-		foreach ( $data_keys as $key ) {
863
-			$value = $request[ $key ];
864
-
865
-			if ( ! is_null( $value ) ) {
866
-				switch ( $key ) {
867
-
868
-					case 'meta_data':
869
-						if ( is_array( $value ) ) {
870
-							foreach ( $value as $meta ) {
871
-								$object->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
872
-							}
873
-						}
874
-						break;
875
-
876
-					default:
877
-						if ( is_callable( array( $object, "set_{$key}" ) ) ) {
878
-							$object->{"set_{$key}"}( $value );
879
-						}
880
-						break;
881
-				}
882
-			}
883
-
884
-		}
885
-
886
-		// Filters an object before it is inserted via the REST API..
887
-		return apply_filters( "getpaid_rest_pre_insert_{$this->post_type}_object", $object, $request );
888
-	}
889
-
890
-	/**
891
-	 * Retrieves data from a GetPaid class.
892
-	 *
893
-	 * @since  1.0.19
894
-	 * @param  GetPaid_Meta_Data[]    $meta_data  meta data objects.
895
-	 * @return array
896
-	 */
897
-	protected function prepare_object_meta_data( $meta_data ) {
898
-		$meta = array();
899
-
900
-		foreach( $meta_data as $object ) {
901
-			$meta[] = $object->get_data();
902
-		}
903
-
904
-		return $meta;
905
-	}
906
-
907
-	/**
908
-	 * Retrieves invoice items.
909
-	 *
910
-	 * @since  1.0.19
911
-	 * @param  WPInv_Invoice $invoice  Invoice items.
912
-	 * @param array            $fields Fields to include.
913
-	 * @return array
914
-	 */
915
-	protected function prepare_invoice_items( $invoice ) {
916
-		$items = array();
917
-
918
-		foreach( $invoice->get_items() as $item ) {
919
-
920
-			$item_data = $item->prepare_data_for_saving();
921
-
922
-			if ( 'amount' == $invoice->get_template() ) {
923
-				$item_data['quantity'] = 1;
924
-			}
925
-
926
-			$items[] = $item_data;
927
-		}
928
-
929
-		return $items;
930
-	}
931
-
932
-	/**
933
-	 * Retrieves data from a GetPaid class.
934
-	 *
935
-	 * @since  1.0.19
936
-	 * @param  GetPaid_Data    $object  Data object.
937
-	 * @param array            $fields Fields to include.
938
-	 * @param string           $context either view or edit.
939
-	 * @return array
940
-	 */
941
-	protected function prepare_object_data( $object, $fields, $context = 'view' ) {
942
-
943
-		$data = array();
944
-
945
-		// Handle all writable props.
946
-		foreach ( array_keys( $this->get_schema_properties() ) as $key ) {
947
-
948
-			// Abort if it is not included.
949
-			if ( ! empty( $fields ) && ! $this->is_field_included( $key, $fields ) ) {
950
-				continue;
951
-			}
952
-
953
-			// Or this current object does not support the field.
954
-			if ( ! $this->object_supports_field( $object, $key ) ) {
955
-				continue;
956
-			}
957
-
958
-			// Handle meta data.
959
-			if ( $key == 'meta_data' ) {
960
-				$data['meta_data'] = $this->prepare_object_meta_data( $object->get_meta_data() );
961
-				continue;
962
-			}
963
-
964
-			// Handle items.
965
-			if ( $key == 'items' && is_a( $object, 'WPInv_Invoice' )  ) {
966
-				$data['items'] = $this->prepare_invoice_items( $object );
967
-				continue;
968
-			}
969
-
970
-			// Booleans.
971
-			if ( is_callable( array( $object, $key ) ) ) {
972
-				$data[ $key ] = $object->$key( $context );
973
-				continue;
974
-			}
975
-
976
-			// Get object value.
977
-			if ( is_callable( array( $object, "get_{$key}" ) ) ) {
978
-				$value = $object->{"get_{$key}"}( $context );
979
-
980
-				// If the value is an instance of GetPaid_Data...
981
-				if ( is_a( $value, 'GetPaid_Data' ) ) {
982
-					$value = $value->get_data( $context );
983
-				}
984
-
985
-				// For objects, retrieves it's properties.
986
-				$data[ $key ] = is_object( $value ) ? get_object_vars( $value ) :  $value ;
987
-				continue;
988
-			}
989
-
990
-		}
991
-
992
-		return $data;
993
-	}
994
-
995
-	/**
996
-	 * Checks if a key should be included in a response.
997
-	 *
998
-	 * @since  1.0.19
999
-	 * @param  GetPaid_Data $object  Data object.
1000
-	 * @param  string       $field_key The key to check for.
1001
-	 * @return bool
1002
-	 */
1003
-	public function object_supports_field( $object, $field_key ) {
1004
-		return apply_filters( "getpaid_rest_{$this->post_type}_object_supports_key", true, $object, $field_key );
1005
-	}
1006
-
1007
-	/**
1008
-	 * Prepare a single object output for response.
1009
-	 *
1010
-	 * @since  1.0.19
1011
-	 * @param  GetPaid_Data    $object  Data object.
1012
-	 * @param  WP_REST_Request $request Request object.
1013
-	 * @return WP_REST_Response
1014
-	 */
1015
-	public function prepare_item_for_response( $object, $request ) {
1016
-
1017
-		// Fetch the fields to include in this response.
1018
-		$fields = $this->get_fields_for_response( $request );
1019
-
1020
-		// Prepare object data.
1021
-		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1022
-		$data    = $this->prepare_object_data( $object, $fields, $context );
1023
-		$data    = $this->add_additional_fields_to_object( $data, $request );
1024
-		$data    = $this->limit_object_to_requested_fields( $data, $fields );
1025
-		$data    = $this->filter_response_by_context( $data, $context );
1026
-
1027
-		// Prepare the response.
1028
-		$response = rest_ensure_response( $data );
1029
-		$response->add_links( $this->prepare_links( $object, $request ) );
1030
-
1031
-		// Filter item response.
1032
-		return apply_filters( "getpaid_rest_prepare_{$this->post_type}_object", $response, $object, $request );
1033
-	}
794
+        // Cache the invoice schema.
795
+        $this->schema = $schema;
796
+
797
+        return $this->add_additional_fields_schema( $this->schema );
798
+    }
799
+
800
+    /**
801
+     * Only return writable props from schema.
802
+     *
803
+     * @param  array $schema Schema.
804
+     * @return bool
805
+     */
806
+    public function filter_writable_props( $schema ) {
807
+        return empty( $schema['readonly'] );
808
+    }
809
+
810
+    /**
811
+     * Sanitizes and validates the list of post statuses.
812
+     *
813
+     * @since 1.0.13
814
+     *
815
+     * @param string|array    $statuses  One or more post statuses.
816
+     * @param WP_REST_Request $request   Full details about the request.
817
+     * @param string          $parameter Additional parameter to pass to validation.
818
+     * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
819
+     */
820
+    public function sanitize_post_statuses( $statuses, $request, $parameter ) {
821
+        return array_intersect( wp_parse_slug_list( $statuses ), $this->get_post_statuses() );
822
+    }
823
+
824
+    /**
825
+     * Retrieves a valid list of post statuses.
826
+     *
827
+     * @since 1.0.19
828
+     *
829
+     * @return array A list of registered item statuses.
830
+     */
831
+    public function get_post_statuses() {
832
+        return get_post_stati();
833
+    }
834
+
835
+    /**
836
+     * Prepare a single object for create or update.
837
+     *
838
+     * @since 1.0.19
839
+     * @param  WP_REST_Request $request Request object.
840
+     * @return GetPaid_Data|WP_Error Data object or WP_Error.
841
+     */
842
+    protected function prepare_item_for_database( $request ) {
843
+
844
+        // Do we have an object?
845
+        if ( empty( $this->crud_class ) || ! class_exists( $this->crud_class ) ) {
846
+            return new WP_Error( 'no_crud_class', __( 'You need to specify a CRUD class for this controller', 'invoicing' ) );
847
+        }
848
+
849
+        // Prepare the object.
850
+        $id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
851
+        $object    = new $this->crud_class( $id );
852
+
853
+        // Abort if an error exists.
854
+        if ( ! empty( $object->last_error ) ) {
855
+            return new WP_Error( 'invalid_item', $object->last_error );
856
+        }
857
+
858
+        $schema    = $this->get_item_schema();
859
+        $data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
860
+
861
+        // Handle all writable props.
862
+        foreach ( $data_keys as $key ) {
863
+            $value = $request[ $key ];
864
+
865
+            if ( ! is_null( $value ) ) {
866
+                switch ( $key ) {
867
+
868
+                    case 'meta_data':
869
+                        if ( is_array( $value ) ) {
870
+                            foreach ( $value as $meta ) {
871
+                                $object->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
872
+                            }
873
+                        }
874
+                        break;
875
+
876
+                    default:
877
+                        if ( is_callable( array( $object, "set_{$key}" ) ) ) {
878
+                            $object->{"set_{$key}"}( $value );
879
+                        }
880
+                        break;
881
+                }
882
+            }
883
+
884
+        }
885
+
886
+        // Filters an object before it is inserted via the REST API..
887
+        return apply_filters( "getpaid_rest_pre_insert_{$this->post_type}_object", $object, $request );
888
+    }
889
+
890
+    /**
891
+     * Retrieves data from a GetPaid class.
892
+     *
893
+     * @since  1.0.19
894
+     * @param  GetPaid_Meta_Data[]    $meta_data  meta data objects.
895
+     * @return array
896
+     */
897
+    protected function prepare_object_meta_data( $meta_data ) {
898
+        $meta = array();
899
+
900
+        foreach( $meta_data as $object ) {
901
+            $meta[] = $object->get_data();
902
+        }
903
+
904
+        return $meta;
905
+    }
906
+
907
+    /**
908
+     * Retrieves invoice items.
909
+     *
910
+     * @since  1.0.19
911
+     * @param  WPInv_Invoice $invoice  Invoice items.
912
+     * @param array            $fields Fields to include.
913
+     * @return array
914
+     */
915
+    protected function prepare_invoice_items( $invoice ) {
916
+        $items = array();
917
+
918
+        foreach( $invoice->get_items() as $item ) {
919
+
920
+            $item_data = $item->prepare_data_for_saving();
921
+
922
+            if ( 'amount' == $invoice->get_template() ) {
923
+                $item_data['quantity'] = 1;
924
+            }
925
+
926
+            $items[] = $item_data;
927
+        }
928
+
929
+        return $items;
930
+    }
931
+
932
+    /**
933
+     * Retrieves data from a GetPaid class.
934
+     *
935
+     * @since  1.0.19
936
+     * @param  GetPaid_Data    $object  Data object.
937
+     * @param array            $fields Fields to include.
938
+     * @param string           $context either view or edit.
939
+     * @return array
940
+     */
941
+    protected function prepare_object_data( $object, $fields, $context = 'view' ) {
942
+
943
+        $data = array();
944
+
945
+        // Handle all writable props.
946
+        foreach ( array_keys( $this->get_schema_properties() ) as $key ) {
947
+
948
+            // Abort if it is not included.
949
+            if ( ! empty( $fields ) && ! $this->is_field_included( $key, $fields ) ) {
950
+                continue;
951
+            }
952
+
953
+            // Or this current object does not support the field.
954
+            if ( ! $this->object_supports_field( $object, $key ) ) {
955
+                continue;
956
+            }
957
+
958
+            // Handle meta data.
959
+            if ( $key == 'meta_data' ) {
960
+                $data['meta_data'] = $this->prepare_object_meta_data( $object->get_meta_data() );
961
+                continue;
962
+            }
963
+
964
+            // Handle items.
965
+            if ( $key == 'items' && is_a( $object, 'WPInv_Invoice' )  ) {
966
+                $data['items'] = $this->prepare_invoice_items( $object );
967
+                continue;
968
+            }
969
+
970
+            // Booleans.
971
+            if ( is_callable( array( $object, $key ) ) ) {
972
+                $data[ $key ] = $object->$key( $context );
973
+                continue;
974
+            }
975
+
976
+            // Get object value.
977
+            if ( is_callable( array( $object, "get_{$key}" ) ) ) {
978
+                $value = $object->{"get_{$key}"}( $context );
979
+
980
+                // If the value is an instance of GetPaid_Data...
981
+                if ( is_a( $value, 'GetPaid_Data' ) ) {
982
+                    $value = $value->get_data( $context );
983
+                }
984
+
985
+                // For objects, retrieves it's properties.
986
+                $data[ $key ] = is_object( $value ) ? get_object_vars( $value ) :  $value ;
987
+                continue;
988
+            }
989
+
990
+        }
991
+
992
+        return $data;
993
+    }
994
+
995
+    /**
996
+     * Checks if a key should be included in a response.
997
+     *
998
+     * @since  1.0.19
999
+     * @param  GetPaid_Data $object  Data object.
1000
+     * @param  string       $field_key The key to check for.
1001
+     * @return bool
1002
+     */
1003
+    public function object_supports_field( $object, $field_key ) {
1004
+        return apply_filters( "getpaid_rest_{$this->post_type}_object_supports_key", true, $object, $field_key );
1005
+    }
1006
+
1007
+    /**
1008
+     * Prepare a single object output for response.
1009
+     *
1010
+     * @since  1.0.19
1011
+     * @param  GetPaid_Data    $object  Data object.
1012
+     * @param  WP_REST_Request $request Request object.
1013
+     * @return WP_REST_Response
1014
+     */
1015
+    public function prepare_item_for_response( $object, $request ) {
1016
+
1017
+        // Fetch the fields to include in this response.
1018
+        $fields = $this->get_fields_for_response( $request );
1019
+
1020
+        // Prepare object data.
1021
+        $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1022
+        $data    = $this->prepare_object_data( $object, $fields, $context );
1023
+        $data    = $this->add_additional_fields_to_object( $data, $request );
1024
+        $data    = $this->limit_object_to_requested_fields( $data, $fields );
1025
+        $data    = $this->filter_response_by_context( $data, $context );
1026
+
1027
+        // Prepare the response.
1028
+        $response = rest_ensure_response( $data );
1029
+        $response->add_links( $this->prepare_links( $object, $request ) );
1030
+
1031
+        // Filter item response.
1032
+        return apply_filters( "getpaid_rest_prepare_{$this->post_type}_object", $response, $object, $request );
1033
+    }
1034 1034
 
1035 1035
 }
Please login to merge, or discard this patch.
includes/wpinv-template-functions.php 1 patch
Indentation   +120 added lines, -120 removed lines patch added patch discarded remove patch
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
  * @param string $default_path The root path to the default template. Defaults to invoicing/templates
141 141
  */
142 142
 function wpinv_get_template_html( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
143
-	return getpaid_template()->get_template( $template_name, $args, $template_path, $default_path );
143
+    return getpaid_template()->get_template( $template_name, $args, $template_path, $default_path );
144 144
 }
145 145
 
146 146
 /**
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
  * @return string
159 159
  */
160 160
 function wpinv_get_theme_template_dir_name() {
161
-	return trailingslashit( apply_filters( 'wpinv_templates_dir', 'invoicing' ) );
161
+    return trailingslashit( apply_filters( 'wpinv_templates_dir', 'invoicing' ) );
162 162
 }
163 163
 
164 164
 /**
@@ -175,122 +175,122 @@  discard block
 block discarded – undo
175 175
 }
176 176
 
177 177
 function wpinv_get_template_part( $slug, $name = null, $load = true ) {
178
-	do_action( 'get_template_part_' . $slug, $slug, $name );
178
+    do_action( 'get_template_part_' . $slug, $slug, $name );
179 179
 
180
-	// Setup possible parts
181
-	$templates = array();
182
-	if ( isset( $name ) )
183
-		$templates[] = $slug . '-' . $name . '.php';
184
-	$templates[] = $slug . '.php';
180
+    // Setup possible parts
181
+    $templates = array();
182
+    if ( isset( $name ) )
183
+        $templates[] = $slug . '-' . $name . '.php';
184
+    $templates[] = $slug . '.php';
185 185
 
186
-	// Allow template parts to be filtered
187
-	$templates = apply_filters( 'wpinv_get_template_part', $templates, $slug, $name );
186
+    // Allow template parts to be filtered
187
+    $templates = apply_filters( 'wpinv_get_template_part', $templates, $slug, $name );
188 188
 
189
-	// Return the part that is found
190
-	return wpinv_locate_tmpl( $templates, $load, false );
189
+    // Return the part that is found
190
+    return wpinv_locate_tmpl( $templates, $load, false );
191 191
 }
192 192
 
193 193
 function wpinv_locate_tmpl( $template_names, $load = false, $require_once = true ) {
194
-	// No file found yet
195
-	$located = false;
194
+    // No file found yet
195
+    $located = false;
196 196
 
197
-	// Try to find a template file
198
-	foreach ( (array)$template_names as $template_name ) {
197
+    // Try to find a template file
198
+    foreach ( (array)$template_names as $template_name ) {
199 199
 
200
-		// Continue if template is empty
201
-		if ( empty( $template_name ) )
202
-			continue;
200
+        // Continue if template is empty
201
+        if ( empty( $template_name ) )
202
+            continue;
203 203
 
204
-		// Trim off any slashes from the template name
205
-		$template_name = ltrim( $template_name, '/' );
204
+        // Trim off any slashes from the template name
205
+        $template_name = ltrim( $template_name, '/' );
206 206
 
207
-		// try locating this template file by looping through the template paths
208
-		foreach( wpinv_get_theme_template_paths() as $template_path ) {
207
+        // try locating this template file by looping through the template paths
208
+        foreach( wpinv_get_theme_template_paths() as $template_path ) {
209 209
 
210
-			if( file_exists( $template_path . $template_name ) ) {
211
-				$located = $template_path . $template_name;
212
-				break;
213
-			}
214
-		}
210
+            if( file_exists( $template_path . $template_name ) ) {
211
+                $located = $template_path . $template_name;
212
+                break;
213
+            }
214
+        }
215 215
 
216
-		if( !empty( $located ) ) {
217
-			break;
218
-		}
219
-	}
216
+        if( !empty( $located ) ) {
217
+            break;
218
+        }
219
+    }
220 220
 
221
-	if ( ( true == $load ) && ! empty( $located ) )
222
-		load_template( $located, $require_once );
221
+    if ( ( true == $load ) && ! empty( $located ) )
222
+        load_template( $located, $require_once );
223 223
 
224
-	return $located;
224
+    return $located;
225 225
 }
226 226
 
227 227
 function wpinv_get_theme_template_paths() {
228
-	$template_dir = wpinv_get_theme_template_dir_name();
228
+    $template_dir = wpinv_get_theme_template_dir_name();
229 229
 
230
-	$file_paths = array(
231
-		1 => trailingslashit( get_stylesheet_directory() ) . $template_dir,
232
-		10 => trailingslashit( get_template_directory() ) . $template_dir,
233
-		100 => wpinv_get_templates_dir()
234
-	);
230
+    $file_paths = array(
231
+        1 => trailingslashit( get_stylesheet_directory() ) . $template_dir,
232
+        10 => trailingslashit( get_template_directory() ) . $template_dir,
233
+        100 => wpinv_get_templates_dir()
234
+    );
235 235
 
236
-	$file_paths = apply_filters( 'wpinv_template_paths', $file_paths );
236
+    $file_paths = apply_filters( 'wpinv_template_paths', $file_paths );
237 237
 
238
-	// sort the file paths based on priority
239
-	ksort( $file_paths, SORT_NUMERIC );
238
+    // sort the file paths based on priority
239
+    ksort( $file_paths, SORT_NUMERIC );
240 240
 
241
-	return array_map( 'trailingslashit', $file_paths );
241
+    return array_map( 'trailingslashit', $file_paths );
242 242
 }
243 243
 
244 244
 function wpinv_checkout_meta_tags() {
245 245
 
246
-	$pages   = array();
247
-	$pages[] = wpinv_get_option( 'success_page' );
248
-	$pages[] = wpinv_get_option( 'failure_page' );
249
-	$pages[] = wpinv_get_option( 'invoice_history_page' );
250
-	$pages[] = wpinv_get_option( 'invoice_subscription_page' );
246
+    $pages   = array();
247
+    $pages[] = wpinv_get_option( 'success_page' );
248
+    $pages[] = wpinv_get_option( 'failure_page' );
249
+    $pages[] = wpinv_get_option( 'invoice_history_page' );
250
+    $pages[] = wpinv_get_option( 'invoice_subscription_page' );
251 251
 
252
-	if( !wpinv_is_checkout() && !is_page( $pages ) ) {
253
-		return;
254
-	}
252
+    if( !wpinv_is_checkout() && !is_page( $pages ) ) {
253
+        return;
254
+    }
255 255
 
256
-	echo '<meta name="robots" content="noindex,nofollow" />' . "\n";
256
+    echo '<meta name="robots" content="noindex,nofollow" />' . "\n";
257 257
 }
258 258
 add_action( 'wp_head', 'wpinv_checkout_meta_tags' );
259 259
 
260 260
 function wpinv_add_body_classes( $class ) {
261
-	$classes = (array)$class;
261
+    $classes = (array)$class;
262 262
 
263
-	if( wpinv_is_checkout() ) {
264
-		$classes[] = 'wpinv-checkout';
265
-		$classes[] = 'wpinv-page';
266
-	}
263
+    if( wpinv_is_checkout() ) {
264
+        $classes[] = 'wpinv-checkout';
265
+        $classes[] = 'wpinv-page';
266
+    }
267 267
 
268
-	if( wpinv_is_success_page() ) {
269
-		$classes[] = 'wpinv-success';
270
-		$classes[] = 'wpinv-page';
271
-	}
268
+    if( wpinv_is_success_page() ) {
269
+        $classes[] = 'wpinv-success';
270
+        $classes[] = 'wpinv-page';
271
+    }
272 272
 
273
-	if( wpinv_is_failed_transaction_page() ) {
274
-		$classes[] = 'wpinv-failed-transaction';
275
-		$classes[] = 'wpinv-page';
276
-	}
273
+    if( wpinv_is_failed_transaction_page() ) {
274
+        $classes[] = 'wpinv-failed-transaction';
275
+        $classes[] = 'wpinv-page';
276
+    }
277 277
 
278
-	if( wpinv_is_invoice_history_page() ) {
279
-		$classes[] = 'wpinv-history';
280
-		$classes[] = 'wpinv-page';
281
-	}
278
+    if( wpinv_is_invoice_history_page() ) {
279
+        $classes[] = 'wpinv-history';
280
+        $classes[] = 'wpinv-page';
281
+    }
282 282
 
283
-	if( wpinv_is_subscriptions_history_page() ) {
284
-		$classes[] = 'wpinv-subscription';
285
-		$classes[] = 'wpinv-page';
286
-	}
283
+    if( wpinv_is_subscriptions_history_page() ) {
284
+        $classes[] = 'wpinv-subscription';
285
+        $classes[] = 'wpinv-page';
286
+    }
287 287
 
288
-	if( wpinv_is_test_mode() ) {
289
-		$classes[] = 'wpinv-test-mode';
290
-		$classes[] = 'wpinv-page';
291
-	}
288
+    if( wpinv_is_test_mode() ) {
289
+        $classes[] = 'wpinv-test-mode';
290
+        $classes[] = 'wpinv-page';
291
+    }
292 292
 
293
-	return array_unique( $classes );
293
+    return array_unique( $classes );
294 294
 }
295 295
 add_filter( 'body_class', 'wpinv_add_body_classes' );
296 296
 
@@ -992,21 +992,21 @@  discard block
 block discarded – undo
992 992
 
993 993
     $formatted_address = str_ireplace( array_keys( $replacements ), $replacements, $format );
994 994
     
995
-	// Remove unavailable tags.
995
+    // Remove unavailable tags.
996 996
     $formatted_address = preg_replace( "/\{\{\w+\}\}/", '', $formatted_address );
997 997
 
998 998
     // Clean up white space.
999
-	$formatted_address = preg_replace( '/  +/', ' ', trim( $formatted_address ) );
999
+    $formatted_address = preg_replace( '/  +/', ' ', trim( $formatted_address ) );
1000 1000
     $formatted_address = preg_replace( '/\n\n+/', "\n", $formatted_address );
1001 1001
     
1002 1002
     // Break newlines apart and remove empty lines/trim commas and white space.
1003
-	$formatted_address = array_filter( array_map( 'wpinv_trim_formatted_address_line', explode( "\n", $formatted_address ) ) );
1003
+    $formatted_address = array_filter( array_map( 'wpinv_trim_formatted_address_line', explode( "\n", $formatted_address ) ) );
1004 1004
 
1005 1005
     // Add html breaks.
1006
-	$formatted_address = implode( $separator, $formatted_address );
1006
+    $formatted_address = implode( $separator, $formatted_address );
1007 1007
 
1008
-	// We're done!
1009
-	return $formatted_address;
1008
+    // We're done!
1009
+    return $formatted_address;
1010 1010
     
1011 1011
 }
1012 1012
 
@@ -1208,7 +1208,7 @@  discard block
 block discarded – undo
1208 1208
 }
1209 1209
 
1210 1210
 function wpinv_empty_cart_message() {
1211
-	return apply_filters( 'wpinv_empty_cart_message', '<span class="wpinv_empty_cart">' . __( 'Your cart is empty.', 'invoicing' ) . '</span>' );
1211
+    return apply_filters( 'wpinv_empty_cart_message', '<span class="wpinv_empty_cart">' . __( 'Your cart is empty.', 'invoicing' ) . '</span>' );
1212 1212
 }
1213 1213
 
1214 1214
 /**
@@ -1560,10 +1560,10 @@  discard block
 block discarded – undo
1560 1560
 
1561 1561
     if ( 0 == count( $form->get_items() ) ) {
1562 1562
         echo aui()->alert(
1563
-			array(
1564
-				'type'    => 'warning',
1565
-				'content' => __( 'No published items found', 'invoicing' ),
1566
-			)
1563
+            array(
1564
+                'type'    => 'warning',
1565
+                'content' => __( 'No published items found', 'invoicing' ),
1566
+            )
1567 1567
         );
1568 1568
         return;
1569 1569
     }
@@ -1580,47 +1580,47 @@  discard block
 block discarded – undo
1580 1580
     $invoice = wpinv_get_invoice( $invoice_id );
1581 1581
 
1582 1582
     if ( empty( $invoice ) ) {
1583
-		return aui()->alert(
1584
-			array(
1585
-				'type'    => 'warning',
1586
-				'content' => __( 'Invoice not found', 'invoicing' ),
1587
-			)
1588
-		);
1583
+        return aui()->alert(
1584
+            array(
1585
+                'type'    => 'warning',
1586
+                'content' => __( 'Invoice not found', 'invoicing' ),
1587
+            )
1588
+        );
1589 1589
     }
1590 1590
 
1591 1591
     if ( $invoice->is_paid() ) {
1592
-		return aui()->alert(
1593
-			array(
1594
-				'type'    => 'warning',
1595
-				'content' => __( 'Invoice has already been paid', 'invoicing' ),
1596
-			)
1597
-		);
1592
+        return aui()->alert(
1593
+            array(
1594
+                'type'    => 'warning',
1595
+                'content' => __( 'Invoice has already been paid', 'invoicing' ),
1596
+            )
1597
+        );
1598 1598
     }
1599 1599
 
1600 1600
     // Get the form elements and items.
1601 1601
     $form     = wpinv_get_default_payment_form();
1602
-	$elements = $invoicing->form_elements->get_form_elements( $form );
1603
-	$items    = $invoicing->form_elements->convert_checkout_items( $invoice->cart_details, $invoice );
1602
+    $elements = $invoicing->form_elements->get_form_elements( $form );
1603
+    $items    = $invoicing->form_elements->convert_checkout_items( $invoice->cart_details, $invoice );
1604 1604
 
1605
-	ob_start();
1606
-	echo "<form class='wpinv_payment_form'>";
1607
-	do_action( 'wpinv_payment_form_top' );
1605
+    ob_start();
1606
+    echo "<form class='wpinv_payment_form'>";
1607
+    do_action( 'wpinv_payment_form_top' );
1608 1608
     echo "<input type='hidden' name='form_id' value='$form'/>";
1609 1609
     echo "<input type='hidden' name='invoice_id' value='$invoice_id'/>";
1610
-	wp_nonce_field( 'wpinv_payment_form', 'wpinv_payment_form' );
1611
-	wp_nonce_field( 'vat_validation', '_wpi_nonce' );
1610
+    wp_nonce_field( 'wpinv_payment_form', 'wpinv_payment_form' );
1611
+    wp_nonce_field( 'vat_validation', '_wpi_nonce' );
1612 1612
 
1613
-	foreach ( $elements as $element ) {
1614
-		do_action( 'wpinv_frontend_render_payment_form_element', $element, $items, $form );
1615
-		do_action( "wpinv_frontend_render_payment_form_{$element['type']}", $element, $items, $form );
1616
-	}
1613
+    foreach ( $elements as $element ) {
1614
+        do_action( 'wpinv_frontend_render_payment_form_element', $element, $items, $form );
1615
+        do_action( "wpinv_frontend_render_payment_form_{$element['type']}", $element, $items, $form );
1616
+    }
1617 1617
 
1618
-	echo "<div class='wpinv_payment_form_errors alert alert-danger d-none'></div>";
1619
-	do_action( 'wpinv_payment_form_bottom' );
1620
-	echo '</form>';
1618
+    echo "<div class='wpinv_payment_form_errors alert alert-danger d-none'></div>";
1619
+    do_action( 'wpinv_payment_form_bottom' );
1620
+    echo '</form>';
1621 1621
 
1622
-	$content = ob_get_clean();
1623
-	return str_replace( 'sr-only', '', $content );
1622
+    $content = ob_get_clean();
1623
+    return str_replace( 'sr-only', '', $content );
1624 1624
 }
1625 1625
 
1626 1626
 /**
@@ -1675,7 +1675,7 @@  discard block
 block discarded – undo
1675 1675
         return "<button class='btn btn-primary getpaid-payment-button' type='button' data-nonce='$nonce' data-form='$form'>$label</button>"; 
1676 1676
     }
1677 1677
 	
1678
-	if ( ! empty( $items ) ) {
1678
+    if ( ! empty( $items ) ) {
1679 1679
         $items  = esc_attr( $items );
1680 1680
         return "<button class='btn btn-primary getpaid-payment-button' type='button' data-nonce='$nonce' data-item='$items'>$label</button>"; 
1681 1681
     }
Please login to merge, or discard this patch.