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/invoice-functions.php (1 issue)

1
<?php
2
/**
3
 * Contains invoice functions.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * Retrieves the current invoice.
13
 */
14
function getpaid_get_current_invoice_id() {
15
16
    // Ensure that we have an invoice key.
17
    if ( empty( $_GET['invoice_key'] ) ) {
18
        return 0;
19
    }
20
21
    // Retrieve an invoice using the key.
22
    $invoice = new WPInv_Invoice( sanitize_text_field( $_GET['invoice_key'] ) );
23
24
    // Compare the invoice key and the parsed key.
25
    if ( $invoice->get_id() != 0 && $invoice->get_key() == sanitize_text_field( $_GET['invoice_key'] ) ) {
26
        return $invoice->get_id();
27
    }
28
29
    return 0;
30
}
31
32
/**
33
 * Checks if the current user cna view an invoice.
34
 */
35
function wpinv_user_can_view_invoice( $invoice ) {
36
    $invoice = new WPInv_Invoice( $invoice );
37
38
    // Abort if the invoice does not exist.
39
    if ( 0 == $invoice->get_id() ) {
40
        return false;
41
    }
42
43
    // Don't allow trash, draft status
44
    if ( $invoice->is_draft() ) {
45
        return false;
46
    }
47
48
    // If users are not required to login to check out, compare the invoice keys.
49
    if ( ! wpinv_require_login_to_checkout() && isset( $_GET['invoice_key'] ) && sanitize_text_field( $_GET['invoice_key'] ) == $invoice->get_key() ) {
50
        return true;
51
    }
52
53
    // Always enable for admins..
54
    if ( wpinv_current_user_can( 'view_invoice', array( 'invoice' => $invoice ) ) || current_user_can( 'view_invoices', $invoice->get_id() ) ) { // Admin user
55
        return true;
56
    }
57
58
    // Else, ensure that this is their invoice.
59
    if ( is_user_logged_in() && $invoice->get_user_id() == get_current_user_id() ) {
60
        return true;
61
    }
62
63
    return apply_filters( 'wpinv_current_user_can_view_invoice', false, $invoice );
64
}
65
66
/**
67
 * Checks if the current user cna view an invoice receipt.
68
 */
69
function wpinv_can_view_receipt( $invoice ) {
70
	return (bool) apply_filters( 'wpinv_can_view_receipt', wpinv_user_can_view_invoice( $invoice ), $invoice );
71
}
72
73
/**
74
 * Returns an array of all invoice post types.
75
 *
76
 * @return array
77
 */
78
function getpaid_get_invoice_post_types() {
79
    $post_types = array(
80
        'wpi_quote'   => __( 'Quote', 'invoicing' ),
81
        'wpi_invoice' => __( 'Invoice', 'invoicing' ),
82
    );
83
84
    // Ensure the quotes addon is installed.
85
    if ( ! defined( 'WPINV_QUOTES_VERSION' ) ) {
86
        unset( $post_types['wpi_quote'] );
87
    }
88
89
    return apply_filters( 'getpaid_invoice_post_types', $post_types );
90
}
91
92
/**
93
 * Checks if this is an invocing post type.
94
 *
95
 *
96
 * @param string $post_type The post type to check for.
97
 */
98
function getpaid_is_invoice_post_type( $post_type ) {
99
    return is_scalar( $post_type ) && ! empty( $post_type ) && array_key_exists( $post_type, getpaid_get_invoice_post_types() );
100
}
101
102
/**
103
 * Creates a new invoice.
104
 *
105
 * @param  array $data   An array of invoice properties.
106
 * @param  bool  $wp_error       Whether to return false or WP_Error on failure.
107
 * @return int|WP_Error|WPInv_Invoice The value 0 or WP_Error on failure. The WPInv_Invoice object on success.
108
 */
109
function wpinv_create_invoice( $data = array(), $deprecated = null, $wp_error = false ) {
110
    $data['invoice_id'] = 0;
111
    return wpinv_insert_invoice( $data, $wp_error );
112
}
113
114
/**
115
 * Updates an existing invoice.
116
 *
117
 * @param  array $data   An array of invoice properties.
118
 * @param  bool  $wp_error       Whether to return false or WP_Error on failure.
119
 * @return int|WP_Error|WPInv_Invoice The value 0 or WP_Error on failure. The WPInv_Invoice object on success.
120
 */
121
function wpinv_update_invoice( $data = array(), $wp_error = false ) {
122
123
    // Backwards compatibility.
124
    if ( ! empty( $data['ID'] ) ) {
125
        $data['invoice_id'] = $data['ID'];
126
    }
127
128
    // Do we have an invoice id?
129
    if ( empty( $data['invoice_id'] ) ) {
130
        return $wp_error ? new WP_Error( 'invalid_invoice_id', __( 'Invalid invoice ID.', 'invoicing' ) ) : 0;
131
    }
132
133
    // Retrieve the invoice.
134
    $invoice = wpinv_get_invoice( $data['invoice_id'] );
135
136
    // And abort if it does not exist.
137
    if ( empty( $invoice ) ) {
138
        return $wp_error ? new WP_Error( 'missing_invoice', __( 'Invoice not found.', 'invoicing' ) ) : 0;
139
    }
140
141
    // Do not update totals for paid / refunded invoices.
142
    if ( $invoice->is_paid() || $invoice->is_refunded() ) {
143
144
        if ( ! empty( $data['items'] ) || ! empty( $data['cart_details'] ) ) {
145
            return $wp_error ? new WP_Error( 'paid_invoice', __( 'You can not update cart items for invoices that have already been paid for.', 'invoicing' ) ) : 0;
146
        }
147
}
148
149
    return wpinv_insert_invoice( $data, $wp_error );
150
151
}
152
153
/**
154
 * Create/Update an invoice
155
 *
156
 * @param  array $data   An array of invoice properties.
157
 * @param  bool  $wp_error       Whether to return false or WP_Error on failure.
158
 * @return int|WP_Error|WPInv_Invoice The value 0 or WP_Error on failure. The WPInv_Invoice object on success.
159
 */
