Issues (850)

Security Analysis    4 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection (1)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection (2)
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-wpinv-ajax.php (1 issue)

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'] ) ) );
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_customers'                 => false,
94
            'get_invoice_items'             => false,
95
            'add_invoice_items'             => false,
96
            'admin_add_invoice_item'        => false,
97
            'recalculate_full_prices'       => false,
98
            'edit_invoice_item'             => false,
99
            'create_invoice_item'           => false,
100
            'remove_invoice_item'           => false,
101
            'get_billing_details'           => false,
102
            'recalculate_invoice_totals'    => false,
103
            'check_new_user_email'          => false,
104
            'run_tool'                      => false,
105
            'payment_form_refresh_prices'   => true,
106
            'file_upload'                   => true,
107
        );
108
109
        foreach ( $ajax_events as $ajax_event => $nopriv ) {
110
            add_action( 'wp_ajax_wpinv_' . $ajax_event, array( __CLASS__, $ajax_event ) );
111
            add_action( 'wp_ajax_getpaid_' . $ajax_event, array( __CLASS__, $ajax_event ) );
112
113
            if ( $nopriv ) {
114
                add_action( 'wp_ajax_nopriv_wpinv_' . $ajax_event, array( __CLASS__, $ajax_event ) );
115
                add_action( 'wp_ajax_nopriv_getpaid_' . $ajax_event, array( __CLASS__, $ajax_event ) );
116
                add_action( 'wpinv_ajax_' . $ajax_event, array( __CLASS__, $ajax_event ) );
117
            }
118
        }
119
    }
120
121
    public static function add_note() {
122
        check_ajax_referer( 'add-invoice-note', '_nonce' );
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
        if ( ! wpinv_current_user_can( 'invoice_add_note', array( 'invoice_id' => $post_id, 'note_type' => $note_type ) ) ) {
129
            die( -1 );
130
        }
131
132
        $is_customer_note = $note_type == 'customer' ? 1 : 0;
133
134
        if ( $post_id > 0 ) {
135
            $note_id = wpinv_insert_payment_note( $post_id, $note, $is_customer_note );
136
137
            if ( $note_id > 0 && ! is_wp_error( $note_id ) ) {
138
                wpinv_get_invoice_note_line_item( $note_id );
139
            }
140
        }
141
142
        die();
143
    }
144
145
    public static function delete_note() {
146
        check_ajax_referer( 'delete-invoice-note', '_nonce' );
147
148
        $note_id = (int)$_POST['note_id'];
149
150
        if ( ! wpinv_current_user_can( 'invoice_delete_note', array( 'note_id' => $note_id ) ) ) {
151
            die( -1 );
152
        }
153
154
        if ( $note_id > 0 ) {
155
            wp_delete_comment( $note_id, true );
156
        }
157
158
        die();
159
    }
160
161
    public static function get_states_field() {
162
        wpinv_get_states_field();
163
164
        die();
165
    }
166
167
    /**
168
     * Retrieves a given user's billing address.
169
     */
170
    public static function get_billing_details() {
171
        // Verify nonce.
172
        check_ajax_referer( 'wpinv-nonce' );
173
174
        // Do we have a user id?
175
        $user_id = (int) $_GET['user_id'];
176
        $invoice_id = ! empty( $_REQUEST['post_id'] ) ? (int) $_REQUEST['post_id'] : 0;
177
178
        if ( empty( $user_id ) || ! is_numeric( $user_id ) ) {
179
            die( -1 );
180
        }
181
182
        // Can the user manage the plugin?
183
        if ( ! wpinv_current_user_can( 'invoice_get_billing_details', array( 'user_id' => $user_id, 'invoice_id' => $invoice_id ) ) ) {
184
            die( -1 );
185
        }
186
187
        // Fetch the billing details.
188
        $billing_details    = wpinv_get_user_address( $user_id );
189
        $billing_details    = apply_filters( 'wpinv_ajax_billing_details', $billing_details, $user_id );
190
191
        // unset the user id and email.
192
        $to_ignore = array( 'user_id', 'email' );
193
194
        foreach ( $to_ignore as $key ) {
195
            if ( isset( $billing_details[ $key ] ) ) {
196
                unset( $billing_details[ $key ] );
197
            }
198
        }
199
200
        wp_send_json_success( $billing_details );
201
202
    }
203
204
    /**
205
     * Checks if a new users email is valid.
206
     */
207
    public static function check_new_user_email() {
208
        // Verify nonce.
209
        check_ajax_referer( 'wpinv-nonce' );
210
211
        $invoice_id = ! empty( $_REQUEST['post_id'] ) ? absint( $_REQUEST['post_id'] ) : 0;
212
213
        if ( empty( $invoice_id ) ) {
214
            die( -1 );
215
        }
216
217
        // Can the user manage the plugin?
218
        if ( ! wpinv_current_user_can( 'invoice_check_new_user_email', array( 'invoice_id' => $invoice_id ) ) ) {
219
            die( -1 );
220
        }
221
222
        // We need an email address.
223
        if ( empty( $_GET['email'] ) ) {
224
            esc_html_e( "Provide the new user's email address", 'invoicing' );
225
            exit;
226
        }
227
228
        // Ensure the email is valid.
229
        $email = sanitize_email( $_GET['email'] );
230
        if ( ! is_email( $email ) ) {
231
            esc_html_e( 'Invalid email address', 'invoicing' );
232
            exit;
233
        }
234
235
        // And it does not exist.
236
        $id = email_exists( $email );
237
        if ( $id ) {
238
            wp_send_json_success( compact( 'id' ) );
239
        }
240
241
        wp_send_json_success( true );
242
    }
