Completed
Push — master ( 260ce8...60cf75 )
by Brian
21s queued 16s
created

WPInv_Ajax::define_ajax()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 9
rs 9.6111
cc 5
nc 3
nop 0
1
<?php
2
/**
3
 * Contains the ajax handlers.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
 
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * WPInv_Ajax class.
13
 */
14
class WPInv_Ajax {
15
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();
23
    }
24
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
		}
38
39
    }
40
    
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
		}
55
    }
56
    
57
    /**
58
	 * Check for GetPaid Ajax request and fire action.
59
	 */
60
	public static function do_wpinv_ajax() {
61
		global $wp_query;
62
63
		if ( ! empty( $_GET['wpinv-ajax'] ) ) {
64
			$wp_query->set( 'wpinv-ajax', sanitize_text_field( wp_unslash( $_GET['wpinv-ajax'] ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_GET['wpinv-ajax']) can also be of type string[]; however, parameter $str of sanitize_text_field() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

64
			$wp_query->set( 'wpinv-ajax', sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $_GET['wpinv-ajax'] ) ) );
Loading history...
65
		}
66
67
		$action = $wp_query->get( 'wpinv-ajax' );
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
		}
75
76
    }
77
78
    /**
79
	 * Hook in ajax methods.
80
	 */
81
    public static function add_ajax_events() {
82
83
        // array( 'event' => is_frontend )
84
        $ajax_events = array(
85
            'add_note'                    => false,
86
            'delete_note'                 => false,
87
            'get_states_field'            => true,
88
            'get_aui_states_field'        => true,
89
            'payment_form'                => true,
90
            'get_payment_form'            => true,
91
            'get_payment_form_states_field' => true,
92
            'get_invoicing_items'         => false,
93
            'get_invoice_items'           => false,
94
            'add_invoice_items'           => false,
95
            'edit_invoice_item'           => false,
96
            'remove_invoice_item'         => false,
97
            'get_billing_details'         => false,
98
            'recalculate_invoice_totals'  => false,
99
            'check_new_user_email'        => false,
100
            'run_tool'                    => false,
101
            'buy_items'                   => true,
102
            'payment_form_refresh_prices' => true,
103
            'ip_geolocation'              => true,
104
        );
105
106
        foreach ( $ajax_events as $ajax_event => $nopriv ) {
107
            add_action( 'wp_ajax_wpinv_' . $ajax_event, array( __CLASS__, $ajax_event ) );
108
            add_action( 'wp_ajax_getpaid_' . $ajax_event, array( __CLASS__, $ajax_event ) );
109
110
            if ( $nopriv ) {
111
                add_action( 'wp_ajax_nopriv_wpinv_' . $ajax_event, array( __CLASS__, $ajax_event ) );
112
                add_action( 'wp_ajax_nopriv_getpaid_' . $ajax_event, array( __CLASS__, $ajax_event ) );
113
                add_action( 'wpinv_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
114
            }
115
        }
116
    }
117
    
118
    public static function add_note() {
119
        check_ajax_referer( 'add-invoice-note', '_nonce' );
120
121
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
122
            die(-1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
123
        }
124
125
        $post_id   = absint( $_POST['post_id'] );
126
        $note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
127
        $note_type = sanitize_text_field( $_POST['note_type'] );
128
129
        $is_customer_note = $note_type == 'customer' ? 1 : 0;
130
131
        if ( $post_id > 0 ) {
132
            $note_id = wpinv_insert_payment_note( $post_id, $note, $is_customer_note );
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_insert_payment_note() has been deprecated. ( Ignorable by Annotation )

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

132
            $note_id = /** @scrutinizer ignore-deprecated */ wpinv_insert_payment_note( $post_id, $note, $is_customer_note );
Loading history...
133
134
            if ( $note_id > 0 && !is_wp_error( $note_id ) ) {
135
                wpinv_get_invoice_note_line_item( $note_id );
136
            }
137
        }
138
139
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
140
    }
141
142
    public static function delete_note() {
143
        check_ajax_referer( 'delete-invoice-note', '_nonce' );
144
145
        if ( !wpinv_current_user_can_manage_invoicing() ) {
146
            die(-1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
147
        }
148
149
        $note_id = (int)$_POST['note_id'];
150
151
        if ( $note_id > 0 ) {
152
            wp_delete_comment( $note_id, true );
153
        }
154
155
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
156
    }
157
    
158
    public static function get_states_field() {
159
        echo wpinv_get_states_field();
160
        
161
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
162
    }
163
164
    /**
165
     * Retrieves a given user's billing address.
166
     */
167
    public static function get_billing_details() {
168
169
        // Verify nonce.
170
        check_ajax_referer( 'wpinv-nonce' );
171
172
        // Can the user manage the plugin?
173
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
174
            die(-1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
175
        }
176
177
        // Do we have a user id?
178
        $user_id = $_GET['user_id'];
179
180
        if ( empty( $user_id ) || ! is_numeric( $user_id ) ) {
181
            die(-1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
182
        }
183
184
        // Fetch the billing details.
185
        $billing_details    = wpinv_get_user_address( $user_id );
186
        $billing_details    = apply_filters( 'wpinv_ajax_billing_details', $billing_details, $user_id );
187
188
        // unset the user id and email.
189
        $to_ignore = array( 'user_id', 'email' );
190
191
        foreach ( $to_ignore as $key ) {
192
            if ( isset( $billing_details[ $key ] ) ) {
193
                unset( $billing_details[ $key ] );
194
            }
195
        }
196
197
        wp_send_json_success( $billing_details );
198
199
    }
200
201
    /**
202
     * Checks if a new users email is valid.
203
     */
204
    public static function check_new_user_email() {
205
206
        // Verify nonce.
207
        check_ajax_referer( 'wpinv-nonce' );
208
209
        // Can the user manage the plugin?
210
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
211
            die(-1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
212
        }
213
214
        // We need an email address.
215
        if ( empty( $_GET['email'] ) ) {
216
            _e( "Provide the new user's email address", 'invoicing' );
217
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
218
        }
219
220
        // Ensure the email is valid.
221
        $email = sanitize_text_field( $_GET['email'] );
222
        if ( ! is_email( $email ) ) {
223
            _e( 'Invalid email address', 'invoicing' );
224
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
225
        }
226
227
        // And it does not exist.
228
        if ( email_exists( $email ) ) {
229
            _e( 'A user with this email address already exists', 'invoicing' );
230
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
231
        }
232
233
        wp_send_json_success( true );
234
    }
235
    
236
    public static function run_tool() {
237
        check_ajax_referer( 'wpinv-nonce', '_nonce' );
238
        if ( !wpinv_current_user_can_manage_invoicing() ) {
239
            die(-1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
240
        }
241
        
242
        $tool = sanitize_text_field( $_POST['tool'] );
243
        
244
        do_action( 'wpinv_run_tool' );
245
        
246
        if ( !empty( $tool ) ) {
247
            do_action( 'wpinv_tool_' . $tool );
248
        }
249
    }
250
251
    /**
252
     * Retrieves the markup for a payment form.
253
     */
254
    public static function get_payment_form() {
255
256
        // Check nonce.
257
        if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'getpaid_ajax_form' ) ) {
258
            _e( 'Error: Reload the page and try again.', 'invoicing' );
259
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
260
        }
261
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
				)
269
            );
270
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
271
        }
