Passed
Push — master ( 85ebca...12017d )
by Stiofan
04:57 queued 01:32
created

WPInv_Invoice::get_meta()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 9
nop 2
dl 0
loc 20
rs 8.8571
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
    
18
    public $pending;
19
    public $items = array();
20
    public $user_info = array();
21
    public $payment_meta = array();
22
    
23
    public $new = false;
24
    public $number = '';
25
    public $mode = 'live';
26
    public $key = '';
27
    public $total = 0.00;
28
    public $subtotal = 0;
29
    public $tax = 0;
30
    public $fees = array();
31
    public $fees_total = 0;
32
    public $discounts = '';
33
    public $discount = 0;
34
    public $discount_code = 0;
35
    public $date = '';
36
    public $due_date = '';
37
    public $completed_date = '';
38
    public $status      = 'pending';
39
    public $post_status = 'pending';
40
    public $old_status = '';
41
    public $status_nicename = '';
42
    public $user_id = 0;
43
    public $first_name = '';
44
    public $last_name = '';
45
    public $email = '';
46
    public $phone = '';
47
    public $address = '';
48
    public $city = '';
49
    public $country = '';
50
    public $state = '';
51
    public $zip = '';
52
    public $transaction_id = '';
53
    public $ip = '';
54
    public $gateway = '';
55
    public $gateway_title = '';
56
    public $currency = '';
57
    public $cart_details = array();
58
    
59
    public $company = '';
60
    public $vat_number = '';
61
    public $vat_rate = '';
62
    public $adddress_confirmed = '';
63
    
64
    public $full_name = '';
65
    public $parent_invoice = 0;
66
    
67
    public function __construct( $invoice_id = false ) {
68
        if( empty( $invoice_id ) ) {
69
            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...
70
        }
71
72
        $this->setup_invoice( $invoice_id );
73
    }
74
75
    public function get( $key ) {
76
        if ( method_exists( $this, 'get_' . $key ) ) {
77
            $value = call_user_func( array( $this, 'get_' . $key ) );
78
        } else {
79
            $value = $this->$key;
80
        }
81
82
        return $value;
83
    }
84
85
    public function set( $key, $value ) {
86
        $ignore = array( 'items', 'cart_details', 'fees', '_ID' );
87
88
        if ( $key === 'status' ) {
89
            $this->old_status = $this->status;
90
        }
91
92
        if ( ! in_array( $key, $ignore ) ) {
93
            $this->pending[ $key ] = $value;
94
        }
95
96
        if( '_ID' !== $key ) {
97
            $this->$key = $value;
98
        }
99
    }
100
101
    public function _isset( $name ) {
102
        if ( property_exists( $this, $name) ) {
103
            return false === empty( $this->$name );
104
        } else {
105
            return null;
106
        }
107
    }
108
109
    private function setup_invoice( $invoice_id ) {
110
        $this->pending = array();
111
112
        if ( empty( $invoice_id ) ) {
113
            return false;
114
        }
115
116
        $invoice = get_post( $invoice_id );
117
118
        if( !$invoice || is_wp_error( $invoice ) ) {
119
            return false;
120
        }
121
122
        if( 'wpi_invoice' !== $invoice->post_type ) {
123
            return false;
124
        }
125
126
        do_action( 'wpinv_pre_setup_invoice', $this, $invoice_id );
127
        
128
        // Primary Identifier
129
        $this->ID              = absint( $invoice_id );
130
        
131
        // We have a payment, get the generic payment_meta item to reduce calls to it
132
        $this->payment_meta    = $this->get_meta();
133
        $this->date            = $invoice->post_date;
134
        $this->due_date        = $this->setup_due_date();
135
        $this->completed_date  = $this->setup_completed_date();
136
        $this->status          = $invoice->post_status;
137
        $this->post_status     = $this->status;
138
        $this->mode            = $this->setup_mode();
139
        $this->parent_invoice  = $invoice->post_parent;
140
        $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...
141
        $this->status_nicename = $this->setup_status_nicename();
142
143
        // Items
144
        $this->fees            = $this->setup_fees();
145
        $this->cart_details    = $this->setup_cart_details();
146
        $this->items           = $this->setup_items();
147
148
        // Currency Based
149
        $this->total           = $this->setup_total();
150
        $this->tax             = $this->setup_tax();
151
        $this->fees_total      = $this->get_fees_total();
152
        $this->subtotal        = $this->setup_subtotal();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->setup_subtotal() can also be of type double. However, the property $subtotal is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
153
        $this->currency        = $this->setup_currency();
154
        
155
        // Gateway based
156
        $this->gateway         = $this->setup_gateway();
157
        $this->gateway_title   = $this->setup_gateway_title();
158
        $this->transaction_id  = $this->setup_transaction_id();
159
        
160
        // User based
161
        $this->ip              = $this->setup_ip();
162
        $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...
163
        $this->email           = get_the_author_meta( 'email', $this->user_id );
164
        
165
        $this->user_info       = $this->setup_user_info();
166
                
167
        $this->first_name      = $this->user_info['first_name'];
168
        $this->last_name       = $this->user_info['last_name'];
169
        $this->company         = $this->user_info['company'];
170
        $this->vat_number      = $this->user_info['vat_number'];
171
        $this->vat_rate        = $this->user_info['vat_rate'];
172
        $this->adddress_confirmed  = $this->user_info['adddress_confirmed'];
173
        $this->address         = $this->user_info['address'];
174
        $this->city            = $this->user_info['city'];
175
        $this->country         = $this->user_info['country'];
176
        $this->state           = $this->user_info['state'];
177
        $this->zip             = $this->user_info['zip'];
178
        $this->phone           = $this->user_info['phone'];
179
        
180
        $this->discounts       = $this->user_info['discount'];
181
            $this->discount        = $this->setup_discount();
182
            $this->discount_code   = $this->setup_discount_code();
183
184
        // Other Identifiers
185
        $this->key             = $this->setup_invoice_key();
186
        $this->number          = $this->setup_invoice_number();
187
        $this->title           = !empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
188
        
189
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
190
        
191
        // Allow extensions to add items to this object via hook
192
        do_action( 'wpinv_setup_invoice', $this, $invoice_id );
193
194
        return true;
195
    }
196
    
197
    private function setup_status_nicename() {
198
        $all_invoice_statuses  = wpinv_get_invoice_statuses();
199
        
200
        $status = array_key_exists( $this->status, $all_invoice_statuses ) ? $all_invoice_statuses[$this->status] : ucfirst( $this->status );
201
202
        return $status;
203
    }
204
    
205
    private function setup_post_name( $post = NULL ) {
206
        $post_name = '';
207
        
208
        if ( !empty( $post ) ) {
209
            if( !empty( $post->post_name ) ) {
210
                $post_name = $post->post_name;
211
            } else if ( !empty( $post->ID ) && !empty( $post->post_title ) ) {
212
                $post_name = sanitize_title( $post->post_title );
213
                
214
                global $wpdb;
215
                $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
216
            }
217
        }
218
219
        $this->post_name   = $post_name;
220
    }
221
    
222
    private function setup_due_date() {
223
        $due_date = $this->get_meta( '_wpinv_due_date' );
224
        
225
        if ( empty( $due_date ) ) {
226
            $overdue_time = strtotime( $this->date ) + ( DAY_IN_SECONDS * absint( wpinv_get_option( 'overdue_days' ) ) );
227
            $due_date = date_i18n( 'Y-m-d', $overdue_time );
228
        } else if ( $due_date == 'none' ) {
229
            $due_date = '';
230
        }
231
        
232
        return $due_date;
233
    }
234
    
235
    private function setup_completed_date() {
236
        $invoice = get_post( $this->ID );
237
238
        if ( 'pending' == $invoice->post_status || 'preapproved' == $invoice->post_status ) {
239
            return false; // This invoice was never paid
240
        }
241
242
        $date = ( $date = $this->get_meta( '_wpinv_completed_date', true ) ) ? $date : $invoice->modified_date;
243
244
        return $date;
245
    }
246
    
247
    private function setup_cart_details() {
248
        $cart_details = isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
249
        return $cart_details;
250
    }
251
    
252
    public function array_convert() {
253
        return get_object_vars( $this );
254
    }
255
    
256
    private function setup_items() {
257
        $items = isset( $this->payment_meta['items'] ) ? maybe_unserialize( $this->payment_meta['items'] ) : array();
258
        return $items;
259
    }
260
    
261
    private function setup_fees() {
262
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
263
        return $payment_fees;
264
    }
265
        
266
    private function setup_currency() {
267
        $currency = isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'wpinv_currency_default', wpinv_get_currency(), $this );
268
        return $currency;
269
    }
270
    
271
    private function setup_discount() {
272
        //$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...
273
        $discount = $this->subtotal - ( $this->total - $this->tax - $this->fees_total );
274
        if ( $discount < 0 ) {
275
            $discount = 0;
276
        }
277
        $discount = wpinv_format_amount( $discount, NULL, true );
278
        
279
        return $discount;
280
    }
281
    
282
    private function setup_discount_code() {
283
        $discount_code = !empty( $this->discounts ) ? $this->discounts : $this->get_meta( '_wpinv_discount_code', true );
284
        return $discount_code;
285
    }
286
    
287
    private function setup_tax() {
288
        $tax = $this->get_meta( '_wpinv_tax', true );
289
290
        // We don't have tax as it's own meta and no meta was passed
291
        if ( '' === $tax ) {            
292
            $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
293
        }
294
295
        return $tax;
296
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% 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...
297
        $tax = $this->get_meta( '_wpinv_tax', true );
298
        return $tax;
299
        */
300
    }
301
302
    private function setup_subtotal() {
303
        $subtotal     = 0;
304
        $cart_details = $this->cart_details;
305
306
        if ( is_array( $cart_details ) ) {
307
            foreach ( $cart_details as $item ) {
308
                if ( isset( $item['subtotal'] ) ) {
309
                    $subtotal += $item['subtotal'];
310
                }
311
            }
312
        } else {
313
            $subtotal  = $this->total;
314
            $tax       = wpinv_use_taxes() ? $this->tax : 0;
315
            $subtotal -= $tax;
316
        }
317
318
        return $subtotal;
319
    }
320
    
321
    private function setup_discounts() {
322
        $discounts = ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
323
        return $discounts;
324
    }
325
    
326
    private function setup_total() {
327
        $amount = $this->get_meta( '_wpinv_total', true );
328
329
        if ( empty( $amount ) && '0.00' != $amount ) {
330
            $meta   = $this->get_meta( '_wpinv_payment_meta', true );
331
            $meta   = maybe_unserialize( $meta );
332
333
            if ( isset( $meta['amount'] ) ) {
334
                $amount = $meta['amount'];
335
            }
336
        }
337
338
        return $amount;
339
    }
340
    
341
    private function setup_mode() {
342
        return $this->get_meta( '_wpinv_mode' );
343
    }
344
345
    private function setup_gateway() {
346
        $gateway = $this->get_meta( '_wpinv_gateway' );
347
        
348
        if ( empty( $gateway ) && 'publish' === $this->status || 'complete' === $this->status ) {
349
            $gateway = 'manual';
350
        }
351
        
352
        return $gateway;
353
    }
354
    
355
    private function setup_gateway_title() {
356
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
357
        return $gateway_title;
358
    }