243
244
    public static function run_tool() {
245
        check_ajax_referer( 'wpinv-nonce', '_nonce' );
246
247
        if ( ! wpinv_current_user_can_manage_invoicing() ) {
248
            die( -1 );
249
        }
250
251
        $tool = sanitize_text_field( $_POST['tool'] );
252
253
        do_action( 'wpinv_run_tool' );
254
255
        if ( ! empty( $tool ) ) {
256
            do_action( 'wpinv_tool_' . $tool );
257
        }
258
    }
259
260
    /**
261
     * Retrieves the markup for a payment form.
262
     */
263
    public static function get_payment_form() {
264
        global $getpaid_force_checkbox;
265
266
        // Is the request set up correctly?
267
		if ( empty( $_GET['form'] ) && empty( $_GET['item'] ) && empty( $_GET['invoice'] ) ) {
268
			aui()->alert(
269
				array(
270
					'type'    => 'warning',
271
					'content' => __( 'No payment form or item provided', 'invoicing' ),
272
                ),
273
                true
274
            );
275
            exit;
276
        }
277
278
        // Payment form or button?
279
		if ( ! empty( $_GET['form'] ) ) {
280
            $form = sanitize_text_field( urldecode( $_GET['form'] ) );
281
282
            if ( false !== strpos( $form, '|' ) ) {
283
                $form_pos = strpos( $form, '|' );
284
                $_items   = getpaid_convert_items_to_array( substr( $form, $form_pos + 1 ) );
285
                $form     = substr( $form, 0, $form_pos );
286
287
                // Retrieve appropriate payment form.
288
                $payment_form = new GetPaid_Payment_Form( $form );
289
                $payment_form = $payment_form->exists() ? $payment_form : new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
290
291
                $items    = array();
292
                $item_ids = array();
293
294
                foreach ( $_items as $item_id => $qty ) {
295
                    if ( ! in_array( $item_id, $item_ids ) ) {
296
                        $item = new GetPaid_Form_Item( $item_id );
297
                        $item->set_quantity( $qty );
298
299
                        if ( 0 == $qty ) {
300
                            $item->set_allow_quantities( true );
301
                            $item->set_is_required( false );
302
                            $getpaid_force_checkbox = true;
303
                        }
304
305
                        $item_ids[] = $item->get_id();
306
                        $items[]    = $item;
307
                    }
308
                }
309
310
                if ( ! $payment_form->is_default() ) {
311
312
                    foreach ( $payment_form->get_items() as $item ) {
313
                        if ( ! in_array( $item->get_id(), $item_ids ) ) {
314
                            $item_ids[] = $item->get_id();
315
                            $items[]    = $item;
316
                        }
317
                    }
318
                }
319
320
                $payment_form->set_items( $items );
321
                $extra_items     = esc_attr( getpaid_convert_items_to_string( $_items ) );
322
                $extra_items_key = md5( NONCE_KEY . AUTH_KEY . $extra_items );
323
                $extra_items     = "<input type='hidden' name='getpaid-form-items' value='$extra_items' />";
324
                $extra_items    .= "<input type='hidden' name='getpaid-form-items-key' value='$extra_items_key' />";
325
                $payment_form->display( $extra_items );
326
                $getpaid_force_checkbox = false;
327
328
            } else {
329
                getpaid_display_payment_form( $form );
330
            }
331
} elseif ( ! empty( $_GET['invoice'] ) ) {
332
		    getpaid_display_invoice_payment_form( (int) urldecode( $_GET['invoice'] ) );
333
        } else {
334
			$items = getpaid_convert_items_to_array( sanitize_text_field( urldecode( $_GET['item'] ) ) );
335
		    getpaid_display_item_payment_form( $items );
336
        }
337
338
        exit;
339
340
    }
341
342
    /**
343
     * Payment forms.
344
     *
345
     * @since 1.0.18
346
     */
347
    public static function payment_form() {
348
349
        // ... form fields...
350
        if ( empty( $_POST['getpaid_payment_form_submission'] ) ) {
351
            esc_html_e( 'Error: Reload the page and try again.', 'invoicing' );
352
            exit;
353
        }
354
355
        // Process the payment form.
356
        $checkout_class = apply_filters( 'getpaid_checkout_class', 'GetPaid_Checkout' );
357
        $checkout       = new $checkout_class( new GetPaid_Payment_Form_Submission() );
358
        $checkout->process_checkout();
359
360
        exit;
361
    }
