Passed
Push — master ( eba4be...60f62d )
by Brian
09:36 queued 04:47
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
            'checkout'                    => false,
90
            'payment_form'                => true,
91
            'get_payment_form'            => true,
92
            'get_payment_form_states_field' => true,
93
            'get_invoicing_items'         => false,
94
            'get_invoice_items'           => false,
95
            'add_invoice_items'           => false,
96
            'edit_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
        );
104
105
        foreach ( $ajax_events as $ajax_event => $nopriv ) {
106
            add_action( 'wp_ajax_wpinv_' . $ajax_event, array( __CLASS__, $ajax_event ) );
107
            add_action( 'wp_ajax_getpaid_' . $ajax_event, array( __CLASS__, $ajax_event ) );
108
109
            if ( $nopriv ) {
110
                add_action( 'wp_ajax_nopriv_wpinv_' . $ajax_event, array( __CLASS__, $ajax_event ) );
111
                add_action( 'wp_ajax_nopriv_getpaid_' . $ajax_event, array( __CLASS__, $ajax_event ) );
112
                add_action( 'wpinv_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
113
            }
114
        }
115
    }
116
    
117
    public static function add_note() {
118
        check_ajax_referer( 'add-invoice-note', '_nonce' );
119
120
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
121
            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...
122
        }
123
124
        $post_id   = absint( $_POST['post_id'] );
125
        $note      = wp_kses_post( trim( stripslashes( $_POST['note'] ) ) );
126
        $note_type = sanitize_text_field( $_POST['note_type'] );
127
128
        $is_customer_note = $note_type == 'customer' ? 1 : 0;
129
130
        if ( $post_id > 0 ) {
131
            $note_id = wpinv_insert_payment_note( $post_id, $note, $is_customer_note );
132
133
            if ( $note_id > 0 && !is_wp_error( $note_id ) ) {
134
                wpinv_get_invoice_note_line_item( $note_id );
135
            }
136
        }
137
138
        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...
139
    }
140
141
    public static function delete_note() {
142
        check_ajax_referer( 'delete-invoice-note', '_nonce' );
143
144
        if ( !wpinv_current_user_can_manage_invoicing() ) {
145
            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...
146
        }
147
148
        $note_id = (int)$_POST['note_id'];
149
150
        if ( $note_id > 0 ) {
151
            wp_delete_comment( $note_id, true );
152
        }
153
154
        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...
155
    }
156
    
157
    public static function get_states_field() {
158
        echo wpinv_get_states_field();
159
        
160
        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...
161
    }
162
    
163
    public static function checkout() {
164
        if ( ! defined( 'WPINV_CHECKOUT' ) ) {
165
            define( 'WPINV_CHECKOUT', true );
166
        }
167
168
        wpinv_process_checkout();
169
        die(0);
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...
170
    }
171
172
    /**
173
     * Retrieves a given user's billing address.
174
     */
175
    public static function get_billing_details() {
176
177
        // Verify nonce.
178
        check_ajax_referer( 'wpinv-nonce' );
179
180
        // Can the user manage the plugin?
181
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
182
            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...
183
        }
184
185
        // Do we have a user id?
186
        $user_id = $_GET['user_id'];
187
188
        if ( empty( $user_id ) || ! is_numeric( $user_id ) ) {
189
            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...
190
        }
191
192
        // Fetch the billing details.
193
        $billing_details    = wpinv_get_user_address( $user_id );
194
        $billing_details    = apply_filters( 'wpinv_ajax_billing_details', $billing_details, $user_id );
195
196
        // unset the user id and email.
197
        $to_ignore = array( 'user_id', 'email' );
198
199
        foreach ( $to_ignore as $key ) {
200
            if ( isset( $billing_details[ $key ] ) ) {
201
                unset( $billing_details[ $key ] );
202
            }
203
        }
204
205
        wp_send_json_success( $billing_details );
206
207
    }
208
209
    /**
210
     * Checks if a new users email is valid.
211
     */
212
    public static function check_new_user_email() {
213
214
        // Verify nonce.
215
        check_ajax_referer( 'wpinv-nonce' );
216
217
        // Can the user manage the plugin?
218
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
219
            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...
220
        }
221
222
        // We need an email address.
