Passed
Pull Request — master (#116)
by
unknown
03:57
created

WPInv_Invoice::generate_key()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Contains functions related to Invoicing plugin.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
 
9
// MUST have WordPress.
10
if ( !defined( 'WPINC' ) ) {
11
    exit( 'Do NOT access this file directly: ' . basename( __FILE__ ) );
12
}
13
14
final class WPInv_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();
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', 'invoicing' );
986
            $comment_author_email = strtolower( __( 'System', 'invoicing' ) ) . '@';
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
        if ( $meta_key == '_wpinv_completed_date' && !empty( $meta_value ) ) {
1156
            $args = array(
1157
                'ID'                => $this->ID,
1158
                'post_date'         => $meta_value,
1159
                'edit_date'         => true,
1160
                'post_date_gmt'     => get_gmt_from_date( $meta_value ),
1161
                'post_modified'     => $meta_value,
1162
                'post_modified_gmt' => get_gmt_from_date( $meta_value )
1163
            );
1164
            wp_update_post( $args );
1165
        }
1166
        
1167
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
1168
    }
1169
1170
    private function process_refund() {
1171
        $process_refund = true;
1172
1173
        // If the payment was not in publish, don't decrement stats as they were never incremented
1174
        if ( 'publish' != $this->old_status || 'wpi-refunded' != $this->status ) {
1175
            $process_refund = false;
1176
        }
1177
1178
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1179
        $process_refund = apply_filters( 'wpinv_should_process_refund', $process_refund, $this );
1180
1181
        if ( false === $process_refund ) {
1182
            return;
1183
        }
1184
1185
        do_action( 'wpinv_pre_refund_invoice', $this );
1186
        
1187
        $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...
1188
        $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...
1189
        $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...
1190
        
1191
        do_action( 'wpinv_post_refund_invoice', $this );
1192
    }
1193
1194
    private function process_failure() {
1195
        $discounts = $this->discounts;
1196
        if ( empty( $discounts ) ) {
1197
            return;
1198
        }
1199
1200
        if ( ! is_array( $discounts ) ) {
1201
            $discounts = array_map( 'trim', explode( ',', $discounts ) );
1202
        }
1203
1204
        foreach ( $discounts as $discount ) {
1205
            wpinv_decrease_discount_usage( $discount );
1206
        }
1207
    }
1208
    
1209
    private function process_pending() {
1210
        $process_pending = true;
1211
1212
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
1213
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'wpi-pending' != $this->status ) {
1214
            $process_pending = false;
1215
        }
1216
1217
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1218
        $process_pending = apply_filters( 'wpinv_should_process_pending', $process_pending, $this );
1219
1220
        if ( false === $process_pending ) {
1221
            return;
1222
        }
1223
1224
        $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...
1225
        $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...
1226
        $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...
1227
1228
        $this->completed_date = '';
1229
        $this->update_meta( '_wpinv_completed_date', '' );
1230
    }
1231
    
1232
    // get data
1233
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
1234
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1235
1236
        if ( $meta_key === '_wpinv_payment_meta' ) {
1237
1238
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
1239
1240
            if ( empty( $meta['key'] ) ) {
1241
                $meta['key'] = $this->setup_invoice_key();
1242
            }
1243
1244
            if ( empty( $meta['date'] ) ) {
1245
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1246
            }
1247
        }
1248
1249
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1250
1251
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1252
    }
1253
    
1254
    public function get_description() {
1255
        $post = get_post( $this->ID );
1256
        
1257
        $description = !empty( $post ) ? $post->post_content : '';
1258
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1259
    }
1260
    
1261
    public function get_status( $nicename = false ) {
1262
        if ( !$nicename ) {
1263
            $status = $this->status;
1264
        } else {
1265
            $status = $this->status_nicename;
1266
        }
1267
        
1268
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1269
    }
1270
    
1271
    public function get_cart_details() {
1272
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1273
    }
1274
    
1275 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...
1276
        $subtotal = wpinv_round_amount( $this->subtotal );
1277
        
1278
        if ( $currency ) {
1279
            $subtotal = wpinv_price( wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
1280
        }
1281
        
1282
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1283
    }
1284
    
1285 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...
1286
        if ( $this->is_free_trial() ) {
1287
            $total = wpinv_round_amount( 0 );
1288
        } else {
1289
            $total = wpinv_round_amount( $this->total );
1290
        }