362
363
    /**
364
     * Payment forms.
365
     *
366
     * @since 1.0.18
367
     */
368
    public static function get_payment_form_states_field() {
369
370
        if ( empty( $_GET['country'] ) || empty( $_GET['form'] ) ) {
371
            exit;
372
        }
373
374
        $elements = getpaid_get_payment_form_elements( (int) $_GET['form'] );
375
376
        if ( empty( $elements ) ) {
377
            exit;
378
        }
379
380
        $address_fields = array();
381
        foreach ( $elements as $element ) {
382
            if ( 'address' === $element['type'] ) {
383
                $address_fields = $element;
384
                break;
385
            }
386
        }
387
388
        if ( empty( $address_fields ) ) {
389
            exit;
390
        }
391
392
        foreach ( $address_fields['fields'] as $address_field ) {
393
394
            if ( 'wpinv_state' == $address_field['name'] ) {
395
396
                $wrap_class  = getpaid_get_form_element_grid_class( $address_field );
397
                $wrap_class  = esc_attr( "$wrap_class getpaid-address-field-wrapper" );
398
                $placeholder = empty( $address_field['placeholder'] ) ? '' : esc_attr( $address_field['placeholder'] );
399
                $description = empty( $address_field['description'] ) ? '' : wp_kses_post( $address_field['description'] );
400
                $value       = is_user_logged_in() ? get_user_meta( get_current_user_id(), '_wpinv_state', true ) : '';
401
                $label       = empty( $address_field['label'] ) ? '' : wp_kses_post( $address_field['label'] );
402
403
                if ( ! empty( $address_field['required'] ) ) {
404
                    $label .= "<span class='text-danger'> *</span>";
405
                }
406
407
                $html = getpaid_get_states_select_markup(
408
                    sanitize_text_field( $_GET['country'] ),
409
                    $value,
410
                    $placeholder,
411
                    $label,
412
                    $description,
413
                    ! empty( $address_field['required'] ),
414
                    $wrap_class,
415
                    sanitize_text_field( $_GET['name'] )
416
                );
417
418
                wp_send_json_success( $html );
419
                exit;
420
421
            }
422
}
423
424
        exit;
425
    }
426
427
    /**
428
     * Recalculates invoice totals.
429
     */
430
    public static function recalculate_invoice_totals() {
431
        // Verify nonce.
432
        check_ajax_referer( 'wpinv-nonce' );
433
434
        $invoice_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
435
436
        if ( empty( $invoice_id ) ) {
437
            die( -1 );
438
        }
439
440
        // Can the user manage the plugin?
441
        if ( ! wpinv_current_user_can( 'invoice_recalculate_totals', array( 'invoice_id' => $invoice_id ) ) ) {
442
            die( -1 );
443
        }
444
445
        // Fetch the invoice.
446
        $invoice = new WPInv_Invoice( $invoice_id );
447
448
        // Ensure it exists.
449
        if ( ! $invoice->get_id() ) {
450
            exit;
451
        }
452
453
        // Maybe set the country, state, currency.
454
        foreach ( array( 'country', 'state', 'currency', 'vat_number', 'discount_code' ) as $key ) {
455
            if ( isset( $_POST[ $key ] ) ) {
456
                $method = "set_$key";
457
                $invoice->$method( sanitize_text_field( $_POST[ $key ] ) );
458
            }
459
        }
460
461
        // Maybe disable taxes.
462
        $invoice->set_disable_taxes( ! empty( $_POST['taxes'] ) );
463
464
        // Discount code.
465
        if ( ! $invoice->is_paid() && ! $invoice->is_refunded() ) {
466
            $discount = new WPInv_Discount( $invoice->get_discount_code() );
467
            if ( $discount->exists() ) {
468
                $invoice->add_discount( getpaid_calculate_invoice_discount( $invoice, $discount ) );
469
            } else {
470
                $invoice->remove_discount( 'discount_code' );
471
            }
472
        }
473
474
        // Recalculate totals.
475
        $invoice->recalculate_total();
476
477
        $total        = wpinv_price( $invoice->get_total(), $invoice->get_currency() );
478
        $suscriptions = getpaid_get_invoice_subscriptions( $invoice );
479
        if ( is_a( $suscriptions, 'WPInv_Subscription' ) && $invoice->is_recurring() && $invoice->is_parent() && $invoice->get_total() != $invoice->get_recurring_total() ) {
480
            $recurring_total = wpinv_price( $invoice->get_recurring_total(), $invoice->get_currency() );
481
            $total          .= '<small class="form-text text-muted">' . sprintf( __( 'Recurring Price: %s', 'invoicing' ), $recurring_total ) . '</small>';
482
        }
483
484
        $totals = array(
485
            'subtotal' => wpinv_price( $invoice->get_subtotal(), $invoice->get_currency() ),
486
            'discount' => wpinv_price( $invoice->get_total_discount(), $invoice->get_currency() ),
487
            'tax'      => wpinv_price( $invoice->get_total_tax(), $invoice->get_currency() ),
488
            'total'    => $total,
489
        );
490
491
        $totals = apply_filters( 'getpaid_invoice_totals', $totals, $invoice );
492
493
        wp_send_json_success( compact( 'totals' ) );
494
    }