359
360
    private function setup_transaction_id() {
361
        $transaction_id = $this->get_meta( '_wpinv_transaction_id' );
362
363
        if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
364
            $gateway        = $this->gateway;
365
            $transaction_id = apply_filters( 'wpinv_get_invoice_transaction_id-' . $gateway, $this->ID );
366
        }
367
368
        return $transaction_id;
369
    }
370
371
    private function setup_ip() {
372
        $ip = $this->get_meta( '_wpinv_user_ip' );
373
        return $ip;
374
    }
375
376
    ///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...
377
        ///$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...
378
        ///return $user_id;
379
    ///}
380
        
381
    private function setup_first_name() {
382
        $first_name = $this->get_meta( '_wpinv_first_name' );
383
        return $first_name;
384
    }
385
    
386
    private function setup_last_name() {
387
        $last_name = $this->get_meta( '_wpinv_last_name' );
388
        return $last_name;
389
    }
390
    
391
    private function setup_company() {
392
        $company = $this->get_meta( '_wpinv_company' );
393
        return $company;
394
    }
395
    
396
    private function setup_vat_number() {
397
        $vat_number = $this->get_meta( '_wpinv_vat_number' );
398
        return $vat_number;
399
    }
400
    
401
    private function setup_vat_rate() {
402
        $vat_rate = $this->get_meta( '_wpinv_vat_rate' );
403
        return $vat_rate;
404
    }
405
    
406
    private function setup_adddress_confirmed() {
407
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed' );
408
        return $adddress_confirmed;
409
    }
410
    
411
    private function setup_phone() {
412
        $phone = $this->get_meta( '_wpinv_phone' );
413
        return $phone;
414
    }
415
    
416
    private function setup_address() {
417
        $address = $this->get_meta( '_wpinv_address', true );
418
        return $address;
419
    }
420
    
421
    private function setup_city() {
422
        $city = $this->get_meta( '_wpinv_city', true );
423
        return $city;
424
    }
425
    
426
    private function setup_country() {
427
        $country = $this->get_meta( '_wpinv_country', true );
428
        return $country;
429
    }
430
    
431
    private function setup_state() {
432
        $state = $this->get_meta( '_wpinv_state', true );
433
        return $state;
434
    }
435
    
436
    private function setup_zip() {
437
        $zip = $this->get_meta( '_wpinv_zip', true );
438
        return $zip;
439
    }
440
441
    private function setup_user_info() {
442
        $defaults = array(
443
            'user_id'        => $this->user_id,
444
            'first_name'     => $this->first_name,
445
            'last_name'      => $this->last_name,
446
            'email'          => get_the_author_meta( 'email', $this->user_id ),
447
            'phone'          => $this->phone,
448
            'address'        => $this->address,
449
            'city'           => $this->city,
450
            'country'        => $this->country,
451
            'state'          => $this->state,
452
            'zip'            => $this->zip,
453
            'company'        => $this->company,
454
            'vat_number'     => $this->vat_number,
455
            'vat_rate'       => $this->vat_rate,
456
            'adddress_confirmed' => $this->adddress_confirmed,
457
            'discount'       => $this->discounts,
458
        );
459
        
460
        $user_info = array();
461
        if ( isset( $this->payment_meta['user_info'] ) ) {
462
            $user_info = maybe_unserialize( $this->payment_meta['user_info'] );
463
            
464
            if ( !empty( $user_info ) && isset( $user_info['user_id'] ) && $post = get_post( $this->ID ) ) {
465
                $this->user_id = $post->post_author;
466
                $this->email = get_the_author_meta( 'email', $this->user_id );
467
                
468
                $user_info['user_id'] = $this->user_id;
469
                $user_info['email'] = $this->email;
470
                $this->payment_meta['user_id'] = $this->user_id;
471
                $this->payment_meta['email'] = $this->email;
472
            }
473
        }
474
        
475
        $user_info    = wp_parse_args( $user_info, $defaults );
476
        
477
        // Get the user, but only if it's been created
478
        $user = get_userdata( $this->user_id );
479
        
480
        if ( !empty( $user ) && $user->ID > 0 ) {
481
            if ( empty( $user_info ) ) {
482
                $user_info = array(
483
                    'user_id'    => $user->ID,
484
                    'first_name' => $user->first_name,
485
                    'last_name'  => $user->last_name,
486
                    'email'      => $user->user_email,
487
                    'discount'   => '',
488
                );
489
            } else {
490
                foreach ( $user_info as $key => $value ) {
491
                    if ( ! empty( $value ) ) {
492
                        continue;
493
                    }
494
495
                    switch( $key ) {
496
                        case 'user_id':
497
                            $user_info[ $key ] = $user->ID;
498
                            break;
499
                        case 'first_name':
500
                            $user_info[ $key ] = $user->first_name;
501
                            break;
502
                        case 'last_name':
503
                            $user_info[ $key ] = $user->last_name;
504
                            break;
505
                        case 'email':
506
                            $user_info[ $key ] = $user->user_email;
507
                            break;
508
                    }
509
                }
510
            }
511
        }
512
513
        return $user_info;
514
    }
515
516
    private function setup_invoice_key() {
517
        $key = $this->get_meta( '_wpinv_key', true );
518
        
519
        return $key;
520
    }
521
522
    private function setup_invoice_number() {
523
        $number = $this->get_meta( '_wpinv_number', true );
524
525
        if ( !$number ) {
526
            $number = wpinv_format_invoice_number( $this->ID );
527
        }
528
529
        return $number;
530
    }
531
    
532
    private function insert_invoice() {
533
        $invoice_title = '';
0 ignored issues
show
Unused Code introduced by
$invoice_title 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...
534
535
        if ($number = $this->get_number()) {
536
            $invoice_title = $number;
537
        } else if ( ! empty( $this->ID ) ) {
538
            $invoice_title = wpinv_format_invoice_number( $this->ID );
539
        } else {
540
            $invoice_title = wpinv_format_invoice_number( 0 );
541
        }
542
543 View Code Duplication
        if ( empty( $this->key ) ) {
544
            $this->key = self::generate_key();
545
            $this->pending['key'] = $this->key;
546
        }
547
548
        if ( empty( $this->ip ) ) {
549
            $this->ip = wpinv_get_ip();
550
            $this->pending['ip'] = $this->ip;
551
        }
552
        
553
        $payment_data = array(
554
            'price'        => $this->total,
555
            'date'         => $this->date,
556
            'user_email'   => $this->email,
557
            'invoice_key'  => $this->key,
558
            'currency'     => $this->currency,
559
            'items'        => $this->items,
560
            'user_info' => array(
561
                'user_id'    => $this->user_id,
562
                'email'      => $this->email,
563
                'first_name' => $this->first_name,
564
                'last_name'  => $this->last_name,
565
                'address'    => $this->address,
566
                'phone'      => $this->phone,
567
                'city'       => $this->city,
568
                'country'    => $this->country,
569
                'state'      => $this->state,
570
                'zip'        => $this->zip,
571
                'company'    => $this->company,
572
                'vat_number' => $this->vat_number,
573
                'discount'   => $this->discounts,
574
            ),
575
            'cart_details' => $this->cart_details,
576
            'status'       => $this->status,
577
            'fees'         => $this->fees,
578
        );
579
        
580
        $post_name      = sanitize_title( $invoice_title );
581
582
        $post_data = array(
583
                        'post_title'    => $invoice_title,
584
                        'post_status'   => $this->status,
585
                        'post_author'   => $this->user_id,
586
                        'post_type'     => 'wpi_invoice',
587
                        'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
588
                        'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
589
                        'post_parent'   => $this->parent_invoice,
590
                    );
591
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
592
593
        // Create a blank invoice
594
        if ( !empty( $this->ID ) ) {
595
            $args['ID']         = $this->ID;
596
            $args['post_name']  = $post_name;
597
            
598
            $invoice_id = wp_update_post( $args );
599
        } else {
600
            $invoice_id = wp_insert_post( $args );
601
            
602
            $post_title = wpinv_format_invoice_number( $invoice_id );
603
            global $wpdb;
604
            $wpdb->update( $wpdb->posts, array( 'post_title' => $post_title, 'post_name' => sanitize_title( $post_title ) ), array( 'ID' => $invoice_id ) );
605
            clean_post_cache( $invoice_id );
606
        }
607
608
        if ( !empty( $invoice_id ) ) {             
609
            $this->ID  = $invoice_id;
610
            $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...
611
            
612
            ///$this->pending['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...
613
            if ( isset( $this->pending['number'] ) ) {
614
                $this->pending['number'] = $post_name;
615
            }
616
            
617
            $this->payment_meta = apply_filters( 'wpinv_payment_meta', $this->payment_meta, $payment_data );
618
            if ( ! empty( $this->payment_meta['fees'] ) ) {
619
                $this->fees = array_merge( $this->fees, $this->payment_meta['fees'] );
620
                foreach( $this->fees as $fee ) {
621
                    $this->increase_fees( $fee['amount'] );
622
                }
623
            }
624
625
            $this->update_meta( '_wpinv_payment_meta', $this->payment_meta );            
626
            $this->new = true;
627
        }
628
629
        return $this->ID;
630
    }