272
273
        // Payment form or button?
274
		if ( ! empty( $_GET['form'] ) ) {
275
            getpaid_display_payment_form( $_GET['form'] );
276
		} else if( ! empty( $_GET['invoice'] ) ) {
277
		    echo getpaid_display_invoice_payment_form( $_GET['invoice'] );
278
        } else {
279
			$items = getpaid_convert_items_to_array( $_GET['item'] );
280
		    getpaid_display_item_payment_form( $items );
281
        }
282
283
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
284
285
    }
286
287
    /**
288
     * Payment forms.
289
     *
290
     * @since 1.0.18
291
     */
292
    public static function payment_form() {
293
294
        // Check nonce.
295
        check_ajax_referer( 'getpaid_form_nonce' );
296
297
        // ... form fields...
298
        if ( empty( $_POST['getpaid_payment_form_submission'] ) ) {
299
            _e( 'Error: Reload the page and try again.', 'invoicing' );
300
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
301
        }
302
303
        // Process the payment form.
304
        $checkout_class = apply_filters( 'getpaid_checkout_class', 'GetPaid_Checkout' );
305
        $checkout       = new $checkout_class( new GetPaid_Payment_Form_Submission() );
306
        $checkout->process_checkout();
307
308
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
309
    }
310
311
    /**
312
     * Payment forms.
313
     *
314
     * @since 1.0.18
315
     */