1291
        if ( $currency ) {
1292
            $total = wpinv_price( wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
1293
        }
1294
        
1295
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1296
    }
1297
    
1298
    public function get_recurring_details( $field = '', $currency = false ) {        
1299
        $data                 = array();
1300
        $data['cart_details'] = $this->cart_details;
1301
        $data['subtotal']     = $this->get_subtotal();
1302
        $data['discount']     = $this->get_discount();
1303
        $data['tax']          = $this->get_tax();
1304
        $data['total']        = $this->get_total();
1305
    
1306
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1307
            $is_free_trial = $this->is_free_trial();
1308
            $discounts = $this->get_discounts( true );
1309
            
1310
            if ( $is_free_trial || !empty( $discounts ) ) {
1311
                $first_use_only = false;
1312
                
1313
                if ( !empty( $discounts ) ) {
1314
                    foreach ( $discounts as $key => $code ) {
1315
                        if ( wpinv_discount_is_recurring( $code, true ) ) {
1316
                            $first_use_only = true;
1317
                            break;
1318
                        }
1319
                    }
1320
                }
1321
                    
1322
                if ( !$first_use_only ) {
1323
                    $data['subtotal'] = wpinv_round_amount( $this->subtotal );
1324
                    $data['discount'] = wpinv_round_amount( $this->discount );
1325
                    $data['tax']      = wpinv_round_amount( $this->tax );
1326
                    $data['total']    = wpinv_round_amount( $this->total );
1327
                } else {
1328
                    $cart_subtotal   = 0;
1329
                    $cart_discount   = 0;
1330
                    $cart_tax        = 0;
1331
1332
                    foreach ( $this->cart_details as $key => $item ) {
1333
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1334
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1335
                        $item_discount  = 0;
1336
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1337
                        
1338
                        if ( wpinv_prices_include_tax() ) {
1339
                            $item_subtotal -= wpinv_round_amount( $item_tax );
1340
                        }
1341
                        
1342
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1343
                        // Do not allow totals to go negative
1344
                        if ( $item_total < 0 ) {
1345
                            $item_total = 0;
1346
                        }
1347
                        
1348
                        $cart_subtotal  += (float)($item_subtotal);
1349
                        $cart_discount  += (float)($item_discount);
1350
                        $cart_tax       += (float)($item_tax);
1351
                        
1352
                        $data['cart_details'][$key]['discount']   = wpinv_round_amount( $item_discount );
1353
                        $data['cart_details'][$key]['tax']        = wpinv_round_amount( $item_tax );
1354
                        $data['cart_details'][$key]['price']      = wpinv_round_amount( $item_total );
1355
                    }
1356
                    
1357
                    $data['subtotal'] = wpinv_round_amount( $cart_subtotal );
1358
                    $data['discount'] = wpinv_round_amount( $cart_discount );
1359
                    $data['tax']      = wpinv_round_amount( $cart_tax );
1360
                    $data['total']    = wpinv_round_amount( $data['subtotal'] + $data['tax'] );
1361
                }
1362
            }
1363
        }
1364
        
1365
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1366
1367
        if ( isset( $data[$field] ) ) {
1368
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1369
        }
1370
        
1371
        return $data;
1372
    }
1373
    
1374
    public function get_final_tax( $currency = false ) {        
1375
        $final_total = wpinv_round_amount( $this->tax );
1376
        if ( $currency ) {
1377
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
1378
        }
1379
        
1380
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1381
    }
1382
    
1383
    public function get_discounts( $array = false ) {
1384
        $discounts = $this->discounts;
1385
        if ( $array && $discounts ) {
1386
            $discounts = explode( ',', $discounts );
1387
        }
1388
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1389
    }
1390
    
1391
    public function get_discount( $currency = false, $dash = false ) {
1392
        if ( !empty( $this->discounts ) ) {
1393
            global $ajax_cart_details;
1394
            $ajax_cart_details = $this->get_cart_details();
1395
            
1396
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
1397
                $cart_items = $ajax_cart_details;
1398
            } else {
1399
                $cart_items = $this->items;
1400
            }
1401
1402
            $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...
1403
        }
1404
        $discount   = wpinv_round_amount( $this->discount );
1405
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1406
        
1407
        if ( $currency ) {
1408
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
1409
        }
1410
        
1411
        $discount   = $dash . $discount;
1412
        