631
632
    public function save( $setup = false ) {
633
        global $wpi_session;
634
        
635
        $saved = false;
636
        if ( empty( $this->items ) ) {
637
            return $saved; // Don't save empty invoice.
638
        }
639
        
640 View Code Duplication
        if ( empty( $this->key ) ) {
641
            $this->key = self::generate_key();
642
            $this->pending['key'] = $this->key;
643
        }
644
        
645
        if ( empty( $this->ID ) ) {
646
            $invoice_id = $this->insert_invoice();
647
648
            if ( false === $invoice_id ) {
649
                $saved = false;
650
            } else {
651
                $this->ID = $invoice_id;
652
            }
653
        }        
654
655
        // If we have something pending, let's save it
656
        if ( !empty( $this->pending ) ) {
657
            $total_increase = 0;
658
            $total_decrease = 0;
659
660
            foreach ( $this->pending as $key => $value ) {
661
                switch( $key ) {
662
                    case 'items':
663
                        // Update totals for pending items
664
                        foreach ( $this->pending[ $key ] as $item ) {
665
                            switch( $item['action'] ) {
666
                                case 'add':
667
                                    $price = $item['price'];
668
                                    $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...
669
670 View Code Duplication
                                    if ( 'publish' === $this->status || 'complete' === $this->status || 'revoked' === $this->status ) {
671
                                        $total_increase += $price;
672
                                    }
673
                                    break;
674
675
                                case 'remove':
676 View Code Duplication
                                    if ( 'publish' === $this->status || 'complete' === $this->status || 'revoked' === $this->status ) {
677
                                        $total_decrease += $item['price'];
678
                                    }
679
                                    break;
680
                            }
681
                        }
682
                        break;
683
                    case 'fees':
684
                        if ( 'publish' !== $this->status && 'complete' !== $this->status && 'revoked' !== $this->status ) {
685
                            break;
686
                        }
687
688
                        if ( empty( $this->pending[ $key ] ) ) {
689
                            break;
690
                        }
691
692
                        foreach ( $this->pending[ $key ] as $fee ) {
693
                            switch( $fee['action'] ) {
694
                                case 'add':
695
                                    $total_increase += $fee['amount'];
696
                                    break;
697
698
                                case 'remove':
699
                                    $total_decrease += $fee['amount'];
700
                                    break;
701
                            }
702
                        }
703
                        break;
704
                    case 'status':
705
                        $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...
706
                        break;
707
                    case 'gateway':
708
                        $this->update_meta( '_wpinv_gateway', $this->gateway );
709
                        break;
710
                    case 'mode':
711
                        $this->update_meta( '_wpinv_mode', $this->mode );
712
                        break;
713
                    case 'transaction_id':
714
                        $this->update_meta( '_wpinv_transaction_id', $this->transaction_id );
715
                        break;
716
                    case 'ip':
717
                        $this->update_meta( '_wpinv_user_ip', $this->ip );
718
                        break;
719
                    ///case 'user_id':
720
                        ///$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...
721
                        ///$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...
722
                        ///break;
723
                    case 'first_name':
724
                        $this->update_meta( '_wpinv_first_name', $this->first_name );
725
                        $this->user_info['first_name'] = $this->first_name;
726
                        break;
727
                    case 'last_name':
728
                        $this->update_meta( '_wpinv_last_name', $this->last_name );
729
                        $this->user_info['last_name'] = $this->last_name;
730
                        break;
731
                    case 'phone':
732
                        $this->update_meta( '_wpinv_phone', $this->phone );
733
                        $this->user_info['phone'] = $this->phone;
734
                        break;
735
                    case 'address':
736
                        $this->update_meta( '_wpinv_address', $this->address );
737
                        $this->user_info['address'] = $this->address;
738
                        break;
739
                    case 'city':
740
                        $this->update_meta( '_wpinv_city', $this->city );
741
                        $this->user_info['city'] = $this->city;
742
                        break;
743
                    case 'country':
744
                        $this->update_meta( '_wpinv_country', $this->country );
745
                        $this->user_info['country'] = $this->country;
746
                        break;
747
                    case 'state':
748
                        $this->update_meta( '_wpinv_state', $this->state );
749
                        $this->user_info['state'] = $this->state;
750
                        break;
751
                    case 'zip':
752
                        $this->update_meta( '_wpinv_zip', $this->zip );
753
                        $this->user_info['zip'] = $this->zip;
754
                        break;
755
                    case 'company':
756
                        $this->update_meta( '_wpinv_company', $this->company );
757
                        $this->user_info['company'] = $this->company;
758
                        break;
759
                    case 'vat_number':
760
                        $this->update_meta( '_wpinv_vat_number', $this->vat_number );
761
                        $this->user_info['vat_number'] = $this->vat_number;
762
                        
763
                        $vat_info = $wpi_session->get( 'user_vat_data' );
764
                        if ( $this->vat_number && !empty( $vat_info ) && isset( $vat_info['number'] ) && isset( $vat_info['valid'] ) && $vat_info['number'] == $this->vat_number ) {
765
                            $adddress_confirmed = isset( $vat_info['adddress_confirmed'] ) ? $vat_info['adddress_confirmed'] : false;
766
                            $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...
767
                            $this->user_info['adddress_confirmed'] = (bool)$adddress_confirmed;
768
                        }
769
    
770
                        break;
771
                    case 'vat_rate':
772
                        $this->update_meta( '_wpinv_vat_rate', $this->vat_rate );
773
                        $this->user_info['vat_rate'] = $this->vat_rate;
774
                        break;
775
                    case 'adddress_confirmed':
776
                        $this->update_meta( '_wpinv_adddress_confirmed', $this->adddress_confirmed );
777
                        $this->user_info['adddress_confirmed'] = $this->adddress_confirmed;
778
                        break;
779
                    
780
                    case 'key':
781
                        $this->update_meta( '_wpinv_key', $this->key );
782
                        break;
783
                    case 'number':
784
                        $this->update_meta( '_wpinv_number', $this->number );
785
                        break;
786
                    case 'date':
787
                        $args = array(
788
                            'ID'        => $this->ID,
789
                            'post_date' => $this->date,
790
                            'edit_date' => true,
791
                        );
792
793
                        wp_update_post( $args );
794
                        break;
795
                    case 'due_date':
796
                        if ( empty( $this->due_date ) ) {
797
                            $this->due_date = 'none';
798
                        }
799
                        
800
                        $this->update_meta( '_wpinv_due_date', $this->due_date );
801
                        break;
802
                    case 'completed_date':
803
                        $this->update_meta( '_wpinv_completed_date', $this->completed_date );
804
                        break;
805
                    case 'discounts':
806
                        if ( ! is_array( $this->discounts ) ) {
807
                            $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...
808
                        }
809
810
                        $this->user_info['discount'] = implode( ',', $this->discounts );
811
                        break;
812
                        
813
                    //case 'tax':
814
                        //$this->update_meta( '_wpinv_tax', wpinv_format_amount( $this->tax, NULL, true ) );
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...
815
                        //break;
816
                    case 'discount':
817
                        $this->update_meta( '_wpinv_discount', wpinv_format_amount( $this->discount, NULL, true ) );
818
                        break;
819
                    case 'discount_code':
820
                        $this->update_meta( '_wpinv_discount_code', $this->discount_code );
821
                        break;
822
                    //case 'fees':
823
                        //$this->update_meta( '_wpinv_fees', $this->fees );
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% 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...
824
                        //break;
825
                    case 'parent_invoice':
826
                        $args = array(
827
                            'ID'          => $this->ID,
828
                            'post_parent' => $this->parent_invoice,
829
                        );
830
                        wp_update_post( $args );
831
                        break;
832
                    default:
833
                        do_action( 'wpinv_save', $this, $key );
834
                        break;
835
                }
836
            }       
837
838
            $this->update_meta( '_wpinv_subtotal', wpinv_format_amount( $this->subtotal, NULL, true ) );
839
            $this->update_meta( '_wpinv_total', wpinv_format_amount( $this->total, NULL, true ) );
840
            $this->update_meta( '_wpinv_tax', wpinv_format_amount( $this->tax, NULL, true ) );
841
            
842
            $this->items    = array_values( $this->items );
843
            
844
            $new_meta = array(
845
                'items'         => $this->items,
846
                'cart_details'  => $this->cart_details,
847
                'fees'          => $this->fees,
848
                'currency'      => $this->currency,
849
                'user_info'     => $this->user_info,
850
            );
851
            
852
            $meta        = $this->get_meta();
853
            $merged_meta = array_merge( $meta, $new_meta );
854
855
            // Only save the payment meta if it's changed
856
            if ( md5( serialize( $meta ) ) !== md5( serialize( $merged_meta) ) ) {
857
                $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...
858
                if ( false !== $updated ) {
859
                    $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...
860
                }
861
            }
862
863
            $this->pending = array();
864
            $saved         = true;
865
        } else {
866
            $this->update_meta( '_wpinv_subtotal', wpinv_format_amount( $this->subtotal, NULL, true ) );
867
            $this->update_meta( '_wpinv_total', wpinv_format_amount( $this->total, NULL, true ) );
868
            $this->update_meta( '_wpinv_tax', wpinv_format_amount( $this->tax, NULL, true ) );
869
        }
870
        
871
        do_action( 'wpinv_invoice_save', $this, $saved );
872
873
        if ( true === $saved || $setup ) {
874
            $this->setup_invoice( $this->ID );
875
        }
876
        
877
        $this->refresh_item_ids();
878
        
879
        return $saved;
880
    }
881
    
882
    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...
883
        $default_args = array(
884
            'label'       => '',
885
            'amount'      => 0,
886
            'type'        => 'fee',
887
            'id'          => '',
888
            'no_tax'      => false,
889
            'item_id'     => 0,
890
        );
891
892
        $fee = wp_parse_args( $args, $default_args );
893
        
894
        if ( !empty( $fee['label'] ) ) {
895
            return false;
896
        }
897
        
898
        $fee['id']  = sanitize_title( $fee['label'] );
899
        
900
        $this->fees[]               = $fee;
901
        
902
        $added_fee               = $fee;
903
        $added_fee['action']     = 'add';
904
        $this->pending['fees'][] = $added_fee;
905
        reset( $this->fees );
906
907
        $this->increase_fees( $fee['amount'] );
908
        return true;
909
    }
910
911
    public function remove_fee( $key ) {
912
        $removed = false;
913
914
        if ( is_numeric( $key ) ) {
915
            $removed = $this->remove_fee_by( 'index', $key );
916
        }
917
918
        return $removed;
919
    }
920
921
    public function remove_fee_by( $key, $value, $global = false ) {
922
        $allowed_fee_keys = apply_filters( 'wpinv_fee_keys', array(
923
            'index', 'label', 'amount', 'type',
924
        ) );
925
926
        if ( ! in_array( $key, $allowed_fee_keys ) ) {
927
            return false;
928
        }
929
930
        $removed = false;
931
        if ( 'index' === $key && array_key_exists( $value, $this->fees ) ) {
932
            $removed_fee             = $this->fees[ $value ];
933
            $removed_fee['action']   = 'remove';
934
            $this->pending['fees'][] = $removed_fee;
935
936
            $this->decrease_fees( $removed_fee['amount'] );
937
938
            unset( $this->fees[ $value ] );
939
            $removed = true;
940
        } else if ( 'index' !== $key ) {
941
            foreach ( $this->fees as $index => $fee ) {
942
                if ( isset( $fee[ $key ] ) && $fee[ $key ] == $value ) {
943
                    $removed_fee             = $fee;
944
                    $removed_fee['action']   = 'remove';
945
                    $this->pending['fees'][] = $removed_fee;
946
947
                    $this->decrease_fees( $removed_fee['amount'] );
948
949
                    unset( $this->fees[ $index ] );
950
                    $removed = true;
951
952
                    if ( false === $global ) {
953
                        break;
954
                    }
955
                }
956
            }
957
        }
958
959
        if ( true === $removed ) {
960
            $this->fees = array_values( $this->fees );
961
        }
962
963
        return $removed;
964
    }
965
966
    
967
968
    public function add_note( $note = '', $customer_type = false, $added_by_user = false, $system = false ) {
969
        // Bail if no note specified
970
        if( !$note ) {
971
            return false;
972
        }
973
974
        if ( empty( $this->ID ) )
975
            return false;
976
        
977
        if ( ( ( is_user_logged_in() && current_user_can( 'manage_options' ) ) || $added_by_user ) && !$system ) {
978
            $user                 = get_user_by( 'id', get_current_user_id() );
979
            $comment_author       = $user->display_name;
980
            $comment_author_email = $user->user_email;
981
        } else {
982
            $comment_author       = __( 'System', 'invoicing' );
983
            $comment_author_email = strtolower( __( 'System', 'invoicing' ) ) . '@';
984
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
985
            $comment_author_email = sanitize_email( $comment_author_email );
986
        }
987
988
        do_action( 'wpinv_pre_insert_invoice_note', $this->ID, $note, $customer_type );
989
990
        $note_id = wp_insert_comment( wp_filter_comment( array(
991
            'comment_post_ID'      => $this->ID,
992
            'comment_content'      => $note,
993
            'comment_agent'        => 'GeoDirectory',
994
            'user_id'              => is_admin() ? get_current_user_id() : 0,
995
            'comment_date'         => current_time( 'mysql' ),
996
            'comment_date_gmt'     => current_time( 'mysql', 1 ),
997
            'comment_approved'     => 1,
998
            'comment_parent'       => 0,
999
            'comment_author'       => $comment_author,
1000
            'comment_author_IP'    => wpinv_get_ip(),
1001
            'comment_author_url'   => '',
1002
            'comment_author_email' => $comment_author_email,
1003
            'comment_type'         => 'wpinv_note'
1004
        ) ) );
1005
1006
        do_action( 'wpinv_insert_payment_note', $note_id, $this->ID, $note );
1007
        
1008
        if ( $customer_type ) {
1009
            add_comment_meta( $note_id, '_wpi_customer_note', 1 );
1010
1011
            do_action( 'wpinv_new_customer_note', array( 'invoice_id' => $this->ID, 'user_note' => $note ) );
1012
        }
1013
1014
        return $note_id;
1015
    }