316
    public static function get_payment_form_states_field() {
317
        global $invoicing;
318
319
        if ( empty( $_GET['country'] ) || empty( $_GET['form'] ) ) {
320
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
321
        }
322
323
        $elements = $invoicing->form_elements->get_form_elements( $_GET['form'] );
324
325
        if ( empty( $elements ) ) {
326
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
327
        }
328
329
        $address_fields = array();
330
        foreach ( $elements as $element ) {
331
            if ( 'address' === $element['type'] ) {
332
                $address_fields = $element;
333
                break;
334
            }
335
        }
336
337
        if ( empty( $address_fields ) ) {
338
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
339
        }
340
341
        foreach( $address_fields['fields'] as $address_field ) {
342
343
            if ( 'wpinv_state' == $address_field['name'] ) {
344
345
                $label = $address_field['label'];
346
347
                if ( ! empty( $address_field['required'] ) ) {
348
                    $label .= "<span class='text-danger'> *</span>";
349
                }
350
351
                $states = wpinv_get_country_states( $_GET['country'] );
352
353
                if ( ! empty( $states ) ) {
354
355
                    $html = aui()->select(
356
                            array(
357
                                'options'          => $states,
358
                                'name'             => esc_attr( $address_field['name'] ),
359
                                'id'               => esc_attr( $address_field['name'] ),
360
                                'placeholder'      => esc_attr( $address_field['placeholder'] ),
361
                                'required'         => (bool) $address_field['required'],
362
                                'no_wrap'          => true,
363
                                'label'            => wp_kses_post( $label ),
364
                                'select2'          => false,
365
                            )
366
                        );
367
368
                } else {
369
370
                    $html = aui()->input(
371
                            array(
372
                                'name'       => esc_attr( $address_field['name'] ),
373
                                'id'         => esc_attr( $address_field['name'] ),
374
                                'required'   => (bool) $address_field['required'],
375
                                'label'      => wp_kses_post( $label ),
376
                                'no_wrap'    => true,
377
                                'type'       => 'text',
378
                            )
379
                        );
380
381
                }
382
383
                wp_send_json_success( str_replace( 'sr-only', '', $html ) );
384
                exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
385
386
            }
387
388
        }
389
    
390
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
391
    }
392
393
    /**
394
     * Recalculates invoice totals.
395
     */
396
    public static function recalculate_invoice_totals() {
397
398
        // Verify nonce.
399
        check_ajax_referer( 'wpinv-nonce' );
400
401
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
402
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
403
        }
404
405
        // We need an invoice.
406
        if ( empty( $_POST['post_id'] ) ) {
407
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
408
        }
409
410
        // Fetch the invoice.
411
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
412
413
        // Ensure it exists.
414
        if ( ! $invoice->get_id() ) {
415
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
416
        }
417
418
        // Maybe set the country, state, currency.
419
        foreach ( array( 'country', 'state', 'currency' ) as $key ) {
420
            if ( isset( $_POST[ $key ] ) ) {
421
                $method = "set_$key";
422
                $invoice->$method( $_POST[ $key ] );
423
            }
424
        }
425
426
        // Maybe disable taxes.
427
        $invoice->set_disable_taxes( ! empty( $_POST['taxes'] ) );
428
429
        // Recalculate totals.
430
        $invoice->recalculate_total();
431
432
        $total = wpinv_price( wpinv_format_amount( $invoice->get_total() ), $invoice->get_currency() );
