Passed
Pull Request — master (#126)
by Kiran
03:46
created

WPInv_Invoice::get_subscription_end()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 20
nc 8
nop 1
dl 0
loc 34
rs 5.3846
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A WPInv_Invoice::has_vat() 0 13 2
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;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
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 ) ) {
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 introduced by
The property post_name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
Are you sure the assignment to $this->post_name is correct as $this->setup_post_name($invoice) (which targets 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();
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
165
        $this->email           = get_the_author_meta( 'email', $this->user_id );
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, $this );
0 ignored issues
show
Documentation introduced by
$this is of type this<WPInv_Invoice>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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;
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 );
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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 ) ) {
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 );
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() {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
383
        ///$user_id = $this->get_meta( '_wpinv_user_id' );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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 );
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 View Code Duplication
        if ( empty( $this->key ) ) {
566
            $this->key = self::generate_key();
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 introduced by
The property _ID does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
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 View Code Duplication
        if ( empty( $this->key ) ) {
654
            $this->key = self::generate_key();
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
$taxes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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 );
0 ignored issues
show
Documentation introduced by
$this->status is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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 );
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
734
                        ///$this->user_info['user_id'] = $this->user_id;
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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 );
0 ignored issues
show
Documentation introduced by
(bool) $adddress_confirmed is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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 );
0 ignored issues
show
Documentation Bug introduced by
It seems like explode(',', $this->discounts) of type array is incompatible with the declared type string of property $discounts.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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 );
0 ignored issues
show
Documentation introduced by
$merged_meta is of type array<string,array|string>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
861
                if ( false !== $updated ) {
862
                    $saved = true;
0 ignored issues
show
Unused Code introduced by
$saved is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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 ) {
0 ignored issues
show
Unused Code introduced by
The parameter $global is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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() && current_user_can( 'manage_options' ) ) || $added_by_user ) && !$system ) {
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 );
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 View Code Duplication
    private function decrease_subtotal( $amount = 0.00 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
    private function decrease_fees( $amount = 0.00 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ) {
0 ignored issues
show
Unused Code introduced by
The parameter $note is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $manual is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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() ) ) ) {
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 );
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 ) ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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
$decrease_store_earnings is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1189
        $decrease_customer_value = apply_filters( 'wpinv_decrease_customer_value_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
$decrease_customer_value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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
$decrease_purchase_count is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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 ) ) {
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
$decrease_store_earnings is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1226
        $decrease_customer_value = apply_filters( 'wpinv_decrease_customer_value_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
$decrease_customer_value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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
$decrease_purchase_count is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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 View Code Duplication
    public function get_subtotal( $currency = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
    public function get_total( $currency = false ) {        
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ) ) {
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   = 0;
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
                    $data['subtotal'] = wpinv_round_amount( $cart_subtotal );
1359
                    $data['discount'] = wpinv_round_amount( $cart_discount );
1360
                    $data['tax']      = wpinv_round_amount( $cart_tax );
1361
                    $data['total']    = wpinv_round_amount( $data['subtotal'] + $data['tax'] );
1362
                }
1363
            }
1364
        }
1365
        
1366
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1367
1368
        if ( isset( $data[$field] ) ) {
1369
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1370
        }
1371
        
1372
        return $data;
1373
    }
1374
    
1375
    public function get_final_tax( $currency = false ) {        
1376
        $final_total = wpinv_round_amount( $this->tax );
1377
        if ( $currency ) {
1378
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
1379
        }
1380
        
1381
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1382
    }
1383
    
1384
    public function get_discounts( $array = false ) {
1385
        $discounts = $this->discounts;
1386
        if ( $array && $discounts ) {
1387
            $discounts = explode( ',', $discounts );
1388
        }
1389
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1390
    }
1391
    