1413
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1414
    }
1415
    
1416
    public function get_discount_code() {
1417
        return $this->discount_code;
1418
    }
1419
    
1420 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...
1421
        $tax = wpinv_round_amount( $this->tax );
1422
        
1423
        if ( $currency ) {
1424
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
1425
        }
1426
        
1427
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1428
    }
1429
    
1430
    public function get_fees( $type = 'all' ) {
1431
        $fees    = array();
1432
1433
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1434
            foreach ( $this->fees as $fee ) {
1435 View Code Duplication
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1436
                    continue;
1437
                }
1438
1439
                $fee['label'] = stripslashes( $fee['label'] );
1440
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1441
                $fees[]    = $fee;
1442
            }
1443
        }
1444
1445
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1446
    }
1447
    
1448
    public function get_fees_total( $type = 'all' ) {
1449
        $fees_total = (float) 0.00;
1450
1451
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1452
        if ( ! empty( $payment_fees ) ) {
1453
            foreach ( $payment_fees as $fee ) {
1454
                $fees_total += (float) $fee['amount'];
1455
            }
1456
        }
1457
1458
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1459
        /*
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...
1460
        $fees = $this->get_fees( $type );
1461
1462
        $fees_total = 0;
1463
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1464
            foreach ( $fees as $fee_id => $fee ) {
1465
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1466
                    continue;
1467
                }
1468
1469
                $fees_total += $fee['amount'];
1470
            }
1471
        }
1472
1473
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1474
        */
1475
    }
1476
1477
    public function get_user_id() {
1478
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1479
    }
1480
    
1481
    public function get_first_name() {
1482
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1483
    }
1484
    
1485
    public function get_last_name() {
1486
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1487
    }
1488
    
1489
    public function get_user_full_name() {
1490
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1491
    }
1492
    
1493
    public function get_user_info() {
1494
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1495
    }
1496
    
1497
    public function get_email() {
1498
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1499
    }
1500
    
1501
    public function get_address() {
1502
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1503
    }
1504
    
1505
    public function get_phone() {
1506
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1507
    }
1508
    
1509
    public function get_number() {
1510
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1511
    }
1512
    
1513
    public function get_items() {
1514
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1515
    }
1516
    
1517
    public function get_key() {
1518
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1519
    }
1520
    
1521
    public function get_transaction_id() {
1522
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1523
    }
1524
    
1525
    public function get_gateway() {
1526
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1527
    }
1528
    
1529
    public function get_gateway_title() {
1530
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1531
        
1532
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1533
    }
1534
    
1535
    public function get_currency() {
1536
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1537
    }
1538
    
1539
    public function get_created_date() {
1540
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1541
    }
1542
    
1543
    public function get_due_date( $display = false ) {
1544
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1545
        
1546
        if ( !$display || empty( $due_date ) ) {
1547
            return $due_date;
1548
        }
1549
        
1550
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
1551
    }
1552
    
1553
    public function get_completed_date() {
1554
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1555
    }
1556
    
1557
    public function get_invoice_date( $formatted = true ) {
1558
        $date_completed = $this->completed_date;
1559
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1560
        
1561
        if ( $invoice_date == '' ) {
1562
            $date_created   = $this->date;
1563
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1564
        }
1565
        
1566
        if ( $formatted && $invoice_date ) {
1567
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
1568
        }
1569
1570
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1571
    }
1572
    
1573
    public function get_ip() {
1574
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1575
    }
1576
        
1577
    public function has_status( $status ) {
1578
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1579
    }
1580
    
1581
    public function add_item( $item_id = 0, $args = array() ) {
1582
        global $wpi_current_id, $wpi_item_id;
1583
        
1584
        $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...
1585
1586
        // Bail if this post isn't a item
1587
        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...
1588
            return false;
1589
        }
1590
        
1591
        $has_quantities = wpinv_item_quantities_enabled();
1592
1593
        // Set some defaults
1594
        $defaults = array(
1595
            'quantity'      => 1,
1596
            'id'            => false,
1597
            'name'          => $item->get_name(),
1598
            'item_price'    => false,
1599
            'custom_price'  => '',
1600
            'discount'      => 0,
1601
            'tax'           => 0.00,
1602
            'meta'          => array(),
1603
            'fees'          => array()
1604
        );
1605
1606
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1607
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1608
1609
        $wpi_current_id         = $this->ID;
