Passed
Push — master ( 25dd78...14f7c3 )
by Brian
15:22 queued 10:50
created

WPInv_Legacy_Invoice::remove_fee()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
rs 10
c 1
b 0
f 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
    public $ID  = 0;
16
    public $title;
17
    public $post_type;
18
    
19
    public $pending;
20
    public $items = array();
21
    public $user_info = array();
22
    public $payment_meta = array();
23
    
24
    public $new = false;
25
    public $number = '';
26
    public $mode = 'live';
27
    public $key = '';
28
    public $total = 0.00;
29
    public $subtotal = 0;
30
    public $disable_taxes = 0;
31
    public $tax = 0;
32
    public $fees = array();
33
    public $fees_total = 0;
34
    public $discounts = '';
35
    public $discount = 0;
36
    public $discount_code = 0;
37
    public $date = '';
38
    public $due_date = '';
39
    public $completed_date = '';
40
    public $status      = 'wpi-pending';
41
    public $post_status = 'wpi-pending';
42
    public $old_status = '';
43
    public $status_nicename = '';
44
    public $user_id = 0;
45
    public $first_name = '';
46
    public $last_name = '';
47
    public $email = '';
48
    public $phone = '';
49
    public $address = '';
50
    public $city = '';
51
    public $country = '';
52
    public $state = '';
53
    public $zip = '';
54
    public $transaction_id = '';
55
    public $ip = '';
56
    public $gateway = '';
57
    public $gateway_title = '';
58
    public $currency = '';
59
    public $cart_details = array();
60
    
61
    public $company = '';
62
    public $vat_number = '';
63
    public $vat_rate = '';
64
    public $adddress_confirmed = '';
65
    
66
    public $full_name = '';
67
    public $parent_invoice = 0;
68
    
69
    public function __construct( $invoice_id = false ) {
70
        if( empty( $invoice_id ) ) {
71
            return false;
72
        }
73
74
        $this->setup_invoice( $invoice_id );
75
    }
76
77
    public function get( $key ) {
78
        if ( method_exists( $this, 'get_' . $key ) ) {
79
            $value = call_user_func( array( $this, 'get_' . $key ) );
80
        } else {
81
            $value = $this->$key;
82
        }
83
84
        return $value;
85
    }
86
87
    public function set( $key, $value ) {
88
        $ignore = array( 'items', 'cart_details', 'fees', '_ID' );
89
90
        if ( $key === 'status' ) {
91
            $this->old_status = $this->status;
92
        }
93
94
        if ( ! in_array( $key, $ignore ) ) {
95
            $this->pending[ $key ] = $value;
96
        }
97
98
        if( '_ID' !== $key ) {
99
            $this->$key = $value;
100
        }
101
    }
102
103
    public function _isset( $name ) {
104
        if ( property_exists( $this, $name) ) {
105
            return false === empty( $this->$name );
106
        } else {
107
            return null;
108
        }
109
    }
110
111
    private function setup_invoice( $invoice_id ) {
112
        $this->pending = array();
113
114
        if ( empty( $invoice_id ) ) {
115
            return false;
116
        }
117
118
        $invoice = get_post( $invoice_id );
119
120
        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...
121
            return false;
122
        }
123
124
        if( !('wpi_invoice' == $invoice->post_type OR 'wpi_quote' == $invoice->post_type) ) {
125
            return false;
126
        }
127
128
        do_action( 'wpinv_pre_setup_invoice', $this, $invoice_id );
129
130
        // Primary Identifier
131
        $this->ID              = absint( $invoice_id );
132
        $this->post_type       = $invoice->post_type;
133
134
        // We have a payment, get the generic payment_meta item to reduce calls to it
135
        $this->payment_meta    = $this->get_meta();
136
        $this->date            = $invoice->post_date;
137
        $this->due_date        = $this->setup_due_date();
138
        $this->completed_date  = $this->setup_completed_date();
139
        $this->status          = $invoice->post_status;
140
141
        if ( 'future' == $this->status ) {
142
            $this->status = 'publish';
143
        }
144
145
        $this->post_status     = $this->status;
146
        $this->mode            = $this->setup_mode();
147
        $this->parent_invoice  = $invoice->post_parent;
148
        $this->post_name       = $this->setup_post_name( $invoice );
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...
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...
149
        $this->status_nicename = $this->setup_status_nicename($invoice->post_status);
150
151
        // Items
152
        $this->fees            = $this->setup_fees();
153
        $this->cart_details    = $this->setup_cart_details();
154
        $this->items           = $this->setup_items();
155
156
        // Currency Based
157
        $this->total           = $this->setup_total();
158
        $this->disable_taxes   = $this->setup_is_taxable();
159
        $this->tax             = $this->setup_tax();
160
        $this->fees_total      = $this->get_fees_total();
161
        $this->subtotal        = $this->setup_subtotal();
162
        $this->currency        = $this->setup_currency();
163
        
164
        // Gateway based
165
        $this->gateway         = $this->setup_gateway();
166
        $this->gateway_title   = $this->setup_gateway_title();
167
        $this->transaction_id  = $this->setup_transaction_id();
168
        
169
        // User based
170
        $this->ip              = $this->setup_ip();
171
        $this->user_id         = !empty( $invoice->post_author ) ? $invoice->post_author : get_current_user_id();///$this->setup_user_id();
172
        $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

172
        $this->email           = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
173
174
        $this->user_info       = $this->setup_user_info();
175
176
        $this->first_name      = $this->user_info['first_name'];
177
        $this->last_name       = $this->user_info['last_name'];
178
        $this->company         = $this->user_info['company'];
179
        $this->vat_number      = $this->user_info['vat_number'];
180
        $this->vat_rate        = $this->user_info['vat_rate'];
181
        $this->adddress_confirmed  = $this->user_info['adddress_confirmed'];
182
        $this->address         = $this->user_info['address'];
183
        $this->city            = $this->user_info['city'];
184
        $this->country         = $this->user_info['country'];
185
        $this->state           = $this->user_info['state'];
186
        $this->zip             = $this->user_info['zip'];
187
        $this->phone           = $this->user_info['phone'];
188
        
189
        $this->discounts       = $this->user_info['discount'];
190
            $this->discount        = $this->setup_discount();
191
            $this->discount_code   = $this->setup_discount_code();
192
193
        // Other Identifiers
194
        $this->key             = $this->setup_invoice_key();
195
        $this->number          = $this->setup_invoice_number();
196
        $this->title           = !empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
197
        
198
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
199
        
200
        // Allow extensions to add items to this object via hook
201
        do_action( 'wpinv_setup_invoice', $this, $invoice_id );
202
203
        return true;
204
    }
205
206
    private function setup_status_nicename( $status ) {
207
        $all_invoice_statuses  = wpinv_get_invoice_statuses( true, true, $this );
208
209
        if ( $this->is_quote() && class_exists( 'Wpinv_Quotes_Shared' ) ) {
210
            $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...
211
        }
212
        $status   = isset( $all_invoice_statuses[$status] ) ? $all_invoice_statuses[$status] : __( $status, 'invoicing' );
213
214
        return apply_filters( 'setup_status_nicename', $status );
215
    }
216
217
    private function setup_post_name( $post = NULL ) {
218
        global $wpdb;
219
        
220
        $post_name = '';
221
        
222
        if ( !empty( $post ) ) {
223
            if( !empty( $post->post_name ) ) {
224
                $post_name = $post->post_name;
225
            } else if ( !empty( $post->ID ) ) {
226
                $post_name = wpinv_generate_post_name( $post->ID );
227
228
                $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
229
            }
230
        }
231
232
        $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...
233
    }
234
    
235
    private function setup_due_date() {
236
        $due_date = $this->get_meta( '_wpinv_due_date' );
237
        
238
        if ( empty( $due_date ) ) {
239
            $overdue_time = strtotime( $this->date ) + ( DAY_IN_SECONDS * absint( wpinv_get_option( 'overdue_days' ) ) );
240
            $due_date = date_i18n( 'Y-m-d', $overdue_time );
241
        } else if ( $due_date == 'none' ) {
242
            $due_date = '';
243
        }
244
        
245
        return $due_date;
246
    }
247
    
248
    private function setup_completed_date() {
249
        $invoice = get_post( $this->ID );
250
251
        if ( 'wpi-pending' == $invoice->post_status || 'preapproved' == $invoice->post_status ) {
252
            return false; // This invoice was never paid
253
        }
254
255
        $date = ( $date = $this->get_meta( '_wpinv_completed_date', true ) ) ? $date : $invoice->modified_date;
256
257
        return $date;
258
    }
259
    
260
    private function setup_cart_details() {
261
        $cart_details = isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
262
        return $cart_details;
263
    }
264
    
265
    public function array_convert() {
266
        return get_object_vars( $this );
267
    }
268
    
269
    private function setup_items() {
270
        $items = isset( $this->payment_meta['items'] ) ? maybe_unserialize( $this->payment_meta['items'] ) : array();
271
        return $items;
272
    }
273
    
274
    private function setup_fees() {
275
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
276
        return $payment_fees;
277
    }
278
        
279
    private function setup_currency() {
280
        $currency = isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'wpinv_currency_default', wpinv_get_currency(), $this );
281
        return $currency;
282
    }