1392
    public function get_discount( $currency = false, $dash = false ) {
1393
        if ( !empty( $this->discounts ) ) {
1394
            global $ajax_cart_details;
1395
            $ajax_cart_details = $this->get_cart_details();
1396
            
1397
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
1398
                $cart_items = $ajax_cart_details;
1399
            } else {
1400
                $cart_items = $this->items;
1401
            }
1402
1403
            $this->discount = wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
0 ignored issues
show
Documentation introduced by
$this->discounts is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1404
        }
1405
        $discount   = wpinv_round_amount( $this->discount );
1406
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1407
        
1408
        if ( $currency ) {
1409
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
1410
        }
1411
        
1412
        $discount   = $dash . $discount;
1413
        
1414
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1415
    }
1416
    
1417
    public function get_discount_code() {
1418
        return $this->discount_code;
1419
    }
1420
    
1421 View Code Duplication
    public function get_tax( $currency = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1422
        $tax = wpinv_round_amount( $this->tax );
1423
        
1424
        if ( $currency ) {
1425
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
1426
        }
1427
        
1428
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1429
    }
1430
    
1431
    public function get_fees( $type = 'all' ) {
1432
        $fees    = array();
1433
1434
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1435
            foreach ( $this->fees as $fee ) {
1436 View Code Duplication
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1437
                    continue;
1438
                }
1439
1440
                $fee['label'] = stripslashes( $fee['label'] );
1441
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1442
                $fees[]    = $fee;
1443
            }
1444
        }
1445
1446
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1447
    }
1448
    
1449
    public function get_fees_total( $type = 'all' ) {
1450
        $fees_total = (float) 0.00;
1451
1452
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1453
        if ( ! empty( $payment_fees ) ) {
1454
            foreach ( $payment_fees as $fee ) {
1455
                $fees_total += (float) $fee['amount'];
1456
            }
1457
        }
1458
1459
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1460
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1461
        $fees = $this->get_fees( $type );
1462
1463
        $fees_total = 0;
1464
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1465
            foreach ( $fees as $fee_id => $fee ) {
1466
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1467
                    continue;
1468
                }
1469
1470
                $fees_total += $fee['amount'];
1471
            }
1472
        }
1473
1474
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1475
        */
1476
    }
1477
1478
    public function get_user_id() {
1479
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1480
    }
1481
    
1482
    public function get_first_name() {
1483
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1484
    }
1485
    
1486
    public function get_last_name() {
1487
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1488
    }
1489
    
1490
    public function get_user_full_name() {
1491
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1492
    }
1493
    
1494
    public function get_user_info() {
1495
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1496
    }
1497
    
1498
    public function get_email() {
1499
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1500
    }
1501
    
1502
    public function get_address() {
1503
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1504
    }
1505
    
1506
    public function get_phone() {
1507
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1508
    }
1509
    
1510
    public function get_number() {
1511
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1512
    }
1513
    
1514
    public function get_items() {
1515
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1516
    }
1517
    
1518
    public function get_key() {
1519
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1520
    }
1521
    
1522
    public function get_transaction_id() {
1523
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1524
    }
1525
    
1526
    public function get_gateway() {
1527
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1528
    }
1529
    
1530
    public function get_gateway_title() {
1531
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1532
        
1533
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1534
    }
1535
    
1536
    public function get_currency() {
1537
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1538
    }
1539
    
1540
    public function get_created_date() {
1541
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1542
    }
1543
    
1544
    public function get_due_date( $display = false ) {
1545
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1546
        
1547
        if ( !$display || empty( $due_date ) ) {
1548
            return $due_date;
1549
        }
1550
        
1551
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
1552
    }
1553
    
1554
    public function get_completed_date() {
1555
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1556
    }
1557
    
1558
    public function get_invoice_date( $formatted = true ) {
1559
        $date_completed = $this->completed_date;
1560
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1561
        
1562
        if ( $invoice_date == '' ) {
1563
            $date_created   = $this->date;
1564
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1565
        }
1566
        
1567
        if ( $formatted && $invoice_date ) {
1568
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
1569
        }
1570
1571
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1572
    }
