Passed
Branch master (50908e)
by Stiofan
07:01
created

WPInv_Invoice::insert_invoice()   F

Complexity

Conditions 18
Paths 4096

Size

Total Lines 96
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 68
c 1
b 0
f 0
nc 4096
nop 0
dl 0
loc 96
rs 0.7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

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

472
                $this->email = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
473
                
474
                $user_info['user_id'] = $this->user_id;
475
                $user_info['email'] = $this->email;
476
                $this->payment_meta['user_id'] = $this->user_id;
477
                $this->payment_meta['email'] = $this->email;
478
            }
479
        }
480
        
481
        $user_info    = wp_parse_args( $user_info, $defaults );
482
        
483
        // Get the user, but only if it's been created
484
        $user = get_userdata( $this->user_id );
485
        
486
        if ( !empty( $user ) && $user->ID > 0 ) {
487
            if ( empty( $user_info ) ) {
488
                $user_info = array(
489
                    'user_id'    => $user->ID,
490
                    'first_name' => $user->first_name,
491
                    'last_name'  => $user->last_name,
492
                    'email'      => $user->user_email,
493
                    'discount'   => '',
494
                );
495
            } else {
496
                foreach ( $user_info as $key => $value ) {
497
                    if ( ! empty( $value ) ) {
498
                        continue;
499
                    }
500
501
                    switch( $key ) {
502
                        case 'user_id':
503
                            $user_info[ $key ] = $user->ID;
504
                            break;
505
                        case 'first_name':
506
                            $user_info[ $key ] = $user->first_name;
507
                            break;
508
                        case 'last_name':
509
                            $user_info[ $key ] = $user->last_name;
510
                            break;
511
                        case 'email':
512
                            $user_info[ $key ] = $user->user_email;
513
                            break;
514
                    }
515
                }
516
            }
517
        }
518
519
        return $user_info;
520
    }
521
522
    private function setup_invoice_key() {
523
        $key = $this->get_meta( '_wpinv_key', true );
524
        
525
        return $key;
526
    }
527
528
    private function setup_invoice_number() {
529
        $number = $this->get_meta( '_wpinv_number', true );
530
531
        if ( !$number ) {
532
            $number = $this->ID;
533
534
            if ( $this->status == 'auto-draft' ) {
535
                if ( wpinv_sequential_number_active( $this->post_type ) ) {
536
                    $next_number = wpinv_get_next_invoice_number( $this->post_type );
537
                    $number      = $next_number;
538
                }
539
            }
540
            
541
            $number = wpinv_format_invoice_number( $number, $this->post_type );
542
        }
543
544
        return $number;
545
    }
546
    
547
    private function insert_invoice() {
548
        global $wpdb;
549
550
        if ( empty( $this->post_type ) ) {
551
            if ( !empty( $this->ID ) && $post_type = get_post_type( $this->ID ) ) {
552
                $this->post_type = $post_type;
553
            } else if ( !empty( $this->parent_invoice ) && $post_type = get_post_type( $this->parent_invoice ) ) {
554
                $this->post_type = $post_type;
555
            } else {
556
                $this->post_type = 'wpi_invoice';
557
            }
558
        }
559
560
        $invoice_number = $this->ID;
561
        if ( $number = $this->get_meta( '_wpinv_number', true ) ) {
562
            $invoice_number = $number;
563
        }
564
565
        if ( empty( $this->key ) ) {
566
            $this->key = self::generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_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

566
            /** @scrutinizer ignore-call */ 
567
            $this->key = self::generate_key();
Loading history...
567
            $this->pending['key'] = $this->key;
568
        }
569
570
        if ( empty( $this->ip ) ) {
571
            $this->ip = wpinv_get_ip();
572
            $this->pending['ip'] = $this->ip;
573
        }
574
        
575
        $payment_data = array(
576
            'price'        => $this->total,
577
            'date'         => $this->date,
578
            'user_email'   => $this->email,
579
            'invoice_key'  => $this->key,
580
            'currency'     => $this->currency,
581
            'items'        => $this->items,
582
            'user_info' => array(
583
                'user_id'    => $this->user_id,
584
                'email'      => $this->email,
585
                'first_name' => $this->first_name,
586
                'last_name'  => $this->last_name,
587
                'address'    => $this->address,
588
                'phone'      => $this->phone,
589
                'city'       => $this->city,
590
                'country'    => $this->country,
591
                'state'      => $this->state,
592
                'zip'        => $this->zip,
593
                'company'    => $this->company,
594
                'vat_number' => $this->vat_number,
595
                'discount'   => $this->discounts,
596
            ),
597
            'cart_details' => $this->cart_details,
598
            'status'       => $this->status,
599
            'fees'         => $this->fees,
600
        );
601
602
        $post_data = array(
603
                        'post_title'    => $invoice_number,
604
                        'post_status'   => $this->status,
605
                        'post_author'   => $this->user_id,
606
                        'post_type'     => $this->post_type,
607
                        'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
608
                        'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
609
                        'post_parent'   => $this->parent_invoice,
610
                    );
611
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
612
613
        // Create a blank invoice
614
        if ( !empty( $this->ID ) ) {
615
            $args['ID']         = $this->ID;
616
617
            $invoice_id = wp_update_post( $args, true );
618
        } else {
619
            $invoice_id = wp_insert_post( $args, true );
620
        }
621
622
        if ( is_wp_error( $invoice_id ) ) {
623
            return false;
624
        }
625
626
        if ( !empty( $invoice_id ) ) {
627
            $this->ID  = $invoice_id;
628
            $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...
629
630
            $this->payment_meta = apply_filters( 'wpinv_payment_meta', $this->payment_meta, $payment_data );
631
            if ( ! empty( $this->payment_meta['fees'] ) ) {
632
                $this->fees = array_merge( $this->fees, $this->payment_meta['fees'] );
633
                foreach( $this->fees as $fee ) {
634
                    $this->increase_fees( $fee['amount'] );
635
                }
636
            }
637
638
            $this->update_meta( '_wpinv_payment_meta', $this->payment_meta );            
639
            $this->new = true;
640
        }
641
642
        return $this->ID;
643
    }
644
645
    public function save( $setup = false ) {
646
        global $wpi_session;
647
        
648
        $saved = false;
649
        if ( empty( $this->items ) ) {
650
            return $saved; // Don't save empty invoice.
651
        }
652
        
653
        if ( empty( $this->key ) ) {
654
            $this->key = self::generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_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

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

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

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

1556
        return date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $due_date ) );
Loading history...
1557
    }
1558
    
1559
    public function get_completed_date() {
1560
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1561
    }
1562
    
1563
    public function get_invoice_date( $formatted = true ) {
1564
        $date_completed = $this->completed_date;
1565
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1566
        
1567
        if ( $invoice_date == '' ) {
1568
            $date_created   = $this->date;
1569
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1570
        }
1571
        
1572
        if ( $formatted && $invoice_date ) {
1573
            $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

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