283
    
284
    private function setup_discount() {
285
        //$discount = $this->get_meta( '_wpinv_discount', true );
286
        $discount = (float)$this->subtotal - ( (float)$this->total - (float)$this->tax - (float)$this->fees_total );
287
        if ( $discount < 0 ) {
288
            $discount = 0;
289
        }
290
        $discount = wpinv_round_amount( $discount );
291
        
292
        return $discount;
293
    }
294
    
295
    private function setup_discount_code() {
296
        $discount_code = !empty( $this->discounts ) ? $this->discounts : $this->get_meta( '_wpinv_discount_code', true );
297
        return $discount_code;
298
    }
299
    
300
    private function setup_tax() {
301
302
        $tax = $this->get_meta( '_wpinv_tax', true );
303
304
        // We don't have tax as it's own meta and no meta was passed
305
        if ( '' === $tax ) {            
306
            $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
307
        }
308
        
309
        if ( $tax < 0 || ! $this->is_taxable() ) {
310
            $tax = 0;
311
        }
312
313
        return $tax;
314
    }
315
316
    /**
317
     * If taxes are enabled, allow users to enable/disable taxes per invoice.
318
     */
319
    private function setup_is_taxable() {
320
        return (int) $this->get_meta( '_wpinv_disable_taxes', true );
321
    }
322
323
    private function setup_subtotal() {
324
        $subtotal     = 0;
325
        $cart_details = $this->cart_details;
326
327
        if ( is_array( $cart_details ) ) {
0 ignored issues
show
introduced by
The condition is_array($cart_details) is always true.
Loading history...
328
            foreach ( $cart_details as $item ) {
329
                if ( isset( $item['subtotal'] ) ) {
330
                    $subtotal += $item['subtotal'];
331
                }
332
            }
333
        } else {
334
            $subtotal  = $this->total;
335
            $tax       = wpinv_use_taxes() ? $this->tax : 0;
336
            $subtotal -= $tax;
337
        }
338
339
        return $subtotal;
340
    }
341
342
    private function setup_discounts() {
343
        $discounts = ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
344
        return $discounts;
345
    }
346
    
347
    private function setup_total() {
348
        $amount = $this->get_meta( '_wpinv_total', true );
349
350
        if ( empty( $amount ) && '0.00' != $amount ) {
351
            $meta   = $this->get_meta( '_wpinv_payment_meta', true );
352
            $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

352
            $meta   = maybe_unserialize( /** @scrutinizer ignore-type */ $meta );
Loading history...
353
354
            if ( isset( $meta['amount'] ) ) {
355
                $amount = $meta['amount'];
356
            }
357
        }
358
359
        if($amount < 0){
360
            $amount = 0;
361
        }
362
363
        return $amount;
364
    }
365
    
366
    private function setup_mode() {
367
        return $this->get_meta( '_wpinv_mode' );
368
    }
369
370
    private function setup_gateway() {
371
        $gateway = $this->get_meta( '_wpinv_gateway' );
372
        
373
        if ( empty( $gateway ) && 'publish' === $this->status ) {
374
            $gateway = 'manual';
375
        }
376
        
377
        return $gateway;
378
    }
379
380
    private function setup_gateway_title() {
381
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
382
        return $gateway_title;
383
    }
384
385
    private function setup_transaction_id() {
386
        $transaction_id = $this->get_meta( '_wpinv_transaction_id' );
387
388
        if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
389
            $gateway        = $this->gateway;
390
            $transaction_id = apply_filters( 'wpinv_get_invoice_transaction_id-' . $gateway, $this->ID );
391
        }
392
393
        return $transaction_id;
394
    }
395
396
    private function setup_ip() {
397
        $ip = $this->get_meta( '_wpinv_user_ip' );
398
        return $ip;
399
    }
400
401
    ///private function setup_user_id() {
402
        ///$user_id = $this->get_meta( '_wpinv_user_id' );
403
        ///return $user_id;
404
    ///}
405
        
406
    private function setup_first_name() {
407
        $first_name = $this->get_meta( '_wpinv_first_name' );
408
        return $first_name;
409
    }
410
    
411
    private function setup_last_name() {
412
        $last_name = $this->get_meta( '_wpinv_last_name' );
413
        return $last_name;
414
    }
415
    
416
    private function setup_company() {
417
        $company = $this->get_meta( '_wpinv_company' );
418
        return $company;
419
    }
420
    
421
    private function setup_vat_number() {
422
        $vat_number = $this->get_meta( '_wpinv_vat_number' );
423
        return $vat_number;
424
    }
425
    
426
    private function setup_vat_rate() {
427
        $vat_rate = $this->get_meta( '_wpinv_vat_rate' );
428
        return $vat_rate;
429
    }
430
    
431
    private function setup_adddress_confirmed() {
432
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed' );
433
        return $adddress_confirmed;
434
    }
435
    
436
    private function setup_phone() {
437
        $phone = $this->get_meta( '_wpinv_phone' );
438
        return $phone;
439
    }
440
    
441
    private function setup_address() {
442
        $address = $this->get_meta( '_wpinv_address', true );
443
        return $address;
444
    }
445
    
446
    private function setup_city() {
447
        $city = $this->get_meta( '_wpinv_city', true );
448
        return $city;
449
    }
450
    
451
    private function setup_country() {
452
        $country = $this->get_meta( '_wpinv_country', true );
453
        return $country;
454
    }
455
    
456
    private function setup_state() {
457
        $state = $this->get_meta( '_wpinv_state', true );
458
        return $state;
459
    }
460
    
461
    private function setup_zip() {
462
        $zip = $this->get_meta( '_wpinv_zip', true );
463
        return $zip;
464
    }
465
466
    private function setup_user_info() {
467
        $defaults = array(
468
            'user_id'        => $this->user_id,
469
            'first_name'     => $this->first_name,
470
            'last_name'      => $this->last_name,
471
            'email'          => get_the_author_meta( 'email', $this->user_id ),
472
            'phone'          => $this->phone,
473
            'address'        => $this->address,
474
            'city'           => $this->city,
475
            'country'        => $this->country,
476
            'state'          => $this->state,
477
            'zip'            => $this->zip,
478
            'company'        => $this->company,
479
            'vat_number'     => $this->vat_number,
480
            'vat_rate'       => $this->vat_rate,
481
            'adddress_confirmed' => $this->adddress_confirmed,
482
            'discount'       => $this->discounts,
483
        );
484
        
485
        $user_info = array();
486
        if ( isset( $this->payment_meta['user_info'] ) ) {
487
            $user_info = maybe_unserialize( $this->payment_meta['user_info'] );
488
            
489
            if ( !empty( $user_info ) && isset( $user_info['user_id'] ) && $post = get_post( $this->ID ) ) {
490
                $this->user_id = $post->post_author;
491
                $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

491
                $this->email = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
492
                
493
                $user_info['user_id'] = $this->user_id;
494
                $user_info['email'] = $this->email;
495
                $this->payment_meta['user_id'] = $this->user_id;
496
                $this->payment_meta['email'] = $this->email;
497
            }
498
        }
499
        
500
        $user_info    = wp_parse_args( $user_info, $defaults );
501
        
502
        // Get the user, but only if it's been created
503
        $user = get_userdata( $this->user_id );
504
        
505
        if ( !empty( $user ) && $user->ID > 0 ) {
506
            if ( empty( $user_info ) ) {
507
                $user_info = array(
508
                    'user_id'    => $user->ID,
509
                    'first_name' => $user->first_name,
510
                    'last_name'  => $user->last_name,
511
                    'email'      => $user->user_email,
512
                    'discount'   => '',
513
                );
514
            } else {
515
                foreach ( $user_info as $key => $value ) {
516
                    if ( ! empty( $value ) ) {
517
                        continue;
518
                    }
519
520
                    switch( $key ) {
521
                        case 'user_id':
522
                            $user_info[ $key ] = $user->ID;
523
                            break;
524
                        case 'first_name':
525
                            $user_info[ $key ] = $user->first_name;
526
                            break;
527
                        case 'last_name':
528
                            $user_info[ $key ] = $user->last_name;
529
                            break;
530
                        case 'email':
531
                            $user_info[ $key ] = $user->user_email;
532
                            break;
533
                    }
534
                }
535
            }
536
        }
537
538
        return $user_info;
539
    }
540
541
    private function setup_invoice_key() {
542
        $key = $this->get_meta( '_wpinv_key', true );
543
        
544
        return $key;
545
    }
546
547
    private function setup_invoice_number() {
548
        $number = $this->get_meta( '_wpinv_number', true );
549
550
        if ( !$number ) {
551
            $number = $this->ID;
552
553
            if ( $this->status == 'auto-draft' ) {
554
                if ( wpinv_sequential_number_active( $this->post_type ) ) {
555
                    $next_number = wpinv_get_next_invoice_number( $this->post_type );
556
                    $number      = $next_number;
557
                }
558
            }
559
            
560
            $number = wpinv_format_invoice_number( $number, $this->post_type );
561
        }
562
563
        return $number;
564
    }
565
    