1573
    
1574
    public function get_ip() {
1575
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1576
    }
1577
        
1578
    public function has_status( $status ) {
1579
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1580
    }
1581
    
1582
    public function add_item( $item_id = 0, $args = array() ) {
1583
        global $wpi_current_id, $wpi_item_id;
1584
        
1585
        $item = new WPInv_Item( $item_id );
0 ignored issues
show
Documentation introduced by
$item_id is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1586
1587
        // Bail if this post isn't a item
1588
        if( !$item || $item->post_type !== 'wpi_item' ) {
0 ignored issues
show
Documentation introduced by
The property post_type does not exist on object<WPInv_Item>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1589
            return false;
1590
        }
1591
        
1592
        $has_quantities = wpinv_item_quantities_enabled();
1593
1594
        // Set some defaults
1595
        $defaults = array(
1596
            'quantity'      => 1,
1597
            'id'            => false,
1598
            'name'          => $item->get_name(),
1599
            'item_price'    => false,
1600
            'custom_price'  => '',
1601
            'discount'      => 0,
1602
            'tax'           => 0.00,
1603
            'meta'          => array(),
1604
            'fees'          => array()
1605
        );
1606
1607
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1608
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1609
1610
        $wpi_current_id         = $this->ID;
1611
        $wpi_item_id            = $item->ID;
1612
        $discounts              = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
$discounts is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1613
        
1614
        $_POST['wpinv_country'] = $this->country;
1615
        $_POST['wpinv_state']   = $this->state;
1616
        
1617
        $found_cart_key         = false;
1618
        
1619
        if ($has_quantities) {
1620
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1621
            
1622
            foreach ( $this->items as $key => $cart_item ) {
1623
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1624
                    continue;
1625
                }
1626
1627
                $this->items[ $key ]['quantity'] += $args['quantity'];
1628
                break;
1629
            }
1630
            
1631
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1632
                if ( $item_id != $cart_item['id'] ) {
1633
                    continue;
1634
                }
1635
1636
                $found_cart_key = $cart_key;
1637
                break;
1638
            }
1639
        }
1640
        
1641
        if ($has_quantities && $found_cart_key !== false) {
1642
            $cart_item          = $this->cart_details[$found_cart_key];
1643
            $item_price         = $cart_item['item_price'];
1644
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1645
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1646
            
1647
            $new_quantity       = $quantity + $args['quantity'];
1648
            $subtotal           = $item_price * $new_quantity;
1649
            
1650
            $args['quantity']   = $new_quantity;
1651
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1652
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1653
            
1654
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1655
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1656
            // The total increase equals the number removed * the item_price
1657
            $total_increased    = wpinv_round_amount( $item_price );
1658
            
1659
            if ( wpinv_prices_include_tax() ) {
1660
                $subtotal -= wpinv_round_amount( $tax );
1661
            }
1662
1663
            $total              = $subtotal - $discount + $tax;
1664
1665
            // Do not allow totals to go negative
1666
            if( $total < 0 ) {
1667
                $total = 0;
1668
            }
1669
            
1670
            $cart_item['quantity']  = $new_quantity;
1671
            $cart_item['subtotal']  = $subtotal;
1672
            $cart_item['discount']  = $discount;
1673
            $cart_item['tax']       = $tax;
1674
            $cart_item['price']     = $total;
1675
            
1676
            $subtotal               = $total_increased - $discount_increased;
1677
            $tax                    = $tax_increased;
1678
            
1679
            $this->cart_details[$found_cart_key] = $cart_item;
1680
        } else {
1681
            // Set custom price.
1682
            if ( $args['custom_price'] !== '' ) {
1683
                $item_price = $args['custom_price'];
1684
            } else {
1685
                // Allow overriding the price
1686
                if ( false !== $args['item_price'] ) {
1687
                    $item_price = $args['item_price'];
1688
                } else {
1689
                    $item_price = wpinv_get_item_price( $item->ID );
1690
                }
1691
            }
1692
1693
            // Sanitizing the price here so we don't have a dozen calls later
1694
            $item_price = wpinv_sanitize_amount( $item_price );
1695
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
1696
        
1697
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1698
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1699
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1700
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1701
1702
            // Setup the items meta item
1703
            $new_item = array(
1704
                'id'       => $item->ID,
1705
                'quantity' => $args['quantity'],
1706
            );
1707
1708
            $this->items[]  = $new_item;
1709
1710
            if ( wpinv_prices_include_tax() ) {
1711
                $subtotal -= wpinv_round_amount( $tax );
1712
            }
1713
1714
            $total      = $subtotal - $discount + $tax;
1715
1716
            // Do not allow totals to go negative
1717
            if( $total < 0 ) {
1718
                $total = 0;
1719
            }
1720
        
1721
            $this->cart_details[] = array(
1722
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
1723
                'id'            => $item->ID,
1724
                'item_price'    => wpinv_round_amount( $item_price ),
1725
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
1726
                'quantity'      => $args['quantity'],
1727
                'discount'      => $discount,
1728
                'subtotal'      => wpinv_round_amount( $subtotal ),
1729
                'tax'           => wpinv_round_amount( $tax ),
1730
                'price'         => wpinv_round_amount( $total ),
1731
                'vat_rate'      => $tax_rate,
1732
                'vat_class'     => $tax_class,
1733
                'meta'          => $args['meta'],
1734
                'fees'          => $args['fees'],
1735
            );
1736
                        
1737
            $subtotal = $subtotal - $discount;
1738
        }