433
434
        if ( $invoice->is_recurring() && $invoice->is_parent() && $invoice->get_total() != $invoice->get_recurring_total() ) {
435
            $recurring_total = wpinv_price( wpinv_format_amount( $invoice->get_recurring_total() ), $invoice->get_currency() );
436
            $total          .= '<small class="form-text text-muted">' . sprintf( __( 'Recurring Price: %s', 'invoicing' ), $recurring_total ) . '</small>';
437
        }
438
439
        $totals = array(
440
            'subtotal' => wpinv_price( wpinv_format_amount( $invoice->get_subtotal() ), $invoice->get_currency() ),
441
            'discount' => wpinv_price( wpinv_format_amount( $invoice->get_total_discount() ), $invoice->get_currency() ),
442
            'tax'      => wpinv_price( wpinv_format_amount( $invoice->get_total_tax() ), $invoice->get_currency() ),
443
            'total'    => $total,
444
        );
445
446
        $totals = apply_filters( 'getpaid_invoice_totals', $totals, $invoice );
447
448
        wp_send_json_success( compact( 'totals' ) );
449
    }
450
451
    /**
452
     * Get items belonging to a given invoice.
453
     */
454
    public static function get_invoice_items() {
455
456
        // Verify nonce.
457
        check_ajax_referer( 'wpinv-nonce' );
458
459
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
460
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
461
        }
462
463
        // We need an invoice and items.
464
        if ( empty( $_POST['post_id'] ) ) {
465
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
466
        }
467
468
        // Fetch the invoice.
469
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
470
471
        // Ensure it exists.
472
        if ( ! $invoice->get_id() ) {
473
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
474
        }
475
476
        // Return an array of invoice items.
477
        $items = array();
478
479
        foreach ( $invoice->get_items() as $item_id => $item ) {
480
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax(  $invoice->get_currency()  );
481
        }
482
483
        wp_send_json_success( compact( 'items' ) );
484
    }
485
486
    /**
487
     * Edits an invoice item.
488
     */
489
    public static function edit_invoice_item() {
490
491
        // Verify nonce.
492
        check_ajax_referer( 'wpinv-nonce' );
493
494
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
495
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
496
        }
497
498
        // We need an invoice and item details.
499
        if ( empty( $_POST['post_id'] ) || empty( $_POST['data'] ) ) {
500
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
501
        }
502
503
        // Fetch the invoice.
504
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
505
506
        // Ensure it exists and its not been paid for.
507
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
508
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
509
        }
510
511
        // Format the data.
512
        $data = wp_list_pluck( $_POST['data'], 'value', 'field' );
513
514
        // Ensure that we have an item id.
515
        if ( empty( $data['id'] ) ) {
516
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
517
        }
518
519
        // Abort if the invoice does not have the specified item.
520
        $item = $invoice->get_item( (int) $data['id'] );
521
522
        if ( empty( $item ) ) {
523
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
524
        }
525
526
        // Update the item.
527
        $item->set_price( $data['price'] );
528
        $item->set_name( $data['name'] );
529
        $item->set_description( $data['description'] );
530
        $item->set_quantity( $data['quantity'] );
531
532
        // Add it to the invoice.
533
        $error = $invoice->add_item( $item );
534
        $alert = false;
535
        if ( is_wp_error( $error ) ) {
536
            $alert = $error->get_error_message();
537
        }
538
539
        // Update totals.
540
        $invoice->recalculate_total();
541
542
        // Save the invoice.
543
        $invoice->save();
544
545
        // Return an array of invoice items.
546
        $items = array();
547
548
        foreach ( $invoice->get_items() as $item_id => $item ) {
549
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax(  $invoice->get_currency()  );
550
        }
551
552
        wp_send_json_success( compact( 'items', 'alert' ) );
553
    }
554
555
    /**
556
     * Deletes an invoice item.
557
     */
558
    public static function remove_invoice_item() {
559
560
        // Verify nonce.
561
        check_ajax_referer( 'wpinv-nonce' );
562
563
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
564
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
565
        }
566
567
        // We need an invoice and an item.
568
        if ( empty( $_POST['post_id'] ) || empty( $_POST['item_id'] ) ) {
569
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
570
        }
571
572
        // Fetch the invoice.
573
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
574
575
        // Ensure it exists and its not been paid for.
576
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
577
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
578
        }