1016
1017 View Code Duplication
    private function increase_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...
1018
        $amount          = (float) $amount;
1019
        $this->subtotal += $amount;
1020
        $this->subtotal  = wpinv_format_amount( $this->subtotal, NULL, true );
1021
1022
        $this->recalculate_total();
1023
    }
1024
1025 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...
1026
        $amount          = (float) $amount;
1027
        $this->subtotal -= $amount;
1028
        $this->subtotal  = wpinv_format_amount( $this->subtotal, NULL, true );
1029
1030
        if ( $this->subtotal < 0 ) {
1031
            $this->subtotal = 0;
1032
        }
1033
1034
        $this->recalculate_total();
1035
    }
1036
1037 View Code Duplication
    private function increase_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...
1038
        $amount            = (float)$amount;
1039
        $this->fees_total += $amount;
1040
        $this->fees_total  = wpinv_format_amount( $this->fees_total, NULL, true );
1041
1042
        $this->recalculate_total();
1043
    }
1044
1045 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...
1046
        $amount            = (float) $amount;
1047
        $this->fees_total -= $amount;
1048
        $this->fees_total  = wpinv_format_amount( $this->fees_total, NULL, true );
1049
1050
        if ( $this->fees_total < 0 ) {
1051
            $this->fees_total = 0;
1052
        }
1053
1054
        $this->recalculate_total();
1055
    }
1056
1057
    public function recalculate_total() {
1058
        global $wpi_nosave;
1059
        
1060
        $this->total = $this->subtotal + $this->tax + $this->fees_total;
1061
        $this->total = wpinv_format_amount( $this->total, NULL, true );
1062
        
1063
        do_action( 'wpinv_invoice_recalculate_total', $this, $wpi_nosave );
1064
    }
1065
    
1066
    public function increase_tax( $amount = 0.00 ) {
1067
        $amount       = (float) $amount;
1068
        $this->tax   += $amount;
1069
1070
        $this->recalculate_total();
1071
    }
1072
1073
    public function decrease_tax( $amount = 0.00 ) {
1074
        $amount     = (float) $amount;
1075
        $this->tax -= $amount;
1076
1077
        if ( $this->tax < 0 ) {
1078
            $this->tax = 0;
1079
        }
1080
1081
        $this->recalculate_total();
1082
    }
1083
1084
    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...
1085
        $old_status = ! empty( $this->old_status ) ? $this->old_status : get_post_status( $this->ID );
1086
        
1087
        if ( $old_status === $new_status && in_array( $new_status, array_keys( wpinv_get_invoice_statuses() ) ) ) {
1088
            return false; // Don't permit status changes that aren't changes
1089
        }
1090
1091
        $do_change = apply_filters( 'wpinv_should_update_invoice_status', true, $this->ID, $new_status, $old_status );
1092
        $updated = false;
1093
1094
        if ( $do_change ) {
1095
            do_action( 'wpinv_before_invoice_status_change', $this->ID, $new_status, $old_status );
1096
1097
            $update_post_data                   = array();
1098
            $update_post_data['ID']             = $this->ID;
1099
            $update_post_data['post_status']    = $new_status;
1100
            $update_post_data['edit_date']      = current_time( 'mysql', 0 );
1101
            $update_post_data['edit_date_gmt']  = current_time( 'mysql', 1 );
1102
            
1103
            $update_post_data = apply_filters( 'wpinv_update_invoice_status_fields', $update_post_data, $this->ID );
1104
1105
            $updated = wp_update_post( $update_post_data );     
1106
           
1107
            // Process any specific status functions
1108
            switch( $new_status ) {
1109
                case 'refunded':
1110
                    $this->process_refund();
1111
                    break;
1112
                case 'failed':
1113
                    $this->process_failure();
1114
                    break;
1115
                case 'pending':
1116
                    $this->process_pending();
1117
                    break;
1118
            }
1119
            
1120
            // Status was changed.
1121
            do_action( 'wpinv_status_' . $new_status, $this->ID, $old_status );
1122
            do_action( 'wpinv_status_' . $old_status . '_to_' . $new_status, $this->ID, $old_status );
1123
            do_action( 'wpinv_update_status', $this->ID, $new_status, $old_status );
1124
        }
1125
1126
        return $updated;
1127
    }
1128
1129
    public function refund() {
1130
        $this->old_status        = $this->status;
1131
        $this->status            = 'refunded';
1132
        $this->pending['status'] = $this->status;
1133
1134
        $this->save();
1135
    }
1136
1137
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
1138
        if ( empty( $meta_key ) ) {
1139
            return false;
1140
        }
1141
1142
        if ( $meta_key == 'key' || $meta_key == 'date' ) {
1143
            $current_meta = $this->get_meta();
1144
            $current_meta[ $meta_key ] = $meta_value;
1145
1146
            $meta_key     = '_wpinv_payment_meta';
1147
            $meta_value   = $current_meta;
1148
        }
1149
1150
        $meta_value = apply_filters( 'wpinv_update_payment_meta_' . $meta_key, $meta_value, $this->ID );
1151
        
1152
        if ( $meta_key == '_wpinv_completed_date' && !empty( $meta_value ) ) {
1153
            $args = array(
1154
                'ID'                => $this->ID,
1155
                'post_date'         => $meta_value,
1156
                'edit_date'         => true,
1157
                'post_date_gmt'     => get_gmt_from_date( $meta_value ),
1158
                'post_modified'     => $meta_value,
1159
                'post_modified_gmt' => get_gmt_from_date( $meta_value )
1160
            );
1161
            wp_update_post( $args );
1162
        }
1163
        
1164
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
1165
    }
1166
1167
    private function process_refund() {
1168
        $process_refund = true;
1169
1170
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
1171 View Code Duplication
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'refunded' != $this->status ) {
1172
            $process_refund = false;
1173
        }
1174
1175
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1176
        $process_refund = apply_filters( 'wpinv_should_process_refund', $process_refund, $this );
1177
1178
        if ( false === $process_refund ) {
1179
            return;
1180
        }
1181
1182
        do_action( 'wpinv_pre_refund_invoice', $this );
1183
        
1184
        $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...
1185
        $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...
1186
        $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...
1187
        
1188
        do_action( 'wpinv_post_refund_invoice', $this );
1189
    }
1190
1191
    private function process_failure() {
1192
        $discounts = $this->discounts;
1193
        if ( empty( $discounts ) ) {
1194
            return;
1195
        }
1196
1197
        if ( ! is_array( $discounts ) ) {
1198
            $discounts = array_map( 'trim', explode( ',', $discounts ) );
1199
        }
1200
1201
        foreach ( $discounts as $discount ) {
1202
            wpinv_decrease_discount_usage( $discount );
1203
        }
1204
    }
1205
    
1206
    private function process_pending() {
1207
        $process_pending = true;
1208
1209
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
1210 View Code Duplication
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'pending' != $this->status ) {
1211
            $process_pending = false;
1212
        }
1213
1214
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1215
        $process_pending = apply_filters( 'wpinv_should_process_pending', $process_pending, $this );
1216
1217
        if ( false === $process_pending ) {
1218
            return;
1219
        }
1220
1221
        $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...
1222
        $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...
1223
        $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...
1224
1225
        $this->completed_date = '';
1226
        $this->update_meta( '_wpinv_completed_date', '' );
1227
    }
1228
    
1229
    // get data
1230
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
1231
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1232
1233
        if ( $meta_key === '_wpinv_payment_meta' ) {
1234
1235
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
1236
1237
            if ( empty( $meta['key'] ) ) {
1238
                $meta['key'] = $this->setup_invoice_key();
1239
            }
1240
1241
            if ( empty( $meta['date'] ) ) {
1242
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1243
            }
1244
        }
1245
1246
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1247
1248
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1249
    }
1250
    
1251
    public function get_description() {
1252
        $post = get_post( $this->ID );
1253
        
1254
        $description = !empty( $post ) ? $post->post_content : '';
1255
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1256
    }
1257
    
1258
    public function get_status( $nicename = false ) {
1259
        if ( !$nicename ) {
1260
            $status = $this->status;
1261
        } else {
1262
            $status = $this->status_nicename;
1263
        }
1264
        
1265
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1266
    }
1267
    
1268
    public function get_cart_details() {
1269
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1270
    }
1271
    
1272 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...
1273
        $subtotal = wpinv_format_amount( $this->subtotal, NULL, !$currency );
1274
        
1275
        if ( $currency ) {
1276
            $subtotal = wpinv_price( $subtotal, $this->get_currency() );
1277
        }
1278
        
1279
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1280
    }
1281
    
1282
    public function get_total( $currency = false ) {        
1283
        if ( $this->is_free_trial() ) {
1284
            $total = wpinv_format_amount( 0, NULL, !$currency );
1285
        } else {
1286
            $total = wpinv_format_amount( $this->total, NULL, !$currency );
1287
        }
1288
        if ( $currency ) {
1289
            $total = wpinv_price( $total, $this->get_currency() );
1290
        }
1291
        
1292
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1293
    }
1294
    
1295
    public function get_recurring_details( $field = '', $currency = false ) {        
1296
        $data                 = array();
1297
        $data['cart_details'] = $this->cart_details;
1298
        $data['subtotal']     = $this->get_subtotal();
1299
        $data['discount']     = $this->get_discount();
1300
        $data['tax']          = $this->get_tax();
1301
        $data['total']        = $this->get_total();
1302
    
1303
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1304
            $is_free_trial = $this->is_free_trial();
1305
            $discounts = $this->get_discounts( true );
1306
            
1307
            if ( $is_free_trial || !empty( $discounts ) ) {
1308
                $first_use_only = false;
1309
                
1310
                if ( !empty( $discounts ) ) {
1311
                    foreach ( $discounts as $key => $code ) {
1312
                        if ( wpinv_discount_is_recurring( $code, true ) ) {
1313
                            $first_use_only = true;
1314
                            break;
1315
                        }
1316
                    }
1317
                }
1318
                    
1319
                if ( !$first_use_only ) {
1320
                    $data['subtotal'] = wpinv_format_amount( $this->subtotal, NULL, true );
1321
                    $data['discount'] = wpinv_format_amount( $this->discount, NULL, true );
1322
                    $data['tax']      = wpinv_format_amount( $this->tax, NULL, true );
1323
                    $data['total']    = wpinv_format_amount( $this->total, NULL, true );
1324
                } else {
1325
                    $cart_subtotal   = 0;
1326
                    $cart_discount   = 0;
1327
                    $cart_tax        = 0;
1328
1329
                    foreach ( $this->cart_details as $key => $item ) {
1330
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1331
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1332
                        $item_discount  = 0;
1333
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1334
                        
1335
                        if ( wpinv_prices_include_tax() ) {
1336
                            $item_subtotal -= wpinv_format_amount( $item_tax, NULL, true );
1337
                        }
1338
                        
1339
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1340
                        // Do not allow totals to go negative
1341
                        if ( $item_total < 0 ) {
1342
                            $item_total = 0;
1343
                        }
1344
                        
1345
                        $cart_subtotal  += (float)($item_subtotal);
1346
                        $cart_discount  += (float)($item_discount);
1347
                        $cart_tax       += (float)($item_tax);
1348
                        
1349
                        $data['cart_details'][$key]['discount']   = wpinv_format_amount( $item_discount, NULL, true );
1350
                        $data['cart_details'][$key]['tax']        = wpinv_format_amount( $item_tax, NULL, true );
1351
                        $data['cart_details'][$key]['price']      = wpinv_format_amount( $item_total, NULL, true );
1352
                    }
1353
                    
1354
                    $data['subtotal'] = wpinv_format_amount( $cart_subtotal, NULL, true );
1355
                    $data['discount'] = wpinv_format_amount( $cart_discount, NULL, true );
1356
                    $data['tax']      = wpinv_format_amount( $cart_tax, NULL, true );
1357
                    $data['total']    = wpinv_format_amount( ( $data['subtotal'] + $data['tax'] ), NULL, true );
1358
                }
1359
            }
1360
        }
