Passed
Push — master ( 9f060d...75dadc )
by Brian
04:41
created

WPInv_Legacy_Invoice::save()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 0
c 1
b 0
f 0
dl 0
loc 1
rs 10
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * Contains functions related to Invoicing plugin.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
 
9
// MUST have WordPress.
10
if ( !defined( 'WPINC' ) ) {
11
    exit( 'Do NOT access this file directly: ' . basename( __FILE__ ) );
12
}
13
14
final class WPInv_Legacy_Invoice {
15
16
    /**
17
     * Invoice id.
18
     */
19
    public $ID  = 0;
20
21
    /**
22
     * The title of the invoice. Usually the invoice number.
23
     */
24
    public $title;
25
26
    /**
27
     * The post type - either wpi_quote or wpi_invoice
28
     */
29
    public $post_type;
30
31
    /**
32
     * An array of pending changes.
33
     */
34
    public $pending;
35
36
    /**
37
     * An array of invoice items.
38
     */
39
    public $items = array();
40
41
    /**
42
     * Information on the invoice client.
43
     */
44
    public $user_info = array();
45
46
    /**
47
     * Payment information for the invoice.
48
     */
49
    public $payment_meta = array();
50
51
    /**
52
     * Whether this invoice exists in the db or not.
53
     */
54
    public $new = false;
55
56
    /**
57
     * The invoice number.
58
     */
59
    public $number = '';
60
61
    /**
62
     * Whether an actual payment occurred (live) or the transaction
63
     * happened in a sandbox environment (test). 
64
     */
65
    public $mode = 'live';
66
67
    /**
68
     * A unique key for this invoice.
69
     */
70
    public $key = '';
71
72
    /**
73
     * The invoice total.
74
     */
75
    public $total = 0.00;
76
77
    /**
78
     * The invoice subtotal.
79
     */
80
    public $subtotal = 0;
81
82
    /**
83
     * 1 to disable taxes and 0 otherwise.
84
     */
85
    public $disable_taxes = 0;
86
87
    /**
88
     * Total tax for this invoice.
89
     */
90
    public $tax = 0;
91
92
    /**
93
     * Other fees for the invoice.
94
     */
95
    public $fees = array();
96
97
    /**
98
     * Total amount of the fees.
99
     */
100
    public $fees_total = 0;
101
102
    /**
103
     * A comma separated array of discount codes.
104
     */
105
    public $discounts = '';
106
107
    /**
108
     * Total discount.
109
     */
110
    public $discount = 0;
111
112
    /**
113
     * Main discount code.
114
     */
115
    public $discount_code = 0;
116
117
    /**
118
     * The date the invoice was created.
119
     */
120
    public $date = '';
121
122
    /**
123
     * The date that the invoice will be due.
124
     */
125
    public $due_date = '';
126
127
    /**
128
     * The date the invoice was paid for.
129
     */
130
    public $completed_date = '';
131
132
    /**
133
     * The invoice status.
134
     */
135
    public $status      = 'wpi-pending';
136
137
    /**
138
     * Same as self::$status.
139
     */
140
    public $post_status = 'wpi-pending';
141
142
    /**
143
     * The old invoice status (used when transitioning statuses).
144
     */
145
    public $old_status = '';
146
147
    /**
148
     * A human readable status name.
149
     */
150
    public $status_nicename = '';
151
152
    /**
153
     * The user id of the invoice client.
154
     */
155
    public $user_id = 0;
156
157
    /**
158
     * The first name of the invoice client.
159
     */
160
    public $first_name = '';
161
162
    /**
163
     * The last name of the invoice client.
164
     */
165
    public $last_name = '';
166
167
    /**
168
     * The email address of the invoice client.
169
     */
170
    public $email = '';
171
172
    /**
173
     * The phone number of the invoice client.
174
     */
175
    public $phone = '';
176
177
    /**
178
     * The street address of the invoice client.
179
     */
180
    public $address = '';
181
182
    /**
183
     * The city of the invoice client.
184
     */
185
    public $city = '';
186
187
    /**
188
     * The country of the invoice client.
189
     */
190
    public $country = '';
191
192
    /**
193
     * The state of the invoice client.
194
     */
195
    public $state = '';
196
197
    /**
198
     * The postal code of the invoice client.
199
     */
200
    public $zip = '';
201
202
    /**
203
     * The transaction id of the invoice.
204
     */
205
    public $transaction_id = '';
206
207
    /**
208
     * The ip address of the invoice client.
209
     */
210
    public $ip = '';
211
212
    /**
213
     * The gateway used to pay the invoice.
214
     */
215
    public $gateway = '';
216
217
    /**
218
     * The title of the gateway used to pay for the invoice.
219
     */
220
    public $gateway_title = '';
221
222
    /**
223
     * The currency of the invoice.
224
     */
225
    public $currency = '';
226
227
    /**
228
     * The cart details of the invoice.
229
     */
230
    public $cart_details = array();
231
232
    /**
233
     * The company of the client.
234
     */
235
    public $company = '';
236
237
    /**
238
     * The vat number of the client.
239
     */
240
    public $vat_number = '';
241
242
    /**
243
     * The vat rate used on the invoice.
244
     */
245
    public $vat_rate = '';
246
247
    /**
248
     * Whether or not the client confirmed the address
249
     */
250
    public $adddress_confirmed = '';
251
    
252
    /**
253
     * The full name of the client.
254
     */
255
    public $full_name = '';
256
257
    /**
258
     * The parent invoice id of this invoice.
259
     */
260
    public $parent_invoice = 0;
261
    
262
    public function __construct( $invoice_id = false ) {
263
        if( empty( $invoice_id ) ) {
264
            return false;
265
        }
266
267
        $this->setup_invoice( $invoice_id );
268
    }
269
270
    public function get( $key ) {
271
        if ( method_exists( $this, 'get_' . $key ) ) {
272
            $value = call_user_func( array( $this, 'get_' . $key ) );
273
        } else {
274
            $value = $this->$key;
275
        }
276
277
        return $value;
278
    }
279
280
    public function set( $key, $value ) {
281
        $ignore = array( 'items', 'cart_details', 'fees', '_ID' );
282
283
        if ( $key === 'status' ) {
284
            $this->old_status = $this->status;
285
        }
286
287
        if ( ! in_array( $key, $ignore ) ) {
288
            $this->pending[ $key ] = $value;
289
        }
290
291
        if( '_ID' !== $key ) {
292
            $this->$key = $value;
293
        }
294
    }
295
296
    public function _isset( $name ) {
297
        if ( property_exists( $this, $name) ) {
298
            return false === empty( $this->$name );
299
        } else {
300
            return null;
301
        }
302
    }
303
304
    private function setup_invoice( $invoice_id ) {
305
        $this->pending = array();
306
307
        if ( empty( $invoice_id ) ) {
308
            return false;
309
        }
310
311
        $invoice = get_post( $invoice_id );
312
313
        if( !$invoice || is_wp_error( $invoice ) ) {
0 ignored issues
show
introduced by
$invoice is of type WP_Post, thus it always evaluated to true.
Loading history...
314
            return false;
315
        }
316
317
        if( !('wpi_invoice' == $invoice->post_type OR 'wpi_quote' == $invoice->post_type) ) {
318
            return false;
319
        }
320
321
        do_action( 'wpinv_pre_setup_invoice', $this, $invoice_id );
322
323
        // Primary Identifier
324
        $this->ID              = absint( $invoice_id );
325
        $this->post_type       = $invoice->post_type;
326
327
        // We have a payment, get the generic payment_meta item to reduce calls to it
328
        $this->payment_meta    = $this->get_meta();
329
        $this->date            = $invoice->post_date;
330
        $this->due_date        = $this->setup_due_date();
331
        $this->completed_date  = $this->setup_completed_date();
332
        $this->status          = $invoice->post_status;
333
334
        if ( 'future' == $this->status ) {
335
            $this->status = 'publish';
336
        }
337
338
        $this->post_status     = $this->status;
339
        $this->mode            = $this->setup_mode();
340
        $this->parent_invoice  = $invoice->post_parent;
341
        $this->post_name       = $this->setup_post_name( $invoice );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->post_name is correct as $this->setup_post_name($invoice) targeting WPInv_Legacy_Invoice::setup_post_name() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug Best Practice introduced by
The property post_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
342
        $this->status_nicename = $this->setup_status_nicename($invoice->post_status);
343
344
        // Items
345
        $this->fees            = $this->setup_fees();
346
        $this->cart_details    = $this->setup_cart_details();
347
        $this->items           = $this->setup_items();
348
349
        // Currency Based
350
        $this->total           = $this->setup_total();
351
        $this->disable_taxes   = $this->setup_is_taxable();
352
        $this->tax             = $this->setup_tax();
353
        $this->fees_total      = $this->get_fees_total();
354
        $this->subtotal        = $this->setup_subtotal();
355
        $this->currency        = $this->setup_currency();
356
        
357
        // Gateway based
358
        $this->gateway         = $this->setup_gateway();
359
        $this->gateway_title   = $this->setup_gateway_title();
360
        $this->transaction_id  = $this->setup_transaction_id();
361
        
362
        // User based
363
        $this->ip              = $this->setup_ip();
364
        $this->user_id         = !empty( $invoice->post_author ) ? $invoice->post_author : get_current_user_id();///$this->setup_user_id();
365
        $this->email           = get_the_author_meta( 'email', $this->user_id );
0 ignored issues
show
Bug introduced by
It seems like $this->user_id can also be of type string; however, parameter $user_id of get_the_author_meta() does only seem to accept false|integer, maybe add an additional type check? ( Ignorable by Annotation )

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

365
        $this->email           = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
366
367
        $this->user_info       = $this->setup_user_info();
368
369
        $this->first_name      = $this->user_info['first_name'];
370
        $this->last_name       = $this->user_info['last_name'];
371
        $this->company         = $this->user_info['company'];
372
        $this->vat_number      = $this->user_info['vat_number'];
373
        $this->vat_rate        = $this->user_info['vat_rate'];
374
        $this->adddress_confirmed  = $this->user_info['adddress_confirmed'];
375
        $this->address         = $this->user_info['address'];
376
        $this->city            = $this->user_info['city'];
377
        $this->country         = $this->user_info['country'];
378
        $this->state           = $this->user_info['state'];
379
        $this->zip             = $this->user_info['zip'];
380
        $this->phone           = $this->user_info['phone'];
381
        
382
        $this->discounts       = $this->user_info['discount'];
383
            $this->discount        = $this->setup_discount();
384
            $this->discount_code   = $this->setup_discount_code();
385
386
        // Other Identifiers
387
        $this->key             = $this->setup_invoice_key();
388
        $this->number          = $this->setup_invoice_number();
389
        $this->title           = !empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
390
        
391
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
392
        
393
        // Allow extensions to add items to this object via hook
394
        do_action( 'wpinv_setup_invoice', $this, $invoice_id );
395
396
        return true;
397
    }
398
399
    private function setup_status_nicename( $status ) {
400
        $all_invoice_statuses  = wpinv_get_invoice_statuses( true, true, $this );
0 ignored issues
show
Bug introduced by
$this of type WPInv_Legacy_Invoice is incompatible with the type WPInv_Invoice|string expected by parameter $invoice of wpinv_get_invoice_statuses(). ( Ignorable by Annotation )

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

400
        $all_invoice_statuses  = wpinv_get_invoice_statuses( true, true, /** @scrutinizer ignore-type */ $this );
Loading history...
401
402
        if ( $this->is_quote() && class_exists( 'Wpinv_Quotes_Shared' ) ) {
403
            $all_invoice_statuses  = Wpinv_Quotes_Shared::wpinv_get_quote_statuses();
0 ignored issues
show
Bug introduced by
The type Wpinv_Quotes_Shared was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
404
        }
405
        $status   = isset( $all_invoice_statuses[$status] ) ? $all_invoice_statuses[$status] : __( $status, 'invoicing' );
406
407
        return apply_filters( 'setup_status_nicename', $status );
408
    }
409
410
    private function setup_post_name( $post = NULL ) {
411
        global $wpdb;
412
        
413
        $post_name = '';
414
        
415
        if ( !empty( $post ) ) {
416
            if( !empty( $post->post_name ) ) {
417
                $post_name = $post->post_name;
418
            } else if ( !empty( $post->ID ) ) {
419
                $post_name = wpinv_generate_post_name( $post->ID );
420
421
                $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
422
            }
423
        }
424
425
        $this->post_name = $post_name;
0 ignored issues
show
Bug Best Practice introduced by
The property post_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
426
    }
427
    
428
    private function setup_due_date() {
429
        $due_date = $this->get_meta( '_wpinv_due_date' );
430
        
431
        if ( empty( $due_date ) ) {
432
            $overdue_time = strtotime( $this->date ) + ( DAY_IN_SECONDS * absint( wpinv_get_option( 'overdue_days' ) ) );
433
            $due_date = date_i18n( 'Y-m-d', $overdue_time );
434
        } else if ( $due_date == 'none' ) {
435
            $due_date = '';
436
        }
437
        
438
        return $due_date;
439
    }
440
    
441
    private function setup_completed_date() {
442
        $invoice = get_post( $this->ID );
443
444
        if ( 'wpi-pending' == $invoice->post_status || 'preapproved' == $invoice->post_status ) {
445
            return false; // This invoice was never paid
446
        }
447
448
        $date = ( $date = $this->get_meta( '_wpinv_completed_date', true ) ) ? $date : $invoice->modified_date;
449
450
        return $date;
451
    }
452
    
453
    private function setup_cart_details() {
454
        $cart_details = isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
455
        return $cart_details;
456
    }
457
    
458
    public function array_convert() {
459
        return get_object_vars( $this );
460
    }
461
    
462
    private function setup_items() {
463
        $items = isset( $this->payment_meta['items'] ) ? maybe_unserialize( $this->payment_meta['items'] ) : array();
464
        return $items;
465
    }
466
    
467
    private function setup_fees() {
468
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
469
        return $payment_fees;
470
    }
471
        
472
    private function setup_currency() {
473
        $currency = isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'wpinv_currency_default', wpinv_get_currency(), $this );
474
        return $currency;
475
    }