223
        if ( empty( $_GET['email'] ) ) {
224
            _e( "Provide the new user's email address", 'invoicing' );
225
            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...
226
        }
227
228
        // Ensure the email is valid.
229
        $email = sanitize_text_field( $_GET['email'] );
230
        if ( ! is_email( $email ) ) {
231
            _e( 'Invalid email address', 'invoicing' );
232
            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...
233
        }
234
235
        // And it does not exist.
236
        if ( email_exists( $email ) ) {
237
            _e( 'A user with this email address already exists', 'invoicing' );
238
            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...
239
        }
240
241
        wp_send_json_success( true );
242
    }
243
    
244
    public static function run_tool() {
245
        check_ajax_referer( 'wpinv-nonce', '_nonce' );
246
        if ( !wpinv_current_user_can_manage_invoicing() ) {
247
            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...
248
        }
249
        
250
        $tool = sanitize_text_field( $_POST['tool'] );
251
        
252
        do_action( 'wpinv_run_tool' );
253
        
254
        if ( !empty( $tool ) ) {
255
            do_action( 'wpinv_tool_' . $tool );
256
        }
257
    }
258
259
    /**
260
     * Retrieves the markup for a payment form.
261
     */
262
    public static function get_payment_form() {
263
264
        // Check nonce.
265
        if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'getpaid_ajax_form' ) ) {
266
            _e( 'Error: Reload the page and try again.', 'invoicing' );
267
            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...
268
        }
269
270
        // Is the request set up correctly?
271
		if ( empty( $_GET['form'] ) && empty( $_GET['item'] ) ) {
272
			echo aui()->alert(
273
				array(
274
					'type'    => 'warning',
275
					'content' => __( 'No payment form or item provided', 'invoicing' ),
276
				)
277
            );
278
            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...
279
        }
280
281
        // Payment form or button?
282
		if ( ! empty( $_GET['form'] ) ) {
283
            echo getpaid_display_payment_form( $_GET['form'] );
284
		} else if( $_GET['invoice'] ) {
285
		    echo getpaid_display_invoice_payment_form( $_GET['invoice'] );
286
        } else {
287
			$items = getpaid_convert_items_to_array( $_GET['item'] );
288
		    echo getpaid_display_item_payment_form( $items );
289
        }
290
        
291
        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...
292
293
    }
294
295
    /**
296
     * Payment forms.
297
     *
298
     * @since 1.0.18
299
     */