1361
        
1362
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1363
1364
        if ( isset( $data[$field] ) ) {
1365
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1366
        }
1367
        
1368
        return $data;
1369
    }
1370
    
1371 View Code Duplication
    public function get_final_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...
1372
        $final_total = wpinv_format_amount( $this->tax, NULL, !$currency );
1373
        if ( $currency ) {
1374
            $final_total = wpinv_price( $final_total, $this->get_currency() );
1375
        }
1376
        
1377
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1378
    }
1379
    
1380
    public function get_discounts( $array = false ) {
1381
        $discounts = $this->discounts;
1382
        if ( $array && $discounts ) {
1383
            $discounts = explode( ',', $discounts );
1384
        }
1385
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1386
    }
1387
    
1388
    public function get_discount( $currency = false, $dash = false ) {
1389
        if ( !empty( $this->discounts ) ) {
1390
            global $ajax_cart_details;
1391
            $ajax_cart_details = $this->get_cart_details();
1392
1393
            $this->discount = wpinv_get_cart_items_discount_amount( $this->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...
1394
        }
1395
        $discount   = wpinv_format_amount( $this->discount, NULL, !$currency );
1396
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1397
        
1398
        if ( $currency ) {
1399
            $discount = wpinv_price( $discount, $this->get_currency() );
1400
        }
1401
        
1402
        $discount   = $dash . $discount;
1403
        
1404
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1405
    }
1406
    
1407
    public function get_discount_code() {
1408
        return $this->discount_code;
1409
    }
1410
    
1411 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...
1412
        $tax = wpinv_format_amount( $this->tax, NULL, !$currency );
1413
        
1414
        if ( $currency ) {
1415
            $tax = wpinv_price( $tax, $this->get_currency() );
1416
        }
1417
        
1418
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1419
    }
1420
    
1421
    public function get_fees( $type = 'all' ) {
1422
        $fees    = array();
1423
1424
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1425
            foreach ( $this->fees as $fee ) {
1426 View Code Duplication
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1427
                    continue;
1428
                }
1429
1430
                $fee['label'] = stripslashes( $fee['label'] );
1431
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1432
                $fees[]    = $fee;
1433
            }
1434
        }
1435
1436
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1437
    }
1438
    
1439
    public function get_fees_total( $type = 'all' ) {
1440
        $fees_total = (float) 0.00;
1441
1442
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1443
        if ( ! empty( $payment_fees ) ) {
1444
            foreach ( $payment_fees as $fee ) {
1445
                $fees_total += (float) $fee['amount'];
1446
            }
1447
        }
1448
1449
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1450
        /*
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...
1451
        $fees = $this->get_fees( $type );
1452
1453
        $fees_total = 0;
1454
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1455
            foreach ( $fees as $fee_id => $fee ) {
1456
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1457
                    continue;
1458
                }
1459
1460
                $fees_total += $fee['amount'];
1461
            }
1462
        }
1463
1464
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1465
        */
1466
    }
1467
1468
    public function get_user_id() {
1469
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1470
    }
1471
    
1472
    public function get_first_name() {
1473
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1474
    }
1475
    
1476
    public function get_last_name() {
1477
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1478
    }
1479
    
1480
    public function get_user_full_name() {
1481
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1482
    }
1483
    
1484
    public function get_user_info() {
1485
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1486
    }
1487
    
1488
    public function get_email() {
1489
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1490
    }
1491
    
1492
    public function get_address() {
1493
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1494
    }
1495
    
1496
    public function get_phone() {
1497
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1498
    }
1499
    
1500
    public function get_number() {
1501
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1502
    }
1503
    
1504
    public function get_items() {
1505
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1506
    }
1507
    
1508
    public function get_key() {
1509
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1510
    }
1511
    
1512
    public function get_transaction_id() {
1513
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1514
    }
1515
    
1516
    public function get_gateway() {
1517
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1518
    }
1519
    
1520
    public function get_gateway_title() {
1521
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1522
        
1523
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1524
    }
1525
    
1526
    public function get_currency() {
1527
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1528
    }
1529
    
1530
    public function get_created_date() {
1531
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1532
    }
1533
    
1534
    public function get_due_date( $display = false ) {
1535
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1536
        
1537
        if ( !$display || empty( $due_date ) ) {
1538
            return $due_date;
1539
        }
1540
        
1541
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
1542
    }
1543
    
1544
    public function get_completed_date() {
1545
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1546
    }
1547
    
1548
    public function get_invoice_date( $formatted = true ) {
1549
        $date_completed = $this->completed_date;
1550
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1551
        
1552
        if ( $invoice_date == '' ) {
1553
            $date_created   = $this->date;
1554
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1555
        }
1556
        
1557
        if ( $formatted && $invoice_date ) {
1558
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
1559
        }
1560
1561
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1562
    }
1563
    
1564
    public function get_ip() {
1565
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1566
    }
1567
        
1568
    public function has_status( $status ) {
1569
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1570
    }
1571
    
1572
    public function add_item( $item_id = 0, $args = array() ) {
1573
        global $wpi_current_id, $wpi_item_id;
1574
        
1575
        $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...
1576
1577
        // Bail if this post isn't a item
1578
        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...
1579
            return false;
1580
        }
1581
        
1582
        $has_quantities = wpinv_item_quantities_enabled();
1583
1584
        // Set some defaults
1585
        $defaults = array(
1586
            'quantity'  => 1,
1587
            'id'        => false,
1588
            'name'      => $item->get_name(),
1589
            'item_price'=> false,
1590
            'discount'  => 0,
1591
            'tax'       => 0.00,
1592
            'meta'      => array(),
1593
            'fees'      => array()
1594
        );
1595
1596
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1597
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1598
1599
        $wpi_current_id         = $this->ID;
1600
        $wpi_item_id            = $item->ID;
1601
        $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...
1602
        
1603
        $_POST['wpinv_country'] = $this->country;
1604
        $_POST['wpinv_state']   = $this->state;
1605
        
1606
        $found_cart_key         = false;
1607
        
1608
        if ($has_quantities) {
1609
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1610
            
1611
            foreach ( $this->items as $key => $cart_item ) {
1612
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1613
                    continue;
1614
                }
1615
1616
                $this->items[ $key ]['quantity'] += $args['quantity'];
1617
                break;
1618
            }
1619
            
1620
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1621
                if ( $item_id != $cart_item['id'] ) {
1622
                    continue;
1623
                }
1624
1625
                $found_cart_key = $cart_key;
1626
                break;
1627
            }
1628
        }
1629
        
1630
        if ($has_quantities && $found_cart_key !== false) {
1631
            $cart_item          = $this->cart_details[$found_cart_key];
1632
            $item_price         = $cart_item['item_price'];
1633
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1634
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1635
            
1636
            $new_quantity       = $quantity + $args['quantity'];
1637
            $subtotal           = $item_price * $new_quantity;
1638
            
1639
            $args['quantity']   = $new_quantity;
1640
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1641
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1642
            
1643
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1644
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1645
            // The total increase equals the number removed * the item_price
1646
            $total_increased    = wpinv_format_amount( $item_price, NULL, true );
1647
            
1648
            if ( wpinv_prices_include_tax() ) {
1649
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1650
            }
1651
1652
            $total              = $subtotal - $discount + $tax;
1653
1654
            // Do not allow totals to go negative
1655
            if( $total < 0 ) {
1656
                $total = 0;
1657
            }
1658
            
1659
            $cart_item['quantity']  = $new_quantity;
1660
            $cart_item['subtotal']  = $subtotal;
1661
            $cart_item['discount']  = $discount;
1662
            $cart_item['tax']       = $tax;
1663
            $cart_item['price']     = $total;
1664
            
1665
            $subtotal               = $total_increased - $discount_increased;
1666
            $tax                    = $tax_increased;
1667
            
1668
            $this->cart_details[$found_cart_key] = $cart_item;
1669
        } else {
1670
            // Allow overriding the price
1671
            if( false !== $args['item_price'] ) {
1672
                $item_price = $args['item_price'];
1673
            } else {
1674
                $item_price = wpinv_get_item_price( $item->ID );
1675
            }
1676
1677
            // Sanitizing the price here so we don't have a dozen calls later
1678
            $item_price = wpinv_sanitize_amount( $item_price );
1679
            $subtotal   = wpinv_format_amount( $item_price * $args['quantity'], NULL, true );
1680
        
1681
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1682
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1683
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1684
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1685
1686
            // Setup the items meta item
1687
            $new_item = array(
1688
                'id'       => $item->ID,
1689
                'quantity' => $args['quantity'],
1690
            );
1691
1692
            $this->items[]  = $new_item;
1693
1694
            if ( wpinv_prices_include_tax() ) {
1695
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1696
            }
1697
1698
            $total      = $subtotal - $discount + $tax;
1699
1700
            // Do not allow totals to go negative
1701
            if( $total < 0 ) {
1702
                $total = 0;
1703
            }
1704
        
1705
            $this->cart_details[] = array(
1706
                'name'        => !empty($args['name']) ? $args['name'] : $item->get_name(),
1707
                'id'          => $item->ID,
1708
                'item_price'  => wpinv_format_amount( $item_price, NULL, true ),
1709
                'quantity'    => $args['quantity'],
1710
                'discount'    => $discount,
1711
                'subtotal'    => wpinv_format_amount( $subtotal, NULL, true ),
1712
                'tax'         => wpinv_format_amount( $tax, NULL, true ),
1713
                'price'       => wpinv_format_amount( $total, NULL, true ),
1714
                'vat_rate'    => $tax_rate,
1715
                'vat_class'   => $tax_class,
1716
                'meta'        => $args['meta'],
1717
                'fees'        => $args['fees'],
1718
            );
1719
                        
1720
            $subtotal = $subtotal - $discount;
1721
        }
1722
        
1723
        $added_item = end( $this->cart_details );
1724
        $added_item['action']  = 'add';
1725
        
1726
        $this->pending['items'][] = $added_item;
1727
        
1728
        $this->increase_subtotal( $subtotal );
1729
        $this->increase_tax( $tax );
1730
1731
        return true;
1732
    }
1733
    
1734
    public function remove_item( $item_id, $args = array() ) {
1735
        // Set some defaults
1736
        $defaults = array(
1737
            'quantity'   => 1,
1738
            'item_price' => false,
1739
            'cart_index' => false,
1740
        );
1741
        $args = wp_parse_args( $args, $defaults );
1742
1743
        // Bail if this post isn't a item
1744
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1745
            return false;
1746
        }