566
    private function insert_invoice() {
567
        global $wpdb;
568
569
        if ( empty( $this->post_type ) ) {
570
            if ( !empty( $this->ID ) && $post_type = get_post_type( $this->ID ) ) {
571
                $this->post_type = $post_type;
572
            } else if ( !empty( $this->parent_invoice ) && $post_type = get_post_type( $this->parent_invoice ) ) {
573
                $this->post_type = $post_type;
574
            } else {
575
                $this->post_type = 'wpi_invoice';
576
            }
577
        }
578
579
        $invoice_number = $this->ID;
580
        if ( $number = $this->get_meta( '_wpinv_number', true ) ) {
581
            $invoice_number = $number;
582
        }
583
584
        if ( empty( $this->key ) ) {
585
            $this->key = self::generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_Legacy_Invoice::generate_key() is not static, but was called statically. ( Ignorable by Annotation )

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

585
            /** @scrutinizer ignore-call */ 
586
            $this->key = self::generate_key();
Loading history...
586
            $this->pending['key'] = $this->key;
587
        }
588
589
        if ( empty( $this->ip ) ) {
590
            $this->ip = wpinv_get_ip();
591
            $this->pending['ip'] = $this->ip;
592
        }
593
        
594
        $payment_data = array(
595
            'price'        => $this->total,
596
            'date'         => $this->date,
597
            'user_email'   => $this->email,
598
            'invoice_key'  => $this->key,
599
            'currency'     => $this->currency,
600
            'items'        => $this->items,
601
            'user_info' => array(
602
                'user_id'    => $this->user_id,
603
                'email'      => $this->email,
604
                'first_name' => $this->first_name,
605
                'last_name'  => $this->last_name,
606
                'address'    => $this->address,
607
                'phone'      => $this->phone,
608
                'city'       => $this->city,
609
                'country'    => $this->country,
610
                'state'      => $this->state,
611
                'zip'        => $this->zip,
612
                'company'    => $this->company,
613
                'vat_number' => $this->vat_number,
614
                'discount'   => $this->discounts,
615
            ),
616
            'cart_details' => $this->cart_details,
617
            'status'       => $this->status,
618
            'fees'         => $this->fees,
619
        );
620
621
        $post_data = array(
622
                        'post_title'    => $invoice_number,
623
                        'post_status'   => $this->status,
624
                        'post_author'   => $this->user_id,
625
                        'post_type'     => $this->post_type,
626
                        'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
627
                        'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
628
                        'post_parent'   => $this->parent_invoice,
629
                    );
630
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
631
632
        // Create a blank invoice
633
        if ( !empty( $this->ID ) ) {
634
            $args['ID']         = $this->ID;
635
636
            $invoice_id = wp_update_post( $args, true );
637
        } else {
638
            $invoice_id = wp_insert_post( $args, true );
639
        }
640
641
        if ( is_wp_error( $invoice_id ) ) {
642
            return false;
643
        }
644
645
        if ( !empty( $invoice_id ) ) {
646
            $this->ID  = $invoice_id;
647
            $this->_ID = $invoice_id;
0 ignored issues
show
Bug Best Practice introduced by
The property _ID does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
648
649
            $this->payment_meta = apply_filters( 'wpinv_payment_meta', $this->payment_meta, $payment_data );
650
            if ( ! empty( $this->payment_meta['fees'] ) ) {
651
                $this->fees = array_merge( $this->fees, $this->payment_meta['fees'] );
652
                foreach( $this->fees as $fee ) {
653
                    $this->increase_fees( $fee['amount'] );
654
                }
655
            }
656
657
            $this->update_meta( '_wpinv_payment_meta', $this->payment_meta );    
658
            $this->new = true;
659
        }
660
661
        return $this->ID;
662
    }
663
664
    public function save( $setup = false ) {
665
        global $wpi_session;
666
        
667
        $saved = false;
668
        if ( empty( $this->items ) ) {
669
            return $saved; // Don't save empty invoice.
670
        }
671
        
672
        if ( empty( $this->key ) ) {
673
            $this->key = self::generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_Legacy_Invoice::generate_key() is not static, but was called statically. ( Ignorable by Annotation )

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

673
            /** @scrutinizer ignore-call */ 
674
            $this->key = self::generate_key();
Loading history...
674
            $this->pending['key'] = $this->key;
675
        }
676
        
677
        if ( empty( $this->ID ) ) {
678
            $invoice_id = $this->insert_invoice();
679
680
            if ( false === $invoice_id ) {
681
                $saved = false;
682
            } else {
683
                $this->ID = $invoice_id;
684
            }
685
        }
686
687
        // If we have something pending, let's save it
688
        if ( !empty( $this->pending ) ) {
689
            $total_increase = 0;
690
            $total_decrease = 0;
691
692
            foreach ( $this->pending as $key => $value ) {
693
                switch( $key ) {
694
                    case 'items':
695
                        // Update totals for pending items
696
                        foreach ( $this->pending[ $key ] as $item ) {
697
                            switch( $item['action'] ) {
698
                                case 'add':
699
                                    $price = $item['price'];
700
                                    $taxes = $item['tax'];
0 ignored issues
show
Unused Code introduced by
The assignment to $taxes is dead and can be removed.
Loading history...
701
702
                                    if ( 'publish' === $this->status ) {
703
                                        $total_increase += $price;
704
                                    }
705
                                    break;
706
707
                                case 'remove':
708
                                    if ( 'publish' === $this->status ) {
709
                                        $total_decrease += $item['price'];
710
                                    }
711
                                    break;
712
                            }
713
                        }
714
                        break;
715
                    case 'fees':
716
                        if ( 'publish' !== $this->status ) {
717
                            break;
718
                        }
719
720
                        if ( empty( $this->pending[ $key ] ) ) {
721
                            break;
722
                        }
723
724
                        foreach ( $this->pending[ $key ] as $fee ) {
725
                            switch( $fee['action'] ) {
726
                                case 'add':
727
                                    $total_increase += $fee['amount'];
728
                                    break;
729
730
                                case 'remove':
731
                                    $total_decrease += $fee['amount'];
732
                                    break;
733
                            }
734
                        }
735
                        break;
736
                    case 'status':
737
                        $this->update_status( $this->status );
738
                        break;
739
                    case 'gateway':
740
                        $this->update_meta( '_wpinv_gateway', $this->gateway );
741
                        break;
742
                    case 'mode':
743
                        $this->update_meta( '_wpinv_mode', $this->mode );
744
                        break;
745
                    case 'transaction_id':
746
                        $this->update_meta( '_wpinv_transaction_id', $this->transaction_id );
747
                        break;
748
                    case 'ip':
749
                        $this->update_meta( '_wpinv_user_ip', $this->ip );
750
                        break;
751
                    ///case 'user_id':
752
                        ///$this->update_meta( '_wpinv_user_id', $this->user_id );
753
                        ///$this->user_info['user_id'] = $this->user_id;
754
                        ///break;
755
                    case 'first_name':
756
                        $this->update_meta( '_wpinv_first_name', $this->first_name );
757
                        $this->user_info['first_name'] = $this->first_name;
758
                        break;
759
                    case 'last_name':
760
                        $this->update_meta( '_wpinv_last_name', $this->last_name );
761
                        $this->user_info['last_name'] = $this->last_name;
762
                        break;
763
                    case 'phone':
764
                        $this->update_meta( '_wpinv_phone', $this->phone );
765
                        $this->user_info['phone'] = $this->phone;
766
                        break;
767
                    case 'address':
768
                        $this->update_meta( '_wpinv_address', $this->address );
769
                        $this->user_info['address'] = $this->address;
770
                        break;
771
                    case 'city':
772
                        $this->update_meta( '_wpinv_city', $this->city );
773
                        $this->user_info['city'] = $this->city;
774
                        break;
775
                    case 'country':
776
                        $this->update_meta( '_wpinv_country', $this->country );
777
                        $this->user_info['country'] = $this->country;
778
                        break;
779
                    case 'state':
780
                        $this->update_meta( '_wpinv_state', $this->state );
781
                        $this->user_info['state'] = $this->state;
782
                        break;
783
                    case 'zip':
784
                        $this->update_meta( '_wpinv_zip', $this->zip );
785
                        $this->user_info['zip'] = $this->zip;
786
                        break;
787
                    case 'company':
788
                        $this->update_meta( '_wpinv_company', $this->company );
789
                        $this->user_info['company'] = $this->company;
790
                        break;
791
                    case 'vat_number':
792
                        $this->update_meta( '_wpinv_vat_number', $this->vat_number );
793
                        $this->user_info['vat_number'] = $this->vat_number;
794
                        
795
                        $vat_info = $wpi_session->get( 'user_vat_data' );
796
                        if ( $this->vat_number && !empty( $vat_info ) && isset( $vat_info['number'] ) && isset( $vat_info['valid'] ) && $vat_info['number'] == $this->vat_number ) {
797
                            $adddress_confirmed = isset( $vat_info['adddress_confirmed'] ) ? $vat_info['adddress_confirmed'] : false;
798
                            $this->update_meta( '_wpinv_adddress_confirmed', (bool)$adddress_confirmed );
799
                            $this->user_info['adddress_confirmed'] = (bool)$adddress_confirmed;
800
                        }
801
    
802
                        break;
803
                    case 'vat_rate':
804
                        $this->update_meta( '_wpinv_vat_rate', $this->vat_rate );
805
                        $this->user_info['vat_rate'] = $this->vat_rate;
806
                        break;
807
                    case 'adddress_confirmed':
808
                        $this->update_meta( '_wpinv_adddress_confirmed', $this->adddress_confirmed );
809
                        $this->user_info['adddress_confirmed'] = $this->adddress_confirmed;
810
                        break;
811
                    
812
                    case 'key':
813
                        $this->update_meta( '_wpinv_key', $this->key );
814
                        break;
815
                    case 'disable_taxes':
816
                        $this->update_meta( '_wpinv_disable_taxes', $this->disable_taxes );
817
                        break;
818
                    case 'date':
819
                        $args = array(
820
                            'ID'        => $this->ID,
821
                            'post_date' => $this->date,
822
                            'edit_date' => true,
823
                        );
824
825
                        wp_update_post( $args );
826
                        break;
827
                    case 'due_date':
828
                        if ( empty( $this->due_date ) ) {
829
                            $this->due_date = 'none';
830
                        }
831
                        
832
                        $this->update_meta( '_wpinv_due_date', $this->due_date );
833
                        break;
834
                    case 'completed_date':
835
                        $this->update_meta( '_wpinv_completed_date', $this->completed_date );
836
                        break;
837
                    case 'discounts':
838
                        if ( ! is_array( $this->discounts ) ) {
839
                            $this->discounts = explode( ',', $this->discounts );
840
                        }
841
842
                        $this->user_info['discount'] = implode( ',', $this->discounts );
843
                        break;
844
                    case 'discount':
845
                        $this->update_meta( '_wpinv_discount', wpinv_round_amount( $this->discount ) );
846
                        break;
847
                    case 'discount_code':
848
                        $this->update_meta( '_wpinv_discount_code', $this->discount_code );
849
                        break;
850
                    case 'parent_invoice':
851
                        $args = array(
852
                            'ID'          => $this->ID,
853
                            'post_parent' => $this->parent_invoice,
854
                        );
855
                        wp_update_post( $args );
856
                        break;
857
                    default:
858
                        do_action( 'wpinv_save', $this, $key );
859
                        break;
860
                }
861
            }
862
863
            $this->update_meta( '_wpinv_subtotal', wpinv_round_amount( $this->subtotal ) );
864
            $this->update_meta( '_wpinv_total', wpinv_round_amount( $this->total ) );
865
            $this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
866
            
867
            $this->items    = array_values( $this->items );
868
            
869
            $new_meta = array(
870
                'items'         => $this->items,
871
                'cart_details'  => $this->cart_details,
872
                'fees'          => $this->fees,
873
                'currency'      => $this->currency,
874
                'user_info'     => $this->user_info,
875
            );
876
            
877
            $meta        = $this->get_meta();
878
            $merged_meta = array_merge( $meta, $new_meta );
879
880
            // Only save the payment meta if it's changed
881
            if ( md5( serialize( $meta ) ) !== md5( serialize( $merged_meta) ) ) {
882
                $updated     = $this->update_meta( '_wpinv_payment_meta', $merged_meta );
883
                if ( false !== $updated ) {
884
                    $saved = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $saved is dead and can be removed.
Loading history...
885
                }
886
            }
887
888
            $this->pending = array();
889
            $saved         = true;
890
        } else {
891
            $this->update_meta( '_wpinv_subtotal', wpinv_round_amount( $this->subtotal ) );
892
            $this->update_meta( '_wpinv_total', wpinv_round_amount( $this->total ) );
893
            $this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
894
        }
895
        
896
        do_action( 'wpinv_invoice_save', $this, $saved );
897
898
        if ( true === $saved || $setup ) {
899
            $this->setup_invoice( $this->ID );
900
        }
901
        
902
        $this->refresh_item_ids();
903
        
904
        return $saved;
905
    }