300
    public static function payment_form() {
301
        global $invoicing, $wpi_checkout_id, $cart_total;
302
303
        // Check nonce.
304
        check_ajax_referer( 'getpaid_form_nonce' );
305
306
        // ... form fields...
307
        if ( empty( $_POST['getpaid_payment_form_submission'] ) ) {
308
            _e( 'Error: Reload the page and try again.', 'invoicing' );
309
            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...
310
        }
311
312
        // Load the submission.
313
        $submission = new GetPaid_Payment_Form_Submission();
314
315
        // Do we have an error?
316
        if ( ! empty( $submission->last_error ) ) {
317
            echo $submission->last_error;
318
            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...
319
        }
320
321
        // We need a billing email.
322
        if ( ! $submission->has_billing_email() || ! is_email( $submission->get_billing_email() ) ) {
323
            wp_send_json_error( __( 'Provide a valid billing email.', 'invoicing' ) );
324
        }
325
326
        // Prepare items.
327
        $items            = $submission->get_items();
328
        $prepared_items   = array();
329
330
        if ( ! empty( $items ) ) {
331
332
            foreach( $items as $item_id => $item ) {
333
334
                if ( $item->can_purchase() ) {
335
                    $prepared_items[] = array(
336
                        'id'           => $item_id,
337
                        'item_price'   => $item->get_price(),
338
                        'custom_price' => $item->get_price(),
339
                        'name'         => $item->get_name(),
340
                        'quantity'     => $item->get_quantity(),
341
                    );
342
                }
343
344
            }
345
346
        }
347
348
        if ( empty( $prepared_items ) ) {
349
            wp_send_json_error( __( 'You have not selected any items.', 'invoicing' ) );
350
        }
351
352
        if ( $submission->has_recurring && 1 != count( $prepared_items ) ) {
353
            wp_send_json_error( __( 'Recurring items should be bought individually.', 'invoicing' ) );
354
        }
355
356
        // Prepare the submission details.
357
        $prepared = array(
358
            'billing_email'                    => sanitize_email( $submission->get_billing_email() ),
359
            __( 'Billing Email', 'invoicing' ) => sanitize_email( $submission->get_billing_email() ),
360
            __( 'Form Id', 'invoicing' )       => absint( $submission->payment_form->get_id() ),
361
        );
362
363
        // Address fields.
364
        $address_fields = array();
365
366
        // Add discount code.
367
        if ( $submission->has_discount_code() ) {
368
            $address_fields['discount'] = array( $submission->get_discount_code() );
369
        }
370
371
        // Are all required fields provided?
372
        $data = $submission->get_data();
373
374
        foreach ( $submission->payment_form->get_elements() as $field ) {
375
376
            if ( ! empty( $field['premade'] ) ) {
377
                continue;
378
            }
379
380
            if ( ! $submission->is_required_field_set( $field ) ) {
381
                wp_send_json_error( __( 'Fill all required fields.', 'invoicing' ) );
382
            }
383
384
            if ( $field['type'] == 'address' ) {
385
386
                foreach ( $field['fields'] as $address_field ) {
387
388
                    if ( empty( $address_field['visible'] ) ) {
389
                        continue;
390
                    }
391
392
                    if ( ! empty( $address_field['required'] ) && empty( $data[ $address_field['name'] ] ) ) {
393
                        wp_send_json_error( __( 'Some required fields have not been filled.', 'invoicing' ) );
394
                    }
395
396
                    if ( isset( $data[ $address_field['name'] ] ) ) {
397
                        $label = str_replace( 'wpinv_', '', $address_field['name'] );
398
                        $address_fields[ $label ] = wpinv_clean( $data[ $address_field['name'] ] );
399
                    }
400
401
                }
402
403
            } else if ( isset( $data[ $field['id'] ] ) ) {
404
                $label = $field['id'];
405
406
                if ( isset( $field['label'] ) ) {
407
                    $label = $field['label'];
408
                }
409
410
                $prepared[ wpinv_clean( $label ) ] = wpinv_clean( $data[ $field['id'] ] );
411
            }
412
413
        }
414
415
        // (Maybe) create the user.
416
        $user = get_user_by( 'email', $prepared['billing_email'] );
417
418
        if ( empty( $user ) ) {
419
            $user = wpinv_create_user( $prepared['billing_email'] );
420
        }
421
422
        if ( is_wp_error( $user ) ) {
423
            wp_send_json_error( $user->get_error_message() );
424
        }
425
426
        if ( is_numeric( $user ) ) {
427
            $user = get_user_by( 'id', $user );
428
        }
429
430
        // Create the invoice.
431
        if ( ! $submission->has_invoice() ) {
432
433
            $invoice = wpinv_insert_invoice(
434
                array(
435
                    'status'        => 'wpi-pending',
436
                    'created_via'   => 'payment_form',
437
                    'user_id'       => $user->ID,
438
                    'cart_details'  => $prepared_items,
439
                    'user_info'     => $address_fields,
440
                ),
441
                true
442
            );
443
444
        } else {
445
446
            $invoice = $submission->get_invoice();
447
448
            if ( $invoice->is_paid() ) {
449
                wp_send_json_error( __( 'This invoice has already been paid for.', 'invoicing' ) );
450
            }
451
452
            $invoice = wpinv_update_invoice(
453
                array(
454
                    'ID'            => $submission->get_invoice()->ID,
455
                    'status'        => 'wpi-pending',
456
                    'cart_details'  => $prepared_items,
457
                    'user_info'     => $address_fields,
458
                ),
459
                true
460
            );
461
462
        }
463
464
        if ( is_wp_error( $invoice ) ) {
465
            wp_send_json_error( $invoice->get_error_message() );
466
        }
467
468
        if ( empty( $invoice ) ) {
469
            wp_send_json_error( __( 'Could not create your invoice.', 'invoicing' ) );
470
        }
471
472
        unset( $prepared['billing_email'] );
473
        update_post_meta( $invoice->ID, 'payment_form_data', $prepared );
474
475
        $wpi_checkout_id = $invoice->ID;
476
        $cart_total = wpinv_price(
477
            wpinv_format_amount(
478
                wpinv_get_cart_total( $invoice->get_cart_details(), NULL, $invoice ) ),
479
                $invoice->get_currency()
480
        );
481
482
        $data                   = array();
483
        $data['invoice_id']     = $invoice->ID;
484
        $data['cart_discounts'] = $invoice->get_discounts( true );
485
486
        wpinv_set_checkout_session( $data );
487
        add_filter( 'wp_redirect', array( $invoicing->form_elements, 'send_redirect_response' ) );
488
        add_action( 'wpinv_pre_send_back_to_checkout', array( $invoicing->form_elements, 'checkout_error' ) );
489
        
490
        if ( ! defined( 'WPINV_CHECKOUT' ) ) {
491
            define( 'WPINV_CHECKOUT', true );
492
        }
493
494
        wpinv_process_checkout();
495
496
        $invoicing->form_elements->checkout_error();
497
498
        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...
499
    }