476
    
477
    private function setup_discount() {
478
        //$discount = $this->get_meta( '_wpinv_discount', true );
479
        $discount = (float)$this->subtotal - ( (float)$this->total - (float)$this->tax - (float)$this->fees_total );
480
        if ( $discount < 0 ) {
481
            $discount = 0;
482
        }
483
        $discount = wpinv_round_amount( $discount );
484
        
485
        return $discount;
486
    }
487
    
488
    private function setup_discount_code() {
489
        $discount_code = !empty( $this->discounts ) ? $this->discounts : $this->get_meta( '_wpinv_discount_code', true );
490
        return $discount_code;
491
    }
492
    
493
    private function setup_tax() {
494
495
        $tax = $this->get_meta( '_wpinv_tax', true );
496
497
        // We don't have tax as it's own meta and no meta was passed
498
        if ( '' === $tax ) {            
499
            $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
500
        }
501
        
502
        if ( $tax < 0 || ! $this->is_taxable() ) {
503
            $tax = 0;
504
        }
505
506
        return $tax;
507
    }
508
509
    /**
510
     * If taxes are enabled, allow users to enable/disable taxes per invoice.
511
     */
512
    private function setup_is_taxable() {
513
        return (int) $this->get_meta( '_wpinv_disable_taxes', true );
514
    }
515
516
    private function setup_subtotal() {
517
        $subtotal     = 0;
518
        $cart_details = $this->cart_details;
519
520
        if ( is_array( $cart_details ) ) {
0 ignored issues
show
introduced by
The condition is_array($cart_details) is always true.
Loading history...
521
            foreach ( $cart_details as $item ) {
522
                if ( isset( $item['subtotal'] ) ) {
523
                    $subtotal += $item['subtotal'];
524
                }
525
            }
526
        } else {
527
            $subtotal  = $this->total;
528
            $tax       = wpinv_use_taxes() ? $this->tax : 0;
529
            $subtotal -= $tax;
530
        }
531
532
        return $subtotal;
533
    }
534
535
    private function setup_discounts() {
536
        $discounts = ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
537
        return $discounts;
538
    }
539
    