579
580
        // Abort if the invoice does not have the specified item.
581
        $item = $invoice->get_item( (int) $_POST['item_id'] );
582
583
        if ( empty( $item ) ) {
584
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
585
        }
586
587
        $invoice->remove_item( (int) $_POST['item_id'] );
588
589
        // Update totals.
590
        $invoice->recalculate_total();
591
592
        // Save the invoice.
593
        $invoice->save();
594
595
        // Return an array of invoice items.
596
        $items = array();
597
598
        foreach ( $invoice->get_items() as $item_id => $item ) {
599
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax(  $invoice->get_currency()  );
600
        }
601
602
        wp_send_json_success( compact( 'items' ) );
603
    }
604
605
    /**
606
     * Adds a items to an invoice.
607
     */
608
    public static function add_invoice_items() {
609
610
        // Verify nonce.
611
        check_ajax_referer( 'wpinv-nonce' );
612
613
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
614
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
615
        }
616
617
        // We need an invoice and items.
618
        if ( empty( $_POST['post_id'] ) || empty( $_POST['items'] ) ) {
619
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
620
        }
621
622
        // Fetch the invoice.
623
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
624
        $alert   = false;
625
626
        // Ensure it exists and its not been paid for.
627
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
628
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
629
        }
630
631
        // Add the items.
632
        foreach ( $_POST['items'] as $data ) {
633
634
            $item = new GetPaid_Form_Item( $data[ 'id' ] );
635
636
            if ( is_numeric( $data[ 'qty' ] ) && (int) $data[ 'qty' ] > 0 ) {
637
                $item->set_quantity( $data[ 'qty' ] );
638
            }
639
640
            if ( $item->get_id() > 0 ) {
641
                $error = $invoice->add_item( $item );
642
643
                if ( is_wp_error( $error ) ) {
644
                    $alert = $error->get_error_message();
645
                }
646
647
            }
648
649
        }
650
651
        // Save the invoice.
652
        $invoice->recalculate_total();
653
        $invoice->save();
654
655
        // Return an array of invoice items.
656
        $items = array();
657
658
        foreach ( $invoice->get_items() as $item_id => $item ) {
659
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax( $invoice->get_currency() );
660
        }
661
662
        wp_send_json_success( compact( 'items', 'alert' ) );
663
    }
664
665
    /**
666
     * Retrieves items that should be added to an invoice.
667
     */
668
    public static function get_invoicing_items() {
669
670
        // Verify nonce.
671
        check_ajax_referer( 'wpinv-nonce' );
672
673
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
674
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
675
        }
676
677
        // We need a search term.
678
        if ( empty( $_GET['search'] ) ) {
679
            wp_send_json_success( array() );
680
        }
681
682
        // Retrieve items.
683
        $item_args = array(
684
            'post_type'      => 'wpi_item',
685
            'orderby'        => 'title',
686
            'order'          => 'ASC',
687
            'posts_per_page' => -1,
688
            'post_status'    => array( 'publish' ),
689
            's'              => trim( $_GET['search'] ),
690
            'meta_query'     => array(
691
                array(
692
                    'key'       => '_wpinv_type',
693
                    'compare'   => '!=',
694
                    'value'     => 'package'
695
                )
696
            )
697
        );
698
699
        $items = get_posts( apply_filters( 'getpaid_ajax_invoice_items_query_args', $item_args ) );
700
        $data  = array();
701
702
        foreach ( $items as $item ) {
703
            $item      = new GetPaid_Form_Item( $item );
704
            $data[] = array(
705
                'id'   => $item->get_id(),
706
                'text' => $item->get_name()
707
            );
708
        }
709
710
        wp_send_json_success( $data );
711
712
    }
713
714
    /**
715
     * Retrieves the states field for AUI forms.
716
     */