1739
        
1740
        $added_item = end( $this->cart_details );
1741
        $added_item['action']  = 'add';
1742
        
1743
        $this->pending['items'][] = $added_item;
1744
        
1745
        $this->increase_subtotal( $subtotal );
1746
        $this->increase_tax( $tax );
1747
1748
        return true;
1749
    }
1750
    
1751
    public function remove_item( $item_id, $args = array() ) {
1752
        // Set some defaults
1753
        $defaults = array(
1754
            'quantity'      => 1,
1755
            'item_price'    => false,
1756
            'custom_price'  => '',
1757
            'cart_index'    => false,
1758
        );
1759
        $args = wp_parse_args( $args, $defaults );
1760
1761
        // Bail if this post isn't a item
1762
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1763
            return false;
1764
        }
1765
        
1766
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1767
1768
        foreach ( $this->items as $key => $item ) {
1769
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1770
                continue;
1771
            }
1772
1773
            if ( false !== $args['cart_index'] ) {
1774
                $cart_index = absint( $args['cart_index'] );
1775
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1776
1777
                if ( ! empty( $cart_item ) ) {
1778
                    // If the cart index item isn't the same item ID, don't remove it
1779
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1780
                        continue;
1781
                    }
1782
                }
1783
            }
1784
1785
            $item_quantity = $this->items[ $key ]['quantity'];
1786
            if ( $item_quantity > $args['quantity'] ) {
1787
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1788
                break;
1789
            } else {
1790
                unset( $this->items[ $key ] );
1791
                break;
1792
            }
1793
        }
1794
1795
        $found_cart_key = false;
1796
        if ( false === $args['cart_index'] ) {
1797
            foreach ( $this->cart_details as $cart_key => $item ) {
1798
                if ( $item_id != $item['id'] ) {
1799
                    continue;
1800
                }
1801
1802
                if ( false !== $args['item_price'] ) {
1803
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1804
                        continue;
1805
                    }
1806
                }
1807
1808
                $found_cart_key = $cart_key;
1809
                break;
1810
            }
1811
        } else {
1812
            $cart_index = absint( $args['cart_index'] );
1813
1814
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1815
                return false; // Invalid cart index passed.
1816
            }
1817
1818
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1819
                return false; // We still need the proper Item ID to be sure.
1820
            }
1821
1822
            $found_cart_key = $cart_index;