906
    
907
    public function add_fee( $args, $global = true ) {
908
        $default_args = array(
909
            'label'       => '',
910
            'amount'      => 0,
911
            'type'        => 'fee',
912
            'id'          => '',
913
            'no_tax'      => false,
914
            'item_id'     => 0,
915
        );
916
917
        $fee = wp_parse_args( $args, $default_args );
918
        
919
        if ( empty( $fee['label'] ) ) {
920
            return false;
921
        }
922
        
923
        $fee['id']  = sanitize_title( $fee['label'] );
924
        
925
        $this->fees[]               = $fee;
926
        
927
        $added_fee               = $fee;
928
        $added_fee['action']     = 'add';
929
        $this->pending['fees'][] = $added_fee;
930
        reset( $this->fees );
931
932
        $this->increase_fees( $fee['amount'] );
933
        return true;
934
    }
935
936
    public function remove_fee( $key ) {
937
        $removed = false;
938
939
        if ( is_numeric( $key ) ) {
940
            $removed = $this->remove_fee_by( 'index', $key );
941
        }
942
943
        return $removed;
944
    }
945
946
    public function remove_fee_by( $key, $value, $global = false ) {
947
        $allowed_fee_keys = apply_filters( 'wpinv_fee_keys', array(
948
            'index', 'label', 'amount', 'type',
949
        ) );
950
951
        if ( ! in_array( $key, $allowed_fee_keys ) ) {
952
            return false;
953
        }
954
955
        $removed = false;
956
        if ( 'index' === $key && array_key_exists( $value, $this->fees ) ) {
957
            $removed_fee             = $this->fees[ $value ];
958
            $removed_fee['action']   = 'remove';
959
            $this->pending['fees'][] = $removed_fee;
960
961
            $this->decrease_fees( $removed_fee['amount'] );
962
963
            unset( $this->fees[ $value ] );
964
            $removed = true;
965
        } else if ( 'index' !== $key ) {
966
            foreach ( $this->fees as $index => $fee ) {
967
                if ( isset( $fee[ $key ] ) && $fee[ $key ] == $value ) {
968
                    $removed_fee             = $fee;
969
                    $removed_fee['action']   = 'remove';
970
                    $this->pending['fees'][] = $removed_fee;
971
972
                    $this->decrease_fees( $removed_fee['amount'] );
973
974
                    unset( $this->fees[ $index ] );
975
                    $removed = true;
976
977
                    if ( false === $global ) {
978
                        break;
979
                    }
980
                }
981
            }
982
        }
983
984
        if ( true === $removed ) {
985
            $this->fees = array_values( $this->fees );
986
        }
987
988
        return $removed;
989
    }
990
991
    
992
993
    public function add_note( $note = '', $customer_type = false, $added_by_user = false, $system = false ) {
994
        // Bail if no note specified
995
        if( !$note ) {
996
            return false;
997
        }
998
999
        if ( empty( $this->ID ) )
1000
            return false;
1001
        
1002
        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...
1003
            $user                 = get_user_by( 'id', get_current_user_id() );
1004
            $comment_author       = $user->display_name;
1005
            $comment_author_email = $user->user_email;
1006
        } else {
1007
            $comment_author       = 'System';
1008
            $comment_author_email = 'system@';
1009
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
1010
            $comment_author_email = sanitize_email( $comment_author_email );
1011
        }
1012
1013
        do_action( 'wpinv_pre_insert_invoice_note', $this->ID, $note, $customer_type );
1014
1015
        $note_id = wp_insert_comment( wp_filter_comment( array(
1016
            'comment_post_ID'      => $this->ID,
1017
            'comment_content'      => $note,
1018
            'comment_agent'        => 'WPInvoicing',
1019
            'user_id'              => is_admin() ? get_current_user_id() : 0,
1020
            'comment_date'         => current_time( 'mysql' ),
1021
            'comment_date_gmt'     => current_time( 'mysql', 1 ),
1022
            'comment_approved'     => 1,
1023
            'comment_parent'       => 0,
1024
            'comment_author'       => $comment_author,
1025
            'comment_author_IP'    => wpinv_get_ip(),
1026
            'comment_author_url'   => '',
1027
            'comment_author_email' => $comment_author_email,
1028
            'comment_type'         => 'wpinv_note'
1029
        ) ) );
1030
1031
        do_action( 'wpinv_insert_payment_note', $note_id, $this->ID, $note );
1032
        
1033
        if ( $customer_type ) {
1034
            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

1034
            add_comment_meta( /** @scrutinizer ignore-type */ $note_id, '_wpi_customer_note', 1 );
Loading history...
1035
1036
            do_action( 'wpinv_new_customer_note', array( 'invoice_id' => $this->ID, 'user_note' => $note ) );
1037
        }
1038
1039
        return $note_id;
1040
    }
1041
1042
    private function increase_subtotal( $amount = 0.00 ) {
1043
        $amount          = (float) $amount;
1044
        $this->subtotal += $amount;
1045
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
1046
1047
        $this->recalculate_total();
1048
    }
1049
1050
    private function decrease_subtotal( $amount = 0.00 ) {
1051
        $amount          = (float) $amount;
1052
        $this->subtotal -= $amount;
1053
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
1054
1055
        if ( $this->subtotal < 0 ) {
1056
            $this->subtotal = 0;
1057
        }
1058
1059
        $this->recalculate_total();
1060
    }
1061
1062
    private function increase_fees( $amount = 0.00 ) {
1063
        $amount            = (float)$amount;
1064
        $this->fees_total += $amount;
1065
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
1066
1067
        $this->recalculate_total();
1068
    }