160
function wpinv_insert_invoice( $data = array(), $wp_error = false ) {
161
162
    // Ensure that we have invoice data.
163
    if ( empty( $data ) ) {
164
        return false;
165
    }
166
167
    // The invoice id will be provided when updating an invoice.
168
    $data['invoice_id'] = ! empty( $data['invoice_id'] ) ? (int) $data['invoice_id'] : false;
169
170
    // Retrieve the invoice.
171
    $invoice = new WPInv_Invoice( $data['invoice_id'] );
172
173
    // Do we have an error?
174
    if ( ! empty( $invoice->last_error ) ) {
175
        return $wp_error ? new WP_Error( 'invalid_invoice_id', $invoice->last_error ) : 0;
176
    }
177
178
    // Backwards compatibility (billing address).
179
    if ( ! empty( $data['user_info'] ) ) {
180
181
        foreach ( $data['user_info'] as $key => $value ) {
182
183
            if ( $key == 'discounts' ) {
184
                $value = (array) $value;
185
                $data['discount_code'] = empty( $value ) ? null : $value[0];
186
            } else {
187
                $data[ $key ] = $value;
188
            }
189
}
190
}
191
192
    // Backwards compatibility.
193
    if ( ! empty( $data['payment_details'] ) ) {
194
195
        foreach ( $data['payment_details'] as $key => $value ) {
196
            $data[ $key ] = $value;
197
        }
198
}
199
200
    // Set up the owner of the invoice.
201
    $user_id = ! empty( $data['user_id'] ) ? wpinv_clean( $data['user_id'] ) : get_current_user_id();
202
203
    // Make sure the user exists.
204
    if ( ! get_userdata( $user_id ) ) {
205
        return $wp_error ? new WP_Error( 'wpinv_invalid_user', __( 'There is no user with that ID.', 'invoicing' ) ) : 0;
206
    }
207
208
    $address = wpinv_get_user_address( $user_id );
209
210
    foreach ( $address as $key => $value ) {
211
212
        if ( $value == '' ) {
213
            $address[ $key ] = null;
214
        } else {
215
            $address[ $key ] = wpinv_clean( $value );
216
        }
217
}
218
219
    // Load new data.
220
    $invoice->set_props(
221
        array(
222
223
            // Basic info.
224
            'template'          => isset( $data['template'] ) ? wpinv_clean( $data['template'] ) : null,
225
            'email_cc'          => isset( $data['email_cc'] ) ? wpinv_clean( $data['email_cc'] ) : null,
226
            'date_created'      => isset( $data['created_date'] ) ? wpinv_clean( $data['created_date'] ) : null,
227
            'due_date'          => isset( $data['due_date'] ) ? wpinv_clean( $data['due_date'] ) : null,
228
            'date_completed'    => isset( $data['date_completed'] ) ? wpinv_clean( $data['date_completed'] ) : null,
229
            'number'            => isset( $data['number'] ) ? wpinv_clean( $data['number'] ) : null,
230
            'key'               => isset( $data['key'] ) ? wpinv_clean( $data['key'] ) : null,
231
            'status'            => isset( $data['status'] ) ? wpinv_clean( $data['status'] ) : null,
232
            'post_type'         => isset( $data['post_type'] ) ? wpinv_clean( $data['post_type'] ) : null,
233
            'user_ip'           => isset( $data['ip'] ) ? wpinv_clean( $data['ip'] ) : wpinv_get_ip(),
234
            'parent_id'         => isset( $data['parent'] ) ? intval( $data['parent'] ) : null,
235
            'mode'              => isset( $data['mode'] ) ? wpinv_clean( $data['mode'] ) : null,
236
            'description'       => isset( $data['description'] ) ? wp_kses_post( $data['description'] ) : null,
237
238
            // Payment info.
239
            'disable_taxes'     => ! empty( $data['disable_taxes'] ),
240
            'currency'          => isset( $data['currency'] ) ? wpinv_clean( $data['currency'] ) : wpinv_get_currency(),
241
            'gateway'           => isset( $data['gateway'] ) ? wpinv_clean( $data['gateway'] ) : null,
242
            'transaction_id'    => isset( $data['transaction_id'] ) ? wpinv_clean( $data['transaction_id'] ) : null,
243
            'discount_code'     => isset( $data['discount_code'] ) ? wpinv_clean( $data['discount_code'] ) : null,
244
            'payment_form'      => isset( $data['payment_form'] ) ? intval( $data['payment_form'] ) : null,
245
            'submission_id'     => isset( $data['submission_id'] ) ? wpinv_clean( $data['submission_id'] ) : null,
246
            'subscription_id'   => isset( $data['subscription_id'] ) ? wpinv_clean( $data['subscription_id'] ) : null,
247
            'is_viewed'         => isset( $data['is_viewed'] ) ? wpinv_clean( $data['is_viewed'] ) : null,
248
            'fees'              => isset( $data['fees'] ) ? wpinv_clean( $data['fees'] ) : null,
249
            'discounts'         => isset( $data['discounts'] ) ? wpinv_clean( $data['discounts'] ) : null,
250
            'taxes'             => isset( $data['taxes'] ) ? wpinv_clean( $data['taxes'] ) : null,
251
252
            // Billing details.
253
            'user_id'           => $data['user_id'],
254
            'first_name'        => isset( $data['first_name'] ) ? wpinv_clean( $data['first_name'] ) : $address['first_name'],
255
            'last_name'         => isset( $data['last_name'] ) ? wpinv_clean( $data['last_name'] ) : $address['last_name'],
256
            'address'           => isset( $data['address'] ) ? wpinv_clean( $data['address'] ) : $address['address'],
257
            'vat_number'        => isset( $data['vat_number'] ) ? wpinv_clean( $data['vat_number'] ) : $address['vat_number'],
258
            'company'           => isset( $data['company'] ) ? wpinv_clean( $data['company'] ) : $address['company'],
259
            'zip'               => isset( $data['zip'] ) ? wpinv_clean( $data['zip'] ) : $address['zip'],
260
            'state'             => isset( $data['state'] ) ? wpinv_clean( $data['state'] ) : $address['state'],
261
            'city'              => isset( $data['city'] ) ? wpinv_clean( $data['city'] ) : $address['city'],
262
            'country'           => isset( $data['country'] ) ? wpinv_clean( $data['country'] ) : $address['country'],
263
            'phone'             => isset( $data['phone'] ) ? wpinv_clean( $data['phone'] ) : $address['phone'],
264
            'address_confirmed' => ! empty( $data['address_confirmed'] ),
265
266
        )
267
    );
268
269
    // Backwards compatibililty.
270
    if ( ! empty( $data['cart_details'] ) && is_array( $data['cart_details'] ) ) {
271
        $data['items'] = array();
272
273
        foreach ( $data['cart_details'] as $_item ) {
274
275
            // Ensure that we have an item id.
276
            if ( empty( $_item['id'] ) ) {
277
                continue;
278
            }
279
280
            // Retrieve the item.
281
            $item = new GetPaid_Form_Item( $_item['id'] );
282
283
            // Ensure that it is purchasable.
284
            if ( ! $item->can_purchase() ) {
285
                continue;
286
            }
287
288
            // Set quantity.
289
            if ( ! empty( $_item['quantity'] ) && is_numeric( $_item['quantity'] ) ) {
290
                $item->set_quantity( $_item['quantity'] );
291
            }
292
293
            // Set price.
294
            if ( isset( $_item['item_price'] ) ) {
295
                $item->set_price( $_item['item_price'] );
296
            }
297
298
            if ( isset( $_item['custom_price'] ) ) {
299
                $item->set_price( $_item['custom_price'] );
300
            }
301
302
            // Set name.
303
            if ( ! empty( $_item['name'] ) ) {
304
                $item->set_name( $_item['name'] );
305
            }
306
307
            // Set description.
308
            if ( isset( $_item['description'] ) ) {
309
                $item->set_custom_description( $_item['description'] );
310
            }
311
312
            // Set meta.
313
            if ( isset( $_item['meta'] ) && is_array( $_item['meta'] ) ) {
314
315
                $item->set_item_meta( $_item['meta'] );
316
317
                if ( isset( $_item['meta']['description'] ) ) {
318
                    $item->set_custom_description( $_item['meta']['description'] );
319
                }
320
            }
321
322
            $data['items'][] = $item;
323
324
        }
325
    }
326
327
    // Add invoice items.
328
    if ( ! empty( $data['items'] ) && is_array( $data['items'] ) ) {
329
330
        $invoice->set_items( array() );
331
332
        foreach ( $data['items'] as $item ) {
333
334
            if ( is_object( $item ) && is_a( $item, 'GetPaid_Form_Item' ) && $item->can_purchase() ) {
335
                $invoice->add_item( $item );
336
            }
337
}
338
}
339
340
    // Save the invoice.
341
    $invoice->recalculate_total();
342
    $invoice->save();
343
344
    if ( ! $invoice->get_id() ) {
345
        return $wp_error ? new WP_Error( 'wpinv_insert_invoice_error', __( 'An error occured when saving your invoice.', 'invoicing' ) ) : 0;
346
    }
347
348
    // Add private note.
349
    if ( ! empty( $data['private_note'] ) ) {
350
        $invoice->add_note( $data['private_note'] );
351
    }
352
353
    // User notes.
354
    if ( ! empty( $data['user_note'] ) ) {
355
        $invoice->add_note( $data['user_note'], true );
356
    }
357
358
    // Created via.
359
    if ( isset( $data['created_via'] ) ) {
360
        update_post_meta( $invoice->get_id(), 'wpinv_created_via', $data['created_via'] );
361
    }
362
363
    // Backwards compatiblity.
364
    if ( $invoice->is_quote() ) {
365
366
        if ( isset( $data['valid_until'] ) ) {
367
            update_post_meta( $invoice->get_id(), 'wpinv_quote_valid_until', $data['valid_until'] );
368
        }
369
}
370
371
    return $invoice;
372
}
373
374
/**
375
 * Retrieves an invoice.
376
 *
377
 * @param int|string|object|WPInv_Invoice|WPInv_Legacy_Invoice|WP_Post $invoice Invoice id, key, transaction id, number or object.
378
 * @param $bool $deprecated
0 ignored issues
show
Documentation Bug introduced by
The doc comment $bool at position 0 could not be parsed: Unknown type name '$bool' at position 0 in $bool.
Loading history...
379
 * @return WPInv_Invoice|null
380
 */