1610
        $wpi_item_id            = $item->ID;
1611
        $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...
1612
        
1613
        $_POST['wpinv_country'] = $this->country;
1614
        $_POST['wpinv_state']   = $this->state;
1615
        
1616
        $found_cart_key         = false;
1617
        
1618
        if ($has_quantities) {
1619
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1620
            
1621
            foreach ( $this->items as $key => $cart_item ) {
1622
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1623
                    continue;
1624
                }
1625
1626
                $this->items[ $key ]['quantity'] += $args['quantity'];
1627
                break;
1628
            }
1629
            
1630
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1631
                if ( $item_id != $cart_item['id'] ) {
1632
                    continue;
1633
                }
1634
1635
                $found_cart_key = $cart_key;
1636
                break;
1637
            }
1638
        }
1639
        
1640
        if ($has_quantities && $found_cart_key !== false) {
1641
            $cart_item          = $this->cart_details[$found_cart_key];
1642
            $item_price         = $cart_item['item_price'];
1643
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1644
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1645
            
1646
            $new_quantity       = $quantity + $args['quantity'];
1647
            $subtotal           = $item_price * $new_quantity;
1648
            
1649
            $args['quantity']   = $new_quantity;
1650
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1651
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1652
            
1653
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1654
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1655
            // The total increase equals the number removed * the item_price
1656
            $total_increased    = wpinv_round_amount( $item_price );
1657
            
1658
            if ( wpinv_prices_include_tax() ) {
1659
                $subtotal -= wpinv_round_amount( $tax );
1660
            }
1661
1662
            $total              = $subtotal - $discount + $tax;
1663
1664
            // Do not allow totals to go negative
1665
            if( $total < 0 ) {
1666
                $total = 0;
1667
            }
1668
            
1669
            $cart_item['quantity']  = $new_quantity;
1670
            $cart_item['subtotal']  = $subtotal;
1671
            $cart_item['discount']  = $discount;
1672
            $cart_item['tax']       = $tax;
1673
            $cart_item['price']     = $total;
1674
            
1675
            $subtotal               = $total_increased - $discount_increased;
1676
            $tax                    = $tax_increased;
1677
            
1678
            $this->cart_details[$found_cart_key] = $cart_item;
1679
        } else {
1680
            // Set custom price.
1681
            if ( $args['custom_price'] !== '' ) {
1682
                $item_price = $args['custom_price'];
1683
            } else {
1684
                // Allow overriding the price
1685
                if ( false !== $args['item_price'] ) {
1686
                    $item_price = $args['item_price'];
1687
                } else {
1688
                    $item_price = wpinv_get_item_price( $item->ID );
1689
                }
1690
            }
1691
1692
            // Sanitizing the price here so we don't have a dozen calls later
1693
            $item_price = wpinv_sanitize_amount( $item_price );
1694
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
1695
        
1696
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1697
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1698
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1699
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1700
1701
            // Setup the items meta item
1702
            $new_item = array(
1703
                'id'       => $item->ID,
1704
                'quantity' => $args['quantity'],
1705
            );
1706
1707
            $this->items[]  = $new_item;
1708
1709
            if ( wpinv_prices_include_tax() ) {
1710
                $subtotal -= wpinv_round_amount( $tax );
1711
            }
1712
1713
            $total      = $subtotal - $discount + $tax;
1714
1715
            // Do not allow totals to go negative
1716
            if( $total < 0 ) {
1717
                $total = 0;
1718
            }
1719
        
1720
            $this->cart_details[] = array(
1721
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
1722
                'id'            => $item->ID,
1723
                'item_price'    => wpinv_round_amount( $item_price ),
1724
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
1725
                'quantity'      => $args['quantity'],
1726
                'discount'      => $discount,
1727
                'subtotal'      => wpinv_round_amount( $subtotal ),
1728
                'tax'           => wpinv_round_amount( $tax ),
1729
                'price'         => wpinv_round_amount( $total ),
1730
                'vat_rate'      => $tax_rate,
1731
                'vat_class'     => $tax_class,
1732
                'meta'          => $args['meta'],
1733
                'fees'          => $args['fees'],
1734
            );
1735
                        
1736
            $subtotal = $subtotal - $discount;
1737
        }
1738
        
1739
        $added_item = end( $this->cart_details );
1740
        $added_item['action']  = 'add';
1741
        