717
    public static function get_aui_states_field() {
718
719
        // Verify nonce.
720
        check_ajax_referer( 'wpinv-nonce' );
721
722
        // We need a country.
723
        if ( empty( $_GET['country'] ) ) {
724
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
725
        }
726
727
        $states = wpinv_get_country_states( trim( $_GET['country'] ) );
728
        $state  = isset( $_GET['state'] ) ? trim( $_GET['state'] ) : wpinv_get_default_state();
729
730
        if ( empty( $states ) ) {
731
732
            $html = aui()->input(
733
                array(
734
                    'type'        => 'text',
735
                    'id'          => 'wpinv_state',
736
                    'name'        => 'wpinv_state',
737
                    'label'       => __( 'State', 'invoicing' ),
738
                    'label_type'  => 'vertical',
739
                    'placeholder' => 'Liège',
740
                    'class'       => 'form-control-sm',
741
                    'value'       => $state,
742
                )
743
            );
744
745
        } else {
746
747
            $html = aui()->select(
748
                array(
749
                    'id'          => 'wpinv_state',
750
                    'name'        => 'wpinv_state',
751
                    'label'       => __( 'State', 'invoicing' ),
752
                    'label_type'  => 'vertical',
753
                    'placeholder' => __( 'Select a state', 'invoicing' ),
754
                    'class'       => 'form-control-sm',
755
                    'value'       => $state,
756
                    'options'     => $states,
757
                    'data-allow-clear' => 'false',
758
                    'select2'          => true,
759
                )
760
            );
761
762
        }
763
764
        wp_send_json_success(
765
            array(
766
                'html'   => $html,
767
                'select' => ! empty ( $states )
768
            )
769
        );
770
771
    }
772
773
    /**
774
     * IP geolocation.
775
     *
776
     * @since 1.0.19
777
     */
778
    public static function ip_geolocation() {
779
780
        // Check nonce.
781
        check_ajax_referer( 'getpaid-ip-location' );
782
783
        // IP address.
784
        if ( empty( $_GET['ip'] ) || ! rest_is_ip_address( $_GET['ip'] ) ) {
785
            _e( 'Invalid IP Address.', 'invoicing' );
786
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
787
        }
788
789
        // Retrieve location info.
790
        $location = getpaid_geolocate_ip_address( $_GET['ip'] );
791
792
        if ( empty( $location ) ) {
793
            _e( 'Unable to find geolocation for the IP Address.', 'invoicing' );
794
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
795
        }
796
797
        // Sorry.
798
        extract( $location );
799
800
        // Prepare the address.
801
        $content = '';
802
803
        if ( ! empty( $location['city'] ) ) {
804
            $content .=  $location['city']  . ', ';
805
        }
806
        
807
        if ( ! empty( $location['region'] ) ) {
808
            $content .=  $location['region']  . ', ';
809
        }
810
        
811
        $content .=  $location['country'] . ' (' . $location['iso'] . ')';
812
813
        $location['address'] = $content;
814
815
        $content  = '<p>'. sprintf( __( '<b>Address:</b> %s', 'invoicing' ), $content ) . '</p>';
816
        $content .= '<p>'. $location['credit'] . '</p>';
817
818
        $location['content'] = $content;
819
820
        wpinv_get_template( 'geolocation.php', $location );
821
822
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
823
    }
824
825
    /**
826
     * Refresh prices.
827
     *
828
     * @since 1.0.19
829
     */