381
function wpinv_get_invoice( $invoice = 0, $deprecated = false ) {
382
383
    // If we are retrieving the invoice from the cart...
384
    if ( $deprecated && empty( $invoice ) ) {
385
        $invoice = (int) getpaid_get_current_invoice_id();
386
    }
387
388
    // Retrieve the invoice.
389
    if ( ! is_a( $invoice, 'WPInv_Invoice' ) ) {
390
        $invoice = new WPInv_Invoice( $invoice );
391
    }
392
393
    // Check if it exists.
394
    if ( $invoice->exists() ) {
395
        return $invoice;
396
    }
397
398
    return null;
399
}
400
401
/**
402
 * Retrieves several invoices.
403
 *
404
 * @param array $args Args to search for.
405
 * @return WPInv_Invoice[]|int[]|object
406
 */
407
function wpinv_get_invoices( $args ) {
408
409
    // Prepare args.
410
    $args = wp_parse_args(
411
        $args,
412
        array(
413
            'status' => array_keys( wpinv_get_invoice_statuses() ),
414
            'type'   => 'wpi_invoice',
415
            'limit'  => get_option( 'posts_per_page' ),
416
            'return' => 'objects',
417
        )
418
    );
419
420
    // Map params to wp_query params.
421
    $map_legacy = array(
422
        'numberposts'    => 'limit',
423
        'post_type'      => 'type',
424
        'post_status'    => 'status',
425
        'post_parent'    => 'parent',
426
        'author'         => 'user',
427
        'posts_per_page' => 'limit',
428
        'paged'          => 'page',
429
        'post__not_in'   => 'exclude',
430
        'post__in'       => 'include',
431
    );
432
433
    foreach ( $map_legacy as $to => $from ) {
434
        if ( isset( $args[ $from ] ) ) {
435
            $args[ $to ] = $args[ $from ];
436
            unset( $args[ $from ] );
437
        }
438
    }
439
440
    // Backwards compatibility.
441
    if ( ! empty( $args['email'] ) && empty( $args['user'] ) ) {
442
        $args['user'] = $args['email'];
443
        unset( $args['email'] );
444
    }
445
446
    // Handle cases where the user is set as an email.
447
    if ( ! empty( $args['author'] ) && is_email( $args['author'] ) ) {
448
        $user = get_user_by( 'email', $args['user'] );
449
450
        if ( $user ) {
451
            $args['author'] = $user->user_email;
452
        }
453
}
454
455
    // We only want invoice ids.
456
    $args['fields'] = 'ids';
457
458
    // Show all posts.
459
    $paginate = true;
460
    if ( isset( $args['paginate'] ) ) {
461
462
        $paginate = $args['paginate'];
463
        $args['no_found_rows'] = empty( $args['paginate'] );
464
        unset( $args['paginate'] );
465
466
    }
467
468
    // Whether to return objects or fields.
469
    $return = $args['return'];
470
    unset( $args['return'] );
471
472
    // Get invoices.
473
    $invoices = new WP_Query( apply_filters( 'wpinv_get_invoices_args', $args ) );
474
475
    // Prepare the results.
476
    if ( 'objects' === $return ) {
477
        $results = array_map( 'wpinv_get_invoice', $invoices->posts );
478
    } elseif ( 'self' === $return ) {
479
        return $invoices;
480
    } else {
481
        $results = $invoices->posts;
482
    }
483
484
    if ( $paginate ) {
485
        return (object) array(
486
            'invoices'      => $results,
487
            'total'         => $invoices->found_posts,
488
            'max_num_pages' => $invoices->max_num_pages,
489
        );
490
    }
491
492
    return $results;
493
494
}
495
496
/**
497
 * Retrieves an invoice's id from a transaction id.
498
 *
499
 * @param string $transaction_id The transaction id to check.
500
 * @return int Invoice id on success or 0 on failure
501
 */