495
496
    /**
497
     * Get items belonging to a given invoice.
498
     */
499
    public static function get_invoice_items() {
500
        // Verify nonce.
501
        check_ajax_referer( 'wpinv-nonce' );
502
503
        $invoice_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
504
505
        if ( empty( $invoice_id ) ) {
506
            exit;
507
        }
508
509
        // Can the user manage the plugin?
510
        if ( ! wpinv_current_user_can( 'invoice_get_items', array( 'invoice_id' => $invoice_id ) ) ) {
511
            exit;
512
        }
513
514
        // Fetch the invoice.
515
        $invoice = new WPInv_Invoice( $invoice_id );
516
517
        // Ensure it exists.
518
        if ( ! $invoice->get_id() ) {
519
            exit;
520
        }
521
522
        // Return an array of invoice items.
523
        $items = array();
524
525
        foreach ( $invoice->get_items() as $item ) {
526
            $items[] = $item->prepare_data_for_invoice_edit_ajax( $invoice->get_currency(), $invoice->is_renewal() );
527
        }
528
529
        wp_send_json_success( compact( 'items' ) );
530
    }
531
532
    /**
533
     * Edits an invoice item.
534
     */
535
    public static function edit_invoice_item() {
536
        // Verify nonce.
537
        check_ajax_referer( 'wpinv-nonce' );
538
539
        // We need an invoice and item details.
540
        if ( empty( $_POST['post_id'] ) || empty( $_POST['data'] ) ) {
541
            exit;
542
        }
543
544
        $invoice_id = absint( $_POST['post_id'] );
545
546
        if ( empty( $invoice_id ) ) {
547
            exit;
548
        }
549
550
        // Can the user manage the plugin?
551
        if ( ! wpinv_current_user_can( 'invoice_edit_item', array( 'invoice_id' => $invoice_id ) ) ) {
552
            exit;
553
        }
554
555
        // Fetch the invoice.
556
        $invoice = new WPInv_Invoice( $invoice_id );
557
558
        // Ensure it exists and its not been paid for.
559
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
560
            exit;
561
        }
562
563
        // Format the data.
564
        $data = wp_kses_post_deep( wp_unslash( wp_list_pluck( $_POST['data'], 'value', 'field' ) ) );
565
566
        // Ensure that we have an item id.
567
        if ( empty( $data['id'] ) ) {
568
            exit;
569
        }
570
571
        // Abort if the invoice does not have the specified item.
572
        $item = $invoice->get_item( (int) $data['id'] );
573
574
        if ( empty( $item ) ) {
575
            exit;
576
        }
577
578
        // Update the item.
579
        $item->set_price( getpaid_standardize_amount( $data['price'] ) );
580
        $item->set_name( sanitize_text_field( $data['name'] ) );
581
        $item->set_description( wp_kses_post( $data['description'] ) );
582
        $item->set_quantity( floatval( $data['quantity'] ) );
583
584
        // Add it to the invoice.
585
        $error = $invoice->add_item( $item );
586
        $alert = false;
587
        if ( is_wp_error( $error ) ) {
588
            $alert = $error->get_error_message();
589
        }
590
591
        // Update totals.
592
        $invoice->recalculate_total();
593
594
        // Save the invoice.
595
        $invoice->save();
596
597
        // Return an array of invoice items.
598
        $items = array();
599
600
        foreach ( $invoice->get_items() as $item ) {
601
            $items[] = $item->prepare_data_for_invoice_edit_ajax( $invoice->get_currency() );
602
        }
603
604
        wp_send_json_success( compact( 'items', 'alert' ) );
605
    }
606
607
    /**
608
     * Creates an invoice item.
609
     */
610
    public static function create_invoice_item() {
611
        // Verify nonce.
612
        check_ajax_referer( 'wpinv-nonce' );
613
614
        // We need an invoice and item details.
615
        if ( empty( $_POST['invoice_id'] ) || empty( $_POST['_wpinv_quick'] ) ) {
616
            exit;
617
        }
618
619
        $invoice_id = absint( $_POST['invoice_id'] );
620
621
        if ( empty( $invoice_id ) ) {
622
            exit;
623
        }
624
625
        if ( ! wpinv_current_user_can( 'invoice_create_item', array( 'invoice_id' => $invoice_id ) ) ) {
626
            exit;
627
        }
628
629
        // Fetch the invoice.
630
        $invoice = new WPInv_Invoice( $invoice_id );
631
632
        // Ensure it exists and its not been paid for.
633
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
634
            exit;
635
        }
636
637
        // Format the data.
638
        $data = wp_kses_post_deep( wp_unslash( $_POST['_wpinv_quick'] ) );
639
640
        $item = new WPInv_Item();
641
        $item->set_price( getpaid_standardize_amount( $data['price'] ) );
642
        $item->set_name( sanitize_text_field( $data['name'] ) );