1747
        
1748
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1749
1750
        foreach ( $this->items as $key => $item ) {
1751
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1752
                continue;
1753
            }
1754
1755
            if ( false !== $args['cart_index'] ) {
1756
                $cart_index = absint( $args['cart_index'] );
1757
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1758
1759
                if ( ! empty( $cart_item ) ) {
1760
                    // If the cart index item isn't the same item ID, don't remove it
1761
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1762
                        continue;
1763
                    }
1764
                }
1765
            }
1766
1767
            $item_quantity = $this->items[ $key ]['quantity'];
1768
            if ( $item_quantity > $args['quantity'] ) {
1769
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1770
                break;
1771
            } else {
1772
                unset( $this->items[ $key ] );
1773
                break;
1774
            }
1775
        }
1776
1777
        $found_cart_key = false;
1778
        if ( false === $args['cart_index'] ) {
1779
            foreach ( $this->cart_details as $cart_key => $item ) {
1780
                if ( $item_id != $item['id'] ) {
1781
                    continue;
1782
                }
1783
1784
                if ( false !== $args['item_price'] ) {
1785
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1786
                        continue;
1787
                    }
1788
                }
1789
1790
                $found_cart_key = $cart_key;
1791
                break;
1792
            }
1793
        } else {
1794
            $cart_index = absint( $args['cart_index'] );
1795
1796
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1797
                return false; // Invalid cart index passed.
1798
            }
1799
1800
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1801
                return false; // We still need the proper Item ID to be sure.
1802
            }
1803
1804
            $found_cart_key = $cart_index;
1805
        }
1806
        
1807
        $cart_item  = $this->cart_details[$found_cart_key];
1808
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1809
        
1810
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1811
            return false; // Invoice must contain at least one item.
1812
        }
1813
        
1814
        $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...
1815
        
1816
        if ( $quantity > $args['quantity'] ) {
1817
            $item_price         = $cart_item['item_price'];
1818
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1819
            
1820
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1821
            $subtotal           = $item_price * $new_quantity;
1822
            
1823
            $args['quantity']   = $new_quantity;
1824
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1825
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1826
            
1827
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_format_amount( ( (float)$cart_item['discount'] / $quantity ), NULL, true ) : 0;
1828
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1829
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_format_amount( ( (float)$cart_item['tax'] / $quantity ), NULL, true ) : 0;
1830
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1831
            
1832
            // The total increase equals the number removed * the item_price
1833
            $total_decrease     = wpinv_format_amount( $item_price, NULL, true );
1834
            
1835
            if ( wpinv_prices_include_tax() ) {
1836
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1837
            }
1838
1839
            $total              = $subtotal - $discount + $tax;
1840
1841
            // Do not allow totals to go negative
1842
            if( $total < 0 ) {
1843
                $total = 0;
1844
            }
1845
            
1846
            $cart_item['quantity']  = $new_quantity;
1847
            $cart_item['subtotal']  = $subtotal;
1848
            $cart_item['discount']  = $discount;
1849
            $cart_item['tax']       = $tax;
1850
            $cart_item['price']     = $total;
1851
            
1852
            $added_item             = $cart_item;
1853
            $added_item['id']       = $item_id;
1854
            $added_item['price']    = $total_decrease;
1855
            $added_item['quantity'] = $args['quantity'];
1856
            
1857
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1858
            
1859
            $this->cart_details[$found_cart_key] = $cart_item;
1860
            
1861
            $remove_item = end( $this->cart_details );
1862
        } else {
1863
            $item_price     = $cart_item['item_price'];
1864
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1865
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1866
        
1867
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1868
            $tax_decrease       = $tax;
1869
1870
            unset( $this->cart_details[$found_cart_key] );
1871
            
1872
            $remove_item             = $args;
1873
            $remove_item['id']       = $item_id;
1874
            $remove_item['price']    = $subtotal_decrease;
1875
            $remove_item['quantity'] = $args['quantity'];
1876
        }
1877
        
1878
        $remove_item['action']      = 'remove';
1879
        $this->pending['items'][]   = $remove_item;
1880
               
1881
        $this->decrease_subtotal( $subtotal_decrease );
1882
        $this->decrease_tax( $tax_decrease );
1883
        
1884
        return true;
1885
    }
1886
    
1887
    public function update_items($temp = false) {
1888
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1889
        
1890
        if ( !empty( $this->cart_details ) ) {
1891
            $wpi_nosave             = $temp;
1892
            $cart_subtotal          = 0;
1893
            $cart_discount          = 0;
1894
            $cart_tax               = 0;
1895
            $cart_details           = array();
1896
            
1897
            $_POST['wpinv_country'] = $this->country;
1898
            $_POST['wpinv_state']   = $this->state;
1899
            
1900
            foreach ( $this->cart_details as $key => $item ) {
1901
                $item_price = $item['item_price'];
1902
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1903
                $amount     = wpinv_format_amount( $item_price * $quantity, NULL, true );
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...
1904
                $subtotal   = $item_price * $quantity;
1905
                
1906
                $wpi_current_id         = $this->ID;
1907
                $wpi_item_id            = $item['id'];
1908
                
1909
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1910
                
1911
                $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...
1912
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1913
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1914
1915
                if ( wpinv_prices_include_tax() ) {
1916
                    $subtotal -= wpinv_format_amount( $tax, NULL, true );
1917
                }
1918
1919
                $total      = $subtotal - $discount + $tax;
1920
1921
                // Do not allow totals to go negative
1922
                if( $total < 0 ) {
1923
                    $total = 0;
1924
                }
1925
1926
                $cart_details[] = array(
1927
                    'id'          => $item['id'],
1928
                    'name'        => $item['name'],
1929
                    'item_price'  => wpinv_format_amount( $item_price, NULL, true ),
1930
                    'quantity'    => $quantity,
1931
                    'discount'    => $discount,
1932
                    'subtotal'    => wpinv_format_amount( $subtotal, NULL, true ),
1933
                    'tax'         => wpinv_format_amount( $tax, NULL, true ),
1934
                    'price'       => wpinv_format_amount( $total, NULL, true ),
1935
                    'vat_rate'    => $tax_rate,
1936
                    'vat_class'   => $tax_class,
1937
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1938
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1939
                );
1940
                
1941
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1942
                $cart_discount  += (float)($discount);
1943
                $cart_tax       += (float)($tax);
1944
            }
1945
            $this->subtotal = wpinv_format_amount( $cart_subtotal, NULL, true );
1946
            $this->tax      = wpinv_format_amount( $cart_tax, NULL, true );
1947
            $this->discount = wpinv_format_amount( $cart_discount, NULL, true );
1948
            
1949
            $this->recalculate_total();
1950
            
1951
            $this->cart_details = $cart_details;
1952
        }
1953
1954
        return $this;
1955
    }
1956
    
1957
    public function recalculate_totals($temp = false) {        
1958
        $this->update_items($temp);
1959
        $this->save( true );
1960
        
1961
        return $this;
1962
    }
1963
    
1964
    public function needs_payment() {
1965
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'pending' ), $this );
1966
1967
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() ) ) {
1968
            $needs_payment = true;
1969
        } else {
1970
            $needs_payment = false;
1971
        }
1972
1973
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1974
    }
1975
    
1976
    public function get_checkout_payment_url( $on_checkout = false, $secret = false ) {
1977
        $pay_url = wpinv_get_checkout_uri();
1978
1979
        if ( is_ssl() ) {
1980
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
1981
        }
1982
        
1983
        $key = $this->get_key();
1984
1985
        if ( $on_checkout ) {
1986
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
1987
        } else {
1988
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
1989
        }
1990
        
1991 View Code Duplication
        if ( $secret ) {
1992
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
1993
        }
1994
1995
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this );
1996
    }
1997
    
1998
    public function get_view_url( $secret = false ) {
1999
        $print_url = get_permalink( $this->ID );
2000
        
2001 View Code Duplication
        if ( $secret ) {
2002
            $print_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $this->get_key() ) ), $print_url );
2003
        }
2004
2005
        return apply_filters( 'wpinv_get_view_url', $print_url, $this );
2006
    }
2007
    
2008
    public function generate_key( $string = '' ) {
2009
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2010
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2011
    }
2012
    
2013
    public function is_recurring() {
2014
        if ( empty( $this->cart_details ) ) {
2015
            return false;
2016
        }
2017
        
2018
        $has_subscription = false;
2019 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2020
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2021
                $has_subscription = true;
2022
                break;
2023
            }
2024
        }
2025
        
2026
        if ( count( $this->cart_details ) > 1 ) {
2027
            $has_subscription = false;
2028
        }
2029
2030
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2031
    }
2032
    
2033
    public function is_free_trial() {
2034
        $is_free_trial = false;
2035
        
2036
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2037
            if ( !empty( $item ) && $item->has_free_trial() ) {
2038
                $is_free_trial = true;
2039
            }
2040
        }
2041
2042
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details );
2043
    }
2044
    
2045
    public function get_recurring( $object = false ) {
2046
        $item = NULL;
2047
        
2048
        if ( empty( $this->cart_details ) ) {
2049
            return $item;
2050
        }
2051
        
2052 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2053
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2054
                $item = $cart_item['id'];
2055
                break;
2056
            }
2057
        }
2058
        
2059
        if ( $object ) {
2060
            $item = $item ? new WPInv_Item( $item ) : NULL;
2061
            
2062
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2063
        }
2064
2065
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2066
    }
2067
    
2068
    public function get_subscription_name() {
2069
        $item = $this->get_recurring( true );
2070
        
2071
        if ( empty( $item ) ) {
2072
            return NULL;
2073
        }
2074
        
2075
        if ( !($name = $item->get_name()) ) {
2076
            $name = $item->post_name;
2077
        }
2078
2079
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2080
    }
2081
        
2082
    public function get_expiration() {
2083
        $expiration = $this->get_meta( '_wpinv_subscr_expiration', true );
2084
        return $expiration;
2085
    }
2086
    
2087
    public function get_cancelled_date( $formatted = true ) {
2088
        $cancelled_date = $this->get_subscription_status() == 'cancelled' ? $this->get_meta( '_wpinv_subscr_cancelled_on', true ) : '';
2089
        
2090
        if ( $formatted && $cancelled_date ) {
2091
            $cancelled_date = date_i18n( get_option( 'date_format' ), strtotime( $cancelled_date ) );
2092
        }
2093
        
2094
        return $cancelled_date;
2095
    }
2096
    
2097
    public function get_trial_end_date( $formatted = true ) {
2098
        if ( !$this->is_free_trial() || !$this->is_paid() ) {
2099
            return NULL;
2100
        }
2101
        
2102
        $trial_end_date = $this->get_subscription_status() == 'trialing' ? $this->get_meta( '_wpinv_subscr_trial_end', true ) : '';
2103
        
2104
        if ( empty( $trial_end_date ) ) {
2105
            $trial_start_time = strtotime( $this->get_subscription_start() );
2106
            $trial_start_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2107
            
2108
            $trial_end_date = date_i18n( 'Y-m-d H:i:s', $trial_start_time );
2109
        }
2110
        
2111
        if ( $formatted && $trial_end_date ) {
2112
            $trial_end_date = date_i18n( get_option( 'date_format' ), strtotime( $trial_end_date ) );
2113
        }
2114
        
2115
        return $trial_end_date;
2116
    }
2117
    