500
501
    /**
502
     * Payment forms.
503
     *
504
     * @since 1.0.18
505
     */
506
    public static function get_payment_form_states_field() {
507
        global $invoicing;
508
509
        if ( empty( $_GET['country'] ) || empty( $_GET['form'] ) ) {
510
            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...
511
        }
512
513
        $elements = $invoicing->form_elements->get_form_elements( $_GET['form'] );
514
515
        if ( empty( $elements ) ) {
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
        $address_fields = array();
520
        foreach ( $elements as $element ) {
521
            if ( 'address' === $element['type'] ) {
522
                $address_fields = $element;
523
                break;
524
            }
525
        }
526
527
        if ( empty( $address_fields ) ) {
528
            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...
529
        }
530
531
        foreach( $address_fields['fields'] as $address_field ) {
532
533
            if ( 'wpinv_state' == $address_field['name'] ) {
534
535
                $label = $address_field['label'];
536
537
                if ( ! empty( $address_field['required'] ) ) {
538
                    $label .= "<span class='text-danger'> *</span>";
539
                }
540
541
                $states = wpinv_get_country_states( $_GET['country'] );
542
543
                if ( ! empty( $states ) ) {
544
545
                    $html = aui()->select(
546
                            array(
547
                                'options'          => $states,
548
                                'name'             => esc_attr( $address_field['name'] ),
549
                                'id'               => esc_attr( $address_field['name'] ),
550
                                'placeholder'      => esc_attr( $address_field['placeholder'] ),
551
                                'required'         => (bool) $address_field['required'],
552
                                'no_wrap'          => true,
553
                                'label'            => wp_kses_post( $label ),
554
                                'select2'          => false,
555
                            )
556
                        );
557
558
                } else {
559
560
                    $html = aui()->input(
561
                            array(
562
                                'name'       => esc_attr( $address_field['name'] ),
563
                                'id'         => esc_attr( $address_field['name'] ),
564
                                'required'   => (bool) $address_field['required'],
565
                                'label'      => wp_kses_post( $label ),
566
                                'no_wrap'    => true,
567
                                'type'       => 'text',
568
                            )
569
                        );
570
571
                }
572
573
                wp_send_json_success( str_replace( 'sr-only', '', $html ) );
574
                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...
575
576
            }
577
578
        }
579
    
580
        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...
581
    }
582
583
    /**
584
     * Recalculates invoice totals.
585
     */
586
    public static function recalculate_invoice_totals() {
587
588
        // Verify nonce.
589
        check_ajax_referer( 'wpinv-nonce' );
590
591
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
592
            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...
593
        }
594
595
        // We need an invoice.
596
        if ( empty( $_POST['post_id'] ) ) {
597
            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...
598
        }
599
600
        // Fetch the invoice.
601
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
602
603
        // Ensure it exists.
604
        if ( ! $invoice->get_id() ) {
605
            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...
606
        }
607
608
        // Maybe set the country, state, currency.
609
        foreach ( array( 'country', 'state', 'currency' ) as $key ) {
610
            if ( isset( $_POST[ $key ] ) ) {
611
                $method = "set_$key";
612
                $invoice->$method( $_POST[ $key ] );
613
            }
614
        }
615
616
        // Maybe disable taxes.
617
        $invoice->set_disable_taxes( ! empty( $_POST['taxes'] ) );
618
619
        // Recalculate totals.
620
        $invoice->recalculate_total();
621
622
        $totals = array(
623
            'subtotal' => wpinv_price( wpinv_format_amount( $invoice->get_subtotal() ) ),
624
            'discount' => wpinv_price( wpinv_format_amount( $invoice->get_total_discount() ) ),
625
            'tax'      => wpinv_price( wpinv_format_amount( $invoice->get_total_tax() ) ),
626
            'total'    => wpinv_price( wpinv_format_amount( $invoice->get_total() ) ),
627
        );
628
629
        $totals = apply_filters( 'getpaid_invoice_totals', $totals, $invoice );
630
631
        wp_send_json_success( compact( 'totals' ) );
632
    }