643
        $item->set_description( wp_kses_post( $data['description'] ) );
644
        $item->set_type( sanitize_text_field( $data['type'] ) );
645
        $item->set_vat_rule( sanitize_text_field( $data['vat_rule'] ) );
646
        $item->set_vat_class( sanitize_text_field( $data['vat_class'] ) );
647
        $item->set_status( 'publish' );
648
        $item->save();
649
650
        if ( ! $item->exists() ) {
651
            $alert = __( 'Could not create invoice item. Please try again.', 'invoicing' );
652
            wp_send_json_success( compact( 'alert' ) );
653
        }
654
655
        if ( ! empty( $data['one-time'] ) ) {
656
            update_post_meta( $item->get_id(), '_wpinv_one_time', 'yes' );
657
        }
658
659
        $item = new GetPaid_Form_Item( $item->get_id() );
660
        $item->set_quantity( floatval( $data['qty'] ) );
661
662
        // Add it to the invoice.
663
        $error = $invoice->add_item( $item );
664
        $alert = false;
665
666
        if ( is_wp_error( $error ) ) {
667
            $alert = $error->get_error_message();
668
            wp_send_json_success( compact( 'alert' ) );
669
         }
670
671
        // Update totals.
672
        $invoice->recalculate_total();
673
674
        // Save the invoice.
675
        $invoice->save();
676
677
        // Save the invoice.
678
        $invoice->recalculate_total();
679
        $invoice->save();
680
        ob_start();
681
        GetPaid_Meta_Box_Invoice_Items::output_row( GetPaid_Meta_Box_Invoice_Items::get_columns( $invoice ), $item, $invoice );
682
        $row = ob_get_clean();
683
        wp_send_json_success( compact( 'row' ) );
684
    }
685
686
    /**
687
     * Deletes an invoice item.
688
     */
689
    public static function remove_invoice_item() {
690
        // Verify nonce.
691
        check_ajax_referer( 'wpinv-nonce' );
692
693
        // We need an invoice and item.
694
        $invoice_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
695
        $item_id = ! empty( $_POST['item_id'] ) ? absint( $_POST['item_id'] ) : 0;
696
697
        if ( empty( $invoice_id ) || empty( $item_id ) ) {
698
            exit;
699
        }
700
701
        // Can the user manage the plugin?
702
        if ( ! wpinv_current_user_can( 'invoice_remove_item', array( 'invoice_id' => $invoice_id, 'item_id' => $item_id ) ) ) {
703
            exit;
704
        }
705
706
        // Fetch the invoice.
707
        $invoice = new WPInv_Invoice( $invoice_id );
708
709
        // Ensure it exists and its not been paid for.
710
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
711
            exit;
712
        }
713
714
        // Abort if the invoice does not have the specified item.
715
        $item = $invoice->get_item( $item_id );
716
717
        if ( empty( $item ) ) {
718
            exit;
719
        }
720
721
        $invoice->remove_item( $item_id );
722
723
        // Update totals.
724
        $invoice->recalculate_total();
725
726
        // Save the invoice.
727
        $invoice->save();
728
729
        // Return an array of invoice items.
730
        $items = array();
731
732
        foreach ( $invoice->get_items() as $item ) {
733
            $items[] = $item->prepare_data_for_invoice_edit_ajax( $invoice->get_currency() );
734
        }
735
736
        wp_send_json_success( compact( 'items' ) );
737
    }
738
739
    /**
740
     * Adds an item to an invoice.
741
     */
742
    public static function recalculate_full_prices() {
743
        // Verify nonce.
744
        check_ajax_referer( 'wpinv-nonce' );
745
746
        $invoice_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
747
748
        if ( empty( $invoice_id ) ) {
749
            exit;
750
        }
751
752
        if ( ! wpinv_current_user_can( 'invoice_recalculate_full_prices', array( 'invoice_id' => $invoice_id ) ) ) {
753
            exit;
754
        }
755
756
        // Fetch the invoice.
757
        $invoice = new WPInv_Invoice( $invoice_id );
758
        $alert   = false;
759
760
        // Ensure it exists and its not been paid for.
761
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
762
            exit;
763
        }
764
765
        $invoice->set_items( array() );
766
767
        if ( ! empty( $_POST['getpaid_items'] ) ) {
768
769
            foreach ( wp_kses_post_deep( wp_unslash( $_POST['getpaid_items'] ) ) as $item_id => $args ) {
770
                $item = new GetPaid_Form_Item( $item_id );
771
772
                if ( $item->exists() ) {
773
                    $item->set_price( getpaid_standardize_amount( $args['price'] ) );
774
                    $item->set_quantity( floatval( $args['quantity'] ) );
775
                    $item->set_name( sanitize_text_field( $args['name'] ) );
776
                    $item->set_description( wp_kses_post( $args['description'] ) );
777
                    $invoice->add_item( $item );
778
                }
779
            }
780
        }
781
782
        $invoice->set_disable_taxes( ! empty( $_POST['disable_taxes'] ) );
783
784
        // Maybe set the country, state, currency.