2118
    public function get_subscription_created( $default = true ) {
2119
        $created = $this->get_meta( '_wpinv_subscr_created', true );
2120
        
2121
        if ( empty( $created ) && $default ) {
2122
            $created = $this->date;
2123
        }
2124
        return $created;
2125
    }
2126
    
2127
    public function get_subscription_start( $formatted = true ) {
2128
        if ( !$this->is_paid() ) {
2129
            return '-';
2130
        }
2131
        $start   = $this->get_subscription_created();
2132
        
2133
        if ( $formatted ) {
2134
            $date = date_i18n( get_option( 'date_format' ), strtotime( $start ) );
2135
        } else {
2136
            $date = date_i18n( 'Y-m-d H:i:s', strtotime( $start ) );
2137
        }
2138
2139
        return $date;
2140
    }
2141
    
2142
    public function get_subscription_end( $formatted = true ) {
2143
        if ( !$this->is_paid() ) {
2144
            return '-';
2145
        }
2146
        $start          = $this->get_subscription_created();
2147
        $interval       = $this->get_subscription_interval();
2148
        $period         = $this->get_subscription_period( true );
2149
        $bill_times     = (int)$this->get_bill_times();
2150
        
2151
        if ( $bill_times == 0 ) {
2152
            return $formatted ? __( 'Until cancelled', 'invoicing' ) : $bill_times;
2153
        }
2154
        
2155
        $total_period = $start . '+' . ( $interval * $bill_times ) . ' ' . $period;
0 ignored issues
show
Unused Code introduced by
$total_period 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...
2156
        
2157
        $end_time = strtotime( $start . '+' . ( $interval * $bill_times ) . ' ' . $period );
2158
        
2159
        if ( $this->is_free_trial() ) {
2160
            $end_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2161
        }
2162
        
2163
        if ( $formatted ) {
2164
            $date = date_i18n( get_option( 'date_format' ), $end_time );
2165
        } else {
2166
            $date = date_i18n( 'Y-m-d H:i:s', $end_time );
2167
        }
2168
2169
        return $date;
2170
    }
2171
    
2172
    public function get_expiration_time() {
2173
        return strtotime( $this->get_expiration(), current_time( 'timestamp' ) );
2174
    }
2175
    
2176
    public function get_original_invoice_id() {        
2177
        return $this->parent_invoice;
2178
    }
2179
    
2180
    public function get_bill_times() {
2181
        $subscription_data = $this->get_subscription_data();
2182
        return $subscription_data['bill_times'];
2183
    }
2184
2185
    public function get_child_payments( $self = false ) {
2186
        $invoices = get_posts( array(
2187
            'post_type'         => 'wpi_invoice',
2188
            'post_parent'       => (int)$this->ID,
2189
            'posts_per_page'    => '999',
2190
            'post_status'       => array( 'publish', 'complete', 'processing', 'renewal' ),
2191
            'orderby'           => 'ID',
2192
            'order'             => 'DESC',
2193
            'fields'            => 'ids'
2194
        ) );
2195
        
2196
        if ( $this->is_free_trial() ) {
2197
            $self = false;
2198
        }
2199
        
2200
        if ( $self && $this->is_paid() ) {
2201
            if ( !empty( $invoices ) ) {
2202
                $invoices[] = (int)$this->ID;
2203
            } else {
2204
                $invoices = array( $this->ID );
2205
            }
2206
            
2207
            $invoices = array_unique( $invoices );
2208
        }
2209
2210
        return $invoices;
2211
    }
2212
2213
    public function get_total_payments( $self = true ) {
2214
        return count( $this->get_child_payments( $self ) );
2215
    }
2216
    
2217
    public function get_subscriptions( $limit = -1 ) {
2218
        $subscriptions = wpinv_get_subscriptions( array( 'parent_invoice_id' => $this->ID, 'numberposts' => $limit ) );
2219
2220
        return $subscriptions;
2221
    }
2222
    
2223
    public function get_subscription_id() {
2224
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2225
        
2226
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2227
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2228
            
2229
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2230
        }
2231
        
2232
        return $subscription_id;
2233
    }
2234
    
2235
    public function get_subscription_status() {
2236
        $subscription_status = $this->get_meta( '_wpinv_subscr_status', true );
2237
2238
        if ( empty( $subscription_status ) ) {
2239
            $status = 'pending';
2240
            
2241
            if ( $this->is_paid() ) {        
2242
                $bill_times   = (int)$this->get_bill_times();
2243
                $times_billed = (int)$this->get_total_payments();
2244
                $expiration = $this->get_subscription_end( false );
2245
                $expired = $bill_times != 0 && $expiration != '' && $expiration != '-' && strtotime( date_i18n( 'Y-m-d', strtotime( $expiration ) ) ) < strtotime( date_i18n( 'Y-m-d', current_time( 'timestamp' ) ) ) ? true : false;
2246
                
2247
                if ( (int)$bill_times == 0 ) {
2248
                    $status = $expired ? 'expired' : 'active';
2249
                } else if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2250
                    $status = 'completed';
2251
                } else if ( $expired ) {
2252
                    $status = 'expired';
2253
                } else if ( $bill_times > 0 ) {
2254
                    $status = 'active';
2255
                } else {
2256
                    $status = 'pending';
2257
                }
2258
            }
2259
            
2260
            if ( $status && $status != $subscription_status ) {
2261
                $subscription_status = $status;
2262
                
2263
                $this->update_meta( '_wpinv_subscr_status', $status );
2264
            }
2265
        }
2266
        
2267
        return $subscription_status;
2268
    }
2269
    
2270
    public function get_subscription_status_label( $status = '' ) {
2271
        $status = !empty( $status ) ? $status : $this->get_subscription_status();
2272
2273
        switch( $status ) {
2274
            case 'active' :
2275
                $status_label = __( 'Active', 'invoicing' );
2276
                break;
2277
2278
            case 'cancelled' :
2279
                $status_label = __( 'Cancelled', 'invoicing' );
2280
                break;
2281
                
2282
            case 'completed' :
2283
                $status_label = __( 'Completed', 'invoicing' );
2284
                break;
2285
2286
            case 'expired' :
2287
                $status_label = __( 'Expired', 'invoicing' );
2288
                break;
2289
2290
            case 'pending' :
2291
                $status_label = __( 'Pending', 'invoicing' );
2292
                break;
2293
2294
            case 'failing' :
2295
                $status_label = __( 'Failing', 'invoicing' );
2296
                break;
2297
                
2298
            case 'stopped' :
2299
                $status_label = __( 'Stopped', 'invoicing' );
2300
                break;
2301
                
2302
            case 'trialing' :
2303
                $status_label = __( 'Trialing', 'invoicing' );
2304
                break;
2305
2306
            default:
2307
                $status_label = $status;
2308
                break;
2309
        }
2310
2311
        return $status_label;
2312
    }
2313
    
2314 View Code Duplication
    public function get_subscription_period( $full = 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...
2315
        $period = $this->get_meta( '_wpinv_subscr_period', true );
2316
        
2317
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2318
            $period = 'D';
2319
        }
2320
        
2321
        if ( $full ) {
2322
            switch( $period ) {
2323
                case 'D':
2324
                    $period = 'day';
2325
                break;
2326
                case 'W':
2327
                    $period = 'week';
2328
                break;
2329
                case 'M':
2330
                    $period = 'month';
2331
                break;
2332
                case 'Y':
2333
                    $period = 'year';
2334
                break;
2335
            }
2336
        }
2337
        
2338
        return $period;
2339
    }
2340
    
2341
    public function get_subscription_interval() {
2342
        $interval = (int)$this->get_meta( '_wpinv_subscr_interval', true );
2343
        
2344
        if ( !$interval > 0 ) {
2345
            $interval = 1;
2346
        }
2347
        
2348
        return $interval;
2349
    }
2350
    
2351 View Code Duplication
    public function get_subscription_trial_period( $full = 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...
2352
        if ( !$this->is_free_trial() ) {
2353
            return '';
2354
        }
2355
        
2356
        $period = $this->get_meta( '_wpinv_subscr_trial_period', true );
2357
        
2358
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2359
            $period = 'D';
2360
        }
2361
        
2362
        if ( $full ) {
2363
            switch( $period ) {
2364
                case 'D':
2365
                    $period = 'day';
2366
                break;
2367
                case 'W':
2368
                    $period = 'week';
2369
                break;
2370
                case 'M':
2371
                    $period = 'month';
2372
                break;
2373
                case 'Y':
2374
                    $period = 'year';
2375
                break;
2376
            }
2377
        }
2378
        
2379
        return $period;
2380
    }
2381
    
2382
    public function get_subscription_trial_interval() {
2383
        if ( !$this->is_free_trial() ) {
2384
            return 0;
2385
        }
2386
        
2387
        $interval = (int)$this->get_meta( '_wpinv_subscr_trial_interval', true );
2388
        
2389
        if ( !$interval > 0 ) {
2390
            $interval = 1;
2391
        }
2392
        
2393
        return $interval;
2394
    }
2395
    
2396 View Code Duplication
    public function failing_subscription() {
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...
2397
        $args = array(
2398
            'status' => 'failing'
2399
        );
2400
2401
        if ( $this->update_subscription( $args ) ) {
2402
            do_action( 'wpinv_subscription_failing', $this->ID, $this );
2403
            return true;
2404
        }
2405
2406
        return false;
2407
    }
2408
    
2409 View Code Duplication
    public function stop_subscription() {
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...
2410
        $args = array(
2411
            'status' => 'stopped'
2412
        );
2413
2414
        if ( $this->update_subscription( $args ) ) {
2415
            do_action( 'wpinv_subscription_stopped', $this->ID, $this );
2416
            return true;
2417
        }
2418
2419
        return false;
2420
    }
2421
    
2422 View Code Duplication
    public function restart_subscription() {
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...
2423
        $args = array(
2424
            'status' => 'active'
2425
        );
2426
2427
        if ( $this->update_subscription( $args ) ) {
2428
            do_action( 'wpinv_subscription_restarted', $this->ID, $this );
2429
            return true;
2430
        }
2431
2432
        return false;
2433
    }
2434
2435
    public function cancel_subscription() {
2436
        $args = array(
2437
            'status' => 'cancelled'
2438
        );
2439
2440
        if ( $this->update_subscription( $args ) ) {
2441
            if ( is_user_logged_in() ) {
2442
                $userdata = get_userdata( get_current_user_id() );
2443
                $user     = $userdata->user_login;
2444
            } else {
2445
                $user = __( 'gateway', 'invoicing' );
2446
            }
2447
            
2448
            $subscription_id = $this->get_subscription_id();
2449
            if ( !$subscription_id ) {
2450
                $subscription_id = $this->ID;
2451
            }
2452
2453
            $note = sprintf( __( 'Subscription %s has been cancelled by %s', 'invoicing' ), $subscription_id, $user );
2454
            $this->add_note( $note );
2455
2456
            do_action( 'wpinv_subscription_cancelled', $this->ID, $this );
2457
            return true;
2458
        }
2459
2460
        return false;
2461
    }
2462
2463
    public function can_cancel() {
2464
        return apply_filters( 'wpinv_subscription_can_cancel', false, $this );
2465
    }
2466
    