1069
1070
    private function decrease_fees( $amount = 0.00 ) {
1071
        $amount            = (float) $amount;
1072
        $this->fees_total -= $amount;
1073
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
1074
1075
        if ( $this->fees_total < 0 ) {
1076
            $this->fees_total = 0;
1077
        }
1078
1079
        $this->recalculate_total();
1080
    }
1081
1082
    public function recalculate_total() {
1083
        global $wpi_nosave;
1084
        
1085
        $this->total = $this->subtotal + $this->tax + $this->fees_total;
1086
        $this->total = wpinv_round_amount( $this->total );
1087
        
1088
        do_action( 'wpinv_invoice_recalculate_total', $this, $wpi_nosave );
1089
    }
1090
    
1091
    public function increase_tax( $amount = 0.00 ) {
1092
        $amount       = (float) $amount;
1093
        $this->tax   += $amount;
1094
1095
        $this->recalculate_total();
1096
    }
1097
1098
    public function decrease_tax( $amount = 0.00 ) {
1099
        $amount     = (float) $amount;
1100
        $this->tax -= $amount;
1101
1102
        if ( $this->tax < 0 ) {
1103
            $this->tax = 0;
1104
        }
1105
1106
        $this->recalculate_total();
1107
    }
1108
1109
    public function update_status( $new_status = false, $note = '', $manual = false ) {
1110
        $old_status = ! empty( $this->old_status ) ? $this->old_status : get_post_status( $this->ID );
1111
1112
        if ( $old_status === $new_status && in_array( $new_status, array_keys( wpinv_get_invoice_statuses( true ) ) ) ) {
1113
            return false; // Don't permit status changes that aren't changes
1114
        }
1115
1116
        $do_change = apply_filters( 'wpinv_should_update_invoice_status', true, $this->ID, $new_status, $old_status );
1117
        $updated = false;
1118
1119
        if ( $do_change ) {
1120
            do_action( 'wpinv_before_invoice_status_change', $this->ID, $new_status, $old_status );
1121
1122
            $update_post_data                   = array();
1123
            $update_post_data['ID']             = $this->ID;
1124
            $update_post_data['post_status']    = $new_status;
1125
            $update_post_data['edit_date']      = current_time( 'mysql', 0 );
1126
            $update_post_data['edit_date_gmt']  = current_time( 'mysql', 1 );
1127
            
1128
            $update_post_data = apply_filters( 'wpinv_update_invoice_status_fields', $update_post_data, $this->ID );
1129
1130
            $updated = wp_update_post( $update_post_data );     
1131
           
1132
            // Process any specific status functions
1133
            switch( $new_status ) {
1134
                case 'wpi-refunded':
1135
                    $this->process_refund();
1136
                    break;
1137
                case 'wpi-failed':
1138
                    $this->process_failure();
1139
                    break;
1140
                case 'wpi-pending':
1141
                    $this->process_pending();
1142
                    break;
1143
            }
1144
            
1145
            // Status was changed.
1146
            do_action( 'wpinv_status_' . $new_status, $this->ID, $old_status );
1147
            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

1147
            do_action( 'wpinv_status_' . /** @scrutinizer ignore-type */ $old_status . '_to_' . $new_status, $this->ID, $old_status );
Loading history...
1148
            do_action( 'wpinv_update_status', $this->ID, $new_status, $old_status );
1149
        }
1150
1151
        return $updated;
1152
    }
1153
1154
    public function refund() {
1155
        $this->old_status        = $this->status;
1156
        $this->status            = 'wpi-refunded';
1157
        $this->pending['status'] = $this->status;
1158
1159
        $this->save();
1160
    }
1161
1162
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
1163
        if ( empty( $meta_key ) ) {
1164
            return false;
1165
        }
1166
1167
        if ( $meta_key == 'key' || $meta_key == 'date' ) {
1168
            $current_meta = $this->get_meta();
1169
            $current_meta[ $meta_key ] = $meta_value;
1170
1171
            $meta_key     = '_wpinv_payment_meta';
1172
            $meta_value   = $current_meta;
1173
        }
1174
1175
        $meta_value = apply_filters( 'wpinv_update_payment_meta_' . $meta_key, $meta_value, $this->ID );
1176
        
1177
        // Do not update created date on invoice marked as paid.
1178
        /*if ( $meta_key == '_wpinv_completed_date' && !empty( $meta_value ) ) {
1179
            $args = array(
1180
                'ID'                => $this->ID,
1181
                'post_date'         => $meta_value,
1182
                'edit_date'         => true,
1183
                'post_date_gmt'     => get_gmt_from_date( $meta_value ),
1184
                'post_modified'     => $meta_value,
1185
                'post_modified_gmt' => get_gmt_from_date( $meta_value )
1186
            );
1187
            wp_update_post( $args );
1188
        }*/
1189
        
1190
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
1191
    }