785
        foreach ( array( 'wpinv_country', 'wpinv_state', 'wpinv_currency', 'wpinv_vat_number', 'wpinv_discount_code' ) as $key ) {
786
            if ( isset( $_POST[ $key ] ) ) {
787
                $_key   = str_replace( 'wpinv_', '', $key );
788
                $method = "set_$_key";
789
                $invoice->$method( sanitize_text_field( $_POST[ $key ] ) );
790
            }
791
        }
792
793
        $discount = new WPInv_Discount( $invoice->get_discount_code() );
794
        if ( $discount->exists() ) {
795
            $invoice->add_discount( getpaid_calculate_invoice_discount( $invoice, $discount ) );
796
        } else {
797
            $invoice->remove_discount( 'discount_code' );
798
        }
799
800
        // Save the invoice.
801
        $invoice->recalculate_total();
802
        $invoice->save();
803
        ob_start();
804
        GetPaid_Meta_Box_Invoice_Items::output( get_post( $invoice->get_id() ), $invoice );
805
        $table = ob_get_clean();
806
        wp_send_json_success( compact( 'table' ) );
807
    }
808
809
    /**
810
     * Adds an item to an invoice.
811
     */
812
    public static function admin_add_invoice_item() {
813
        // Verify nonce.
814
        check_ajax_referer( 'wpinv-nonce' );
815
816
        // We need an invoice and item.
817
        $invoice_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
818
        $item_id = ! empty( $_POST['item_id'] ) ? absint( $_POST['item_id'] ) : 0;
819
820
        if ( empty( $invoice_id ) || empty( $item_id ) ) {
821
            exit;
822
        }
823
824
        // Can the user manage the plugin?
825
        if ( ! wpinv_current_user_can( 'invoice_add_item', array( 'invoice_id' => $invoice_id, 'item_id' => $item_id ) ) ) {
826
            exit;
827
        }
828
829
        // Fetch the invoice.
830
        $invoice = new WPInv_Invoice( $invoice_id );
831
        $alert   = false;
832
833
        // Ensure it exists and its not been paid for.
834
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
835
            exit;
836
        }
837
838
        // Add the item.
839
        $item  = new GetPaid_Form_Item( $item_id );
840
841
        $error = $invoice->add_item( $item );
842
843
        if ( is_wp_error( $error ) ) {
844
            $alert = $error->get_error_message();
845
            wp_send_json_success( compact( 'alert' ) );
846
        }
847
848
        // Save the invoice.
849
        $invoice->recalculate_total();
850
        $invoice->save();
851
        ob_start();
852
        GetPaid_Meta_Box_Invoice_Items::output_row( GetPaid_Meta_Box_Invoice_Items::get_columns( $invoice ), $item, $invoice );
853
        $row = ob_get_clean();
854
        wp_send_json_success( compact( 'row' ) );
855
    }
856
857
    /**
858
     * Adds a items to an invoice.
859
     */