540
    private function setup_total() {
541
        $amount = $this->get_meta( '_wpinv_total', true );
542
543
        if ( empty( $amount ) && '0.00' != $amount ) {
544
            $meta   = $this->get_meta( '_wpinv_payment_meta', true );
545
            $meta   = maybe_unserialize( $meta );
0 ignored issues
show
Bug introduced by
It seems like $meta can also be of type array and array; however, parameter $data of maybe_unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

545
            $meta   = maybe_unserialize( /** @scrutinizer ignore-type */ $meta );
Loading history...
546
547
            if ( isset( $meta['amount'] ) ) {
548
                $amount = $meta['amount'];
549
            }
550
        }
551
552
        if($amount < 0){
553
            $amount = 0;
554
        }
555
556
        return $amount;
557
    }
558
    
559
    private function setup_mode() {
560
        return $this->get_meta( '_wpinv_mode' );
561
    }
562
563
    private function setup_gateway() {
564
        $gateway = $this->get_meta( '_wpinv_gateway' );
565
        
566
        if ( empty( $gateway ) && 'publish' === $this->status ) {
567
            $gateway = 'manual';
568
        }
569
        
570
        return $gateway;
571
    }
572
573
    private function setup_gateway_title() {
574
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
575
        return $gateway_title;
576
    }
577
578
    private function setup_transaction_id() {
579
        $transaction_id = $this->get_meta( '_wpinv_transaction_id' );
580
581
        if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
582
            $gateway        = $this->gateway;
583
            $transaction_id = apply_filters( 'wpinv_get_invoice_transaction_id-' . $gateway, $this->ID );
584
        }
585
586
        return $transaction_id;
587
    }
588
589
    private function setup_ip() {
590
        $ip = $this->get_meta( '_wpinv_user_ip' );
591
        return $ip;
592
    }
593
594
    ///private function setup_user_id() {
595
        ///$user_id = $this->get_meta( '_wpinv_user_id' );
596
        ///return $user_id;
597
    ///}
598
        
599
    private function setup_first_name() {
600
        $first_name = $this->get_meta( '_wpinv_first_name' );
601
        return $first_name;
602
    }
603
    
604
    private function setup_last_name() {
605
        $last_name = $this->get_meta( '_wpinv_last_name' );
606
        return $last_name;
607
    }
608
    
609
    private function setup_company() {
610
        $company = $this->get_meta( '_wpinv_company' );
611
        return $company;
612
    }
613
    
614
    private function setup_vat_number() {
615
        $vat_number = $this->get_meta( '_wpinv_vat_number' );
616
        return $vat_number;
617
    }
618
    
619
    private function setup_vat_rate() {
620
        $vat_rate = $this->get_meta( '_wpinv_vat_rate' );
621
        return $vat_rate;
622
    }
623
    
624
    private function setup_adddress_confirmed() {
625
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed' );
626
        return $adddress_confirmed;
627
    }
628
    
629
    private function setup_phone() {
630
        $phone = $this->get_meta( '_wpinv_phone' );
631
        return $phone;
632
    }
633
    
634
    private function setup_address() {
635
        $address = $this->get_meta( '_wpinv_address', true );
636
        return $address;
637
    }
638
    
639
    private function setup_city() {
640
        $city = $this->get_meta( '_wpinv_city', true );
641
        return $city;
642
    }
643
    
644
    private function setup_country() {
645
        $country = $this->get_meta( '_wpinv_country', true );
646
        return $country;
647
    }
648
    
649
    private function setup_state() {
650
        $state = $this->get_meta( '_wpinv_state', true );
651
        return $state;
652
    }
653
    
654
    private function setup_zip() {
655
        $zip = $this->get_meta( '_wpinv_zip', true );
656
        return $zip;
657
    }
658
659
    private function setup_user_info() {
660
        $defaults = array(
661
            'user_id'        => $this->user_id,
662
            'first_name'     => $this->first_name,
663
            'last_name'      => $this->last_name,
664
            'email'          => get_the_author_meta( 'email', $this->user_id ),
665
            'phone'          => $this->phone,
666
            'address'        => $this->address,
667
            'city'           => $this->city,
668
            'country'        => $this->country,
669
            'state'          => $this->state,
670
            'zip'            => $this->zip,
671
            'company'        => $this->company,
672
            'vat_number'     => $this->vat_number,
673
            'vat_rate'       => $this->vat_rate,
674
            'adddress_confirmed' => $this->adddress_confirmed,
675
            'discount'       => $this->discounts,
676
        );
677
        
678
        $user_info = array();
679
        if ( isset( $this->payment_meta['user_info'] ) ) {
680
            $user_info = maybe_unserialize( $this->payment_meta['user_info'] );
681
            
682
            if ( !empty( $user_info ) && isset( $user_info['user_id'] ) && $post = get_post( $this->ID ) ) {
683
                $this->user_id = $post->post_author;
684
                $this->email = get_the_author_meta( 'email', $this->user_id );
0 ignored issues
show
Bug introduced by
$this->user_id of type string is incompatible with the type false|integer expected by parameter $user_id of get_the_author_meta(). ( Ignorable by Annotation )

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

684
                $this->email = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
685
                
686
                $user_info['user_id'] = $this->user_id;
687
                $user_info['email'] = $this->email;
688
                $this->payment_meta['user_id'] = $this->user_id;
689
                $this->payment_meta['email'] = $this->email;
690
            }
691
        }
692
        
693
        $user_info    = wp_parse_args( $user_info, $defaults );
694
        
695
        // Get the user, but only if it's been created
696
        $user = get_userdata( $this->user_id );
697
        
698
        if ( !empty( $user ) && $user->ID > 0 ) {
699
            if ( empty( $user_info ) ) {
700
                $user_info = array(
701
                    'user_id'    => $user->ID,
702
                    'first_name' => $user->first_name,
703
                    'last_name'  => $user->last_name,
704
                    'email'      => $user->user_email,
705
                    'discount'   => '',
706
                );
707
            } else {
708
                foreach ( $user_info as $key => $value ) {
709
                    if ( ! empty( $value ) ) {
710
                        continue;
711
                    }
712
713
                    switch( $key ) {
714
                        case 'user_id':
715
                            $user_info[ $key ] = $user->ID;
716
                            break;
717
                        case 'first_name':
718
                            $user_info[ $key ] = $user->first_name;
719
                            break;
720
                        case 'last_name':
721
                            $user_info[ $key ] = $user->last_name;
722
                            break;
723
                        case 'email':
724
                            $user_info[ $key ] = $user->user_email;
725
                            break;
726
                    }
727
                }
728
            }
729
        }
730
731
        return $user_info;
732
    }
733
734
    private function setup_invoice_key() {
735
        $key = $this->get_meta( '_wpinv_key', true );
736
        
737
        return $key;
738
    }
739
740
    private function setup_invoice_number() {
741
        $number = $this->get_meta( '_wpinv_number', true );
742
743
        if ( !$number ) {
744
            $number = $this->ID;
745
746
            if ( $this->status == 'auto-draft' ) {
747
                if ( wpinv_sequential_number_active( $this->post_type ) ) {
748
                    $next_number = wpinv_get_next_invoice_number( $this->post_type );
749
                    $number      = $next_number;
750
                }
751
            }
752
            
753
            $number = wpinv_format_invoice_number( $number, $this->post_type );
754
        }
755
756
        return $number;
757
    }
758
759
    public function save() {}
760
    
761
    public function add_fee( $args ) {
762
        $default_args = array(
763
            'label'       => '',
764
            'amount'      => 0,
765
            'type'        => 'fee',
766
            'id'          => '',
767
            'no_tax'      => false,
768
            'item_id'     => 0,
769
        );
770
771
        $fee = wp_parse_args( $args, $default_args );
772
        
773
        if ( empty( $fee['label'] ) ) {
774
            return false;
775
        }
776
        
777
        $fee['id']  = sanitize_title( $fee['label'] );
778
        
779
        $this->fees[]               = $fee;
780
        
781
        $added_fee               = $fee;
782
        $added_fee['action']     = 'add';
783
        $this->pending['fees'][] = $added_fee;
784
        reset( $this->fees );
785
786
        $this->increase_fees( $fee['amount'] );
787
        return true;
788
    }
789
790
    public function remove_fee( $key ) {
791
        $removed = false;
792
793
        if ( is_numeric( $key ) ) {
794
            $removed = $this->remove_fee_by( 'index', $key );
795
        }
796
797
        return $removed;
798
    }