1742
        $this->pending['items'][] = $added_item;
1743
        
1744
        $this->increase_subtotal( $subtotal );
1745
        $this->increase_tax( $tax );
1746
1747
        return true;
1748
    }
1749
    
1750
    public function remove_item( $item_id, $args = array() ) {
1751
        // Set some defaults
1752
        $defaults = array(
1753
            'quantity'      => 1,
1754
            'item_price'    => false,
1755
            'custom_price'  => '',
1756
            'cart_index'    => false,
1757
        );
1758
        $args = wp_parse_args( $args, $defaults );
1759
1760
        // Bail if this post isn't a item
1761
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1762
            return false;
1763
        }
1764
        
1765
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1766
1767
        foreach ( $this->items as $key => $item ) {
1768
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1769
                continue;
1770
            }
1771
1772
            if ( false !== $args['cart_index'] ) {
1773
                $cart_index = absint( $args['cart_index'] );
1774
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1775
1776
                if ( ! empty( $cart_item ) ) {
1777
                    // If the cart index item isn't the same item ID, don't remove it
1778
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1779
                        continue;
1780
                    }
1781
                }
1782
            }
1783
1784
            $item_quantity = $this->items[ $key ]['quantity'];
1785
            if ( $item_quantity > $args['quantity'] ) {
1786
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1787
                break;
1788
            } else {
1789
                unset( $this->items[ $key ] );
1790
                break;
1791
            }
1792
        }
1793
1794
        $found_cart_key = false;
1795
        if ( false === $args['cart_index'] ) {
1796
            foreach ( $this->cart_details as $cart_key => $item ) {
1797
                if ( $item_id != $item['id'] ) {
1798
                    continue;
1799
                }
1800
1801
                if ( false !== $args['item_price'] ) {
1802
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1803
                        continue;
1804
                    }
1805
                }
1806
1807
                $found_cart_key = $cart_key;
1808
                break;
1809
            }
1810
        } else {
1811
            $cart_index = absint( $args['cart_index'] );
1812
1813
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1814
                return false; // Invalid cart index passed.
1815
            }
1816
1817
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1818
                return false; // We still need the proper Item ID to be sure.
1819
            }
1820
1821
            $found_cart_key = $cart_index;
1822
        }
1823
        
1824
        $cart_item  = $this->cart_details[$found_cart_key];
1825
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1826
        
1827
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1828
            return false; // Invoice must contain at least one item.
1829
        }
1830
        
1831
        $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...
1832
        
1833
        if ( $quantity > $args['quantity'] ) {
1834
            $item_price         = $cart_item['item_price'];
1835
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1836
            
1837
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1838
            $subtotal           = $item_price * $new_quantity;
1839
            
1840
            $args['quantity']   = $new_quantity;
1841
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1842
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1843
            
1844
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
1845
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1846
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
1847
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1848
            
1849
            // The total increase equals the number removed * the item_price
1850
            $total_decrease     = wpinv_round_amount( $item_price );
1851
            
1852
            if ( wpinv_prices_include_tax() ) {
1853
                $subtotal -= wpinv_round_amount( $tax );
1854
            }
1855
1856
            $total              = $subtotal - $discount + $tax;
1857
1858
            // Do not allow totals to go negative
1859
            if( $total < 0 ) {
1860
                $total = 0;
1861
            }
1862
            
1863
            $cart_item['quantity']  = $new_quantity;
1864
            $cart_item['subtotal']  = $subtotal;
1865
            $cart_item['discount']  = $discount;
1866
            $cart_item['tax']       = $tax;
1867
            $cart_item['price']     = $total;
1868
            
1869
            $added_item             = $cart_item;
1870
            $added_item['id']       = $item_id;
1871
            $added_item['price']    = $total_decrease;
1872
            $added_item['quantity'] = $args['quantity'];
1873
            
1874
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1875
            
1876
            $this->cart_details[$found_cart_key] = $cart_item;
1877
            
1878
            $remove_item = end( $this->cart_details );
1879
        } else {
1880
            $item_price     = $cart_item['item_price'];
1881
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1882
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1883
        
1884
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1885
            $tax_decrease       = $tax;
1886
1887
            unset( $this->cart_details[$found_cart_key] );
1888
            
1889
            $remove_item             = $args;
1890
            $remove_item['id']       = $item_id;
1891
            $remove_item['price']    = $subtotal_decrease;
1892
            $remove_item['quantity'] = $args['quantity'];
1893
        }