633
634
    /**
635
     * Get items belonging to a given invoice.
636
     */
637
    public static function get_invoice_items() {
638
639
        // Verify nonce.
640
        check_ajax_referer( 'wpinv-nonce' );
641
642
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
643
            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...
644
        }
645
646
        // We need an invoice and items.
647
        if ( empty( $_POST['post_id'] ) ) {
648
            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...
649
        }
650
651
        // Fetch the invoice.
652
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
653
654
        // Ensure it exists.
655
        if ( ! $invoice->get_id() ) {
656
            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...
657
        }
658
659
        // Return an array of invoice items.
660
        $items = array();
661
662
        foreach ( $invoice->get_items() as $item_id => $item ) {
663
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax();
664
        }
665
666
        wp_send_json_success( compact( 'items' ) );
667
    }
668
669
    /**
670
     * Edits an invoice item.
671
     */
672
    public static function edit_invoice_item() {
673
674
        // Verify nonce.
675
        check_ajax_referer( 'wpinv-nonce' );
676
677
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
678
            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...
679
        }
680
681
        // We need an invoice and item details.
682
        if ( empty( $_POST['post_id'] ) || empty( $_POST['data'] ) ) {
683
            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...
684
        }
685
686
        // Fetch the invoice.
687
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
688
689
        // Ensure it exists and its not been paid for.
690
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
691
            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...
692
        }
693
694
        // Format the data.
695
        $data = wp_list_pluck( $_POST['data'], 'value', 'field' );
696
697
        // Ensure that we have an item id.
698
        if ( empty( $data['id'] ) ) {
699
            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...
700
        }
701
702
        // Abort if the invoice does not have the specified item.
703
        $item = $invoice->get_item( (int) $data['id'] );
704
705
        if ( empty( $item ) ) {
706
            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...
707
        }
708
709
        // Update the item.
710
        $item->set_price( $data['price'] );
711
        $item->set_name( $data['name'] );
712
        $item->set_description( $data['description'] );
713
        $item->set_quantity( $data['quantity'] );
714
715
        // Add it to the invoice.
716
        $invoice->add_item( $item );
717
718
        // Update totals.
719
        $invoice->recalculate_total();
720
721
        // Save the invoice.
722
        $invoice->save();
723
724
        // Return an array of invoice items.
725
        $items = array();
726
727
        foreach ( $invoice->get_items() as $item_id => $item ) {
728
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax();
729
        }
730
731
        wp_send_json_success( compact( 'items' ) );
732
    }
733
    /**
734
     * Adds a items to an invoice.
735
     */
736
    public static function add_invoice_items() {
737
738
        // Verify nonce.
739
        check_ajax_referer( 'wpinv-nonce' );
740
741
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
742
            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...
743
        }
744
745
        // We need an invoice and items.
746
        if ( empty( $_POST['post_id'] ) || empty( $_POST['items'] ) ) {
747
            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...
748
        }
749
750
        // Fetch the invoice.
751
        $invoice = new WPInv_Invoice( trim( $_POST['post_id'] ) );
752
        $alert   = false;
753
754
        // Ensure it exists and its not been paid for.
755
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
756
            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...
757
        }
758
759
        // Add the items.