799
800
    public function remove_fee_by( $key, $value, $global = false ) {
801
        $allowed_fee_keys = apply_filters( 'wpinv_fee_keys', array(
802
            'index', 'label', 'amount', 'type',
803
        ) );
804
805
        if ( ! in_array( $key, $allowed_fee_keys ) ) {
806
            return false;
807
        }
808
809
        $removed = false;
810
        if ( 'index' === $key && array_key_exists( $value, $this->fees ) ) {
811
            $removed_fee             = $this->fees[ $value ];
812
            $removed_fee['action']   = 'remove';
813
            $this->pending['fees'][] = $removed_fee;
814
815
            $this->decrease_fees( $removed_fee['amount'] );
816
817
            unset( $this->fees[ $value ] );
818
            $removed = true;
819
        } else if ( 'index' !== $key ) {
820
            foreach ( $this->fees as $index => $fee ) {
821
                if ( isset( $fee[ $key ] ) && $fee[ $key ] == $value ) {
822
                    $removed_fee             = $fee;
823
                    $removed_fee['action']   = 'remove';
824
                    $this->pending['fees'][] = $removed_fee;
825
826
                    $this->decrease_fees( $removed_fee['amount'] );
827
828
                    unset( $this->fees[ $index ] );
829
                    $removed = true;
830
831
                    if ( false === $global ) {
832
                        break;
833
                    }
834
                }
835
            }
836
        }
837
838
        if ( true === $removed ) {
839
            $this->fees = array_values( $this->fees );
840
        }
841
842
        return $removed;
843
    }
844
845
    
846
847
    public function add_note( $note = '', $customer_type = false, $added_by_user = false, $system = false ) {
848
        // Bail if no note specified
849
        if( !$note ) {
850
            return false;
851
        }
852
853
        if ( empty( $this->ID ) )
854
            return false;
855
        
856
        if ( ( ( is_user_logged_in() && wpinv_current_user_can_manage_invoicing() ) || $added_by_user ) && !$system ) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (is_user_logged_in() && ...d_by_user) && ! $system, Probably Intended Meaning: is_user_logged_in() && w...d_by_user && ! $system)
Loading history...
857
            $user                 = get_user_by( 'id', get_current_user_id() );
858
            $comment_author       = $user->display_name;
859
            $comment_author_email = $user->user_email;
860
        } else {
861
            $comment_author       = 'System';
862
            $comment_author_email = 'system@';
863
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
864
            $comment_author_email = sanitize_email( $comment_author_email );
865
        }
866
867
        do_action( 'wpinv_pre_insert_invoice_note', $this->ID, $note, $customer_type );
868
869
        $note_id = wp_insert_comment( wp_filter_comment( array(
870
            'comment_post_ID'      => $this->ID,
871
            'comment_content'      => $note,
872
            'comment_agent'        => 'WPInvoicing',
873
            'user_id'              => is_admin() ? get_current_user_id() : 0,
874
            'comment_date'         => current_time( 'mysql' ),
875
            'comment_date_gmt'     => current_time( 'mysql', 1 ),
876
            'comment_approved'     => 1,
877
            'comment_parent'       => 0,
878
            'comment_author'       => $comment_author,
879
            'comment_author_IP'    => wpinv_get_ip(),
880
            'comment_author_url'   => '',
881
            'comment_author_email' => $comment_author_email,
882
            'comment_type'         => 'wpinv_note'
883
        ) ) );
884
885
        do_action( 'wpinv_insert_payment_note', $note_id, $this->ID, $note );
886
        
887
        if ( $customer_type ) {
888
            add_comment_meta( $note_id, '_wpi_customer_note', 1 );
0 ignored issues
show
Bug introduced by
$note_id of type false is incompatible with the type integer expected by parameter $comment_id of add_comment_meta(). ( Ignorable by Annotation )

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

888
            add_comment_meta( /** @scrutinizer ignore-type */ $note_id, '_wpi_customer_note', 1 );
Loading history...
889
890
            do_action( 'wpinv_new_customer_note', array( 'invoice_id' => $this->ID, 'user_note' => $note ) );
891
        }
892
893
        return $note_id;
894
    }
895
896
    private function increase_subtotal( $amount = 0.00 ) {
897
        $amount          = (float) $amount;
898
        $this->subtotal += $amount;
899
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
900
901
        $this->recalculate_total();
902
    }
903
904
    private function decrease_subtotal( $amount = 0.00 ) {
905
        $amount          = (float) $amount;
906
        $this->subtotal -= $amount;
907
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
908
909
        if ( $this->subtotal < 0 ) {
910
            $this->subtotal = 0;
911
        }
912
913
        $this->recalculate_total();
914
    }
915
916
    private function increase_fees( $amount = 0.00 ) {
917
        $amount            = (float)$amount;
918
        $this->fees_total += $amount;
919
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
920
921
        $this->recalculate_total();
922
    }
923
924
    private function decrease_fees( $amount = 0.00 ) {
925
        $amount            = (float) $amount;
926
        $this->fees_total -= $amount;
927
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
928
929
        if ( $this->fees_total < 0 ) {
930
            $this->fees_total = 0;
931
        }
932
933
        $this->recalculate_total();
934
    }
935
936
    public function recalculate_total() {
937
        global $wpi_nosave;
938
        
939
        $this->total = $this->subtotal + $this->tax + $this->fees_total;
940
        $this->total = wpinv_round_amount( $this->total );
941
        
942
        do_action( 'wpinv_invoice_recalculate_total', $this, $wpi_nosave );
943
    }
944
    
945
    public function increase_tax( $amount = 0.00 ) {
946
        $amount       = (float) $amount;
947
        $this->tax   += $amount;
948
949
        $this->recalculate_total();
950
    }
951
952
    public function decrease_tax( $amount = 0.00 ) {
953
        $amount     = (float) $amount;
954
        $this->tax -= $amount;
955
956
        if ( $this->tax < 0 ) {
957
            $this->tax = 0;
958
        }
959
960
        $this->recalculate_total();
961
    }
962
963
    public function update_status( $new_status = false, $note = '', $manual = false ) {
964
        $old_status = ! empty( $this->old_status ) ? $this->old_status : get_post_status( $this->ID );
965
966
        if ( $old_status === $new_status && in_array( $new_status, array_keys( wpinv_get_invoice_statuses( true ) ) ) ) {
967
            return false; // Don't permit status changes that aren't changes
968
        }
969
970
        $do_change = apply_filters( 'wpinv_should_update_invoice_status', true, $this->ID, $new_status, $old_status );
971
        $updated = false;
972
973
        if ( $do_change ) {
974
            do_action( 'wpinv_before_invoice_status_change', $this->ID, $new_status, $old_status );
975
976
            $update_post_data                   = array();
977
            $update_post_data['ID']             = $this->ID;
978
            $update_post_data['post_status']    = $new_status;
979
            $update_post_data['edit_date']      = current_time( 'mysql', 0 );
980
            $update_post_data['edit_date_gmt']  = current_time( 'mysql', 1 );
981
            
982
            $update_post_data = apply_filters( 'wpinv_update_invoice_status_fields', $update_post_data, $this->ID );
983
984
            $updated = wp_update_post( $update_post_data );
985
            
986
            // Status was changed.
987
            do_action( 'wpinv_status_' . $new_status, $this->ID, $old_status );
988
            do_action( 'wpinv_status_' . $old_status . '_to_' . $new_status, $this->ID, $old_status );
0 ignored issues
show
Bug introduced by
Are you sure $old_status of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

988
            do_action( 'wpinv_status_' . /** @scrutinizer ignore-type */ $old_status . '_to_' . $new_status, $this->ID, $old_status );
Loading history...
989
            do_action( 'wpinv_update_status', $this->ID, $new_status, $old_status );
990
        }
991
992
        return $updated;
993
    }
994
995
    public function refund() {
996
        $this->old_status        = $this->status;
997
        $this->status            = 'wpi-refunded';
998
        $this->pending['status'] = $this->status;
999
1000
        $this->save();
1001
    }
1002
1003
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
1004
        if ( empty( $meta_key ) ) {
1005
            return false;
1006
        }
1007
1008
        if ( $meta_key == 'key' || $meta_key == 'date' ) {
1009
            $current_meta = $this->get_meta();
1010
            $current_meta[ $meta_key ] = $meta_value;
1011
1012
            $meta_key     = '_wpinv_payment_meta';
1013
            $meta_value   = $current_meta;
1014
        }