1823
        }
1824
        
1825
        $cart_item  = $this->cart_details[$found_cart_key];
1826
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1827
        
1828
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1829
            return false; // Invoice must contain at least one item.
1830
        }
1831
        
1832
        $discounts  = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
$discounts is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1833
        
1834
        if ( $quantity > $args['quantity'] ) {
1835
            $item_price         = $cart_item['item_price'];
1836
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1837
            
1838
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1839
            $subtotal           = $item_price * $new_quantity;
1840
            
1841
            $args['quantity']   = $new_quantity;
1842
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1843
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1844
            
1845
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
1846
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1847
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
1848
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1849
            
1850
            // The total increase equals the number removed * the item_price
1851
            $total_decrease     = wpinv_round_amount( $item_price );
1852
            
1853
            if ( wpinv_prices_include_tax() ) {
1854
                $subtotal -= wpinv_round_amount( $tax );
1855
            }
1856
1857
            $total              = $subtotal - $discount + $tax;
1858
1859
            // Do not allow totals to go negative
1860
            if( $total < 0 ) {
1861
                $total = 0;
1862
            }
1863
            
1864
            $cart_item['quantity']  = $new_quantity;
1865
            $cart_item['subtotal']  = $subtotal;
1866
            $cart_item['discount']  = $discount;
1867
            $cart_item['tax']       = $tax;
1868
            $cart_item['price']     = $total;
1869
            
1870
            $added_item             = $cart_item;
1871
            $added_item['id']       = $item_id;
1872
            $added_item['price']    = $total_decrease;
1873
            $added_item['quantity'] = $args['quantity'];
1874
            
1875
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1876
            
1877
            $this->cart_details[$found_cart_key] = $cart_item;
1878
            
1879
            $remove_item = end( $this->cart_details );
1880
        } else {
1881
            $item_price     = $cart_item['item_price'];
1882
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1883
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1884
        
1885
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1886
            $tax_decrease       = $tax;
1887
1888
            unset( $this->cart_details[$found_cart_key] );
1889
            
1890
            $remove_item             = $args;
1891
            $remove_item['id']       = $item_id;
1892
            $remove_item['price']    = $subtotal_decrease;
1893
            $remove_item['quantity'] = $args['quantity'];
1894
        }
1895
        
1896
        $remove_item['action']      = 'remove';
1897
        $this->pending['items'][]   = $remove_item;
1898
               
1899
        $this->decrease_subtotal( $subtotal_decrease );
1900
        $this->decrease_tax( $tax_decrease );
1901
        
1902
        return true;
1903
    }
1904
    