1192
1193
    private function process_refund() {
1194
        $process_refund = true;
1195
1196
        // If the payment was not in publish, don't decrement stats as they were never incremented
1197
        if ( 'publish' != $this->old_status || 'wpi-refunded' != $this->status ) {
1198
            $process_refund = false;
1199
        }
1200
1201
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1202
        $process_refund = apply_filters( 'wpinv_should_process_refund', $process_refund, $this );
1203
1204
        if ( false === $process_refund ) {
1205
            return;
1206
        }
1207
1208
        do_action( 'wpinv_pre_refund_invoice', $this );
1209
        
1210
        $decrease_store_earnings = apply_filters( 'wpinv_decrease_store_earnings_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_store_earnings is dead and can be removed.
Loading history...
1211
        $decrease_customer_value = apply_filters( 'wpinv_decrease_customer_value_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_customer_value is dead and can be removed.
Loading history...
1212
        $decrease_purchase_count = apply_filters( 'wpinv_decrease_customer_purchase_count_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_purchase_count is dead and can be removed.
Loading history...
1213
        
1214
        do_action( 'wpinv_post_refund_invoice', $this );
1215
    }
1216
1217
    private function process_failure() {
1218
        $discounts = $this->discounts;
1219
        if ( empty( $discounts ) ) {
1220
            return;
1221
        }
1222
1223
        if ( ! is_array( $discounts ) ) {
0 ignored issues
show
introduced by
The condition is_array($discounts) is always false.
Loading history...
1224
            $discounts = array_map( 'trim', explode( ',', $discounts ) );
1225
        }
1226
1227
        foreach ( $discounts as $discount ) {
1228
            wpinv_decrease_discount_usage( $discount );
1229
        }
1230
    }
1231
    
1232
    private function process_pending() {
1233
        $process_pending = true;
1234
1235
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
1236
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'wpi-pending' != $this->status ) {
1237
            $process_pending = false;
1238
        }
1239
1240
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1241
        $process_pending = apply_filters( 'wpinv_should_process_pending', $process_pending, $this );
1242
1243
        if ( false === $process_pending ) {
1244
            return;
1245
        }
1246
1247
        $decrease_store_earnings = apply_filters( 'wpinv_decrease_store_earnings_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_store_earnings is dead and can be removed.
Loading history...
1248
        $decrease_customer_value = apply_filters( 'wpinv_decrease_customer_value_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_customer_value is dead and can be removed.
Loading history...
1249
        $decrease_purchase_count = apply_filters( 'wpinv_decrease_customer_purchase_count_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_purchase_count is dead and can be removed.
Loading history...
1250
1251
        $this->completed_date = '';
1252
        $this->update_meta( '_wpinv_completed_date', '' );
1253
    }
1254
    
1255
    // get data
1256
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
1257
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1258
1259
        if ( $meta_key === '_wpinv_payment_meta' ) {
1260
1261
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
1262
1263
            if ( empty( $meta['key'] ) ) {
1264
                $meta['key'] = $this->setup_invoice_key();
1265
            }
1266
1267
            if ( empty( $meta['date'] ) ) {
1268
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1269
            }
1270
        }
1271
1272
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1273
1274
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1275
    }
1276
    
1277
    public function get_description() {
1278
        $post = get_post( $this->ID );
1279
        
1280
        $description = !empty( $post ) ? $post->post_content : '';
1281
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1282
    }
1283
    
1284
    public function get_status( $nicename = false ) {
1285
        if ( !$nicename ) {
1286
            $status = $this->status;
1287
        } else {
1288
            $status = $this->status_nicename;
1289
        }
1290
        
1291
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1292
    }
1293
    
1294
    public function get_cart_details() {
1295
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1296
    }
1297
    
1298
    public function get_subtotal( $currency = false ) {
1299
        $subtotal = wpinv_round_amount( $this->subtotal );
1300
        
1301
        if ( $currency ) {
1302
            $subtotal = wpinv_price( wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
1303
        }
1304
        
1305
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1306
    }
1307
    
1308
    public function get_total( $currency = false ) {        
1309
        if ( $this->is_free_trial() ) {
1310
            $total = wpinv_round_amount( 0 );
1311
        } else {
1312
            $total = wpinv_round_amount( $this->total );
1313
        }
1314
        if ( $currency ) {
1315
            $total = wpinv_price( wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
1316
        }
1317
        
1318
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1319
    }
1320
    
1321
    public function get_recurring_details( $field = '', $currency = false ) {        
1322
        $data                 = array();
1323
        $data['cart_details'] = $this->cart_details;
1324
        $data['subtotal']     = $this->get_subtotal();
1325
        $data['discount']     = $this->get_discount();
1326
        $data['tax']          = $this->get_tax();
1327
        $data['total']        = $this->get_total();
1328
    
1329
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1330
            $is_free_trial = $this->is_free_trial();
1331
            $discounts = $this->get_discounts( true );
1332
            
1333
            if ( $is_free_trial || !empty( $discounts ) ) {
1334
                $first_use_only = false;
1335
                
1336
                if ( !empty( $discounts ) ) {
1337
                    foreach ( $discounts as $key => $code ) {
1338
                        if ( wpinv_discount_is_recurring( $code, true ) && !$this->is_renewal() ) {
1339
                            $first_use_only = true;
1340
                            break;
1341
                        }
1342
                    }
1343
                }
1344
                    
1345
                if ( !$first_use_only ) {
1346
                    $data['subtotal'] = wpinv_round_amount( $this->subtotal );
1347
                    $data['discount'] = wpinv_round_amount( $this->discount );
1348
                    $data['tax']      = wpinv_round_amount( $this->tax );
1349
                    $data['total']    = wpinv_round_amount( $this->total );
1350
                } else {
1351
                    $cart_subtotal   = 0;
1352
                    $cart_discount   = $this->discount;
1353
                    $cart_tax        = 0;
1354
1355
                    foreach ( $this->cart_details as $key => $item ) {
1356
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1357
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1358
                        $item_discount  = 0;
1359
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1360
                        
1361
                        if ( wpinv_prices_include_tax() ) {
1362
                            $item_subtotal -= wpinv_round_amount( $item_tax );
1363
                        }
1364
                        
1365
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1366
                        // Do not allow totals to go negative
1367
                        if ( $item_total < 0 ) {
1368
                            $item_total = 0;
1369
                        }
1370
                        
1371
                        $cart_subtotal  += (float)($item_subtotal);
1372
                        $cart_discount  += (float)($item_discount);
1373
                        $cart_tax       += (float)($item_tax);
1374
                        
1375
                        $data['cart_details'][$key]['discount']   = wpinv_round_amount( $item_discount );
1376
                        $data['cart_details'][$key]['tax']        = wpinv_round_amount( $item_tax );
1377
                        $data['cart_details'][$key]['price']      = wpinv_round_amount( $item_total );
1378
                    }
1379
1380
	                $total = $data['subtotal'] - $data['discount'] + $data['tax'];
1381
	                if ( $total < 0 ) {
1382
		                $total = 0;
1383
	                }
1384
1385
                    $data['subtotal'] = wpinv_round_amount( $cart_subtotal );
1386
                    $data['discount'] = wpinv_round_amount( $cart_discount );
1387
                    $data['tax']      = wpinv_round_amount( $cart_tax );
1388
                    $data['total']    = wpinv_round_amount( $total );
1389
                }
1390
            }
1391
        }
1392
        
1393
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1394
1395
        if ( isset( $data[$field] ) ) {
1396
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1397
        }
1398
        
1399
        return $data;
1400
    }
1401
    
1402
    public function get_final_tax( $currency = false ) {        
1403
        $final_total = wpinv_round_amount( $this->tax );
1404
        if ( $currency ) {
1405
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
1406
        }
1407
        
1408
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1409
    }
1410
    
1411
    public function get_discounts( $array = false ) {
1412
        $discounts = $this->discounts;
1413
        if ( $array && $discounts ) {
1414
            $discounts = explode( ',', $discounts );
1415
        }
1416
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1417
    }
1418
    
1419
    public function get_discount( $currency = false, $dash = false ) {
1420
        if ( !empty( $this->discounts ) ) {
1421
            global $ajax_cart_details;
1422
            $ajax_cart_details = $this->get_cart_details();
1423
            
1424
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
1425
                $cart_items = $ajax_cart_details;
1426
            } else {
1427
                $cart_items = $this->items;
1428
            }
1429
1430
            $this->discount = wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
1431
        }
1432
        $discount   = wpinv_round_amount( $this->discount );
1433
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1434
        
1435
        if ( $currency ) {
1436
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
1437
        }
1438
        
1439
        $discount   = $dash . $discount;
1440
        
1441
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1442
    }
1443
    
1444
    public function get_discount_code() {
1445
        return $this->discount_code;
1446
    }
1447
1448
    // Checks if the invoice is taxable. Does not check if taxes are enabled on the site.
1449
    public function is_taxable() {
1450
        return (int) $this->disable_taxes === 0;
1451
    }
1452
1453
    public function get_tax( $currency = false ) {
1454
        $tax = wpinv_round_amount( $this->tax );
1455
1456
        if ( $currency ) {
1457
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
1458
        }
1459
1460
        if ( ! $this->is_taxable() ) {
1461
            $tax = wpinv_round_amount( 0.00 );
1462
        }
1463
1464
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1465
    }
1466
    
1467
    public function get_fees( $type = 'all' ) {
1468
        $fees    = array();
1469
1470
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1471
            foreach ( $this->fees as $fee ) {
1472
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1473
                    continue;
1474
                }
1475
1476
                $fee['label'] = stripslashes( $fee['label'] );
1477
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1478
                $fees[]    = $fee;
1479
            }
1480
        }
1481
1482
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1483
    }
1484
    
1485
    public function get_fees_total( $type = 'all' ) {
1486
        $fees_total = (float) 0.00;
1487
1488
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1489
        if ( ! empty( $payment_fees ) ) {
1490
            foreach ( $payment_fees as $fee ) {
1491
                $fees_total += (float) $fee['amount'];
1492
            }
1493
        }
1494
1495
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1496
        /*
1497
        $fees = $this->get_fees( $type );
1498
1499
        $fees_total = 0;
1500
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1501
            foreach ( $fees as $fee_id => $fee ) {
1502
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1503
                    continue;
1504
                }
1505
1506
                $fees_total += $fee['amount'];
1507
            }
1508
        }
1509
1510
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1511
        */
1512
    }
1513
1514
    public function get_user_id() {
1515
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1516
    }
1517
    
1518
    public function get_first_name() {
1519
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1520
    }
1521
    
1522
    public function get_last_name() {
1523
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1524
    }
1525
    
1526
    public function get_user_full_name() {
1527
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1528
    }
1529
    
1530
    public function get_user_info() {
1531
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1532
    }
1533
    
1534
    public function get_email() {
1535
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1536
    }
1537
    
1538
    public function get_address() {
1539
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1540
    }
1541
    
1542
    public function get_phone() {
1543
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1544
    }
1545
    
1546
    public function get_number() {
1547
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1548
    }
1549
    
1550
    public function get_items() {
1551
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1552
    }
1553
    
1554
    public function get_key() {
1555
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1556
    }
1557
    
1558
    public function get_transaction_id() {
1559
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1560
    }
1561
    
1562
    public function get_gateway() {
1563
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1564
    }
1565
    
1566
    public function get_gateway_title() {
1567
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1568
        
1569
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1570
    }
1571
    
1572
    public function get_currency() {
1573
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1574
    }
1575
    
1576
    public function get_created_date() {
1577
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1578
    }
1579
    
1580
    public function get_due_date( $display = false ) {
1581
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1582
        
1583
        if ( !$display || empty( $due_date ) ) {
1584
            return $due_date;
1585
        }
1586
        
1587
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
0 ignored issues
show
Bug introduced by
It seems like get_option('date_format') can also be of type false; however, parameter $format of date_i18n() 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

1587
        return date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $due_date ) );
Loading history...
1588
    }
1589
    
1590
    public function get_completed_date() {
1591
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1592
    }
1593
    
1594
    public function get_invoice_date( $formatted = true ) {
1595
        $date_completed = $this->completed_date;
1596
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1597
        
1598
        if ( $invoice_date == '' ) {
1599
            $date_created   = $this->date;
1600
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1601
        }
1602
        
1603
        if ( $formatted && $invoice_date ) {
1604
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
0 ignored issues
show
Bug introduced by
It seems like get_option('date_format') can also be of type false; however, parameter $format of date_i18n() 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

1604
            $invoice_date   = date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $invoice_date ) );
Loading history...
1605
        }
1606
1607
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1608
    }
1609
    
1610
    public function get_ip() {
1611
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1612
    }
1613
        
1614
    public function has_status( $status ) {
1615
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1616
    }
1617
    