1015
1016
        $meta_value = apply_filters( 'wpinv_update_payment_meta_' . $meta_key, $meta_value, $this->ID );
1017
        
1018
        // Do not update created date on invoice marked as paid.
1019
        /*if ( $meta_key == '_wpinv_completed_date' && !empty( $meta_value ) ) {
1020
            $args = array(
1021
                'ID'                => $this->ID,
1022
                'post_date'         => $meta_value,
1023
                'edit_date'         => true,
1024
                'post_date_gmt'     => get_gmt_from_date( $meta_value ),
1025
                'post_modified'     => $meta_value,
1026
                'post_modified_gmt' => get_gmt_from_date( $meta_value )
1027
            );
1028
            wp_update_post( $args );
1029
        }*/
1030
        
1031
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
1032
    }
1033
1034
    // get data
1035
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
1036
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1037
1038
        if ( $meta_key === '_wpinv_payment_meta' ) {
1039
1040
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
1041
1042
            if ( empty( $meta['key'] ) ) {
1043
                $meta['key'] = $this->setup_invoice_key();
1044
            }
1045
1046
            if ( empty( $meta['date'] ) ) {
1047
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1048
            }
1049
        }
1050
1051
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1052
1053
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1054
    }
1055
    
1056
    public function get_description() {
1057
        $post = get_post( $this->ID );
1058
        
1059
        $description = !empty( $post ) ? $post->post_content : '';
1060
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1061
    }
1062
    
1063
    public function get_status( $nicename = false ) {
1064
        if ( !$nicename ) {
1065
            $status = $this->status;
1066
        } else {
1067
            $status = $this->status_nicename;
1068
        }
1069
        
1070
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1071
    }
1072
    
1073
    public function get_cart_details() {
1074
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1075
    }
1076
    