830
    public static function payment_form_refresh_prices() {
831
832
        // Check nonce.
833
        check_ajax_referer( 'getpaid_form_nonce' );
834
835
        // ... form fields...
836
        if ( empty( $_POST['getpaid_payment_form_submission'] ) ) {
837
            _e( 'Error: Reload the page and try again.', 'invoicing' );
838
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
839
        }
840
841
        // Load the submission.
842
        $submission = new GetPaid_Payment_Form_Submission();
843
844
        // Do we have an error?
845
        if ( ! empty( $submission->last_error ) ) {
846
            echo $submission->last_error;
847
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
848
        }
849
850
        // Prepare the result.
851
        $result = array(
852
853
            'submission_id' => $submission->id,
854
            'has_recurring' => $submission->has_recurring,
855
            'is_free'       => $submission->get_payment_details(),
856
857
            'totals'        => array(
858
                'subtotal'  => wpinv_price( wpinv_format_amount( $submission->subtotal_amount ), $submission->get_currency() ),
859
                'discount'  => wpinv_price( wpinv_format_amount( $submission->get_total_discount() ), $submission->get_currency() ),
860
                'fees'      => wpinv_price( wpinv_format_amount( $submission->get_total_fees() ), $submission->get_currency() ),
861
                'tax'       => wpinv_price( wpinv_format_amount( $submission->get_total_tax() ), $submission->get_currency() ),
862
                'total'     => wpinv_price( wpinv_format_amount( $submission->get_total() ), $submission->get_currency() ),
863
            ),
864
865
            'texts'         => array(
866
                '.getpaid-checkout-total-payable' => wpinv_price( wpinv_format_amount( $submission->get_total() ), $submission->get_currency() ),
867
            )
868
869
        );
870
871
        // Add items.
872
        $items = $submission->get_items();
873
        if ( ! empty( $items ) ) {
874
            $result['items'] = array();
875
876
            foreach( $items as $item_id => $item ) {
877
                $result['items']["$item_id"] = wpinv_price( wpinv_format_amount( $item->get_price() * $item->get_quantity() ) );
878
            }
879
        }
880
881
        // Add invoice.
882
        if ( $submission->has_invoice() ) {
883
            $result['invoice'] = $submission->get_invoice()->ID;
884
        }
885
886
        // Add discount code.
887
        if ( $submission->has_discount_code() ) {
888
            $result['discount_code'] = $submission->get_discount_code();
889
        }
890
891
        // Filter the result.
892
        $result = apply_filters( 'getpaid_payment_form_ajax_refresh_prices', $result, $submission );
893
894
        wp_send_json_success( $result );
895
    }
896
897
    /**
898
     * Lets users buy items via ajax.
899
     *
900
     * @since 1.0.0
901
     */