760
        foreach ( $_POST['items'] as $data ) {
761
762
            $item = new GetPaid_Form_Item( $data[ 'id' ] );
763
764
            if ( is_numeric( $data[ 'qty' ] ) && (int) $data[ 'qty' ] > 0 ) {
765
                $item->set_quantity( $data[ 'qty' ] );
766
            }
767
768
            if ( $item->get_id() > 0 ) {
769
                if ( ! $invoice->add_item( $item ) ) {
770
                    $alert = __( 'An invoice can only contain one recurring item', 'invoicing' );
771
                }
772
            }
773
774
        }
775
776
        // Save the invoice.
777
        $invoice->recalculate_total();
778
        $invoice->save();
779
780
        // Return an array of invoice items.
781
        $items = array();
782
783
        foreach ( $invoice->get_items() as $item_id => $item ) {
784
            $items[ $item_id ] = $item->prepare_data_for_invoice_edit_ajax();
785
        }
786
787
        wp_send_json_success( compact( 'items', 'alert' ) );
788
    }
789
790
    /**
791
     * Retrieves items that should be added to an invoice.
792
     */
793
    public static function get_invoicing_items() {
794
795
        // Verify nonce.
796
        check_ajax_referer( 'wpinv-nonce' );
797
798
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
799
            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...
800
        }
801
802
        // We need a search term.
803
        if ( empty( $_GET['search'] ) ) {
804
            wp_send_json_success( array() );
805
        }
806
807
        // Retrieve items.
808
        $item_args = array(
809
            'post_type'      => 'wpi_item',
810
            'orderby'        => 'title',
811
            'order'          => 'ASC',
812
            'posts_per_page' => -1,
813
            'post_status'    => array( 'publish' ),
814
            's'              => trim( $_GET['search'] ),
815
            'meta_query'     => array(
816
                array(
817
                    'key'       => '_wpinv_type',
818
                    'compare'   => '!=',
819
                    'value'     => 'package'
820
                )
821
            )
822
        );
823
824
        $items = get_posts( apply_filters( 'getpaid_ajax_invoice_items_query_args', $item_args ) );
825
        $data  = array();
826
827
        foreach ( $items as $item ) {
828
            $item      = new GetPaid_Form_Item( $item );
829
            $data[] = array(
830
                'id'   => $item->get_id(),
831
                'text' => $item->get_name()
832
            );
833
        }
834
835
        wp_send_json_success( $data );
836
837
    }
838
839
    /**
840
     * Retrieves the states field for AUI forms.
841
     */
842
    public static function get_aui_states_field() {
843
844
        // Verify nonce.
845
        check_ajax_referer( 'wpinv-nonce' );
846
847
        // We need a country.
848
        if ( empty( $_GET['country'] ) ) {
849
            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...
850
        }
851
852
        $states = wpinv_get_country_states( trim( $_GET['country'] ) );
853
        $state  = isset( $_GET['state'] ) ? trim( $_GET['state'] ) : wpinv_get_default_state();
854
855
        if ( empty( $states ) ) {
856
857
            $html = aui()->input(
858
                array(
859
                    'type'        => 'text',
860
                    'id'          => 'wpinv_state',
861
                    'name'        => 'wpinv_state',
862
                    'label'       => __( 'State', 'invoicing' ),
863
                    'label_type'  => 'vertical',
864
                    'placeholder' => 'Liège',
865
                    'class'       => 'form-control-sm',
866
                    'value'       => $state,
867
                )
868
            );
869
870
        } else {
871
872
            $html = aui()->select(
873
                array(
874
                    'id'          => 'wpinv_state',
875
                    'name'        => 'wpinv_state',
876
                    'label'       => __( 'State', 'invoicing' ),
877
                    'label_type'  => 'vertical',
878
                    'placeholder' => __( 'Select a state', 'invoicing' ),
879
                    'class'       => 'form-control-sm',
880
                    'value'       => $state,
881
                    'options'     => $states,
882
                    'data-allow-clear' => 'false',
883
                    'select2'          => true,
884
                )
885
            );
886
887
        }
888
889
        wp_send_json_success(
890
            array(
891
                'html'   => $html,
892
                'select' => ! empty ( $states )
893
            )
894
        );
895
896
    }
897
898
    /**
899
     * Refresh prices.
900
     *
901
     * @since 1.0.19
902
     */