1077
    public function get_subtotal( $currency = false ) {
1078
        $subtotal = wpinv_round_amount( $this->subtotal );
1079
        
1080
        if ( $currency ) {
1081
            $subtotal = wpinv_price( wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
0 ignored issues
show
Bug introduced by
wpinv_format_amount($subtotal, NULL, ! $currency) of type string is incompatible with the type double expected by parameter $amount of wpinv_price(). ( Ignorable by Annotation )

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

1081
            $subtotal = wpinv_price( /** @scrutinizer ignore-type */ wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
Loading history...
1082
        }
1083
        
1084
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1085
    }
1086
    
1087
    public function get_total( $currency = false ) {        
1088
        if ( $this->is_free_trial() ) {
1089
            $total = wpinv_round_amount( 0 );
1090
        } else {
1091
            $total = wpinv_round_amount( $this->total );
1092
        }
1093
        if ( $currency ) {
1094
            $total = wpinv_price( wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
0 ignored issues
show
Bug introduced by
wpinv_format_amount($total, NULL, ! $currency) of type string is incompatible with the type double expected by parameter $amount of wpinv_price(). ( Ignorable by Annotation )

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

1094
            $total = wpinv_price( /** @scrutinizer ignore-type */ wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
Loading history...
1095
        }
1096
        
1097
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1098
    }
1099
    
1100
    public function get_recurring_details( $field = '', $currency = false ) {        
1101
        $data                 = array();
1102
        $data['cart_details'] = $this->cart_details;
1103
        $data['subtotal']     = $this->get_subtotal();
1104
        $data['discount']     = $this->get_discount();
1105
        $data['tax']          = $this->get_tax();
1106
        $data['total']        = $this->get_total();
1107
    
1108
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1109
            $is_free_trial = $this->is_free_trial();
1110
            $discounts = $this->get_discounts( true );
1111
            
1112
            if ( $is_free_trial || !empty( $discounts ) ) {
1113
                $first_use_only = false;
1114
                
1115
                if ( !empty( $discounts ) ) {
1116
                    foreach ( $discounts as $key => $code ) {
1117
                        if ( wpinv_discount_is_recurring( $code, true ) && !$this->is_renewal() ) {
1118
                            $first_use_only = true;
1119
                            break;
1120
                        }
1121
                    }
1122
                }
1123
                    
1124
                if ( !$first_use_only ) {
1125
                    $data['subtotal'] = wpinv_round_amount( $this->subtotal );
1126
                    $data['discount'] = wpinv_round_amount( $this->discount );
1127
                    $data['tax']      = wpinv_round_amount( $this->tax );
1128
                    $data['total']    = wpinv_round_amount( $this->total );
1129
                } else {
1130
                    $cart_subtotal   = 0;
1131
                    $cart_discount   = $this->discount;
1132
                    $cart_tax        = 0;
1133
1134
                    foreach ( $this->cart_details as $key => $item ) {
1135
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1136
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1137
                        $item_discount  = 0;
1138
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1139
                        
1140
                        if ( wpinv_prices_include_tax() ) {
1141
                            $item_subtotal -= wpinv_round_amount( $item_tax );
1142
                        }
1143
                        
1144
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1145
                        // Do not allow totals to go negative
1146
                        if ( $item_total < 0 ) {
1147
                            $item_total = 0;
1148
                        }
1149
                        
1150
                        $cart_subtotal  += (float)($item_subtotal);
1151
                        $cart_discount  += (float)($item_discount);
1152
                        $cart_tax       += (float)($item_tax);
1153
                        
1154
                        $data['cart_details'][$key]['discount']   = wpinv_round_amount( $item_discount );
1155
                        $data['cart_details'][$key]['tax']        = wpinv_round_amount( $item_tax );
1156
                        $data['cart_details'][$key]['price']      = wpinv_round_amount( $item_total );
1157
                    }
1158
1159
	                $total = $data['subtotal'] - $data['discount'] + $data['tax'];
1160
	                if ( $total < 0 ) {
1161
		                $total = 0;
1162
	                }
1163
1164
                    $data['subtotal'] = wpinv_round_amount( $cart_subtotal );
1165
                    $data['discount'] = wpinv_round_amount( $cart_discount );
1166
                    $data['tax']      = wpinv_round_amount( $cart_tax );
1167
                    $data['total']    = wpinv_round_amount( $total );
1168
                }
1169
            }
1170
        }
1171
        
1172
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1173
1174
        if ( isset( $data[$field] ) ) {
1175
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1176
        }
1177
        
1178
        return $data;
1179
    }
1180
    
1181
    public function get_final_tax( $currency = false ) {        
1182
        $final_total = wpinv_round_amount( $this->tax );
1183
        if ( $currency ) {
1184
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
0 ignored issues
show
Bug introduced by
wpinv_format_amount($fin...tal, NULL, ! $currency) of type string is incompatible with the type double expected by parameter $amount of wpinv_price(). ( Ignorable by Annotation )

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

1184
            $final_total = wpinv_price( /** @scrutinizer ignore-type */ wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
Loading history...
1185
        }
1186
        
1187
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1188
    }
1189
    
1190
    public function get_discounts( $array = false ) {
1191
        $discounts = $this->discounts;
1192
        if ( $array && $discounts ) {
1193
            $discounts = explode( ',', $discounts );
1194
        }
1195
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1196
    }
1197
    
1198
    public function get_discount( $currency = false, $dash = false ) {
1199
        if ( !empty( $this->discounts ) ) {
1200
            global $ajax_cart_details;
1201
            $ajax_cart_details = $this->get_cart_details();
1202
            
1203
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
1204
                $cart_items = $ajax_cart_details;
1205
            } else {
1206
                $cart_items = $this->items;
1207
            }
1208
1209
            $this->discount = wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
0 ignored issues
show
Unused Code introduced by
The call to wpinv_get_cart_items_discount_amount() has too many arguments starting with $cart_items. ( Ignorable by Annotation )

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

1209
            $this->discount = /** @scrutinizer ignore-call */ wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Deprecated Code introduced by
The function wpinv_get_cart_items_discount_amount() has been deprecated. ( Ignorable by Annotation )

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

1209
            $this->discount = /** @scrutinizer ignore-deprecated */ wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
Loading history...
1210
        }
1211
        $discount   = wpinv_round_amount( $this->discount );
1212
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1213
        
1214
        if ( $currency ) {
1215
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
0 ignored issues
show
Bug introduced by
wpinv_format_amount($discount, NULL, ! $currency) of type string is incompatible with the type double expected by parameter $amount of wpinv_price(). ( Ignorable by Annotation )

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

1215
            $discount = wpinv_price( /** @scrutinizer ignore-type */ wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
Loading history...
1216
        }
1217
        
1218
        $discount   = $dash . $discount;
1219
        
1220
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1221
    }
1222
    
1223
    public function get_discount_code() {
1224
        return $this->discount_code;
1225
    }
1226
1227
    // Checks if the invoice is taxable. Does not check if taxes are enabled on the site.
1228
    public function is_taxable() {
1229
        return (int) $this->disable_taxes === 0;
1230
    }
1231
1232
    public function get_tax( $currency = false ) {
1233
        $tax = wpinv_round_amount( $this->tax );
1234
1235
        if ( $currency ) {
1236
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
0 ignored issues
show
Bug introduced by
wpinv_format_amount($tax, NULL, ! $currency) of type string is incompatible with the type double expected by parameter $amount of wpinv_price(). ( Ignorable by Annotation )

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

1236
            $tax = wpinv_price( /** @scrutinizer ignore-type */ wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
Loading history...
1237
        }
1238
1239
        if ( ! $this->is_taxable() ) {
1240
            $tax = wpinv_round_amount( 0.00 );
1241
        }
1242
1243
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1244
    }
1245
    
1246
    public function get_fees( $type = 'all' ) {
1247
        $fees    = array();
1248
1249
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1250
            foreach ( $this->fees as $fee ) {
1251
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1252
                    continue;
1253
                }
1254
1255
                $fee['label'] = stripslashes( $fee['label'] );
1256
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1257
                $fees[]    = $fee;
1258
            }
1259
        }
1260
1261
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1262
    }
1263
    
1264
    public function get_fees_total( $type = 'all' ) {
1265
        $fees_total = (float) 0.00;
1266
1267
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1268
        if ( ! empty( $payment_fees ) ) {
1269
            foreach ( $payment_fees as $fee ) {
1270
                $fees_total += (float) $fee['amount'];
1271
            }
1272
        }
1273
1274
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1275
        /*
1276
        $fees = $this->get_fees( $type );
1277
1278
        $fees_total = 0;
1279
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1280
            foreach ( $fees as $fee_id => $fee ) {
1281
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1282
                    continue;
1283
                }
1284
1285
                $fees_total += $fee['amount'];
1286
            }
1287
        }
1288
1289
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1290
        */
1291
    }
1292
1293
    public function get_user_id() {
1294
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1295
    }
1296
    
1297
    public function get_first_name() {
1298
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1299
    }
1300
    
1301
    public function get_last_name() {
1302
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1303
    }
1304
    
1305
    public function get_user_full_name() {
1306
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1307
    }
1308
    
1309
    public function get_user_info() {
1310
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1311
    }
1312
    
1313
    public function get_email() {
1314
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1315
    }
1316
    
1317
    public function get_address() {
1318
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1319
    }
1320
    
1321
    public function get_phone() {
1322
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1323
    }
1324
    
1325
    public function get_number() {
1326
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1327
    }
1328
    
1329
    public function get_items() {
1330
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1331
    }
1332
    
1333
    public function get_key() {
1334
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1335
    }
1336
    
1337
    public function get_transaction_id() {
1338
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1339
    }
1340
    
1341
    public function get_gateway() {
1342
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1343
    }
1344
    
1345
    public function get_gateway_title() {
1346
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1347
        
1348
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1349
    }
1350
    
1351
    public function get_currency() {
1352
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1353
    }
1354
    
1355
    public function get_created_date() {
1356
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1357
    }
1358
    
1359
    public function get_due_date( $display = false ) {
1360
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1361
1362
        if ( ! $display ) {
1363
            return $due_date;
1364
        }
1365
        
1366
        return getpaid_format_date( $this->due_date );
1367
    }
1368
    
1369
    public function get_completed_date() {
1370
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1371
    }
1372
    
1373
    public function get_invoice_date( $formatted = true ) {
1374
        $date_completed = $this->completed_date;
1375
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1376
        
1377
        if ( $invoice_date == '' ) {
1378
            $date_created   = $this->date;
1379
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1380
        }
1381
        
1382
        if ( $formatted && $invoice_date ) {
1383
            $invoice_date   = getpaid_format_date( $invoice_date );
1384
        }
1385
1386
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1387
    }
1388
    
1389
    public function get_ip() {
1390
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1391
    }
1392
        
1393
    public function has_status( $status ) {
1394
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1395
    }
1396
    
1397
    public function add_item( $item_id = 0, $args = array() ) {
1398
        global $wpi_current_id, $wpi_item_id;
1399
        
1400
        $item = new WPInv_Item( $item_id );
1401
1402
        // Bail if this post isn't a item
1403
        if( !$item || $item->post_type !== 'wpi_item' ) {
0 ignored issues
show
introduced by
$item is of type WPInv_Item, thus it always evaluated to true.
Loading history...
Bug Best Practice introduced by
The property post_type does not exist on WPInv_Item. Since you implemented __get, consider adding a @property annotation.
Loading history...
1404
            return false;
1405
        }
1406
        
1407
        $has_quantities = wpinv_item_quantities_enabled();
1408
1409
        // Set some defaults
1410
        $defaults = array(
1411
            'quantity'      => 1,
1412
            'id'            => false,
1413
            'name'          => $item->get_name(),
1414
            'item_price'    => false,
1415
            'custom_price'  => '',
1416
            'discount'      => 0,
1417
            'tax'           => 0.00,
1418
            'meta'          => array(),
1419
            'fees'          => array()
1420
        );
1421
1422
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1423
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1424
1425
        $wpi_current_id         = $this->ID;
1426
        $wpi_item_id            = $item->ID;
1427
        $discounts              = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
1428
        
1429
        $_POST['wpinv_country'] = $this->country;
1430
        $_POST['wpinv_state']   = $this->state;
1431
        
1432
        $found_cart_key         = false;
1433
        
1434
        if ($has_quantities) {
1435
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1436
            
1437
            foreach ( $this->items as $key => $cart_item ) {
1438
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1439
                    continue;
1440
                }
1441
1442
                $this->items[ $key ]['quantity'] += $args['quantity'];
1443
                break;
1444
            }
1445
            
1446
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1447
                if ( $item_id != $cart_item['id'] ) {
1448
                    continue;
1449
                }
1450
1451
                $found_cart_key = $cart_key;
1452
                break;
1453
            }
1454
        }
1455
        
1456
        if ($has_quantities && $found_cart_key !== false) {
1457
            $cart_item          = $this->cart_details[$found_cart_key];
1458
            $item_price         = $cart_item['item_price'];
1459
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1460
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1461
            
1462
            $new_quantity       = $quantity + $args['quantity'];
1463
            $subtotal           = $item_price * $new_quantity;
1464
            
1465
            $args['quantity']   = $new_quantity;
1466
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1467
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1468
            
1469
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1470
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1471
            // The total increase equals the number removed * the item_price
1472
            $total_increased    = wpinv_round_amount( $item_price );
1473
            
1474
            if ( wpinv_prices_include_tax() ) {
1475
                $subtotal -= wpinv_round_amount( $tax );
1476
            }
1477
1478
            $total              = $subtotal - $discount + $tax;
1479
1480
            // Do not allow totals to go negative
1481
            if( $total < 0 ) {
1482
                $total = 0;
1483
            }
1484
            
1485
            $cart_item['quantity']  = $new_quantity;
1486
            $cart_item['subtotal']  = $subtotal;
1487
            $cart_item['discount']  = $discount;
1488
            $cart_item['tax']       = $tax;
1489
            $cart_item['price']     = $total;
1490
            
1491
            $subtotal               = $total_increased - $discount_increased;
1492
            $tax                    = $tax_increased;
1493
            
1494
            $this->cart_details[$found_cart_key] = $cart_item;
1495
        } else {
1496
            // Set custom price.
1497
            if ( $args['custom_price'] !== '' ) {
1498
                $item_price = $args['custom_price'];
1499
            } else {
1500
                // Allow overriding the price
1501
                if ( false !== $args['item_price'] ) {
1502
                    $item_price = $args['item_price'];
1503
                } else {
1504
                    $item_price = wpinv_get_item_price( $item->ID );
1505
                }
1506
            }
1507
1508
            // Sanitizing the price here so we don't have a dozen calls later
1509
            $item_price = wpinv_sanitize_amount( $item_price );
1510
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
1511
        
1512
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1513
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1514
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1515
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1516
1517
            // Setup the items meta item
1518
            $new_item = array(
1519
                'id'       => $item->ID,
1520
                'quantity' => $args['quantity'],
1521
            );
1522
1523
            $this->items[]  = $new_item;
1524
1525
            if ( wpinv_prices_include_tax() ) {
1526
                $subtotal -= wpinv_round_amount( $tax );
1527
            }
1528
1529
            $total      = $subtotal - $discount + $tax;
1530
1531
            // Do not allow totals to go negative
1532
            if( $total < 0 ) {
1533
                $total = 0;
1534
            }
1535
        
1536
            $this->cart_details[] = array(
1537
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
1538
                'id'            => $item->ID,
1539
                'item_price'    => wpinv_round_amount( $item_price ),
1540
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
1541
                'quantity'      => $args['quantity'],
1542
                'discount'      => $discount,
1543
                'subtotal'      => wpinv_round_amount( $subtotal ),
1544
                'tax'           => wpinv_round_amount( $tax ),
1545
                'price'         => wpinv_round_amount( $total ),
1546
                'vat_rate'      => $tax_rate,
1547
                'vat_class'     => $tax_class,
1548
                'meta'          => $args['meta'],
1549
                'fees'          => $args['fees'],
1550
            );
1551
   
1552
            $subtotal = $subtotal - $discount;
1553
        }
1554
        
1555
        $added_item = end( $this->cart_details );
1556
        $added_item['action']  = 'add';
1557
        
1558
        $this->pending['items'][] = $added_item;
1559
        
1560
        $this->increase_subtotal( $subtotal );
1561
        $this->increase_tax( $tax );
1562
1563
        return true;
1564
    }
1565
    
1566
    public function remove_item( $item_id, $args = array() ) {
1567
        // Set some defaults
1568
        $defaults = array(
1569
            'quantity'      => 1,
1570
            'item_price'    => false,
1571
            'custom_price'  => '',
1572
            'cart_index'    => false,
1573
        );
1574
        $args = wp_parse_args( $args, $defaults );
1575
1576
        // Bail if this post isn't a item
1577
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1578
            return false;
1579
        }
1580
        
1581
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1582
1583
        foreach ( $this->items as $key => $item ) {
1584
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1585
                continue;
1586
            }
1587
1588
            if ( false !== $args['cart_index'] ) {
1589
                $cart_index = absint( $args['cart_index'] );
1590
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1591
1592
                if ( ! empty( $cart_item ) ) {
1593
                    // If the cart index item isn't the same item ID, don't remove it
1594
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1595
                        continue;
1596
                    }
1597
                }
1598
            }