1905
    public function update_items($temp = false) {
1906
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1907
        
1908
        if ( !empty( $this->cart_details ) ) {
1909
            $wpi_nosave             = $temp;
1910
            $cart_subtotal          = 0;
1911
            $cart_discount          = 0;
1912
            $cart_tax               = 0;
1913
            $cart_details           = array();
1914
            
1915
            $_POST['wpinv_country'] = $this->country;
1916
            $_POST['wpinv_state']   = $this->state;
1917
            
1918
            foreach ( $this->cart_details as $key => $item ) {
1919
                $item_price = $item['item_price'];
1920
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1921
                $amount     = wpinv_round_amount( $item_price * $quantity );
0 ignored issues
show
Unused Code introduced by
$amount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1922
                $subtotal   = $item_price * $quantity;
1923
                
1924
                $wpi_current_id         = $this->ID;
1925
                $wpi_item_id            = $item['id'];
1926
                
1927
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1928
                
1929
                $tax_rate   = wpinv_get_tax_rate( $this->country, $this->state, $wpi_item_id );
0 ignored issues
show
Documentation introduced by
$this->country is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->state is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1930
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1931
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1932
1933
                if ( wpinv_prices_include_tax() ) {
1934
                    $subtotal -= wpinv_round_amount( $tax );
1935
                }
1936
1937
                $total      = $subtotal - $discount + $tax;
1938
1939
                // Do not allow totals to go negative
1940
                if( $total < 0 ) {
1941
                    $total = 0;
1942
                }
1943
1944
                $cart_details[] = array(
1945
                    'id'          => $item['id'],
1946
                    'name'        => $item['name'],
1947
                    'item_price'  => wpinv_round_amount( $item_price ),
1948
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
1949
                    'quantity'    => $quantity,
1950
                    'discount'    => $discount,
1951
                    'subtotal'    => wpinv_round_amount( $subtotal ),
1952
                    'tax'         => wpinv_round_amount( $tax ),
1953
                    'price'       => wpinv_round_amount( $total ),
1954
                    'vat_rate'    => $tax_rate,
1955
                    'vat_class'   => $tax_class,
1956
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1957
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1958
                );
1959
                
1960
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1961
                $cart_discount  += (float)($discount);
1962
                $cart_tax       += (float)($tax);
1963
            }
1964
            if ( $cart_subtotal < 0 ) {
1965
                $cart_subtotal = 0;
1966
            }
1967
            if ( $cart_tax < 0 ) {
1968
                $cart_tax = 0;
1969
            }
1970
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
1971
            $this->tax      = wpinv_round_amount( $cart_tax );
1972
            $this->discount = wpinv_round_amount( $cart_discount );
1973
            
1974
            $this->recalculate_total();
1975
            
1976
            $this->cart_details = $cart_details;
1977
        }
1978
1979
        return $this;
1980
    }
1981
    
1982
    public function recalculate_totals($temp = false) {        
1983
        $this->update_items($temp);
1984
        $this->save( true );
1985
        
1986
        return $this;
1987
    }
1988
    
1989
    public function needs_payment() {
1990
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'wpi-pending' ), $this );
1991
1992
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() || $this->is_initial_free() ) ) {
1993
            $needs_payment = true;
1994
        } else {
1995
            $needs_payment = false;
1996
        }
1997
1998
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1999
    }
2000
    
2001
    public function get_checkout_payment_url( $with_key = false, $secret = false ) {
2002
        $pay_url = wpinv_get_checkout_uri();
2003
2004
        if ( is_ssl() ) {
2005
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
2006
        }
2007
        
2008
        $key = $this->get_key();
2009
2010
        if ( $with_key ) {
2011
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
2012
        } else {
2013
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
2014
        }
2015
        
2016
        if ( $secret ) {
2017
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
2018
        }
2019
2020
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this, $with_key, $secret );
2021
    }
2022
    
2023
    public function get_view_url( $with_key = false ) {
2024
        $invoice_url = get_permalink( $this->ID );
2025
2026
        if ( $with_key ) {
2027
            $invoice_url = add_query_arg( 'invoice_key', $this->get_key(), $invoice_url );
2028
        }
2029
2030
        return apply_filters( 'wpinv_get_view_url', $invoice_url, $this, $with_key );
2031
    }
2032
    
2033
    public function generate_key( $string = '' ) {
2034
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2035
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2036
    }
2037
    
2038
    public function is_recurring() {
2039
        if ( empty( $this->cart_details ) ) {
2040
            return false;
2041
        }
2042
        
2043
        $has_subscription = false;
2044 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2045
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2046
                $has_subscription = true;
2047
                break;
2048
            }
2049
        }
2050
        
2051
        if ( count( $this->cart_details ) > 1 ) {
2052
            $has_subscription = false;
2053
        }
2054
2055
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2056
    }
2057
    
2058
    public function is_free_trial() {
2059
        $is_free_trial = false;
2060
        
2061
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2062
            if ( !empty( $item ) && $item->has_free_trial() ) {
2063
                $is_free_trial = true;
2064
            }
2065
        }
2066
2067
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details );
2068
    }
2069
    