903
    public static function payment_form_refresh_prices() {
904
905
        // Check nonce.
906
        check_ajax_referer( 'getpaid_form_nonce' );
907
908
        // ... form fields...
909
        if ( empty( $_POST['getpaid_payment_form_submission'] ) ) {
910
            _e( 'Error: Reload the page and try again.', 'invoicing' );
911
            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...
912
        }
913
914
        // Load the submission.
915
        $submission = new GetPaid_Payment_Form_Submission();
916
917
        // Do we have an error?
918
        if ( ! empty( $submission->last_error ) ) {
919
            echo $submission->last_error;
920
            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...
921
        }
922
923
        // Prepare the result.
924
        $result = array(
925
            'submission_id' => $submission->id,
926
            'has_recurring' => $submission->has_recurring,
927
            'is_free'       => $submission->get_payment_details(),
928
            'totals'        => array(
929
                'subtotal'  => wpinv_price( wpinv_format_amount( $submission->subtotal_amount ), $submission->get_currency() ),
930
                'discount'  => wpinv_price( wpinv_format_amount( $submission->get_total_discount() ), $submission->get_currency() ),
931
                'fees'      => wpinv_price( wpinv_format_amount( $submission->get_total_fees() ), $submission->get_currency() ),
932
                'tax'       => wpinv_price( wpinv_format_amount( $submission->get_total_tax() ), $submission->get_currency() ),
933
                'total'     => wpinv_price( wpinv_format_amount( $submission->get_total() ), $submission->get_currency() ),
934
            ),
935
        );
936
937
        // Add items.
938
        $items = $submission->get_items();
939
        if ( ! empty( $items ) ) {
940
            $result['items'] = array();
941
942
            foreach( $items as $item_id => $item ) {
943
                $result['items']["$item_id"] = wpinv_price( wpinv_format_amount( $item->get_price() * $item->get_quantity() ) );
944
            }
945
        }
946
947
        // Add invoice.
948
        if ( $submission->has_invoice() ) {
949
            $result['invoice'] = $submission->get_invoice()->ID;
950
        }
951
952
        // Add discount code.
953
        if ( $submission->has_discount_code() ) {
954
            $result['discount_code'] = $submission->get_discount_code();
955
        }
956
957
        // Filter the result.
958
        $result = apply_filters( 'getpaid_payment_form_ajax_refresh_prices', $result, $submission );
959
960
        wp_send_json_success( $result );
961
    }
962
963
    /**
964
     * Lets users buy items via ajax.
965
     *
966
     * @since 1.0.0
967
     */