502
function wpinv_get_id_by_transaction_id( $transaction_id ) {
503
    return WPInv_Invoice::get_invoice_id_by_field( $transaction_id, 'transaction_id' );
504
}
505
506
/**
507
 * Retrieves an invoice's id from the invoice number.
508
 *
509
 * @param string $invoice_number The invoice number to check.
510
 * @return int Invoice id on success or 0 on failure
511
 */
512
function wpinv_get_id_by_invoice_number( $invoice_number ) {
513
    return WPInv_Invoice::get_invoice_id_by_field( $invoice_number, 'number' );
514
}
515
516
/**
517
 * Retrieves an invoice's id from the invoice key.
518
 *
519
 * @param string $invoice_key The invoice key to check.
520
 * @return int Invoice id on success or 0 on failure
521
 */
522
function wpinv_get_invoice_id_by_key( $invoice_key ) {
523
    return WPInv_Invoice::get_invoice_id_by_field( $invoice_key, 'key' );
524
}
525
526
/**
527
 * Retrieves an invoice's notes.
528
 *
529
 * @param int|string|object|WPInv_Invoice|WPInv_Legacy_Invoice|WP_Post $invoice Invoice id, key, transaction id, number or object.
530
 * @param string $type Optionally filter by type i.e customer|system
531
 * @return array|null
532
 */
533
function wpinv_get_invoice_notes( $invoice = 0, $type = '' ) {
534
535
    // Prepare the invoice.
536
    $invoice = wpinv_get_invoice( $invoice );
537
    if ( empty( $invoice ) ) {
538
        return null;
539
    }
540
541
    // Fetch notes.
542
    $notes = getpaid_notes()->get_invoice_notes( $invoice->get_id(), $type );
543
544
    // Filter the notes.
545
    return apply_filters( 'wpinv_invoice_notes', $notes, $invoice->get_id(), $type );
546
}
547
548
/**
549
 * Returns an array of columns to display on the invoices page.
550
 *
551
 * @param string $post_type
552
 */
553
function wpinv_get_user_invoices_columns( $post_type = 'wpi_invoice' ) {
554
555
    $label   = getpaid_get_post_type_label( $post_type, false );
556
    $label   = empty( $label ) ? __( 'Invoice', 'invoicing' ) : sanitize_text_field( $label );
557
    $columns = array(
558
559
		'invoice-number'  => array(
560
			'title' => $label,
561
			'class' => 'text-left',
562
		),
563
564
		'created-date'    => array(
565
			'title' => __( 'Created Date', 'invoicing' ),
566
			'class' => 'text-left',
567
		),
568
569
		'payment-date'    => array(
570
			'title' => __( 'Payment Date', 'invoicing' ),
571
			'class' => 'text-left',
572
		),
573
574
		'invoice-status'  => array(
575
			'title' => __( 'Status', 'invoicing' ),
576
			'class' => 'text-center',
577
		),
578
579
		'invoice-total'   => array(
580
			'title' => __( 'Total', 'invoicing' ),
581
			'class' => 'text-right',
582
		),
583
584
		'invoice-actions' => array(
585
			'title' => '&nbsp;',
586
			'class' => 'text-center',
587
		),
588
589
	);
590
591
    return apply_filters( 'wpinv_user_invoices_columns', $columns, $post_type );
592
}
593
594
/**
595
 * Displays the invoice receipt.
596
 */
597
function wpinv_payment_receipt() {
598
599
    // Find the invoice.
600
    $invoice_id = getpaid_get_current_invoice_id();
601
    $invoice = new WPInv_Invoice( $invoice_id );
602
603
    // Abort if non was found.
604
    if ( empty( $invoice_id ) || $invoice->is_draft() ) {
605
606
        return aui()->alert(
607
            array(
608
                'type'    => 'warning',
609
                'content' => __( 'We could not find your invoice', 'invoicing' ),
610
            )
611
        );
612
613
    }
614
615
    // Can the user view this invoice?
616
    if ( ! wpinv_can_view_receipt( $invoice_id ) ) {
617
618
        return aui()->alert(
619
            array(
620
                'type'    => 'warning',
621
                'content' => __( 'You are not allowed to view this receipt', 'invoicing' ),
622
            )
623
        );
624
625
    }
626
627
    // Load the template.
628
    return wpinv_get_template_html( 'invoice-receipt.php', compact( 'invoice' ) );
629
630
}
631
632
/**
633
 * Displays the invoice history.
634
 */
635
function getpaid_invoice_history( $user_id = 0, $post_type = 'wpi_invoice' ) {
636
637
    // Ensure that we have a user id.
638
    if ( empty( $user_id ) || ! is_numeric( $user_id ) ) {
639
        $user_id = get_current_user_id();
640
    }
641
642
    $label = getpaid_get_post_type_label( $post_type );
643
    $label = empty( $label ) ? __( 'Invoices', 'invoicing' ) : sanitize_text_field( $label );
644
645
    // View user id.
646
    if ( empty( $user_id ) ) {
647
648
        return aui()->alert(
649
            array(
650
                'type'    => 'warning',
651
                'content' => sprintf(
652
                    __( 'You must be logged in to view your %s.', 'invoicing' ),
653
                    strtolower( $label )
654
                ),
655
            )
656
        );
657
658
    }
659
660
    // Fetch invoices.
661
    $invoices = wpinv_get_invoices(
662
        array(
663
            'page'     => ( get_query_var( 'paged' ) ) ? absint( get_query_var( 'paged' ) ) : 1,
664
            'user'     => $user_id,
665
            'paginate' => true,
666
            'type'     => $post_type,
667
            'status'   => array_keys( wpinv_get_invoice_statuses( false, false, $post_type ) ),
668
        )
669
    );
670
671
    if ( empty( $invoices->total ) ) {
672
673
        return aui()->alert(
674
            array(
675
                'type'    => 'info',
676
                'content' => sprintf(
677
                    __( 'No %s found.', 'invoicing' ),
678
                    strtolower( $label )
679
                ),
680
            )
681
        );
682
683
    }
684
685
    // Load the template.
686
    return wpinv_get_template_html( 'invoice-history.php', compact( 'invoices', 'post_type' ) );
687
688
}
689
690
/**
691
 * Formats an invoice number given an invoice type.
692
 */