1618
    public function add_item( $item_id = 0, $args = array() ) {
1619
        global $wpi_current_id, $wpi_item_id;
1620
        
1621
        $item = new WPInv_Item( $item_id );
1622
1623
        // Bail if this post isn't a item
1624
        if( !$item || $item->post_type !== 'wpi_item' ) {
0 ignored issues
show
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...
introduced by
$item is of type WPInv_Item, thus it always evaluated to true.
Loading history...
1625
            return false;
1626
        }
1627
        
1628
        $has_quantities = wpinv_item_quantities_enabled();
1629
1630
        // Set some defaults
1631
        $defaults = array(
1632
            'quantity'      => 1,
1633
            'id'            => false,
1634
            'name'          => $item->get_name(),
1635
            'item_price'    => false,
1636
            'custom_price'  => '',
1637
            'discount'      => 0,
1638
            'tax'           => 0.00,
1639
            'meta'          => array(),
1640
            'fees'          => array()
1641
        );
1642
1643
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1644
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1645
1646
        $wpi_current_id         = $this->ID;
1647
        $wpi_item_id            = $item->ID;
1648
        $discounts              = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
1649
        
1650
        $_POST['wpinv_country'] = $this->country;
1651
        $_POST['wpinv_state']   = $this->state;
1652
        
1653
        $found_cart_key         = false;
1654
        
1655
        if ($has_quantities) {
1656
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1657
            
1658
            foreach ( $this->items as $key => $cart_item ) {
1659
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1660
                    continue;
1661
                }
1662
1663
                $this->items[ $key ]['quantity'] += $args['quantity'];
1664
                break;
1665
            }
1666
            
1667
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1668
                if ( $item_id != $cart_item['id'] ) {
1669
                    continue;
1670
                }
1671
1672
                $found_cart_key = $cart_key;
1673
                break;
1674
            }
1675
        }
1676
        
1677
        if ($has_quantities && $found_cart_key !== false) {
1678
            $cart_item          = $this->cart_details[$found_cart_key];
1679
            $item_price         = $cart_item['item_price'];
1680
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1681
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1682
            
1683
            $new_quantity       = $quantity + $args['quantity'];
1684
            $subtotal           = $item_price * $new_quantity;
1685
            
1686
            $args['quantity']   = $new_quantity;
1687
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1688
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1689
            
1690
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1691
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1692
            // The total increase equals the number removed * the item_price
1693
            $total_increased    = wpinv_round_amount( $item_price );
1694
            
1695
            if ( wpinv_prices_include_tax() ) {
1696
                $subtotal -= wpinv_round_amount( $tax );
1697
            }
1698
1699
            $total              = $subtotal - $discount + $tax;
1700
1701
            // Do not allow totals to go negative
1702
            if( $total < 0 ) {
1703
                $total = 0;
1704
            }
1705
            
1706
            $cart_item['quantity']  = $new_quantity;
1707
            $cart_item['subtotal']  = $subtotal;
1708
            $cart_item['discount']  = $discount;
1709
            $cart_item['tax']       = $tax;
1710
            $cart_item['price']     = $total;
1711
            
1712
            $subtotal               = $total_increased - $discount_increased;
1713
            $tax                    = $tax_increased;
1714
            
1715
            $this->cart_details[$found_cart_key] = $cart_item;
1716
        } else {
1717
            // Set custom price.
1718
            if ( $args['custom_price'] !== '' ) {
1719
                $item_price = $args['custom_price'];
1720
            } else {
1721
                // Allow overriding the price
1722
                if ( false !== $args['item_price'] ) {
1723
                    $item_price = $args['item_price'];
1724
                } else {
1725
                    $item_price = wpinv_get_item_price( $item->ID );
1726
                }
1727
            }
1728
1729
            // Sanitizing the price here so we don't have a dozen calls later
1730
            $item_price = wpinv_sanitize_amount( $item_price );
1731
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
1732
        
1733
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1734
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1735
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1736
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1737
1738
            // Setup the items meta item
1739
            $new_item = array(
1740
                'id'       => $item->ID,
1741
                'quantity' => $args['quantity'],
1742
            );
1743
1744
            $this->items[]  = $new_item;
1745
1746
            if ( wpinv_prices_include_tax() ) {
1747
                $subtotal -= wpinv_round_amount( $tax );
1748
            }
1749
1750
            $total      = $subtotal - $discount + $tax;
1751
1752
            // Do not allow totals to go negative
1753
            if( $total < 0 ) {
1754
                $total = 0;
1755
            }
1756
        
1757
            $this->cart_details[] = array(
1758
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
1759
                'id'            => $item->ID,
1760
                'item_price'    => wpinv_round_amount( $item_price ),
1761
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
1762
                'quantity'      => $args['quantity'],
1763
                'discount'      => $discount,
1764
                'subtotal'      => wpinv_round_amount( $subtotal ),
1765
                'tax'           => wpinv_round_amount( $tax ),
1766
                'price'         => wpinv_round_amount( $total ),
1767
                'vat_rate'      => $tax_rate,
1768
                'vat_class'     => $tax_class,
1769
                'meta'          => $args['meta'],
1770
                'fees'          => $args['fees'],
1771
            );
1772
   
1773
            $subtotal = $subtotal - $discount;
1774
        }
1775
        
1776
        $added_item = end( $this->cart_details );
1777
        $added_item['action']  = 'add';
1778
        
1779
        $this->pending['items'][] = $added_item;
1780
        
1781
        $this->increase_subtotal( $subtotal );
1782
        $this->increase_tax( $tax );
1783
1784
        return true;
1785
    }
1786
    
1787
    public function remove_item( $item_id, $args = array() ) {
1788
        // Set some defaults
1789
        $defaults = array(
1790
            'quantity'      => 1,
1791
            'item_price'    => false,
1792
            'custom_price'  => '',
1793
            'cart_index'    => false,
1794
        );
1795
        $args = wp_parse_args( $args, $defaults );
1796
1797
        // Bail if this post isn't a item
1798
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1799
            return false;
1800
        }
1801
        
1802
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1803
1804
        foreach ( $this->items as $key => $item ) {
1805
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1806
                continue;
1807
            }
1808
1809
            if ( false !== $args['cart_index'] ) {
1810
                $cart_index = absint( $args['cart_index'] );
1811
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1812
1813
                if ( ! empty( $cart_item ) ) {
1814
                    // If the cart index item isn't the same item ID, don't remove it
1815
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1816
                        continue;
1817
                    }
1818
                }
1819
            }
1820
1821
            $item_quantity = $this->items[ $key ]['quantity'];
1822
            if ( $item_quantity > $args['quantity'] ) {
1823
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1824
                break;
1825
            } else {
1826
                unset( $this->items[ $key ] );
1827
                break;
1828
            }
1829
        }
1830
1831
        $found_cart_key = false;
1832
        if ( false === $args['cart_index'] ) {
1833
            foreach ( $this->cart_details as $cart_key => $item ) {
1834
                if ( $item_id != $item['id'] ) {
1835
                    continue;
1836
                }
1837
1838
                if ( false !== $args['item_price'] ) {
1839
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1840
                        continue;
1841
                    }
1842
                }
1843
1844
                $found_cart_key = $cart_key;
1845
                break;
1846
            }
1847
        } else {
1848
            $cart_index = absint( $args['cart_index'] );
1849
1850
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1851
                return false; // Invalid cart index passed.
1852
            }
1853
1854
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1855
                return false; // We still need the proper Item ID to be sure.
1856
            }
1857
1858
            $found_cart_key = $cart_index;
1859
        }
1860
        
1861
        $cart_item  = $this->cart_details[$found_cart_key];
1862
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1863
        
1864
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1865
            //return false; // Invoice must contain at least one item.
1866
        }
1867
        
1868
        $discounts  = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
1869
        
1870
        if ( $quantity > $args['quantity'] ) {
1871
            $item_price         = $cart_item['item_price'];
1872
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1873
            
1874
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1875
            $subtotal           = $item_price * $new_quantity;
1876
            
1877
            $args['quantity']   = $new_quantity;
1878
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1879
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1880
            
1881
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
1882
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1883
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
1884
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1885
            
1886
            // The total increase equals the number removed * the item_price
1887
            $total_decrease     = wpinv_round_amount( $item_price );
1888
            
1889
            if ( wpinv_prices_include_tax() ) {
1890
                $subtotal -= wpinv_round_amount( $tax );
1891
            }
1892
1893
            $total              = $subtotal - $discount + $tax;
1894
1895
            // Do not allow totals to go negative
1896
            if( $total < 0 ) {
1897
                $total = 0;
1898
            }
1899
            
1900
            $cart_item['quantity']  = $new_quantity;
1901
            $cart_item['subtotal']  = $subtotal;
1902
            $cart_item['discount']  = $discount;
1903
            $cart_item['tax']       = $tax;
1904
            $cart_item['price']     = $total;
1905
            
1906
            $added_item             = $cart_item;
1907
            $added_item['id']       = $item_id;
1908
            $added_item['price']    = $total_decrease;
1909
            $added_item['quantity'] = $args['quantity'];
1910
            
1911
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1912
            
1913
            $this->cart_details[$found_cart_key] = $cart_item;
1914
            
1915
            $remove_item = end( $this->cart_details );
1916
        } else {
1917
            $item_price     = $cart_item['item_price'];
1918
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1919
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1920
        
1921
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1922
            $tax_decrease       = $tax;
1923
1924
            unset( $this->cart_details[$found_cart_key] );
1925
            
1926
            $remove_item             = $args;
1927
            $remove_item['id']       = $item_id;
1928
            $remove_item['price']    = $subtotal_decrease;
1929
            $remove_item['quantity'] = $args['quantity'];
1930
        }
1931
        
1932
        $remove_item['action']      = 'remove';
1933
        $this->pending['items'][]   = $remove_item;
1934
               
1935
        $this->decrease_subtotal( $subtotal_decrease );
1936
        $this->decrease_tax( $tax_decrease );
1937
        
1938
        return true;
1939
    }
1940
    