968
    public static function buy_items() {
969
        $user_id = get_current_user_id();
970
971
        if ( empty( $user_id ) ) { // If not logged in then lets redirect to the login page
972
            wp_send_json( array(
973
                '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

973
                'success' => wp_login_url( /** @scrutinizer ignore-type */ wp_get_referer() )
Loading history...
974
            ) );
975
        } else {
976
            // Only check nonce if logged in as it could be cached when logged out.
977
            if ( ! isset( $_POST['wpinv_buy_nonce'] ) || ! wp_verify_nonce( $_POST['wpinv_buy_nonce'], 'wpinv_buy_items' ) ) {
978
                wp_send_json( array(
979
                    'error' => __( 'Security checks failed.', 'invoicing' )
980
                ) );
981
                wp_die();
982
            }
983
984
            // allow to set a custom price through post_id
985
            $items = $_POST['items'];
986
            $related_post_id = isset( $_POST['post_id'] ) ? (int)$_POST['post_id'] : 0;
987
            $custom_item_price = $related_post_id ? abs( get_post_meta( $related_post_id, '_wpi_custom_price', true ) ) : 0;
988
989
            $cart_items = array();
990
            if ( $items ) {
991
                $items = explode( ',', $items );
992
993
                foreach( $items as $item ) {
994
                    $item_id = $item;
995
                    $quantity = 1;
996
997
                    if ( strpos( $item, '|' ) !== false ) {
998
                        $item_parts = explode( '|', $item );
999
                        $item_id = $item_parts[0];
1000
                        $quantity = $item_parts[1];
1001
                    }
1002
1003
                    if ( $item_id && $quantity ) {
1004
                        $cart_items_arr = array(
1005
                            'id'            => (int)$item_id,
1006
                            'quantity'      => (int)$quantity
1007
                        );
1008
1009
                        // If there is a related post id then add it to meta
1010
                        if ( $related_post_id ) {
1011
                            $cart_items_arr['meta'] = array(
1012
                                'post_id'   => $related_post_id
1013
                            );
1014
                        }
1015
1016
                        // If there is a custom price then set it.
1017
                        if ( $custom_item_price ) {
1018
                            $cart_items_arr['custom_price'] = $custom_item_price;
1019
                        }
1020
1021
                        $cart_items[] = $cart_items_arr;
1022
                    }
1023
                }
1024
            }
1025
1026
            /**
1027
             * Filter the wpinv_buy shortcode cart items on the fly.
1028
             *
1029
             * @param array $cart_items The cart items array.
1030
             * @param int $related_post_id The related post id if any.
1031
             * @since 1.0.0
1032
             */
1033
            $cart_items = apply_filters( 'wpinv_buy_cart_items', $cart_items, $related_post_id );
1034
1035
            // Make sure its not in the cart already, if it is then redirect to checkout.
1036
            $cart_invoice = wpinv_get_invoice_cart();
1037
1038
            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...
1039
                wp_send_json( array(
1040
                    'success' =>  $cart_invoice->get_checkout_payment_url()
1041
                ) );
1042
                wp_die();
1043
            }
1044
1045
            // Check if user has invoice with same items waiting to be paid.
1046
            $user_invoices = wpinv_get_users_invoices( $user_id , 10 , false , 'wpi-pending' );
1047
            if ( !empty( $user_invoices ) ) {
1048
                foreach( $user_invoices as $user_invoice ) {
1049
                    $user_cart_details = array();
1050
                    $invoice  = wpinv_get_invoice( $user_invoice->ID );
1051
                    $cart_details = $invoice->get_cart_details();
1052
1053
                    if ( !empty( $cart_details ) ) {
1054
                        foreach ( $cart_details as $invoice_item ) {
1055
                            $ii_arr = array();
1056
                            $ii_arr['id'] = (int)$invoice_item['id'];
1057
                            $ii_arr['quantity'] = (int)$invoice_item['quantity'];
1058
1059
                            if (isset( $invoice_item['meta'] ) && !empty( $invoice_item['meta'] ) ) {
1060
                                $ii_arr['meta'] = $invoice_item['meta'];
1061
                            }
1062
1063
                            if ( isset( $invoice_item['custom_price'] ) && !empty( $invoice_item['custom_price'] ) ) {
1064
                                $ii_arr['custom_price'] = $invoice_item['custom_price'];
1065
                            }
1066
1067
                            $user_cart_details[] = $ii_arr;
1068
                        }
1069
                    }
1070
1071
                    if ( !empty( $user_cart_details ) && serialize( $cart_items ) == serialize( $user_cart_details ) ) {
1072
                        wp_send_json( array(
1073
                            'success' =>  $invoice->get_checkout_payment_url()
1074
                        ) );
1075
                        wp_die();
1076
                    }
1077
                }
1078
            }
1079
1080
            // Create invoice and send user to checkout
1081
            if ( !empty( $cart_items ) ) {
1082
                $invoice_data = array(
1083
                    'status'        =>  'wpi-pending',
1084
                    'created_via'   =>  'wpi',
1085
                    'user_id'       =>  $user_id,
1086
                    'cart_details'  =>  $cart_items,
1087
                );
1088
1089
                $invoice = wpinv_insert_invoice( $invoice_data, true );
1090
1091
                if ( !empty( $invoice ) && isset( $invoice->ID ) ) {
1092
                    wp_send_json( array(
1093
                        '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

1093
                        '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...
1094
                    ) );
1095
                } else {
1096
                    wp_send_json( array(
1097
                        'error' => __( 'Invoice failed to create', 'invoicing' )
1098
                    ) );
1099
                }
1100
            } else {
1101
                wp_send_json( array(
1102
                    'error' => __( 'Items not valid.', 'invoicing' )
1103
                ) );
1104
            }
1105
        }
1106
1107
        wp_die();
1108
    }
1109
}
1110
1111
WPInv_Ajax::init();