693
function wpinv_format_invoice_number( $number, $type = '' ) {
694
695
    // Allow other plugins to overide this.
696
    $check = apply_filters( 'wpinv_pre_format_invoice_number', null, $number, $type );
697
    if ( null !== $check ) {
698
        return $check;
699
    }
700
701
    // Ensure that we have a numeric number.
702
    if ( ! is_numeric( $number ) ) {
703
        return $number;
704
    }
705
706
    // Format the number.
707
    $padd             = absint( (int) wpinv_get_option( 'invoice_number_padd', 5 ) );
708
    $prefix           = sanitize_text_field( (string) wpinv_get_option( 'invoice_number_prefix', 'INV-' ) );
709
    $prefix           = sanitize_text_field( apply_filters( 'getpaid_invoice_type_prefix', $prefix, $type ) );
710
    $postfix          = sanitize_text_field( (string) wpinv_get_option( 'invoice_number_postfix' ) );
711
    $postfix          = sanitize_text_field( apply_filters( 'getpaid_invoice_type_postfix', $postfix, $type ) );
712
    $formatted_number = zeroise( absint( $number ), $padd );
713
714
    // Add the prefix and post fix.
715
    $formatted_number = $prefix . $formatted_number . $postfix;
716
717
    return apply_filters( 'wpinv_format_invoice_number', $formatted_number, $number, $prefix, $postfix, $padd );
718
}
719
720
/**
721
 * Returns the next invoice number.
722
 *
723
 * @param string $type.
724
 * @return int|null|bool
725
 */
726
function wpinv_get_next_invoice_number( $type = '' ) {
727
728
    // Allow plugins to overide this.
729
    $check = apply_filters( 'wpinv_get_pre_next_invoice_number', null, $type );
730
    if ( null !== $check ) {
731
        return $check;
732
    }
733
734
    // Ensure sequential invoice numbers is active.
735
    if ( ! wpinv_sequential_number_active() ) {
736
        return false;
737
    }
738
739
    // Retrieve the current number and the start number.
740
    $number = (int) get_option( 'wpinv_last_invoice_number', 0 );
741
    $start  = absint( (int) wpinv_get_option( 'invoice_sequence_start', 1 ) );
742
743
    // Ensure that we are starting at a positive integer.
744
    $start  = max( $start, 1 );
745
746
    // If this is the first invoice, use the start number.
747
    $number = max( $start, $number );
748
749
    // Format the invoice number.
750
    $formatted_number = wpinv_format_invoice_number( $number, $type );
751
752
    // Ensure that this number is unique.
753
    $invoice_id = WPInv_Invoice::get_invoice_id_by_field( $formatted_number, 'number' );
754
755
    // We found a match. Nice.
756
    if ( empty( $invoice_id ) ) {
757
        update_option( 'wpinv_last_invoice_number', $number );
758
        return apply_filters( 'wpinv_get_next_invoice_number', $number );
759
    }
760
761
    update_option( 'wpinv_last_invoice_number', $number + 1 );
762
    return wpinv_get_next_invoice_number( $type );
763
764
}
765
766
/**
767
 * The prefix used for invoice paths.
768
 */
769
function wpinv_post_name_prefix( $post_type = 'wpi_invoice' ) {
770
    return apply_filters( 'wpinv_post_name_prefix', 'inv-', $post_type );
771
}
772
773
function wpinv_generate_post_name( $post_ID ) {
774
    $prefix = wpinv_post_name_prefix( get_post_type( $post_ID ) );
775
    $post_name = sanitize_title( $prefix . $post_ID );
776
777
    return apply_filters( 'wpinv_generate_post_name', $post_name, $post_ID, $prefix );
778
}
779
780
/**
781
 * Checks if an invoice was viewed by the customer.
782
 *
783
 * @param int|string|object|WPInv_Invoice|WPInv_Legacy_Invoice|WP_Post $invoice Invoice id, key, transaction id, number or object.
784
 */
785
function wpinv_is_invoice_viewed( $invoice ) {
786
    $invoice = new WPInv_Invoice( $invoice );
787
    return (bool) $invoice->get_is_viewed();
788
}
789
790
/**
791
 * Marks an invoice as viewed.
792
 *
793
 * @param int|string|object|WPInv_Invoice|WPInv_Legacy_Invoice|WP_Post $invoice Invoice id, key, transaction id, number or object.
794
 */
795
function getpaid_maybe_mark_invoice_as_viewed( $invoice ) {
796
    $invoice = new WPInv_Invoice( $invoice );
797
798
    if ( get_current_user_id() == $invoice->get_user_id() && ! $invoice->get_is_viewed() ) {
799
        $invoice->set_is_viewed( true );
800
        $invoice->save();
801
    }
802
803
}
804
add_action( 'wpinv_invoice_print_before_display', 'getpaid_maybe_mark_invoice_as_viewed' );
805
add_action( 'wpinv_before_receipt', 'getpaid_maybe_mark_invoice_as_viewed' );
806
807
/**
808
 * Processes an invoice refund.
809
 *
810
 * @param WPInv_Invoice $invoice
811
 * @param array $status_transition
812
 * @todo: descrease customer/store earnings
813
 */