2070
    public function is_initial_free() {
2071
        $is_initial_free = false;
2072
        
2073
        if ( ! ( (float)wpinv_round_amount( $this->get_total() ) > 0 ) && $this->is_parent() && $this->is_recurring() && ! $this->is_free_trial() && ! $this->is_free() ) {
2074
            $is_initial_free = true;
2075
        }
2076
2077
        return apply_filters( 'wpinv_invoice_is_initial_free', $is_initial_free, $this->cart_details );
2078
    }
2079
    
2080
    public function get_recurring( $object = false ) {
2081
        $item = NULL;
2082
        
2083
        if ( empty( $this->cart_details ) ) {
2084
            return $item;
2085
        }
2086
        
2087 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2088
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2089
                $item = $cart_item['id'];
2090
                break;
2091
            }
2092
        }
2093
        
2094
        if ( $object ) {
2095
            $item = $item ? new WPInv_Item( $item ) : NULL;
2096
            
2097
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2098
        }
2099
2100
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2101
    }
2102
    
2103
    public function get_subscription_name() {
2104
        $item = $this->get_recurring( true );
2105
        
2106
        if ( empty( $item ) ) {
2107
            return NULL;
2108
        }
2109
        
2110
        if ( !($name = $item->get_name()) ) {
2111
            $name = $item->post_name;
2112
        }
2113
2114
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2115
    }
2116
    
2117
    public function get_subscription_id() {
2118
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2119
        
2120
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2121
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2122
            
2123
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2124
        }
2125
        
2126
        return $subscription_id;
2127
    }
2128
    
2129
    public function is_parent() {
2130
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2131
2132
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2133
    }
2134
    
2135
    public function is_renewal() {
2136
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2137
2138
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2139
    }
2140
    
2141
    public function get_parent_payment() {
2142
        $parent_payment = NULL;
2143
        
2144
        if ( $this->is_renewal() ) {
2145
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2146
        }
2147
        
2148
        return $parent_payment;
2149
    }
2150
    
2151
    public function is_paid() {
2152
        $is_paid = $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) );
2153
2154
        return apply_filters( 'wpinv_invoice_is_paid', $is_paid, $this );
2155
    }
2156
    
2157
    public function is_refunded() {
2158
        $is_refunded = $this->has_status( array( 'wpi-refunded' ) );
2159
2160
        return apply_filters( 'wpinv_invoice_is_refunded', $is_refunded, $this );
2161
    }
2162
    
2163
    public function is_free() {
2164
        $is_free = false;
2165
        
2166
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
2167
            if ( $this->is_parent() && $this->is_recurring() ) {
2168
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
2169
            } else {
2170
                $is_free = true;
2171
            }
2172
        }
2173
        
2174
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
2175
    }
2176
    
2177
    public function has_vat() {
2178
        global $wpinv_euvat, $wpi_country;
2179
        
2180
        $requires_vat = false;
2181
        
2182
        if ( $this->country ) {
2183
            $wpi_country        = $this->country;
2184
            
2185
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2186
        }
2187
        
2188
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2189
    }
2190
    
2191
    public function refresh_item_ids() {
2192
        $item_ids = array();
2193
        
2194
        if ( !empty( $this->cart_details ) ) {
2195
            foreach ( $this->cart_details as $key => $item ) {
2196
                if ( !empty( $item['id'] ) ) {
2197
                    $item_ids[] = $item['id'];
2198
                }
2199
            }
2200
        }
2201
        
2202
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2203
        
2204
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2205
    }
2206
    
2207
    public function get_invoice_quote_type( $post_id ) {
2208
        if ( empty( $post_id ) ) {
2209
            return '';
2210
        }
2211
2212
        $type = get_post_type( $post_id );
2213
2214
        if ( 'wpi_invoice' === $type ) {
2215
            $post_type = __('Invoice', 'invoicing');
2216
        } else{
2217
            $post_type = __('Quote', 'invoicing');
2218
        }
2219
2220
        return apply_filters('get_invoice_type_label', $post_type, $post_id);
2221
    }
2222
}
2223