860
    public static function add_invoice_items() {
861
        // Verify nonce.
862
        check_ajax_referer( 'wpinv-nonce' );
863
864
        $invoice_id = ! empty( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
865
866
        // We need an invoice and items.
867
        if ( empty( $invoice_id ) || empty( $_POST['items'] ) ) {
868
            exit;
869
        }
870
871
        // Can the user manage the plugin?
872
        if ( ! wpinv_current_user_can( 'invoice_add_items', array( 'invoice_id' => $invoice_id ) ) ) {
873
            exit;
874
        }
875
876
        // Fetch the invoice.
877
        $invoice = new WPInv_Invoice( $invoice_id );
878
        $alert   = false;
879
880
        // Ensure it exists and its not been paid for.
881
        if ( ! $invoice->get_id() || $invoice->is_paid() || $invoice->is_refunded() ) {
882
            exit;
883
        }
884
885
        // Add the items.
886
        foreach ( wp_kses_post_deep( wp_unslash( $_POST['items'] ) ) as $data ) {
887
888
            $item = new GetPaid_Form_Item( (int) $data['id'] );
889
890
            if ( is_numeric( $data['qty'] ) && (float) $data['qty'] > 0 ) {
891
                $item->set_quantity( floatval( $data['qty'] ) );
892
            }
893
894
            if ( $item->get_id() > 0 ) {
895
                $error = $invoice->add_item( $item );
896
897
                if ( is_wp_error( $error ) ) {
898
                    $alert = $error->get_error_message();
899
                }
900
}
901
}
902
903
        // Save the invoice.
904
        $invoice->recalculate_total();
905
        $invoice->save();
906
907
        // Return an array of invoice items.
908
        $items = array();
909
910
        foreach ( $invoice->get_items() as $item ) {
911
            $items[] = $item->prepare_data_for_invoice_edit_ajax( $invoice->get_currency() );
912
        }
913
914
        wp_send_json_success( compact( 'items', 'alert' ) );
915
    }
916
917
    /**
918
     * Retrieves items that should be added to an invoice.
919
     */
920
    public static function get_invoicing_items() {
921
        // Verify nonce.
922
        check_ajax_referer( 'wpinv-nonce' );
923
924
        // Can the user manage the plugin?
925
        if ( ! wpinv_current_user_can( 'get_invoicing_items' ) ) {
926
            exit;
927
        }
928
929
        // We need a search term.
930
        if ( empty( $_GET['search'] ) ) {
931
            wp_send_json_success( array() );
932
        }
933
934
        // Retrieve items.
935
        $item_args = array(
936
            'post_type'      => 'wpi_item',
937
            'orderby'        => 'title',
938
            'order'          => 'ASC',
939
            'posts_per_page' => -1,
940
            'post_status'    => array( 'publish' ),
941
            's'              => sanitize_text_field( urldecode( $_GET['search'] ) ),
942
            'meta_query'     => array(
943
                array(
944
                    'key'     => '_wpinv_type',
945
                    'compare' => '!=',
946
                    'value'   => 'package',
947
                ),
948
                array(
949
                    'key'     => '_wpinv_one_time',
950
                    'compare' => 'NOT EXISTS',
951
                ),
952
            ),
953
        );
954
955
        if ( ! empty( $_GET['ignore'] ) ) {
956
            $item_args['exclude'] = wp_parse_id_list( sanitize_text_field( $_GET['ignore'] ) );
957
        }
958
959
        $items = get_posts( apply_filters( 'getpaid_ajax_invoice_items_query_args', $item_args ) );
960
        $data  = array();
961
962
        $is_payment_form = ( ! empty( $_GET['post_id'] ) && 'wpi_payment_form' == get_post_type( (int) $_GET['post_id'] ) );
963
964
        foreach ( $items as $item ) {
965
            $item      = new GetPaid_Form_Item( $item );
966
            $data[] = array(
967
                'id'        => (int) $item->get_id(),
968
                'text'      => strip_tags( $item->get_name() ),
969
                'form_data' => $is_payment_form ? $item->prepare_data_for_use( false ) : '',
970
            );
971
        }
972
973
        wp_send_json_success( $data );
974
975
    }
976
977
    /**
978
     * Retrieves items that should be added to an invoice.
979
     */
980
    public static function get_customers() {
981
        // Verify nonce.
982
        check_ajax_referer( 'wpinv-nonce' );
983
984
        $invoice_id = ! empty( $_REQUEST['post_id'] ) ? (int) $_REQUEST['post_id'] : 0;
985
986
        // Can the user manage the plugin?
987
        if ( ! wpinv_current_user_can( 'invoice_get_customers', array( 'invoice_id' => $invoice_id ) ) ) {
988
            die( -1 );
0 ignored issues
show
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...
989
        }
990
991
        // We need a search term.
992
        if ( empty( $_GET['search'] ) ) {
993
            wp_send_json_success( array() );
994
        }
995
996
        // Retrieve customers.
997
998
        $customer_args = array(
999
            'fields'         => array( 'ID', 'user_email', 'display_name' ),
1000
            'orderby'        => 'display_name',
1001
            'search'         => '*' . sanitize_text_field( $_GET['search'] ) . '*',
1002
            'search_columns' => array( 'user_login', 'user_email', 'display_name' ),
1003
        );
1004
1005
        $customers = get_users( apply_filters( 'getpaid_ajax_invoice_customers_query_args', $customer_args ) );
1006
        $data      = array();
1007
1008
        foreach ( $customers as $customer ) {
1009
            $data[] = array(
1010
                'id'   => (int) $customer->ID,
1011
                'text' => strip_tags( sprintf( _x( '%1$s (%2$s)', 'user dropdown', 'invoicing' ), $customer->display_name, $customer->user_email ) ),
1012
            );
1013
        }
1014
1015
        wp_send_json_success( $data );
1016
1017
    }
1018
1019
    /**
1020
     * Retrieves the states field for AUI forms.
1021
     */
1022
    public static function get_aui_states_field() {
1023
1024
        // We need a country.
1025
        if ( empty( $_GET['country'] ) ) {
1026
            exit;
1027
        }
1028
1029
        $states = wpinv_get_country_states( sanitize_text_field( $_GET['country'] ) );
1030
        $state  = isset( $_GET['state'] ) ? sanitize_text_field( $_GET['state'] ) : wpinv_get_default_state();
1031
        $name   = isset( $_GET['name'] ) ? sanitize_text_field( $_GET['name'] ) : 'wpinv_state';
1032
        $class  = isset( $_GET['class'] ) ? sanitize_text_field( $_GET['class'] ) : 'form-control-sm';
1033
1034
        if ( empty( $states ) ) {
1035
1036
            $html = aui()->input(
1037
                array(
1038
                    'type'        => 'text',
1039
                    'id'          => 'wpinv_state',
1040
                    'name'        => $name,
1041
                    'label'       => __( 'State', 'invoicing' ),
1042
                    'label_type'  => 'vertical',
1043
                    'placeholder' => __( 'State', 'invoicing' ),
1044
                    'class'       => $class,
1045
                    'value'       => $state,
1046
                )
1047
            );
1048
1049
        } else {
1050
1051
            $html = aui()->select(
1052
                array(
1053
                    'id'               => 'wpinv_state',
1054
                    'name'             => $name,
1055
                    'label'            => __( 'State', 'invoicing' ),
1056
                    'label_type'       => 'vertical',
1057
                    'placeholder'      => __( 'Select a state', 'invoicing' ),
1058
                    'class'            => $class,
1059
                    'value'            => $state,
1060
                    'options'          => $states,
1061
                    'data-allow-clear' => 'false',
1062
                    'select2'          => true,
1063
                )
1064
            );
1065
1066
        }
1067
1068
        wp_send_json_success(
1069
            array(
1070
                'html'   => $html,
1071
                'select' => ! empty( $states ),
1072
            )
1073
        );
1074
1075
    }
1076
1077
    /**
1078
     * Refresh prices.
1079
     *
1080
     * @since 1.0.19
1081
     */
1082
    public static function payment_form_refresh_prices() {
1083
1084
        // ... form fields...
1085
        if ( empty( $_POST['getpaid_payment_form_submission'] ) ) {
1086
            esc_html_e( 'Error: Reload the page and try again.', 'invoicing' );
1087
            exit;
1088
        }
1089
1090
        // Load the submission.
1091
        $submission = new GetPaid_Payment_Form_Submission();
1092
1093
        // Do we have an error?
1094
        if ( ! empty( $submission->last_error ) ) {
1095
            wp_send_json_error(
1096
                array(
1097
                    'code'  => $submission->last_error_code,
1098
                    'error' => $submission->last_error,
1099
                )
1100
            );
1101
        }
1102
1103
        // Prepare the response.
1104
        $response = new GetPaid_Payment_Form_Submission_Refresh_Prices( $submission );
1105
1106
        // Filter the response.
1107
        $response = apply_filters( 'getpaid_payment_form_ajax_refresh_prices', $response->response, $submission );
1108
1109
        wp_send_json_success( $response );
1110
    }
1111
1112
    /**
1113
	 * Handles file uploads.
1114
	 *
1115
	 * @since       1.0.0
1116
	 * @return      void
1117
	 */
1118
	public static function file_upload() {
1119
1120
        // Check nonce.
1121
        check_ajax_referer( 'getpaid_form_nonce' );
1122
1123
        if ( empty( $_POST['form_id'] ) || empty( $_POST['field_name'] ) || empty( $_FILES['file'] ) ) {
1124
            wp_die( esc_html_e( 'Bad Request', 'invoicing' ), 400 );
1125
        }
1126
1127
        // Fetch form.
1128
        $form = new GetPaid_Payment_Form( intval( $_POST['form_id'] ) );
1129
1130
        if ( ! $form->is_active() ) {
1131
            wp_send_json_error( __( 'Payment form not active', 'invoicing' ) );
1132
        }
1133
1134
        // Fetch appropriate field.
1135
        $upload_field = current( wp_list_filter( $form->get_elements(), array( 'id' => sanitize_text_field( $_POST['field_name'] ) ) ) );
1136
        if ( empty( $upload_field ) ) {
1137
            wp_send_json_error( __( 'Invalid upload field.', 'invoicing' ) );
1138
        }
1139
1140
        // Prepare allowed file types.
1141
        $file_types = isset( $upload_field['file_types'] ) ? $upload_field['file_types'] : array( 'jpg|jpeg|jpe', 'gif', 'png' );
1142
        $all_types  = getpaid_get_allowed_mime_types();
1143
        $mime_types = array();
1144
1145
        foreach ( $file_types as $file_type ) {
1146
            if ( isset( $all_types[ $file_type ] ) ) {
1147
                $mime_types[] = $all_types[ $file_type ];
1148
            }
1149
        }
1150
1151
        if ( ! in_array( $_FILES['file']['type'], $mime_types ) ) {
1152
            wp_send_json_error( __( 'Unsupported file type.', 'invoicing' ) );
1153
        }
1154
1155
        // Upload file.
1156
        $file_name = explode( '.', strtolower( $_FILES['file']['name'] ) );
1157
        $file_name = uniqid( 'getpaid-' ) . '.' . array_pop( $file_name );
1158
1159
        $uploaded = wp_upload_bits(
1160
            $file_name,
1161
            null,
1162
            file_get_contents( $_FILES['file']['tmp_name'] )
1163
        );
1164
1165
        if ( ! empty( $uploaded['error'] ) ) {
1166
            wp_send_json_error( $uploaded['error'] );
1167
        }
1168
1169
        // Retrieve response.
1170
        $response = sprintf(
1171
            '<input type="hidden" name="%s[%s]" value="%s" />',
1172
            esc_attr( sanitize_text_field( $_POST['field_name'] ) ),
1173
            esc_url( $uploaded['url'] ),
1174
            esc_attr( sanitize_text_field( strtolower( $_FILES['file']['name'] ) ) )
1175
        );
1176
1177
        wp_send_json_success( $response );
1178
1179
	}
1180
1181
}
1182
1183
WPInv_Ajax::init();
1184