814
function getpaid_maybe_process_refund( $invoice, $status_transition ) {
815
816
    if ( empty( $status_transition['from'] ) || ! in_array( $status_transition['from'], array( 'publish', 'wpi-processing', 'wpi-renewal' ) ) ) {
817
        return;
818
    }
819
820
    $discount_code = $invoice->get_discount_code();
821
    if ( ! empty( $discount_code ) ) {
822
        $discount = wpinv_get_discount_obj( $discount_code );
823
824
        if ( $discount->exists() ) {
825
            $discount->increase_usage( -1 );
826
        }
827
}
828
829
    do_action( 'wpinv_pre_refund_invoice', $invoice, $invoice->get_id() );
830
    do_action( 'wpinv_refund_invoice', $invoice, $invoice->get_id() );
831
    do_action( 'wpinv_post_refund_invoice', $invoice, $invoice->get_id() );
832
}
833
add_action( 'getpaid_invoice_status_wpi-refunded', 'getpaid_maybe_process_refund', 10, 2 );
834
835
836
/**
837
 * Processes invoice payments.
838
 *
839
 * @param int $invoice_id
840
 */
841
function getpaid_process_invoice_payment( $invoice_id ) {
842
843
    // Fetch the invoice.
844
    $invoice = new WPInv_Invoice( $invoice_id );
845
846
    // We only want to do this once.
847
    if ( 1 == get_post_meta( $invoice->get_id(), 'wpinv_processed_payment', true ) ) {
848
        return;
849
    }
850
851
    update_post_meta( $invoice->get_id(), 'wpinv_processed_payment', 1 );
852
853
    // Fires when processing a payment.
854
    do_action( 'getpaid_process_payment', $invoice );
855
856
    // Fire an action for each invoice item.
857
    foreach ( $invoice->get_items() as $item ) {
858
        do_action( 'getpaid_process_item_payment', $item, $invoice );
859
    }
860
861
    // Increase discount usage.
862
    $discount_code = $invoice->get_discount_code();
863
    if ( ! empty( $discount_code ) && ! $invoice->is_renewal() ) {
864
        $discount = wpinv_get_discount_obj( $discount_code );
865
866
        if ( $discount->exists() ) {
867
            $discount->increase_usage();
868
        }
869
}
870
871
    // Record reverse vat.
872
    if ( 'invoice' === $invoice->get_type() && wpinv_use_taxes() && ! $invoice->get_disable_taxes() ) {
873
874
        $taxes = $invoice->get_total_tax();
875
        if ( empty( $taxes ) && GetPaid_Payment_Form_Submission_Taxes::is_eu_transaction( $invoice->get_country() ) ) {
876
            $invoice->add_note( __( 'VAT was reverse charged', 'invoicing' ), false, false, true );
877
        }
878
}
879
880
}
881
add_action( 'getpaid_invoice_payment_status_changed', 'getpaid_process_invoice_payment' );
882
883
/**
884
 * Returns an array of invoice item columns
885
 *
886
 * @param int|WPInv_Invoice $invoice
887
 * @return array
888
 */
889
function getpaid_invoice_item_columns( $invoice ) {
890
891
    // Prepare the invoice.
892
    $invoice = new WPInv_Invoice( $invoice );
893
894
    // Abort if there is no invoice.
895
    if ( 0 == $invoice->get_id() ) {
896
        return array();
897
    }
898
899
    // Line item columns.
900
    $columns = apply_filters(
901
        'getpaid_invoice_item_columns',
902
        array(
903
            'name'     => __( 'Item', 'invoicing' ),
904
            'price'    => __( 'Price', 'invoicing' ),
905
            'tax_rate' => __( 'Tax Rate', 'invoicing' ),
906
            'quantity' => __( 'Quantity', 'invoicing' ),
907
            'subtotal' => __( 'Item Subtotal', 'invoicing' ),
908
        ),
909
        $invoice
910
    );
911
912
    // Quantities.
913
    if ( isset( $columns['quantity'] ) ) {
914
915
        if ( 'hours' == $invoice->get_template() ) {
916
            $columns['quantity'] = __( 'Hours', 'invoicing' );
917
        }
918
919
        if ( ! wpinv_item_quantities_enabled() || 'amount' == $invoice->get_template() ) {
920
            unset( $columns['quantity'] );
921
        }
922
    }
923
924
    // Price.
925
    if ( isset( $columns['price'] ) ) {
926
927
        if ( 'amount' == $invoice->get_template() ) {
928
            $columns['price'] = __( 'Amount', 'invoicing' );
929
        }
930
931
        if ( 'hours' == $invoice->get_template() ) {
932
            $columns['price'] = __( 'Rate', 'invoicing' );
933
        }
934
}
935
936
    // Sub total.
937
    if ( isset( $columns['subtotal'] ) ) {
938
939
        if ( 'amount' == $invoice->get_template() ) {
940
            unset( $columns['subtotal'] );
941
        }
942
}
943
944
    // Tax rates.
945
    if ( isset( $columns['tax_rate'] ) ) {
946
947
        if ( 0 == $invoice->get_total_tax() ) {
948
            unset( $columns['tax_rate'] );
949
        }
950
    }
951
952
    return $columns;
953
}
954
955
/**
956
 * Returns an array of invoice totals rows
957
 *
958
 * @param int|WPInv_Invoice $invoice
959
 * @return array
960
 */
961
function getpaid_invoice_totals_rows( $invoice ) {
962
963
    // Prepare the invoice.
964
    $invoice = new WPInv_Invoice( $invoice );
965
966
    // Abort if there is no invoice.
967
    if ( 0 == $invoice->get_id() ) {
968
        return array();
969
    }
970
971
    $totals = apply_filters(
972
        'getpaid_invoice_totals_rows',
973
        array(
974
            'subtotal' => __( 'Subtotal', 'invoicing' ),
975
            'shipping' => __( 'Shipping', 'invoicing' ),
976
            'tax'      => __( 'Tax', 'invoicing' ),
977
            'fee'      => __( 'Fee', 'invoicing' ),
978
            'discount' => __( 'Discount', 'invoicing' ),
979
            'total'    => __( 'Total', 'invoicing' ),
980
        ),
981
        $invoice
982
    );
983
984
    if ( ! $invoice->has_shipping() ) {
985
        unset( $totals['shipping'] );
986
    }
987
988
    if ( ( $invoice->get_disable_taxes() || ! wpinv_use_taxes() ) && isset( $totals['tax'] ) ) {
989
        unset( $totals['tax'] );
990
    }
991
992
    // If we have taxes, display individual taxes.
993
    if ( isset( $totals['tax'] ) && wpinv_display_individual_tax_rates() ) {
994
995
        $new_totals = array();
996
        foreach ( $totals as $key => $label ) {
997
998
            if ( 'tax' !== $key ) {
999
                $new_totals[ $key ] = $label;
1000
                continue;
1001
            }
1002
1003
            $taxes = array_keys( $invoice->get_taxes() );
1004
            if ( ! empty( $taxes ) ) {
1005
1006
                foreach ( $taxes as $tax ) {
1007
                    $new_totals[ 'tax__' . $tax ] = $tax;
1008
                }
1009
            }
1010
        }
1011
1012
        $totals = $new_totals;
1013
    }
1014
1015
    if ( 0 == $invoice->get_total_fees() && isset( $totals['fee'] ) ) {
1016
        unset( $totals['fee'] );
1017
    }
1018
1019
    if ( 0 == $invoice->get_total_discount() && isset( $totals['discount'] ) ) {
1020
        unset( $totals['discount'] );
1021
    }
1022
1023
    return $totals;
1024
}
1025
1026
/**
1027
 * This function is called whenever an invoice is created.
1028
 *
1029
 * @param WPInv_Invoice $invoice
1030
 */