902
    public static function buy_items() {
903
        $user_id = get_current_user_id();
904
905
        if ( empty( $user_id ) ) { // If not logged in then lets redirect to the login page
906
            wp_send_json( array(
907
                'success' => wp_login_url( wp_get_referer() )
0 ignored issues
show
Bug introduced by
It seems like wp_get_referer() can also be of type false; however, parameter $redirect of wp_login_url() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

907
                'success' => wp_login_url( /** @scrutinizer ignore-type */ wp_get_referer() )
Loading history...
908
            ) );
909
        } else {
910
            // Only check nonce if logged in as it could be cached when logged out.
911
            if ( ! isset( $_POST['wpinv_buy_nonce'] ) || ! wp_verify_nonce( $_POST['wpinv_buy_nonce'], 'wpinv_buy_items' ) ) {
912
                wp_send_json( array(
913
                    'error' => __( 'Security checks failed.', 'invoicing' )
914
                ) );
915
                wp_die();
916
            }
917
918
            // allow to set a custom price through post_id
919
            $items = $_POST['items'];
920
            $related_post_id = isset( $_POST['post_id'] ) ? (int)$_POST['post_id'] : 0;
921
            $custom_item_price = $related_post_id ? abs( get_post_meta( $related_post_id, '_wpi_custom_price', true ) ) : 0;
922
923
            $cart_items = array();
924
            if ( $items ) {
925
                $items = explode( ',', $items );
926
927
                foreach( $items as $item ) {
928
                    $item_id = $item;
929
                    $quantity = 1;
930
931
                    if ( strpos( $item, '|' ) !== false ) {
932
                        $item_parts = explode( '|', $item );
933
                        $item_id = $item_parts[0];
934
                        $quantity = $item_parts[1];
935
                    }
936
937
                    if ( $item_id && $quantity ) {
938
                        $cart_items_arr = array(
939
                            'id'            => (int)$item_id,
940
                            'quantity'      => (int)$quantity
941
                        );
942
943
                        // If there is a related post id then add it to meta
944
                        if ( $related_post_id ) {
945
                            $cart_items_arr['meta'] = array(
946
                                'post_id'   => $related_post_id
947
                            );
948
                        }
949
950
                        // If there is a custom price then set it.
951
                        if ( $custom_item_price ) {
952
                            $cart_items_arr['custom_price'] = $custom_item_price;
953
                        }
954
955
                        $cart_items[] = $cart_items_arr;
956
                    }
957
                }
958
            }
959
960
            /**
961
             * Filter the wpinv_buy shortcode cart items on the fly.
962
             *
963
             * @param array $cart_items The cart items array.
964
             * @param int $related_post_id The related post id if any.
965
             * @since 1.0.0
966
             */
967
            $cart_items = apply_filters( 'wpinv_buy_cart_items', $cart_items, $related_post_id );
968
969
            // Make sure its not in the cart already, if it is then redirect to checkout.
970
            $cart_invoice = wpinv_get_invoice_cart();
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_get_invoice_cart() has been deprecated. ( Ignorable by Annotation )

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

970
            $cart_invoice = /** @scrutinizer ignore-deprecated */ wpinv_get_invoice_cart();
Loading history...
971
972
            if ( isset( $cart_invoice->items ) && !empty( $cart_invoice->items ) && !empty( $cart_items ) && serialize( $cart_invoice->items ) == serialize( $cart_items ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The property items does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
973
                wp_send_json( array(
974
                    'success' =>  $cart_invoice->get_checkout_payment_url()
975
                ) );
976
                wp_die();
977
            }
978
979
            // Check if user has invoice with same items waiting to be paid.
980
            $user_invoices = wpinv_get_users_invoices( $user_id , 10 , false , 'wpi-pending' );
981
            if ( !empty( $user_invoices ) ) {
982
                foreach( $user_invoices as $user_invoice ) {
983
                    $user_cart_details = array();
984
                    $invoice  = wpinv_get_invoice( $user_invoice->ID );
985
                    $cart_details = $invoice->get_cart_details();
986
987
                    if ( !empty( $cart_details ) ) {
988
                        foreach ( $cart_details as $invoice_item ) {
989
                            $ii_arr = array();
990
                            $ii_arr['id'] = (int)$invoice_item['id'];
991
                            $ii_arr['quantity'] = (int)$invoice_item['quantity'];
992
993
                            if (isset( $invoice_item['meta'] ) && !empty( $invoice_item['meta'] ) ) {
994
                                $ii_arr['meta'] = $invoice_item['meta'];
995
                            }
996
997
                            if ( isset( $invoice_item['custom_price'] ) && !empty( $invoice_item['custom_price'] ) ) {
998
                                $ii_arr['custom_price'] = $invoice_item['custom_price'];
999
                            }
1000
1001
                            $user_cart_details[] = $ii_arr;
1002
                        }
1003
                    }
1004
1005
                    if ( !empty( $user_cart_details ) && serialize( $cart_items ) == serialize( $user_cart_details ) ) {
1006
                        wp_send_json( array(
1007
                            'success' =>  $invoice->get_checkout_payment_url()
1008
                        ) );
1009
                        wp_die();
1010
                    }
1011
                }
1012
            }
1013
1014
            // Create invoice and send user to checkout
1015
            if ( !empty( $cart_items ) ) {
1016
                $invoice_data = array(
1017
                    'status'        =>  'wpi-pending',
1018
                    'created_via'   =>  'wpi',
1019
                    'user_id'       =>  $user_id,
1020
                    'cart_details'  =>  $cart_items,
1021
                );
1022
1023
                $invoice = wpinv_insert_invoice( $invoice_data, true );
1024
1025
                if ( !empty( $invoice ) && isset( $invoice->ID ) ) {
1026
                    wp_send_json( array(
1027
                        'success' =>  $invoice->get_checkout_payment_url()
0 ignored issues
show
Bug introduced by
The method get_checkout_payment_url() does not exist on WP_Error. ( Ignorable by Annotation )

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

1027
                        'success' =>  $invoice->/** @scrutinizer ignore-call */ get_checkout_payment_url()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1028
                    ) );
1029
                } else {
1030
                    wp_send_json( array(
1031
                        'error' => __( 'Invoice failed to create', 'invoicing' )
1032
                    ) );
1033
                }
1034
            } else {
1035
                wp_send_json( array(
1036
                    'error' => __( 'Items not valid.', 'invoicing' )
1037
                ) );
1038
            }
1039
        }
1040
1041
        wp_die();
1042
    }
1043
}
1044
1045
WPInv_Ajax::init();