1599
1600
            $item_quantity = $this->items[ $key ]['quantity'];
1601
            if ( $item_quantity > $args['quantity'] ) {
1602
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1603
                break;
1604
            } else {
1605
                unset( $this->items[ $key ] );
1606
                break;
1607
            }
1608
        }
1609
1610
        $found_cart_key = false;
1611
        if ( false === $args['cart_index'] ) {
1612
            foreach ( $this->cart_details as $cart_key => $item ) {
1613
                if ( $item_id != $item['id'] ) {
1614
                    continue;
1615
                }
1616
1617
                if ( false !== $args['item_price'] ) {
1618
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1619
                        continue;
1620
                    }
1621
                }
1622
1623
                $found_cart_key = $cart_key;
1624
                break;
1625
            }
1626
        } else {
1627
            $cart_index = absint( $args['cart_index'] );
1628
1629
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1630
                return false; // Invalid cart index passed.
1631
            }
1632
1633
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1634
                return false; // We still need the proper Item ID to be sure.
1635
            }
1636
1637
            $found_cart_key = $cart_index;
1638
        }
1639
        
1640
        $cart_item  = $this->cart_details[$found_cart_key];
1641
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1642
        
1643
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1644
            //return false; // Invoice must contain at least one item.
1645
        }
1646
        
1647
        $discounts  = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
1648
        
1649
        if ( $quantity > $args['quantity'] ) {
1650
            $item_price         = $cart_item['item_price'];
1651
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1652
            
1653
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1654
            $subtotal           = $item_price * $new_quantity;
1655
            
1656
            $args['quantity']   = $new_quantity;
1657
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1658
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1659
            
1660
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
1661
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1662
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
1663
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1664
            
1665
            // The total increase equals the number removed * the item_price
1666
            $total_decrease     = wpinv_round_amount( $item_price );
1667
            
1668
            if ( wpinv_prices_include_tax() ) {
1669
                $subtotal -= wpinv_round_amount( $tax );
1670
            }
1671
1672
            $total              = $subtotal - $discount + $tax;
1673
1674
            // Do not allow totals to go negative
1675
            if( $total < 0 ) {
1676
                $total = 0;
1677
            }
1678
            
1679
            $cart_item['quantity']  = $new_quantity;
1680
            $cart_item['subtotal']  = $subtotal;
1681
            $cart_item['discount']  = $discount;
1682
            $cart_item['tax']       = $tax;
1683
            $cart_item['price']     = $total;
1684
            
1685
            $added_item             = $cart_item;
1686
            $added_item['id']       = $item_id;
1687
            $added_item['price']    = $total_decrease;
1688
            $added_item['quantity'] = $args['quantity'];
1689
            
1690
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1691
            
1692
            $this->cart_details[$found_cart_key] = $cart_item;
1693
            
1694
            $remove_item = end( $this->cart_details );
1695
        } else {
1696
            $item_price     = $cart_item['item_price'];
1697
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1698
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1699
        
1700
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1701
            $tax_decrease       = $tax;
1702
1703
            unset( $this->cart_details[$found_cart_key] );
1704
            
1705
            $remove_item             = $args;
1706
            $remove_item['id']       = $item_id;
1707
            $remove_item['price']    = $subtotal_decrease;
1708
            $remove_item['quantity'] = $args['quantity'];
1709
        }
1710
        
1711
        $remove_item['action']      = 'remove';
1712
        $this->pending['items'][]   = $remove_item;
1713
               
1714
        $this->decrease_subtotal( $subtotal_decrease );
1715
        $this->decrease_tax( $tax_decrease );
1716
        
1717
        return true;
1718
    }
1719
    
1720
    public function update_items($temp = false) {
1721
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1722
        
1723
        if ( !empty( $this->cart_details ) ) {
1724
            $wpi_nosave             = $temp;
1725
            $cart_subtotal          = 0;
1726
            $cart_discount          = 0;
1727
            $cart_tax               = 0;
1728
            $cart_details           = array();
1729
1730
            $_POST['wpinv_country'] = $this->country;
1731
            $_POST['wpinv_state']   = $this->state;
1732
1733
            foreach ( $this->cart_details as $key => $item ) {
1734
                $item_price = $item['item_price'];
1735
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1736
                $amount     = wpinv_round_amount( $item_price * $quantity );
0 ignored issues
show
Unused Code introduced by
The assignment to $amount is dead and can be removed.
Loading history...
1737
                $subtotal   = $item_price * $quantity;
1738
                
1739
                $wpi_current_id         = $this->ID;
1740
                $wpi_item_id            = $item['id'];
1741
                
1742
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_get_cart_item_discount_amount() has been deprecated. ( Ignorable by Annotation )

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

1742
                $discount   = /** @scrutinizer ignore-deprecated */ wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
Loading history...
Unused Code introduced by
The call to wpinv_get_cart_item_discount_amount() has too many arguments starting with $item. ( Ignorable by Annotation )

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

1742
                $discount   = /** @scrutinizer ignore-call */ wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1743
                
1744
                $tax_rate   = wpinv_get_tax_rate( $this->country, $this->state, $wpi_item_id );
1745
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1746
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1747
1748
                if ( ! $this->is_taxable() ) {
1749
                    $tax = 0;
1750
                }
1751
1752
                if ( wpinv_prices_include_tax() ) {
1753
                    $subtotal -= wpinv_round_amount( $tax );
1754
                }
1755
1756
                $total      = $subtotal - $discount + $tax;
1757
1758
                // Do not allow totals to go negative
1759
                if( $total < 0 ) {
1760
                    $total = 0;
1761
                }
1762
1763
                $cart_details[] = array(
1764
                    'id'          => $item['id'],
1765
                    'name'        => $item['name'],
1766
                    'item_price'  => wpinv_round_amount( $item_price ),
1767
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
1768
                    'quantity'    => $quantity,
1769
                    'discount'    => $discount,
1770
                    'subtotal'    => wpinv_round_amount( $subtotal ),
1771
                    'tax'         => wpinv_round_amount( $tax ),
1772
                    'price'       => wpinv_round_amount( $total ),
1773
                    'vat_rate'    => $tax_rate,
1774
                    'vat_class'   => $tax_class,
1775
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1776
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1777
                );
1778
1779
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1780
                $cart_discount  += (float)($discount);
1781
                $cart_tax       += (float)($tax);
1782
            }
1783
            if ( $cart_subtotal < 0 ) {
1784
                $cart_subtotal = 0;
1785
            }
1786
            if ( $cart_tax < 0 ) {
1787
                $cart_tax = 0;
1788
            }
1789
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
1790
            $this->tax      = wpinv_round_amount( $cart_tax );
1791
            $this->discount = wpinv_round_amount( $cart_discount );
1792
            
1793
            $this->recalculate_total();
1794
            
1795
            $this->cart_details = $cart_details;
1796
        }
1797
1798
        return $this;
1799
    }