1031
function getpaid_new_invoice( $invoice ) {
1032
1033
    if ( ! $invoice->get_status() ) {
1034
        return;
1035
    }
1036
1037
    // Add an invoice created note.
1038
    $invoice->add_note(
1039
        sprintf(
1040
            __( '%1$s created with the status "%2$s".', 'invoicing' ),
1041
            ucfirst( $invoice->get_invoice_quote_type() ),
1042
            wpinv_status_nicename( $invoice->get_status(), $invoice )
1043
        )
1044
    );
1045
1046
}
1047
add_action( 'getpaid_new_invoice', 'getpaid_new_invoice' );
1048
1049
/**
1050
 * This function updates invoice caches.
1051
 *
1052
 * @param WPInv_Invoice $invoice
1053
 */
1054
function getpaid_update_invoice_caches( $invoice ) {
1055
1056
    // Cache invoice number.
1057
    wp_cache_set( $invoice->get_number(), $invoice->get_id(), 'getpaid_invoice_numbers_to_invoice_ids' );
1058
1059
    // Cache invoice key.
1060
    wp_cache_set( $invoice->get_key(), $invoice->get_id(), 'getpaid_invoice_keys_to_invoice_ids' );
1061
1062
    // (Maybe) cache transaction id.
1063
    $transaction_id = $invoice->get_transaction_id();
1064
1065
    if ( ! empty( $transaction_id ) ) {
1066
        wp_cache_set( $transaction_id, $invoice->get_id(), 'getpaid_invoice_transaction_ids_to_invoice_ids' );
1067
    }
1068
1069
}
1070
add_action( 'getpaid_new_invoice', 'getpaid_update_invoice_caches', 5 );
1071
add_action( 'getpaid_update_invoice', 'getpaid_update_invoice_caches', 5 );
1072
1073
/**
1074
 * Duplicates an invoice.
1075
 *
1076
 * Please note that this function does not save the duplicated invoice.
1077
 *
1078
 * @param  WPInv_Invoice $old_invoice The invoice to duplicate
1079
 * @return WPInv_Invoice The new invoice.
1080
 */
1081
function getpaid_duplicate_invoice( $old_invoice ) {
1082
1083
    // Create the new invoice.
1084
    $invoice = new WPInv_Invoice();
1085
    $invoice->set_props(
1086
        array(
1087
1088
            // Basic info.
1089
            'template'          => $old_invoice->get_template(),
1090
            'email_cc'          => $old_invoice->get_email_cc(),
1091
            'post_type'         => $old_invoice->get_post_type(),
1092
            'user_ip'           => $old_invoice->get_user_ip(),
1093
            'parent_id'         => $old_invoice->get_parent_id(),
1094
            'mode'              => $old_invoice->get_mode(),
1095
            'description'       => $old_invoice->get_description(),
1096
            'created_via'       => $old_invoice->get_created_via(),
1097
            'status'            => $old_invoice->get_default_status(),
1098
1099
            // Payment info.
1100
            'disable_taxes'     => $old_invoice->get_disable_taxes(),
1101
            'currency'          => $old_invoice->get_currency(),
1102
            'gateway'           => $old_invoice->get_gateway(),
1103
            'discount_code'     => $old_invoice->get_discount_code(),
1104
            'payment_form'      => $old_invoice->get_payment_form(),
1105
            'submission_id'     => $old_invoice->get_submission_id(),
1106
            'subscription_id'   => $old_invoice->get_subscription_id(),
1107
            'fees'              => $old_invoice->get_fees(),
1108
            'discounts'         => $old_invoice->get_discounts(),
1109
            'taxes'             => $old_invoice->get_taxes(),
1110
            'items'             => $old_invoice->get_items(),
1111
1112
            // Billing details.
1113
            'user_id'           => $old_invoice->get_user_id(),
1114
            'first_name'        => $old_invoice->get_first_name(),
1115
            'last_name'         => $old_invoice->get_last_name(),
1116
            'address'           => $old_invoice->get_address(),
1117
            'vat_number'        => $old_invoice->get_vat_number(),
1118
            'company'           => $old_invoice->get_company(),
1119
            'zip'               => $old_invoice->get_zip(),
1120
            'state'             => $old_invoice->get_state(),
1121
            'city'              => $old_invoice->get_city(),
1122
            'country'           => $old_invoice->get_country(),
1123
            'phone'             => $old_invoice->get_phone(),
1124
            'address_confirmed' => $old_invoice->get_address_confirmed(),
1125
1126
        )
1127
    );
1128
1129
    // Recalculate totals.
1130
    $invoice->recalculate_total();
1131
1132
    return $invoice;
1133
}
1134
1135
/**
1136
 * Retrieves invoice meta fields.
1137
 *
1138
 * @param WPInv_Invoice $invoice
1139
 * @return array
1140
 */