1894
        
1895
        $remove_item['action']      = 'remove';
1896
        $this->pending['items'][]   = $remove_item;
1897
               
1898
        $this->decrease_subtotal( $subtotal_decrease );
1899
        $this->decrease_tax( $tax_decrease );
1900
        
1901
        return true;
1902
    }
1903
    
1904
    public function update_items($temp = false) {
1905
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1906
        
1907
        if ( !empty( $this->cart_details ) ) {
1908
            $wpi_nosave             = $temp;
1909
            $cart_subtotal          = 0;
1910
            $cart_discount          = 0;
1911
            $cart_tax               = 0;
1912
            $cart_details           = array();
1913
            
1914
            $_POST['wpinv_country'] = $this->country;
1915
            $_POST['wpinv_state']   = $this->state;
1916
            
1917
            foreach ( $this->cart_details as $key => $item ) {
1918
                $item_price = $item['item_price'];
1919
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1920
                $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...
1921
                $subtotal   = $item_price * $quantity;
1922
                
1923
                $wpi_current_id         = $this->ID;
1924
                $wpi_item_id            = $item['id'];
1925
                
1926
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1927
                
1928
                $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...
1929
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1930
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1931
1932
                if ( wpinv_prices_include_tax() ) {
1933
                    $subtotal -= wpinv_round_amount( $tax );
1934
                }
1935
1936
                $total      = $subtotal - $discount + $tax;
1937
1938
                // Do not allow totals to go negative
1939
                if( $total < 0 ) {
1940
                    $total = 0;
1941
                }
1942
1943
                $cart_details[] = array(
1944
                    'id'          => $item['id'],
1945
                    'name'        => $item['name'],
1946
                    'item_price'  => wpinv_round_amount( $item_price ),
1947
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
1948
                    'quantity'    => $quantity,
1949
                    'discount'    => $discount,
1950
                    'subtotal'    => wpinv_round_amount( $subtotal ),
1951
                    'tax'         => wpinv_round_amount( $tax ),
1952
                    'price'       => wpinv_round_amount( $total ),
1953
                    'vat_rate'    => $tax_rate,
1954
                    'vat_class'   => $tax_class,
1955
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1956
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1957
                );
1958
                
1959
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1960
                $cart_discount  += (float)($discount);
1961
                $cart_tax       += (float)($tax);
1962
            }
1963
            if ( $cart_subtotal < 0 ) {
1964
                $cart_subtotal = 0;
1965
            }
1966
            if ( $cart_tax < 0 ) {
1967
                $cart_tax = 0;
1968
            }
1969
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
1970
            $this->tax      = wpinv_round_amount( $cart_tax );
1971
            $this->discount = wpinv_round_amount( $cart_discount );
1972
            
1973
            $this->recalculate_total();
1974
            
1975
            $this->cart_details = $cart_details;
1976
        }
1977
1978
        return $this;
1979
    }
1980
    
1981
    public function recalculate_totals($temp = false) {        
1982
        $this->update_items($temp);
1983
        $this->save( true );
1984
        
1985
        return $this;
1986
    }
1987
    
1988
    public function needs_payment() {
1989
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'wpi-pending' ), $this );
1990
1991
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() ) ) {
1992
            $needs_payment = true;
1993
        } else {
1994
            $needs_payment = false;
1995
        }
1996
1997
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1998
    }
1999
    
2000
    public function get_checkout_payment_url( $with_key = false, $secret = false ) {
2001
        $pay_url = wpinv_get_checkout_uri();
2002
2003
        if ( is_ssl() ) {
2004
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
2005
        }
2006
        
2007
        $key = $this->get_key();
2008
2009
        if ( $with_key ) {
2010
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
2011
        } else {
2012
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
2013
        }
2014
        
2015
        if ( $secret ) {
2016
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
2017
        }
2018
2019
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this, $with_key, $secret );
2020
    }
2021
    
2022
    public function get_view_url( $with_key = false ) {
2023
        $invoice_url = get_permalink( $this->ID );
2024
2025
        if ( $with_key ) {
2026
            $invoice_url = add_query_arg( 'invoice_key', $this->get_key(), $invoice_url );
2027
        }
2028
2029
        return apply_filters( 'wpinv_get_view_url', $invoice_url, $this, $with_key );
2030
    }