1800
    
1801
    public function recalculate_totals($temp = false) {        
1802
        $this->update_items($temp);
1803
        $this->save( true );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Legacy_Invoice::save() has too many arguments starting with true. ( Ignorable by Annotation )

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

1803
        $this->/** @scrutinizer ignore-call */ 
1804
               save( true );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1804
        
1805
        return $this;
1806
    }
1807
    
1808
    public function needs_payment() {
1809
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'wpi-pending' ), $this );
1810
1811
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() || $this->is_initial_free() ) ) {
1812
            $needs_payment = true;
1813
        } else {
1814
            $needs_payment = false;
1815
        }
1816
1817
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1818
    }
1819
    
1820
    public function get_checkout_payment_url( $with_key = false, $secret = false ) {
1821
        $pay_url = wpinv_get_checkout_uri();
1822
1823
        if ( is_ssl() ) {
1824
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
1825
        }
1826
        
1827
        $key = $this->get_key();
1828
1829
        if ( $with_key ) {
1830
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
1831
        } else {
1832
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
1833
        }
1834
        
1835
        if ( $secret ) {
1836
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
1837
        }
1838
1839
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this, $with_key, $secret );
1840
    }
1841
    
1842
    public function get_view_url( $with_key = false ) {
1843
        $invoice_url = get_permalink( $this->ID );
1844
1845
        if ( $with_key ) {
1846
            $invoice_url = add_query_arg( 'invoice_key', $this->get_key(), $invoice_url );
1847
        }
1848
1849
        return apply_filters( 'wpinv_get_view_url', $invoice_url, $this, $with_key );
1850
    }
1851
    
1852
    public function generate_key( $string = '' ) {
1853
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1854
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
1855
    }
1856
    
1857
    public function is_recurring() {
1858
        if ( empty( $this->cart_details ) ) {
1859
            return false;
1860
        }
1861
        
1862
        $has_subscription = false;
1863
        foreach( $this->cart_details as $cart_item ) {
1864
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
1865
                $has_subscription = true;
1866
                break;
1867
            }
1868
        }
1869
        
1870
        if ( count( $this->cart_details ) > 1 ) {
1871
            $has_subscription = false;
1872
        }
1873
1874
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
1875
    }
1876
1877
    public function is_free_trial() {
1878
        $is_free_trial = false;
1879
        
1880
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
1881
            if ( !empty( $item ) && $item->has_free_trial() ) {
1882
                $is_free_trial = true;
1883
            }
1884
        }
1885
1886
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details, $this );
1887
    }
1888
1889
    public function is_initial_free() {
1890
        $is_initial_free = false;
1891
        
1892
        if ( ! ( (float)wpinv_round_amount( $this->get_total() ) > 0 ) && $this->is_parent() && $this->is_recurring() && ! $this->is_free_trial() && ! $this->is_free() ) {
1893
            $is_initial_free = true;
1894
        }
1895
1896
        return apply_filters( 'wpinv_invoice_is_initial_free', $is_initial_free, $this->cart_details );
1897
    }
1898
    
1899
    public function get_recurring( $object = false ) {
1900
        $item = NULL;
1901
        
1902
        if ( empty( $this->cart_details ) ) {
1903
            return $item;
1904
        }
1905
        
1906
        foreach( $this->cart_details as $cart_item ) {
1907
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
1908
                $item = $cart_item['id'];
1909
                break;
1910
            }
1911
        }
1912
        
1913
        if ( $object ) {
1914
            $item = $item ? new WPInv_Item( $item ) : NULL;
1915
            
1916
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
1917
        }
1918
1919
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
1920
    }
1921
1922
    public function get_subscription_name() {
1923
        $item = $this->get_recurring( true );
1924
1925
        if ( empty( $item ) ) {
1926
            return NULL;
1927
        }
1928
1929
        if ( !($name = $item->get_name()) ) {
1930
            $name = $item->post_name;
0 ignored issues
show
Bug Best Practice introduced by
The property post_name does not exist on WPInv_Item. Since you implemented __get, consider adding a @property annotation.
Loading history...
1931
        }
1932
1933
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
1934
    }
1935
1936
    public function get_subscription_id() {
1937
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
1938
1939
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
1940
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
1941
1942
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
1943
        }
1944
        
1945
        return $subscription_id;
1946
    }
1947
    
1948
    public function is_parent() {
1949
        $is_parent = empty( $this->parent_invoice ) ? true : false;
1950
1951
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
1952
    }
1953
    
1954
    public function is_renewal() {
1955
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
1956
1957
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
1958
    }
1959
    
1960
    public function get_parent_payment() {
1961
        $parent_payment = NULL;
1962
        
1963
        if ( $this->is_renewal() ) {
1964
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
1965
        }
1966
        
1967
        return $parent_payment;
1968
    }
1969
    
1970
    public function is_paid() {
1971
        $is_paid = $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) );
1972
1973
        return apply_filters( 'wpinv_invoice_is_paid', $is_paid, $this );
1974
    }
1975
1976
    /**
1977
     * Checks if this is a quote object.
1978
     * 
1979
     * @since 1.0.15
1980
     */
1981
    public function is_quote() {
1982
        return 'wpi_quote' === $this->post_type;
1983
    }
1984
    
1985
    public function is_refunded() {
1986
        $is_refunded = $this->has_status( array( 'wpi-refunded' ) );
1987
1988
        return apply_filters( 'wpinv_invoice_is_refunded', $is_refunded, $this );
1989
    }
1990
    
1991
    public function is_free() {
1992
        $is_free = false;
1993
        
1994
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
1995
            if ( $this->is_parent() && $this->is_recurring() ) {
1996
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
1997
            } else {
1998
                $is_free = true;
1999
            }
2000
        }
2001
        
2002
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
2003
    }
2004
    
2005
    public function has_vat() {
2006
        global $wpinv_euvat, $wpi_country;
2007
        
2008
        $requires_vat = false;
2009
        
2010
        if ( $this->country ) {
2011
            $wpi_country        = $this->country;
2012
            
2013
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2014
        }
2015
        
2016
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2017
    }
2018
    
2019
    public function refresh_item_ids() {
2020
        $item_ids = array();
2021
        
2022
        if ( !empty( $this->cart_details ) ) {
2023
            foreach ( $this->cart_details as $key => $item ) {
2024
                if ( !empty( $item['id'] ) ) {
2025
                    $item_ids[] = $item['id'];
2026
                }
2027
            }
2028
        }
2029
        
2030
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2031
        
2032
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2033
    }
2034
    
2035
    public function get_invoice_quote_type( $post_id ) {
2036
        if ( empty( $post_id ) ) {
2037
            return '';
2038
        }
2039
2040
        $type = get_post_type( $post_id );
2041
2042
        if ( 'wpi_invoice' === $type ) {
2043
            $post_type = __('Invoice', 'invoicing');
2044
        } else{
2045
            $post_type = __('Quote', 'invoicing');
2046
        }
2047
2048
        return apply_filters('get_invoice_type_label', $post_type, $post_id);
2049
    }
2050
}
2051