1941
    public function update_items($temp = false) {
1942
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1943
        
1944
        if ( !empty( $this->cart_details ) ) {
1945
            $wpi_nosave             = $temp;
1946
            $cart_subtotal          = 0;
1947
            $cart_discount          = 0;
1948
            $cart_tax               = 0;
1949
            $cart_details           = array();
1950
1951
            $_POST['wpinv_country'] = $this->country;
1952
            $_POST['wpinv_state']   = $this->state;
1953
1954
            foreach ( $this->cart_details as $key => $item ) {
1955
                $item_price = $item['item_price'];
1956
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1957
                $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...
1958
                $subtotal   = $item_price * $quantity;
1959
                
1960
                $wpi_current_id         = $this->ID;
1961
                $wpi_item_id            = $item['id'];
1962
                
1963
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1964
                
1965
                $tax_rate   = wpinv_get_tax_rate( $this->country, $this->state, $wpi_item_id );
1966
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1967
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1968
1969
                if ( ! $this->is_taxable() ) {
1970
                    $tax = 0;
1971
                }
1972
1973
                if ( wpinv_prices_include_tax() ) {
1974
                    $subtotal -= wpinv_round_amount( $tax );
1975
                }
1976
1977
                $total      = $subtotal - $discount + $tax;
1978
1979
                // Do not allow totals to go negative
1980
                if( $total < 0 ) {
1981
                    $total = 0;
1982
                }
1983
1984
                $cart_details[] = array(
1985
                    'id'          => $item['id'],
1986
                    'name'        => $item['name'],
1987
                    'item_price'  => wpinv_round_amount( $item_price ),
1988
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
1989
                    'quantity'    => $quantity,
1990
                    'discount'    => $discount,
1991
                    'subtotal'    => wpinv_round_amount( $subtotal ),
1992
                    'tax'         => wpinv_round_amount( $tax ),
1993
                    'price'       => wpinv_round_amount( $total ),
1994
                    'vat_rate'    => $tax_rate,
1995
                    'vat_class'   => $tax_class,
1996
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1997
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1998
                );
1999
2000
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
2001
                $cart_discount  += (float)($discount);
2002
                $cart_tax       += (float)($tax);
2003
            }
2004
            if ( $cart_subtotal < 0 ) {
2005
                $cart_subtotal = 0;
2006
            }
2007
            if ( $cart_tax < 0 ) {
2008
                $cart_tax = 0;
2009
            }
2010
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
2011
            $this->tax      = wpinv_round_amount( $cart_tax );
2012
            $this->discount = wpinv_round_amount( $cart_discount );
2013
            
2014
            $this->recalculate_total();
2015
            
2016
            $this->cart_details = $cart_details;
2017
        }
2018
2019
        return $this;
2020
    }
2021
    
2022
    public function recalculate_totals($temp = false) {        
2023
        $this->update_items($temp);
2024
        $this->save( true );
2025
        
2026
        return $this;
2027
    }
2028
    
2029
    public function needs_payment() {
2030
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'wpi-pending' ), $this );
2031
2032
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() || $this->is_initial_free() ) ) {
2033
            $needs_payment = true;
2034
        } else {
2035
            $needs_payment = false;
2036
        }
2037
2038
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
2039
    }
2040
    
2041
    public function get_checkout_payment_url( $with_key = false, $secret = false ) {
2042
        $pay_url = wpinv_get_checkout_uri();
2043
2044
        if ( is_ssl() ) {
2045
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
2046
        }
2047
        
2048
        $key = $this->get_key();
2049
2050
        if ( $with_key ) {
2051
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
2052
        } else {
2053
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
2054
        }
2055
        
2056
        if ( $secret ) {
2057
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
2058
        }
2059
2060
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this, $with_key, $secret );
2061
    }
2062
    
2063
    public function get_view_url( $with_key = false ) {
2064
        $invoice_url = get_permalink( $this->ID );
2065
2066
        if ( $with_key ) {
2067
            $invoice_url = add_query_arg( 'invoice_key', $this->get_key(), $invoice_url );
2068
        }
2069
2070
        return apply_filters( 'wpinv_get_view_url', $invoice_url, $this, $with_key );
2071
    }
2072
    
2073
    public function generate_key( $string = '' ) {
2074
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2075
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2076
    }
2077
    
2078
    public function is_recurring() {
2079
        if ( empty( $this->cart_details ) ) {
2080
            return false;
2081
        }
2082
        
2083
        $has_subscription = false;
2084
        foreach( $this->cart_details as $cart_item ) {
2085
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2086
                $has_subscription = true;
2087
                break;
2088
            }
2089
        }
2090
        
2091
        if ( count( $this->cart_details ) > 1 ) {
2092
            $has_subscription = false;
2093
        }
2094
2095
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2096
    }
2097
2098
    public function is_free_trial() {
2099
        $is_free_trial = false;
2100
        
2101
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2102
            if ( !empty( $item ) && $item->has_free_trial() ) {
2103
                $is_free_trial = true;
2104
            }
2105
        }
2106
2107
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details, $this );
2108
    }
2109
2110
    public function is_initial_free() {
2111
        $is_initial_free = false;
2112
        
2113
        if ( ! ( (float)wpinv_round_amount( $this->get_total() ) > 0 ) && $this->is_parent() && $this->is_recurring() && ! $this->is_free_trial() && ! $this->is_free() ) {
2114
            $is_initial_free = true;
2115
        }
2116
2117
        return apply_filters( 'wpinv_invoice_is_initial_free', $is_initial_free, $this->cart_details );
2118
    }
2119
    
2120
    public function get_recurring( $object = false ) {
2121
        $item = NULL;
2122
        
2123
        if ( empty( $this->cart_details ) ) {
2124
            return $item;
2125
        }
2126
        
2127
        foreach( $this->cart_details as $cart_item ) {
2128
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2129
                $item = $cart_item['id'];
2130
                break;
2131
            }
2132
        }
2133
        
2134
        if ( $object ) {
2135
            $item = $item ? new WPInv_Item( $item ) : NULL;
2136
            
2137
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2138
        }
2139
2140
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2141
    }
2142
2143
    public function get_subscription_name() {
2144
        $item = $this->get_recurring( true );
2145
2146
        if ( empty( $item ) ) {
2147
            return NULL;
2148
        }
2149
2150
        if ( !($name = $item->get_name()) ) {
2151
            $name = $item->post_name;
2152
        }
2153
2154
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2155
    }
2156
2157
    public function get_subscription_id() {
2158
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2159
2160
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2161
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2162
2163
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2164
        }
2165
        
2166
        return $subscription_id;
2167
    }
2168
    
2169
    public function is_parent() {
2170
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2171
2172
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2173
    }
2174
    
2175
    public function is_renewal() {
2176
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2177
2178
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2179
    }
2180
    
2181
    public function get_parent_payment() {
2182
        $parent_payment = NULL;
2183
        
2184
        if ( $this->is_renewal() ) {
2185
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2186
        }
2187
        
2188
        return $parent_payment;
2189
    }
2190
    
2191
    public function is_paid() {
2192
        $is_paid = $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) );
2193
2194
        return apply_filters( 'wpinv_invoice_is_paid', $is_paid, $this );
2195
    }
2196
2197
    /**
2198
     * Checks if this is a quote object.
2199
     * 
2200
     * @since 1.0.15
2201
     */
2202
    public function is_quote() {
2203
        return 'wpi_quote' === $this->post_type;
2204
    }
2205
    
2206
    public function is_refunded() {
2207
        $is_refunded = $this->has_status( array( 'wpi-refunded' ) );
2208
2209
        return apply_filters( 'wpinv_invoice_is_refunded', $is_refunded, $this );
2210
    }
2211
    
2212
    public function is_free() {
2213
        $is_free = false;
2214
        
2215
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
2216
            if ( $this->is_parent() && $this->is_recurring() ) {
2217
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
2218
            } else {
2219
                $is_free = true;
2220
            }
2221
        }
2222
        
2223
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
2224
    }
2225
    
2226
    public function has_vat() {
2227
        global $wpinv_euvat, $wpi_country;
2228
        
2229
        $requires_vat = false;
2230
        
2231
        if ( $this->country ) {
2232
            $wpi_country        = $this->country;
2233
            
2234
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2235
        }
2236
        
2237
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2238
    }
2239
    
2240
    public function refresh_item_ids() {
2241
        $item_ids = array();
2242
        
2243
        if ( !empty( $this->cart_details ) ) {
2244
            foreach ( $this->cart_details as $key => $item ) {
2245
                if ( !empty( $item['id'] ) ) {
2246
                    $item_ids[] = $item['id'];
2247
                }
2248
            }
2249
        }
2250
        
2251
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2252
        
2253
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2254
    }
2255
    
2256
    public function get_invoice_quote_type( $post_id ) {
2257
        if ( empty( $post_id ) ) {
2258
            return '';
2259
        }
2260
2261
        $type = get_post_type( $post_id );
2262
2263
        if ( 'wpi_invoice' === $type ) {
2264
            $post_type = __('Invoice', 'invoicing');
2265
        } else{
2266
            $post_type = __('Quote', 'invoicing');
2267
        }
2268
2269
        return apply_filters('get_invoice_type_label', $post_type, $post_id);
2270
    }
2271
}
2272