2031
    
2032
    public function generate_key( $string = '' ) {
2033
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2034
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2035
    }
2036
    
2037
    public function is_recurring() {
2038
        if ( empty( $this->cart_details ) ) {
2039
            return false;
2040
        }
2041
        
2042
        $has_subscription = false;
2043 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2044
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2045
                $has_subscription = true;
2046
                break;
2047
            }
2048
        }
2049
        
2050
        if ( count( $this->cart_details ) > 1 ) {
2051
            $has_subscription = false;
2052
        }
2053
2054
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2055
    }
2056
    
2057
    public function is_free_trial() {
2058
        $is_free_trial = false;
2059
        
2060
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2061
            if ( !empty( $item ) && $item->has_free_trial() ) {
2062
                $is_free_trial = true;
2063
            }
2064
        }
2065
2066
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details );
2067
    }
2068
    
2069
    public function get_recurring( $object = false ) {
2070
        $item = NULL;
2071
        
2072
        if ( empty( $this->cart_details ) ) {
2073
            return $item;
2074
        }
2075
        
2076 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2077
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2078
                $item = $cart_item['id'];
2079
                break;
2080
            }
2081
        }
2082
        
2083
        if ( $object ) {
2084
            $item = $item ? new WPInv_Item( $item ) : NULL;
2085
            
2086
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2087
        }
2088
2089
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2090
    }
2091
    
2092
    public function get_subscription_name() {
2093
        $item = $this->get_recurring( true );
2094
        
2095
        if ( empty( $item ) ) {
2096
            return NULL;
2097
        }
2098
        
2099
        if ( !($name = $item->get_name()) ) {
2100
            $name = $item->post_name;
2101
        }
2102
2103
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2104
    }
2105
    
2106
    public function get_subscription_id() {
2107
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2108
        
2109
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2110
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2111
            
2112
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2113
        }
2114
        
2115
        return $subscription_id;
2116
    }
2117
2118
    public function is_parent() {
2119
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2120
2121
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2122
    }
2123
    
2124
    public function is_renewal() {
2125
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2126
2127
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2128
    }
2129
    
2130
    public function get_parent_payment() {
2131
        $parent_payment = NULL;
2132
        
2133
        if ( $this->is_renewal() ) {
2134
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2135
        }
2136
        
2137
        return $parent_payment;
2138
    }
2139
    
2140
    public function is_paid() {
2141
        if ( $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) ) ) {
2142
            return true;
2143
        }
2144
        
2145
        return false;
2146
    }
2147
    
2148
    public function is_refunded() {
2149
        $is_refunded = $this->has_status( array( 'wpi-refunded' ) );
2150
2151
        return apply_filters( 'wpinv_invoice_is_refunded', $is_refunded, $this );
2152
    }
2153
    
2154
    public function is_free() {
2155
        $is_free = false;
2156
        
2157
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
2158
            if ( $this->is_parent() && $this->is_recurring() ) {
2159
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
2160
            } else {
2161
                $is_free = true;
2162
            }
2163
        }
2164
        
2165
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
2166
    }
2167
    
2168
    public function has_vat() {
2169
        global $wpinv_euvat, $wpi_country;
2170
        
2171
        $requires_vat = false;
2172
        
2173
        if ( $this->country ) {
2174
            $wpi_country        = $this->country;
2175
            
2176
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2177
        }
2178
        
2179
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2180
    }
2181
    
2182
    public function refresh_item_ids() {
2183
        $item_ids = array();
2184
        
2185
        if ( !empty( $this->cart_details ) ) {
2186
            foreach ( $this->cart_details as $key => $item ) {
2187
                if ( !empty( $item['id'] ) ) {
2188
                    $item_ids[] = $item['id'];
2189
                }
2190
            }
2191
        }
2192
        
2193
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2194
        
2195
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2196
    }
2197
    
2198
    public function get_invoice_quote_type( $post_id ) {
2199
        if ( empty( $post_id ) ) {
2200
            return '';
2201
        }
2202
2203
        $type = get_post_type( $post_id );
2204
2205
        if ( 'wpi_invoice' === $type ) {
2206
            $post_type = __('Invoice', 'invoicing');
2207
        } else{
2208
            $post_type = __('Quote', 'invoicing');
2209
        }
2210
2211
        return $post_type;
2212
    }
2213
}
2214