1141
function getpaid_get_invoice_meta( $invoice ) {
1142
1143
    // Load the invoice meta.
1144
    $meta = array(
1145
1146
        'number'         => array(
1147
            'label' => sprintf(
1148
                __( '%s Number', 'invoicing' ),
1149
                ucfirst( $invoice->get_invoice_quote_type() )
1150
            ),
1151
            'value' => sanitize_text_field( $invoice->get_number() ),
1152
        ),
1153
1154
        'status'         => array(
1155
            'label' => sprintf(
1156
                __( '%s Status', 'invoicing' ),
1157
                ucfirst( $invoice->get_invoice_quote_type() )
1158
            ),
1159
            'value' => $invoice->get_status_label_html(),
1160
        ),
1161
1162
        'date'           => array(
1163
            'label' => sprintf(
1164
                __( '%s Date', 'invoicing' ),
1165
                ucfirst( $invoice->get_invoice_quote_type() )
1166
            ),
1167
            'value' => getpaid_format_date( $invoice->get_created_date() ),
1168
        ),
1169
1170
        'date_paid'      => array(
1171
            'label' => __( 'Paid On', 'invoicing' ),
1172
            'value' => getpaid_format_date( $invoice->get_completed_date() ),
1173
        ),
1174
1175
        'gateway'        => array(
1176
            'label' => __( 'Payment Method', 'invoicing' ),
1177
            'value' => sanitize_text_field( $invoice->get_gateway_title() ),
1178
        ),
1179
1180
        'transaction_id' => array(
1181
            'label' => __( 'Transaction ID', 'invoicing' ),
1182
            'value' => sanitize_text_field( $invoice->get_transaction_id() ),
1183
        ),
1184
1185
        'due_date'       => array(
1186
            'label' => __( 'Due Date', 'invoicing' ),
1187
            'value' => getpaid_format_date( $invoice->get_due_date() ),
1188
        ),
1189
1190
        'vat_number'     => array(
1191
            'label' => __( 'VAT Number', 'invoicing' ),
1192
            'value' => sanitize_text_field( $invoice->get_vat_number() ),
1193
        ),
1194
1195
    );
1196
1197
    $additional_meta = get_post_meta( $invoice->get_id(), 'additional_meta_data', true );
1198
1199
    if ( ! empty( $additional_meta ) ) {
1200
1201
        foreach ( $additional_meta as $label => $value ) {
1202
            $meta[ sanitize_key( $label ) ] = array(
1203
                'label' => esc_html( $label ),
1204
                'value' => esc_html( $value ),
1205
            );
1206
        }
1207
}
1208
    // If it is not paid, remove the date of payment.
1209
    if ( ! $invoice->is_paid() && ! $invoice->is_refunded() ) {
1210
        unset( $meta['date_paid'] );
1211
        unset( $meta['transaction_id'] );
1212
    }
1213
1214
    if ( ! $invoice->is_paid() || 'none' == $invoice->get_gateway() ) {
1215
        unset( $meta['gateway'] );
1216
    }
1217
1218
    // Only display the due date if due dates are enabled.
1219
    if ( ! $invoice->needs_payment() || ! wpinv_get_option( 'overdue_active' ) ) {
1220
        unset( $meta['due_date'] );
1221
    }
1222
1223
    // Only display the vat number if taxes are enabled.
1224
    if ( ! wpinv_use_taxes() ) {
1225
        unset( $meta['vat_number'] );
1226
    }
1227
1228
    // Link to the parent invoice.
1229
    if ( $invoice->get_parent_id() > 0 ) {
1230
1231
        $meta['parent'] = array(
1232
1233
            'label' => sprintf(
1234
                __( 'Parent %s', 'invoicing' ),
1235
                ucfirst( $invoice->get_invoice_quote_type() )
1236
            ),
1237
1238
            'value' => wpinv_invoice_link( $invoice->get_parent_id() ),
1239
1240
        );
1241
1242
    }
1243
1244
    if ( $invoice->is_recurring() ) {
1245
1246
        $subscription = getpaid_get_invoice_subscriptions( $invoice );
1247
        if ( ! empty( $subscription ) && ! is_array( $subscription ) && $subscription->exists() ) {
1248
1249
            // Display the renewal date.
1250
            if ( $subscription->is_active() && 'cancelled' != $subscription->get_status() ) {
1251
1252
                $meta['renewal_date'] = array(
1253
                    'label' => __( 'Renews On', 'invoicing' ),
1254
                    'value' => getpaid_format_date( $subscription->get_expiration() ) .
1255
                    sprintf(
1256
                        ' <a class="small" href="%s">%s<a>',
1257
                        $subscription->get_view_url(),
1258
                        __( '(View Subscription)', 'invoicing' )
1259
                    ),
1260
                );
1261
1262
            }
1263
1264
            if ( $invoice->is_parent() ) {
1265
1266
                // Display the recurring amount.
1267
                $meta['recurring_total'] = array(
1268
1269
                    'label' => __( 'Recurring Amount', 'invoicing' ),
1270
                    'value' => wpinv_price( $subscription->get_recurring_amount(), $invoice->get_currency() ),
1271
1272
                );
1273
1274
            }
1275
        }
1276
    }
1277
1278
    // Add the invoice total to the meta.
1279
    $meta['invoice_total'] = array(
1280
1281
        'label' => __( 'Total Amount', 'invoicing' ),
1282
        'value' => wpinv_price( $invoice->get_total(), $invoice->get_currency() ),
1283
1284
    );
1285
1286
    // Provide a way for third party plugins to filter the meta.
1287
    $meta = apply_filters( 'getpaid_invoice_meta_data', $meta, $invoice );
1288
1289
    return $meta;
1290
1291
}
1292
1293
/**
1294
 * Returns an array of valid invoice status classes.
1295
 *
1296
 * @return array
1297
 */
1298
function getpaid_get_invoice_status_classes() {
1299
1300
	return apply_filters(
1301
		'getpaid_get_invoice_status_classes',
1302
		array(
1303
            'wpi-quote-declined' => 'bg-danger',
1304
            'wpi-failed'         => 'bg-danger',
1305
			'wpi-processing'     => 'bg-info',
1306
			'wpi-onhold'         => 'bg-warning text-dark',
1307
			'wpi-quote-accepted' => 'bg-success',
1308
			'publish'            => 'bg-success',
1309
			'wpi-renewal'        => 'bg-primary',
1310
            'wpi-cancelled'      => 'bg-secondary',
1311
            'wpi-pending'        => 'bg-dark text-white',
1312
            'wpi-quote-pending'  => 'bg-dark text-white',
1313
            'wpi-refunded'       => 'bg-secondary',
1314
		)
1315
	);
1316
1317
}
1318
1319
/**
1320
 * Returns an invoice's tax rate percentage.
1321
 *
1322
 * @param WPInv_Invoice $invoice
1323
 * @param GetPaid_Form_Item $item
1324
 * @return float
1325
 */
1326
function getpaid_get_invoice_tax_rate( $invoice, $item ) {
1327
1328
    $rates   = getpaid_get_item_tax_rates( $item, $invoice->get_country(), $invoice->get_state() );
1329
	$rates   = getpaid_filter_item_tax_rates( $item, $rates );
1330
    $rates   = wp_list_pluck( $rates, 'rate' );
1331
1332
    return array_sum( $rates );
1333
1334
}
1335