2467
    public function add_subscription( $data = array() ) {
2468
        if ( empty( $this->ID ) ) {
2469
            return false;
2470
        }
2471
2472
        $defaults = array(
2473
            'period'            => '',
2474
            'initial_amount'    => '',
2475
            'recurring_amount'  => '',
2476
            'interval'          => 0,
2477
            'trial_interval'    => 0,
2478
            'trial_period'      => '',
2479
            'bill_times'        => 0,
2480
            'item_id'           => 0,
2481
            'created'           => '',
2482
            'expiration'        => '',
2483
            'status'            => '',
2484
            'profile_id'        => '',
2485
        );
2486
2487
        $args = wp_parse_args( $data, $defaults );
2488
2489
        if ( $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2490
            if ( 'active' == $args['status'] || $args['status'] == 'trialing' ) {
2491
                $args['status'] = 'expired';
2492
            }
2493
        }
2494
2495
        do_action( 'wpinv_subscription_pre_create', $args, $data, $this );
2496
        
2497
        if ( !empty( $args ) ) {
2498
            foreach ( $args as $key => $value ) {
2499
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2500
            }
2501
        }
2502
2503
        do_action( 'wpinv_subscription_post_create', $args, $data, $this );
2504
2505
        return true;
2506
    }
2507
    
2508
    public function update_subscription( $args = array() ) {
2509
        if ( empty( $this->ID ) ) {
2510
            return false;
2511
        }
2512
2513
        if ( !empty( $args['expiration'] ) && $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2514
            if ( !isset( $args['status'] ) || ( isset( $args['status'] ) && ( 'active' == $args['status'] || $args['status'] == 'trialing' ) ) ) {
2515
                $args['status'] = 'expired';
2516
            }
2517
        }
2518
2519
        if ( isset( $args['status'] ) && $args['status'] == 'cancelled' && empty( $args['cancelled_on'] ) ) {
2520
            $args['cancelled_on'] = date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) );
2521
        }
2522
2523
        do_action( 'wpinv_subscription_pre_update', $args, $this );
2524
        
2525
        if ( !empty( $args ) ) {
2526
            foreach ( $args as $key => $value ) {
2527
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2528
            }
2529
        }
2530
2531
        do_action( 'wpinv_subscription_post_update', $args, $this );
2532
2533
        return true;
2534
    }
2535
    
2536
    public function renew_subscription() {
2537
        $parent_invoice = $this->get_parent_payment();
2538
        $parent_invoice = empty( $parent_invoice ) ? $this : $parent_invoice;
2539
        
2540
        $current_time   = current_time( 'timestamp' );
2541
        $start          = $this->get_subscription_created();
2542
        $start          = $start ? strtotime( $start ) : $current_time;
2543
        $expires        = $this->get_expiration_time();
2544
        
2545
        if ( !$expires ) {
2546
            $expires    = strtotime( '+' . $parent_invoice->get_subscription_interval() . ' ' . $parent_invoice->get_subscription_period( true ), $start );
2547
        }
2548
        
2549
        $expiration     = date_i18n( 'Y-m-d 23:59:59', $expires );
2550
        $expiration     = apply_filters( 'wpinv_subscription_renewal_expiration', $expiration, $this->ID, $this );
2551
        $bill_times     = $parent_invoice->get_bill_times();
2552
        $times_billed   = $parent_invoice->get_total_payments();
2553
        
2554
        if ( $parent_invoice->get_subscription_status() == 'trialing' && ( $times_billed > 0 || strtotime( date_i18n( 'Y-m-d' ) ) < strtotime( $parent_invoice->get_trial_end_date( false ) ) ) ) {
2555
            $args = array(
2556
                'status'     => 'active',
2557
            );
2558
2559
            $parent_invoice->update_subscription( $args );
2560
        }
2561
        
2562
        do_action( 'wpinv_subscription_pre_renew', $this->ID, $expiration, $this );
2563
2564
        $status       = 'active';
2565
        if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2566
            $this->complete_subscription();
2567
            $status = 'completed';
2568
        }
2569
2570
        $args = array(
2571
            'expiration' => $expiration,
2572
            'status'     => $status,
2573
        );
2574
2575
        $this->update_subscription( $args );
2576
2577
        do_action( 'wpinv_subscription_post_renew', $this->ID, $expiration, $this );
2578
        do_action( 'wpinv_recurring_set_subscription_status', $this->ID, $status, $this );
2579
    }
2580
    
2581
    public function complete_subscription() {
2582
        $args = array(
2583
            'status' => 'completed'
2584
        );
2585
2586
        if ( $this->update_subscription( $args ) ) {
2587
            do_action( 'wpinv_subscription_completed', $this->ID, $this );
2588
        }
2589
    }
2590
    
2591
    public function expire_subscription() {
2592
        $args = array(
2593
            'status' => 'expired'
2594
        );
2595
2596
        if ( $this->update_subscription( $args ) ) {
2597
            do_action( 'wpinv_subscription_expired', $this->ID, $this );
2598
        }
2599
    }
2600
2601
    public function get_cancel_url() {
2602
        $url = wp_nonce_url( add_query_arg( array( 'wpi_action' => 'cancel_subscription', 'sub_id' => $this->ID ) ), 'wpinv-recurring-cancel' );
2603
2604
        return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
2605
    }
2606
2607
    public function can_update() {
2608
        return apply_filters( 'wpinv_subscription_can_update', false, $this );
2609
    }
2610
2611
    public function get_update_url() {
2612
        $url = add_query_arg( array( 'action' => 'update', 'sub_id' => $this->ID ) );
2613
2614
        return apply_filters( 'wpinv_subscription_update_url', $url, $this );
2615
    }
2616
2617
    public function is_parent() {
2618
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2619
2620
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2621
    }
2622
    
2623
    public function is_renewal() {
2624
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2625
2626
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2627
    }
2628
    
2629
    public function get_parent_payment() {
2630
        $parent_payment = NULL;
2631
        
2632
        if ( $this->is_renewal() ) {
2633
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2634
        }
2635
        
2636
        return $parent_payment;
2637
    }
2638
    
2639
    public function is_subscription_active() {
2640
        $ret = false;
2641
        
2642
        $subscription_status = $this->get_subscription_status();
2643
2644
        if( ! $this->is_subscription_expired() && ( $subscription_status == 'active' || $subscription_status == 'cancelled' || $subscription_status == 'trialing' ) ) {
2645
            $ret = true;
2646
        }
2647
2648
        return apply_filters( 'wpinv_subscription_is_active', $ret, $this->ID, $this );
2649
    }
2650
2651
    public function is_subscription_expired() {
2652
        $ret = false;
2653
        $subscription_status = $this->get_subscription_status();
2654
2655
        if ( $subscription_status == 'expired' ) {
2656
            $ret = true;
2657
        } else if ( 'active' === $subscription_status || 'cancelled' === $subscription_status || 'trialing' == $subscription_status ) {
2658
            $ret        = false;
2659
            $expiration = $this->get_expiration_time();
2660
2661
            if ( $expiration && strtotime( 'NOW', current_time( 'timestamp' ) ) > $expiration ) {
2662
                $ret = true;
2663
2664
                if ( 'active' === $subscription_status || 'trialing' === $subscription_status ) {
2665
                    $this->expire_subscription();
2666
                }
2667
            }
2668
        }
2669
2670
        return apply_filters( 'wpinv_subscription_is_expired', $ret, $this->ID, $this );
2671
    }
2672
    
2673
    public function get_new_expiration( $item_id = 0, $trial = true ) {
2674
        $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...
2675
        $interval = $item->get_recurring_interval();
2676
        $period = $item->get_recurring_period( true );
2677
        
2678
        $expiration_time = strtotime( '+' . $interval . ' ' . $period );
2679
        
2680
        if ( $trial && $this->is_free_trial() && $item->has_free_trial() ) {
2681
            $expiration_time += ( wpinv_period_in_days( $item->get_trial_interval(), $item->get_trial_period() ) * DAY_IN_SECONDS ) ;
2682
        }
2683
2684
        return date_i18n( 'Y-m-d 23:59:59', $expiration_time );
2685
    }
2686
    
2687
    public function get_subscription_data( $filed = '' ) {
2688
        $fields = array( 'item_id', 'status', 'period', 'initial_amount', 'recurring_amount', 'interval', 'bill_times', 'trial_period', 'trial_interval', 'expiration', 'profile_id', 'created', 'cancelled_on' );
2689
        
2690
        $subscription_meta = array();
2691
        foreach ( $fields as $field ) {
2692
            $subscription_meta[ $field ] = $this->get_meta( '_wpinv_subscr_' . $field );
2693
        }
2694
        
2695
        $item = $this->get_recurring( true );
2696
        
2697
        if ( !empty( $item ) ) {
2698
            if ( empty( $subscription_meta['item_id'] ) ) {
2699
                $subscription_meta['item_id'] = $item->ID;
2700
            }
2701
            if ( empty( $subscription_meta['period'] ) ) {
2702
                $subscription_meta['period'] = $item->get_recurring_period();
2703
            }
2704
            if ( empty( $subscription_meta['interval'] ) ) {
2705
                $subscription_meta['interval'] = $item->get_recurring_interval();
2706
            }
2707
            if ( $item->has_free_trial() ) {
2708
                if ( empty( $subscription_meta['trial_period'] ) ) {
2709
                    $subscription_meta['trial_period'] = $item->get_trial_period();
2710
                }
2711
                if ( empty( $subscription_meta['trial_interval'] ) ) {
2712
                    $subscription_meta['trial_interval'] = $item->get_trial_interval();
2713
                }
2714
            } else {
2715
                $subscription_meta['trial_period']      = '';
2716
                $subscription_meta['trial_interval']    = 0;
2717
            }
2718
            if ( !$subscription_meta['bill_times'] && $subscription_meta['bill_times'] !== 0 ) {
2719
                $subscription_meta['bill_times'] = $item->get_recurring_limit();
2720
            }
2721
            if ( $subscription_meta['initial_amount'] === '' || $subscription_meta['recurring_amount'] === '' ) {
2722
                $subscription_meta['initial_amount']    = wpinv_format_amount( $this->get_total() );
2723
                $subscription_meta['recurring_amount']  = wpinv_format_amount( $this->get_recurring_details( 'total' ) );
2724
            }
2725
        }
2726
        
2727
        if ( $filed === '' ) {
2728
            return apply_filters( 'wpinv_get_invoice_subscription_data', $subscription_meta, $this );
2729
        }
2730
        
2731
        $value = isset( $subscription_meta[$filed] ) ? $subscription_meta[$filed] : '';
2732
        
2733
        return apply_filters( 'wpinv_invoice_subscription_data_value', $value, $subscription_meta, $this );
2734
    }
2735
    
2736
    public function is_paid() {
2737
        if ( $this->has_status( array( 'publish', 'complete', 'processing', 'renewal' ) ) ) {
2738
            return true;
2739
        }
2740
        
2741
        return false;
2742
    }
2743
    
2744
    public function has_vat() {
2745
        global $wpinv_euvat, $wpi_country;
2746
        
2747
        $requires_vat = false;
2748
        
2749
        if ( $this->country ) {
2750
            $wpi_country        = $this->country;
2751
            
2752
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2753
        }
2754
        
2755
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2756
    }
2757
    
2758
    public function refresh_item_ids() {
2759
        $item_ids = array();
2760
        
2761
        if ( !empty( $this->cart_details ) ) {
2762
            foreach ( $this->cart_details as $key => $item ) {
2763
                if ( !empty( $item['id'] ) ) {
2764
                    $item_ids[] = $item['id'];
2765
                }
2766
            }
2767
        }
2768
        
2769
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2770
        
2771
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2772
    }
2773
}
2774