Passed
Pull Request — master (#35)
by Stiofan
03:59
created

WPInv_Invoice::get_meta()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 5
nop 2
dl 0
loc 17
rs 9.2
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
            if ( empty( $meta['key'] ) ) {
1235
                $meta['key'] = $this->setup_invoice_key();
1236
            }
1237
1238
            if ( empty( $meta['date'] ) ) {
1239
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1240
            }
1241
        }
1242
1243
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1244
1245
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1246
    }
1247
    
1248
    public function get_description() {
1249
        $post = get_post( $this->ID );
1250
        
1251
        $description = !empty( $post ) ? $post->post_content : '';
1252
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1253
    }
1254
    
1255
    public function get_status( $nicename = false ) {
1256
        if ( !$nicename ) {
1257
            $status = $this->status;
1258
        } else {
1259
            $status = $this->status_nicename;
1260
        }
1261
        
1262
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1263
    }
1264
    
1265
    public function get_cart_details() {
1266
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1267
    }
1268
    
1269 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...
1270
        $subtotal = wpinv_format_amount( $this->subtotal, NULL, !$currency );
1271
        
1272
        if ( $currency ) {
1273
            $subtotal = wpinv_price( $subtotal, $this->get_currency() );
1274
        }
1275
        
1276
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1277
    }
1278
    
1279
    public function get_total( $currency = false ) {        
1280
        if ( $this->is_free_trial() ) {
1281
            $total = wpinv_format_amount( 0, NULL, !$currency );
1282
        } else {
1283
            $total = wpinv_format_amount( $this->total, NULL, !$currency );
1284
        }
1285
        if ( $currency ) {
1286
            $total = wpinv_price( $total, $this->get_currency() );
1287
        }
1288
        
1289
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1290
    }
1291
    
1292
    public function get_recurring_details( $field = '', $currency = false ) {        
1293
        $data                 = array();
1294
        $data['cart_details'] = $this->cart_details;
1295
        $data['subtotal']     = $this->get_subtotal();
1296
        $data['discount']     = $this->get_discount();
1297
        $data['tax']          = $this->get_tax();
1298
        $data['total']        = $this->get_total();
1299
    
1300
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1301
            $is_free_trial = $this->is_free_trial();
1302
            $discounts = $this->get_discounts( true );
1303
            
1304
            if ( $is_free_trial || !empty( $discounts ) ) {
1305
                $first_use_only = false;
1306
                
1307
                if ( !empty( $discounts ) ) {
1308
                    foreach ( $discounts as $key => $code ) {
1309
                        if ( wpinv_discount_is_recurring( $code, true ) ) {
1310
                            $first_use_only = true;
1311
                            break;
1312
                        }
1313
                    }
1314
                }
1315
                    
1316
                if ( !$first_use_only ) {
1317
                    $data['subtotal'] = wpinv_format_amount( $this->subtotal, NULL, true );
1318
                    $data['discount'] = wpinv_format_amount( $this->discount, NULL, true );
1319
                    $data['tax']      = wpinv_format_amount( $this->tax, NULL, true );
1320
                    $data['total']    = wpinv_format_amount( $this->total, NULL, true );
1321
                } else {
1322
                    $cart_subtotal   = 0;
1323
                    $cart_discount   = 0;
1324
                    $cart_tax        = 0;
1325
1326
                    foreach ( $this->cart_details as $key => $item ) {
1327
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1328
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1329
                        $item_discount  = 0;
1330
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1331
                        
1332
                        if ( wpinv_prices_include_tax() ) {
1333
                            $item_subtotal -= wpinv_format_amount( $item_tax, NULL, true );
1334
                        }
1335
                        
1336
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1337
                        // Do not allow totals to go negative
1338
                        if ( $item_total < 0 ) {
1339
                            $item_total = 0;
1340
                        }
1341
                        
1342
                        $cart_subtotal  += (float)($item_subtotal);
1343
                        $cart_discount  += (float)($item_discount);
1344
                        $cart_tax       += (float)($item_tax);
1345
                        
1346
                        $data['cart_details'][$key]['discount']   = wpinv_format_amount( $item_discount, NULL, true );
1347
                        $data['cart_details'][$key]['tax']        = wpinv_format_amount( $item_tax, NULL, true );
1348
                        $data['cart_details'][$key]['price']      = wpinv_format_amount( $item_total, NULL, true );
1349
                    }
1350
                    
1351
                    $data['subtotal'] = wpinv_format_amount( $cart_subtotal, NULL, true );
1352
                    $data['discount'] = wpinv_format_amount( $cart_discount, NULL, true );
1353
                    $data['tax']      = wpinv_format_amount( $cart_tax, NULL, true );
1354
                    $data['total']    = wpinv_format_amount( ( $data['subtotal'] + $data['tax'] ), NULL, true );
1355
                }
1356
            }
1357
        }
1358
        
1359
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1360
1361
        if ( isset( $data[$field] ) ) {
1362
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1363
        }
1364
        
1365
        return $data;
1366
    }
1367
    
1368 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...
1369
        $final_total = wpinv_format_amount( $this->tax, NULL, !$currency );
1370
        if ( $currency ) {
1371
            $final_total = wpinv_price( $final_total, $this->get_currency() );
1372
        }
1373
        
1374
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1375
    }
1376
    
1377
    public function get_discounts( $array = false ) {
1378
        $discounts = $this->discounts;
1379
        if ( $array && $discounts ) {
1380
            $discounts = explode( ',', $discounts );
1381
        }
1382
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1383
    }
1384
    
1385
    public function get_discount( $currency = false, $dash = false ) {
1386
        if ( !empty( $this->discounts ) ) {
1387
            global $ajax_cart_details;
1388
            $ajax_cart_details = $this->get_cart_details();
1389
1390
            $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...
1391
        }
1392
        $discount   = wpinv_format_amount( $this->discount, NULL, !$currency );
1393
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1394
        
1395
        if ( $currency ) {
1396
            $discount = wpinv_price( $discount, $this->get_currency() );
1397
        }
1398
        
1399
        $discount   = $dash . $discount;
1400
        
1401
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1402
    }
1403
    
1404
    public function get_discount_code() {
1405
        return $this->discount_code;
1406
    }
1407
    
1408 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...
1409
        $tax = wpinv_format_amount( $this->tax, NULL, !$currency );
1410
        
1411
        if ( $currency ) {
1412
            $tax = wpinv_price( $tax, $this->get_currency() );
1413
        }
1414
        
1415
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1416
    }
1417
    
1418
    public function get_fees( $type = 'all' ) {
1419
        $fees    = array();
1420
1421
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1422
            foreach ( $this->fees as $fee ) {
1423 View Code Duplication
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1424
                    continue;
1425
                }
1426
1427
                $fee['label'] = stripslashes( $fee['label'] );
1428
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1429
                $fees[]    = $fee;
1430
            }
1431
        }
1432
1433
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1434
    }
1435
    
1436
    public function get_fees_total( $type = 'all' ) {
1437
        $fees_total = (float) 0.00;
1438
1439
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1440
        if ( ! empty( $payment_fees ) ) {
1441
            foreach ( $payment_fees as $fee ) {
1442
                $fees_total += (float) $fee['amount'];
1443
            }
1444
        }
1445
1446
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1447
        /*
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...
1448
        $fees = $this->get_fees( $type );
1449
1450
        $fees_total = 0;
1451
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1452
            foreach ( $fees as $fee_id => $fee ) {
1453
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1454
                    continue;
1455
                }
1456
1457
                $fees_total += $fee['amount'];
1458
            }
1459
        }
1460
1461
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1462
        */
1463
    }
1464
1465
    public function get_user_id() {
1466
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1467
    }
1468
    
1469
    public function get_first_name() {
1470
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1471
    }
1472
    
1473
    public function get_last_name() {
1474
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1475
    }
1476
    
1477
    public function get_user_full_name() {
1478
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1479
    }
1480
    
1481
    public function get_user_info() {
1482
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1483
    }
1484
    
1485
    public function get_email() {
1486
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1487
    }
1488
    
1489
    public function get_address() {
1490
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1491
    }
1492
    
1493
    public function get_phone() {
1494
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1495
    }
1496
    
1497
    public function get_number() {
1498
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1499
    }
1500
    
1501
    public function get_items() {
1502
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1503
    }
1504
    
1505
    public function get_key() {
1506
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1507
    }
1508
    
1509
    public function get_transaction_id() {
1510
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1511
    }
1512
    
1513
    public function get_gateway() {
1514
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1515
    }
1516
    
1517
    public function get_gateway_title() {
1518
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1519
        
1520
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1521
    }
1522
    
1523
    public function get_currency() {
1524
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1525
    }
1526
    
1527
    public function get_created_date() {
1528
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1529
    }
1530
    
1531
    public function get_due_date( $display = false ) {
1532
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1533
        
1534
        if ( !$display || empty( $due_date ) ) {
1535
            return $due_date;
1536
        }
1537
        
1538
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
1539
    }
1540
    
1541
    public function get_completed_date() {
1542
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1543
    }
1544
    
1545
    public function get_invoice_date( $formatted = true ) {
1546
        $date_completed = $this->completed_date;
1547
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1548
        
1549
        if ( $invoice_date == '' ) {
1550
            $date_created   = $this->date;
1551
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1552
        }
1553
        
1554
        if ( $formatted && $invoice_date ) {
1555
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
1556
        }
1557
1558
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1559
    }
1560
    
1561
    public function get_ip() {
1562
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1563
    }
1564
        
1565
    public function has_status( $status ) {
1566
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1567
    }
1568
    
1569
    public function add_item( $item_id = 0, $args = array() ) {
1570
        global $wpi_current_id, $wpi_item_id;
1571
        
1572
        $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...
1573
1574
        // Bail if this post isn't a item
1575
        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...
1576
            return false;
1577
        }
1578
        
1579
        $has_quantities = wpinv_item_quantities_enabled();
1580
1581
        // Set some defaults
1582
        $defaults = array(
1583
            'quantity'  => 1,
1584
            'id'        => false,
1585
            'name'      => $item->get_name(),
1586
            'item_price'=> false,
1587
            'discount'  => 0,
1588
            'tax'       => 0.00,
1589
            'meta'      => array(),
1590
            'fees'      => array()
1591
        );
1592
1593
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1594
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1595
1596
        $wpi_current_id         = $this->ID;
1597
        $wpi_item_id            = $item->ID;
1598
        $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...
1599
        
1600
        $_POST['wpinv_country'] = $this->country;
1601
        $_POST['wpinv_state']   = $this->state;
1602
        
1603
        $found_cart_key         = false;
1604
        
1605
        if ($has_quantities) {
1606
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1607
            
1608
            foreach ( $this->items as $key => $cart_item ) {
1609
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1610
                    continue;
1611
                }
1612
1613
                $this->items[ $key ]['quantity'] += $args['quantity'];
1614
                break;
1615
            }
1616
            
1617
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1618
                if ( $item_id != $cart_item['id'] ) {
1619
                    continue;
1620
                }
1621
1622
                $found_cart_key = $cart_key;
1623
                break;
1624
            }
1625
        }
1626
        
1627
        if ($has_quantities && $found_cart_key !== false) {
1628
            $cart_item          = $this->cart_details[$found_cart_key];
1629
            $item_price         = $cart_item['item_price'];
1630
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1631
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1632
            
1633
            $new_quantity       = $quantity + $args['quantity'];
1634
            $subtotal           = $item_price * $new_quantity;
1635
            
1636
            $args['quantity']   = $new_quantity;
1637
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1638
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1639
            
1640
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1641
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1642
            // The total increase equals the number removed * the item_price
1643
            $total_increased    = wpinv_format_amount( $item_price, NULL, true );
1644
            
1645
            if ( wpinv_prices_include_tax() ) {
1646
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1647
            }
1648
1649
            $total              = $subtotal - $discount + $tax;
1650
1651
            // Do not allow totals to go negative
1652
            if( $total < 0 ) {
1653
                $total = 0;
1654
            }
1655
            
1656
            $cart_item['quantity']  = $new_quantity;
1657
            $cart_item['subtotal']  = $subtotal;
1658
            $cart_item['discount']  = $discount;
1659
            $cart_item['tax']       = $tax;
1660
            $cart_item['price']     = $total;
1661
            
1662
            $subtotal               = $total_increased - $discount_increased;
1663
            $tax                    = $tax_increased;
1664
            
1665
            $this->cart_details[$found_cart_key] = $cart_item;
1666
        } else {
1667
            // Allow overriding the price
1668
            if( false !== $args['item_price'] ) {
1669
                $item_price = $args['item_price'];
1670
            } else {
1671
                $item_price = wpinv_get_item_price( $item->ID );
1672
            }
1673
1674
            // Sanitizing the price here so we don't have a dozen calls later
1675
            $item_price = wpinv_sanitize_amount( $item_price );
1676
            $subtotal   = wpinv_format_amount( $item_price * $args['quantity'], NULL, true );
1677
        
1678
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1679
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1680
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1681
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1682
1683
            // Setup the items meta item
1684
            $new_item = array(
1685
                'id'       => $item->ID,
1686
                'quantity' => $args['quantity'],
1687
            );
1688
1689
            $this->items[]  = $new_item;
1690
1691
            if ( wpinv_prices_include_tax() ) {
1692
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1693
            }
1694
1695
            $total      = $subtotal - $discount + $tax;
1696
1697
            // Do not allow totals to go negative
1698
            if( $total < 0 ) {
1699
                $total = 0;
1700
            }
1701
        
1702
            $this->cart_details[] = array(
1703
                'name'        => !empty($args['name']) ? $args['name'] : $item->get_name(),
1704
                'id'          => $item->ID,
1705
                'item_price'  => wpinv_format_amount( $item_price, NULL, true ),
1706
                'quantity'    => $args['quantity'],
1707
                'discount'    => $discount,
1708
                'subtotal'    => wpinv_format_amount( $subtotal, NULL, true ),
1709
                'tax'         => wpinv_format_amount( $tax, NULL, true ),
1710
                'price'       => wpinv_format_amount( $total, NULL, true ),
1711
                'vat_rate'    => $tax_rate,
1712
                'vat_class'   => $tax_class,
1713
                'meta'        => $args['meta'],
1714
                'fees'        => $args['fees'],
1715
            );
1716
                        
1717
            $subtotal = $subtotal - $discount;
1718
        }
1719
        
1720
        $added_item = end( $this->cart_details );
1721
        $added_item['action']  = 'add';
1722
        
1723
        $this->pending['items'][] = $added_item;
1724
        
1725
        $this->increase_subtotal( $subtotal );
1726
        $this->increase_tax( $tax );
1727
1728
        return true;
1729
    }
1730
    
1731
    public function remove_item( $item_id, $args = array() ) {
1732
        // Set some defaults
1733
        $defaults = array(
1734
            'quantity'   => 1,
1735
            'item_price' => false,
1736
            'cart_index' => false,
1737
        );
1738
        $args = wp_parse_args( $args, $defaults );
1739
1740
        // Bail if this post isn't a item
1741
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1742
            return false;
1743
        }
1744
        
1745
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1746
1747
        foreach ( $this->items as $key => $item ) {
1748
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1749
                continue;
1750
            }
1751
1752
            if ( false !== $args['cart_index'] ) {
1753
                $cart_index = absint( $args['cart_index'] );
1754
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1755
1756
                if ( ! empty( $cart_item ) ) {
1757
                    // If the cart index item isn't the same item ID, don't remove it
1758
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1759
                        continue;
1760
                    }
1761
                }
1762
            }
1763
1764
            $item_quantity = $this->items[ $key ]['quantity'];
1765
            if ( $item_quantity > $args['quantity'] ) {
1766
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1767
                break;
1768
            } else {
1769
                unset( $this->items[ $key ] );
1770
                break;
1771
            }
1772
        }
1773
1774
        $found_cart_key = false;
1775
        if ( false === $args['cart_index'] ) {
1776
            foreach ( $this->cart_details as $cart_key => $item ) {
1777
                if ( $item_id != $item['id'] ) {
1778
                    continue;
1779
                }
1780
1781
                if ( false !== $args['item_price'] ) {
1782
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1783
                        continue;
1784
                    }
1785
                }
1786
1787
                $found_cart_key = $cart_key;
1788
                break;
1789
            }
1790
        } else {
1791
            $cart_index = absint( $args['cart_index'] );
1792
1793
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1794
                return false; // Invalid cart index passed.
1795
            }
1796
1797
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1798
                return false; // We still need the proper Item ID to be sure.
1799
            }
1800
1801
            $found_cart_key = $cart_index;
1802
        }
1803
        
1804
        $cart_item  = $this->cart_details[$found_cart_key];
1805
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1806
        
1807
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1808
            return false; // Invoice must contain at least one item.
1809
        }
1810
        
1811
        $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...
1812
        
1813
        if ( $quantity > $args['quantity'] ) {
1814
            $item_price         = $cart_item['item_price'];
1815
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1816
            
1817
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1818
            $subtotal           = $item_price * $new_quantity;
1819
            
1820
            $args['quantity']   = $new_quantity;
1821
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1822
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1823
            
1824
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_format_amount( ( (float)$cart_item['discount'] / $quantity ), NULL, true ) : 0;
1825
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1826
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_format_amount( ( (float)$cart_item['tax'] / $quantity ), NULL, true ) : 0;
1827
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1828
            
1829
            // The total increase equals the number removed * the item_price
1830
            $total_decrease     = wpinv_format_amount( $item_price, NULL, true );
1831
            
1832
            if ( wpinv_prices_include_tax() ) {
1833
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1834
            }
1835
1836
            $total              = $subtotal - $discount + $tax;
1837
1838
            // Do not allow totals to go negative
1839
            if( $total < 0 ) {
1840
                $total = 0;
1841
            }
1842
            
1843
            $cart_item['quantity']  = $new_quantity;
1844
            $cart_item['subtotal']  = $subtotal;
1845
            $cart_item['discount']  = $discount;
1846
            $cart_item['tax']       = $tax;
1847
            $cart_item['price']     = $total;
1848
            
1849
            $added_item             = $cart_item;
1850
            $added_item['id']       = $item_id;
1851
            $added_item['price']    = $total_decrease;
1852
            $added_item['quantity'] = $args['quantity'];
1853
            
1854
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1855
            
1856
            $this->cart_details[$found_cart_key] = $cart_item;
1857
            
1858
            $remove_item = end( $this->cart_details );
1859
        } else {
1860
            $item_price     = $cart_item['item_price'];
1861
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1862
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1863
        
1864
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1865
            $tax_decrease       = $tax;
1866
1867
            unset( $this->cart_details[$found_cart_key] );
1868
            
1869
            $remove_item             = $args;
1870
            $remove_item['id']       = $item_id;
1871
            $remove_item['price']    = $subtotal_decrease;
1872
            $remove_item['quantity'] = $args['quantity'];
1873
        }
1874
        
1875
        $remove_item['action']      = 'remove';
1876
        $this->pending['items'][]   = $remove_item;
1877
               
1878
        $this->decrease_subtotal( $subtotal_decrease );
1879
        $this->decrease_tax( $tax_decrease );
1880
        
1881
        return true;
1882
    }
1883
    
1884
    public function update_items($temp = false) {
1885
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1886
        
1887
        if ( !empty( $this->cart_details ) ) {
1888
            $wpi_nosave             = $temp;
1889
            $cart_subtotal          = 0;
1890
            $cart_discount          = 0;
1891
            $cart_tax               = 0;
1892
            $cart_details           = array();
1893
            
1894
            $_POST['wpinv_country'] = $this->country;
1895
            $_POST['wpinv_state']   = $this->state;
1896
            
1897
            foreach ( $this->cart_details as $key => $item ) {
1898
                $item_price = $item['item_price'];
1899
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1900
                $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...
1901
                $subtotal   = $item_price * $quantity;
1902
                
1903
                $wpi_current_id         = $this->ID;
1904
                $wpi_item_id            = $item['id'];
1905
                
1906
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1907
                
1908
                $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...
1909
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1910
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1911
1912
                if ( wpinv_prices_include_tax() ) {
1913
                    $subtotal -= wpinv_format_amount( $tax, NULL, true );
1914
                }
1915
1916
                $total      = $subtotal - $discount + $tax;
1917
1918
                // Do not allow totals to go negative
1919
                if( $total < 0 ) {
1920
                    $total = 0;
1921
                }
1922
1923
                $cart_details[] = array(
1924
                    'id'          => $item['id'],
1925
                    'name'        => $item['name'],
1926
                    'item_price'  => wpinv_format_amount( $item_price, NULL, true ),
1927
                    'quantity'    => $quantity,
1928
                    'discount'    => $discount,
1929
                    'subtotal'    => wpinv_format_amount( $subtotal, NULL, true ),
1930
                    'tax'         => wpinv_format_amount( $tax, NULL, true ),
1931
                    'price'       => wpinv_format_amount( $total, NULL, true ),
1932
                    'vat_rate'    => $tax_rate,
1933
                    'vat_class'   => $tax_class,
1934
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1935
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1936
                );
1937
                
1938
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1939
                $cart_discount  += (float)($discount);
1940
                $cart_tax       += (float)($tax);
1941
            }
1942
            $this->subtotal = wpinv_format_amount( $cart_subtotal, NULL, true );
1943
            $this->tax      = wpinv_format_amount( $cart_tax, NULL, true );
1944
            $this->discount = wpinv_format_amount( $cart_discount, NULL, true );
1945
            
1946
            $this->recalculate_total();
1947
            
1948
            $this->cart_details = $cart_details;
1949
        }
1950
1951
        return $this;
1952
    }
1953
    
1954
    public function recalculate_totals($temp = false) {        
1955
        $this->update_items($temp);
1956
        $this->save( true );
1957
        
1958
        return $this;
1959
    }
1960
    
1961
    public function needs_payment() {
1962
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'pending' ), $this );
1963
1964
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() ) ) {
1965
            $needs_payment = true;
1966
        } else {
1967
            $needs_payment = false;
1968
        }
1969
1970
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1971
    }
1972
    
1973
    public function get_checkout_payment_url( $on_checkout = false, $secret = false ) {
1974
        $pay_url = wpinv_get_checkout_uri();
1975
1976
        if ( is_ssl() ) {
1977
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
1978
        }
1979
        
1980
        $key = $this->get_key();
1981
1982
        if ( $on_checkout ) {
1983
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
1984
        } else {
1985
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
1986
        }
1987
        
1988 View Code Duplication
        if ( $secret ) {
1989
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
1990
        }
1991
1992
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this );
1993
    }
1994
    
1995
    public function get_view_url( $secret = false ) {
1996
        $print_url = get_permalink( $this->ID );
1997
        
1998 View Code Duplication
        if ( $secret ) {
1999
            $print_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $this->get_key() ) ), $print_url );
2000
        }
2001
2002
        return apply_filters( 'wpinv_get_view_url', $print_url, $this );
2003
    }
2004
    
2005
    public function generate_key( $string = '' ) {
2006
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2007
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2008
    }
2009
    
2010
    public function is_recurring() {
2011
        if ( empty( $this->cart_details ) ) {
2012
            return false;
2013
        }
2014
        
2015
        $has_subscription = false;
2016 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2017
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2018
                $has_subscription = true;
2019
                break;
2020
            }
2021
        }
2022
        
2023
        if ( count( $this->cart_details ) > 1 ) {
2024
            $has_subscription = false;
2025
        }
2026
2027
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2028
    }
2029
    
2030
    public function is_free_trial() {
2031
        $is_free_trial = false;
2032
        
2033
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2034
            if ( !empty( $item ) && $item->has_free_trial() ) {
2035
                $is_free_trial = true;
2036
            }
2037
        }
2038
2039
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details );
2040
    }
2041
    
2042
    public function get_recurring( $object = false ) {
2043
        $item = NULL;
2044
        
2045
        if ( empty( $this->cart_details ) ) {
2046
            return $item;
2047
        }
2048
        
2049 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2050
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2051
                $item = $cart_item['id'];
2052
                break;
2053
            }
2054
        }
2055
        
2056
        if ( $object ) {
2057
            $item = $item ? new WPInv_Item( $item ) : NULL;
2058
            
2059
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2060
        }
2061
2062
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2063
    }
2064
    
2065
    public function get_subscription_name() {
2066
        $item = $this->get_recurring( true );
2067
        
2068
        if ( empty( $item ) ) {
2069
            return NULL;
2070
        }
2071
        
2072
        if ( !($name = $item->get_name()) ) {
2073
            $name = $item->post_name;
2074
        }
2075
2076
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2077
    }
2078
        
2079
    public function get_expiration() {
2080
        $expiration = $this->get_meta( '_wpinv_subscr_expiration', true );
2081
        return $expiration;
2082
    }
2083
    
2084
    public function get_cancelled_date( $formatted = true ) {
2085
        $cancelled_date = $this->get_subscription_status() == 'cancelled' ? $this->get_meta( '_wpinv_subscr_cancelled_on', true ) : '';
2086
        
2087
        if ( $formatted && $cancelled_date ) {
2088
            $cancelled_date = date_i18n( get_option( 'date_format' ), strtotime( $cancelled_date ) );
2089
        }
2090
        
2091
        return $cancelled_date;
2092
    }
2093
    
2094
    public function get_trial_end_date( $formatted = true ) {
2095
        if ( !$this->is_free_trial() || !$this->is_paid() ) {
2096
            return NULL;
2097
        }
2098
        
2099
        $trial_end_date = $this->get_subscription_status() == 'trialing' ? $this->get_meta( '_wpinv_subscr_trial_end', true ) : '';
2100
        
2101
        if ( empty( $trial_end_date ) ) {
2102
            $trial_start_time = strtotime( $this->get_subscription_start() );
2103
            $trial_start_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2104
            
2105
            $trial_end_date = date_i18n( 'Y-m-d H:i:s', $trial_start_time );
2106
        }
2107
        
2108
        if ( $formatted && $trial_end_date ) {
2109
            $trial_end_date = date_i18n( get_option( 'date_format' ), strtotime( $trial_end_date ) );
2110
        }
2111
        
2112
        return $trial_end_date;
2113
    }
2114
    
2115
    public function get_subscription_created( $default = true ) {
2116
        $created = $this->get_meta( '_wpinv_subscr_created', true );
2117
        
2118
        if ( empty( $created ) && $default ) {
2119
            $created = $this->date;
2120
        }
2121
        return $created;
2122
    }
2123
    
2124
    public function get_subscription_start( $formatted = true ) {
2125
        if ( !$this->is_paid() ) {
2126
            return '-';
2127
        }
2128
        $start   = $this->get_subscription_created();
2129
        
2130
        if ( $formatted ) {
2131
            $date = date_i18n( get_option( 'date_format' ), strtotime( $start ) );
2132
        } else {
2133
            $date = date_i18n( 'Y-m-d H:i:s', strtotime( $start ) );
2134
        }
2135
2136
        return $date;
2137
    }
2138
    
2139
    public function get_subscription_end( $formatted = true ) {
2140
        if ( !$this->is_paid() ) {
2141
            return '-';
2142
        }
2143
        $start          = $this->get_subscription_created();
2144
        $interval       = $this->get_subscription_interval();
2145
        $period         = $this->get_subscription_period( true );
2146
        $bill_times     = (int)$this->get_bill_times();
2147
        
2148
        if ( $bill_times == 0 ) {
2149
            return $formatted ? __( 'Until cancelled', 'invoicing' ) : $bill_times;
2150
        }
2151
        
2152
        $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...
2153
        
2154
        $end_time = strtotime( $start . '+' . ( $interval * $bill_times ) . ' ' . $period );
2155
        
2156
        if ( $this->is_free_trial() ) {
2157
            $end_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2158
        }
2159
        
2160
        if ( $formatted ) {
2161
            $date = date_i18n( get_option( 'date_format' ), $end_time );
2162
        } else {
2163
            $date = date_i18n( 'Y-m-d H:i:s', $end_time );
2164
        }
2165
2166
        return $date;
2167
    }
2168
    
2169
    public function get_expiration_time() {
2170
        return strtotime( $this->get_expiration(), current_time( 'timestamp' ) );
2171
    }
2172
    
2173
    public function get_original_invoice_id() {        
2174
        return $this->parent_invoice;
2175
    }
2176
    
2177
    public function get_bill_times() {
2178
        $subscription_data = $this->get_subscription_data();
2179
        return $subscription_data['bill_times'];
2180
    }
2181
2182
    public function get_child_payments( $self = false ) {
2183
        $invoices = get_posts( array(
2184
            'post_type'         => 'wpi_invoice',
2185
            'post_parent'       => (int)$this->ID,
2186
            'posts_per_page'    => '999',
2187
            'post_status'       => array( 'publish', 'complete', 'processing', 'renewal' ),
2188
            'orderby'           => 'ID',
2189
            'order'             => 'DESC',
2190
            'fields'            => 'ids'
2191
        ) );
2192
        
2193
        if ( $this->is_free_trial() ) {
2194
            $self = false;
2195
        }
2196
        
2197
        if ( $self && $this->is_paid() ) {
2198
            if ( !empty( $invoices ) ) {
2199
                $invoices[] = (int)$this->ID;
2200
            } else {
2201
                $invoices = array( $this->ID );
2202
            }
2203
            
2204
            $invoices = array_unique( $invoices );
2205
        }
2206
2207
        return $invoices;
2208
    }
2209
2210
    public function get_total_payments( $self = true ) {
2211
        return count( $this->get_child_payments( $self ) );
2212
    }
2213
    
2214
    public function get_subscriptions( $limit = -1 ) {
2215
        $subscriptions = wpinv_get_subscriptions( array( 'parent_invoice_id' => $this->ID, 'numberposts' => $limit ) );
2216
2217
        return $subscriptions;
2218
    }
2219
    
2220
    public function get_subscription_id() {
2221
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2222
        
2223
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2224
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2225
            
2226
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2227
        }
2228
        
2229
        return $subscription_id;
2230
    }
2231
    
2232
    public function get_subscription_status() {
2233
        $subscription_status = $this->get_meta( '_wpinv_subscr_status', true );
2234
2235
        if ( empty( $subscription_status ) ) {
2236
            $status = 'pending';
2237
            
2238
            if ( $this->is_paid() ) {        
2239
                $bill_times   = (int)$this->get_bill_times();
2240
                $times_billed = (int)$this->get_total_payments();
2241
                $expiration = $this->get_subscription_end( false );
2242
                $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;
2243
                
2244
                if ( (int)$bill_times == 0 ) {
2245
                    $status = $expired ? 'expired' : 'active';
2246
                } else if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2247
                    $status = 'completed';
2248
                } else if ( $expired ) {
2249
                    $status = 'expired';
2250
                } else if ( $bill_times > 0 ) {
2251
                    $status = 'active';
2252
                } else {
2253
                    $status = 'pending';
2254
                }
2255
            }
2256
            
2257
            if ( $status && $status != $subscription_status ) {
2258
                $subscription_status = $status;
2259
                
2260
                $this->update_meta( '_wpinv_subscr_status', $status );
2261
            }
2262
        }
2263
        
2264
        return $subscription_status;
2265
    }
2266
    
2267
    public function get_subscription_status_label( $status = '' ) {
2268
        $status = !empty( $status ) ? $status : $this->get_subscription_status();
2269
2270
        switch( $status ) {
2271
            case 'active' :
2272
                $status_label = __( 'Active', 'invoicing' );
2273
                break;
2274
2275
            case 'cancelled' :
2276
                $status_label = __( 'Cancelled', 'invoicing' );
2277
                break;
2278
                
2279
            case 'completed' :
2280
                $status_label = __( 'Completed', 'invoicing' );
2281
                break;
2282
2283
            case 'expired' :
2284
                $status_label = __( 'Expired', 'invoicing' );
2285
                break;
2286
2287
            case 'pending' :
2288
                $status_label = __( 'Pending', 'invoicing' );
2289
                break;
2290
2291
            case 'failing' :
2292
                $status_label = __( 'Failing', 'invoicing' );
2293
                break;
2294
                
2295
            case 'stopped' :
2296
                $status_label = __( 'Stopped', 'invoicing' );
2297
                break;
2298
                
2299
            case 'trialing' :
2300
                $status_label = __( 'Trialing', 'invoicing' );
2301
                break;
2302
2303
            default:
2304
                $status_label = $status;
2305
                break;
2306
        }
2307
2308
        return $status_label;
2309
    }
2310
    
2311 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...
2312
        $period = $this->get_meta( '_wpinv_subscr_period', true );
2313
        
2314
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2315
            $period = 'D';
2316
        }
2317
        
2318
        if ( $full ) {
2319
            switch( $period ) {
2320
                case 'D':
2321
                    $period = 'day';
2322
                break;
2323
                case 'W':
2324
                    $period = 'week';
2325
                break;
2326
                case 'M':
2327
                    $period = 'month';
2328
                break;
2329
                case 'Y':
2330
                    $period = 'year';
2331
                break;
2332
            }
2333
        }
2334
        
2335
        return $period;
2336
    }
2337
    
2338
    public function get_subscription_interval() {
2339
        $interval = (int)$this->get_meta( '_wpinv_subscr_interval', true );
2340
        
2341
        if ( !$interval > 0 ) {
2342
            $interval = 1;
2343
        }
2344
        
2345
        return $interval;
2346
    }
2347
    
2348 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...
2349
        if ( !$this->is_free_trial() ) {
2350
            return '';
2351
        }
2352
        
2353
        $period = $this->get_meta( '_wpinv_subscr_trial_period', true );
2354
        
2355
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2356
            $period = 'D';
2357
        }
2358
        
2359
        if ( $full ) {
2360
            switch( $period ) {
2361
                case 'D':
2362
                    $period = 'day';
2363
                break;
2364
                case 'W':
2365
                    $period = 'week';
2366
                break;
2367
                case 'M':
2368
                    $period = 'month';
2369
                break;
2370
                case 'Y':
2371
                    $period = 'year';
2372
                break;
2373
            }
2374
        }
2375
        
2376
        return $period;
2377
    }
2378
    
2379
    public function get_subscription_trial_interval() {
2380
        if ( !$this->is_free_trial() ) {
2381
            return 0;
2382
        }
2383
        
2384
        $interval = (int)$this->get_meta( '_wpinv_subscr_trial_interval', true );
2385
        
2386
        if ( !$interval > 0 ) {
2387
            $interval = 1;
2388
        }
2389
        
2390
        return $interval;
2391
    }
2392
    
2393 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...
2394
        $args = array(
2395
            'status' => 'failing'
2396
        );
2397
2398
        if ( $this->update_subscription( $args ) ) {
2399
            do_action( 'wpinv_subscription_failing', $this->ID, $this );
2400
            return true;
2401
        }
2402
2403
        return false;
2404
    }
2405
    
2406 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...
2407
        $args = array(
2408
            'status' => 'stopped'
2409
        );
2410
2411
        if ( $this->update_subscription( $args ) ) {
2412
            do_action( 'wpinv_subscription_stopped', $this->ID, $this );
2413
            return true;
2414
        }
2415
2416
        return false;
2417
    }
2418
    
2419 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...
2420
        $args = array(
2421
            'status' => 'active'
2422
        );
2423
2424
        if ( $this->update_subscription( $args ) ) {
2425
            do_action( 'wpinv_subscription_restarted', $this->ID, $this );
2426
            return true;
2427
        }
2428
2429
        return false;
2430
    }
2431
2432
    public function cancel_subscription() {
2433
        $args = array(
2434
            'status' => 'cancelled'
2435
        );
2436
2437
        if ( $this->update_subscription( $args ) ) {
2438
            if ( is_user_logged_in() ) {
2439
                $userdata = get_userdata( get_current_user_id() );
2440
                $user     = $userdata->user_login;
2441
            } else {
2442
                $user = __( 'gateway', 'invoicing' );
2443
            }
2444
            
2445
            $subscription_id = $this->get_subscription_id();
2446
            if ( !$subscription_id ) {
2447
                $subscription_id = $this->ID;
2448
            }
2449
2450
            $note = sprintf( __( 'Subscription %s has been cancelled by %s', 'invoicing' ), $subscription_id, $user );
2451
            $this->add_note( $note );
2452
2453
            do_action( 'wpinv_subscription_cancelled', $this->ID, $this );
2454
            return true;
2455
        }
2456
2457
        return false;
2458
    }
2459
2460
    public function can_cancel() {
2461
        return apply_filters( 'wpinv_subscription_can_cancel', false, $this );
2462
    }
2463
    
2464
    public function add_subscription( $data = array() ) {
2465
        if ( empty( $this->ID ) ) {
2466
            return false;
2467
        }
2468
2469
        $defaults = array(
2470
            'period'            => '',
2471
            'initial_amount'    => '',
2472
            'recurring_amount'  => '',
2473
            'interval'          => 0,
2474
            'trial_interval'    => 0,
2475
            'trial_period'      => '',
2476
            'bill_times'        => 0,
2477
            'item_id'           => 0,
2478
            'created'           => '',
2479
            'expiration'        => '',
2480
            'status'            => '',
2481
            'profile_id'        => '',
2482
        );
2483
2484
        $args = wp_parse_args( $data, $defaults );
2485
2486
        if ( $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2487
            if ( 'active' == $args['status'] || $args['status'] == 'trialing' ) {
2488
                $args['status'] = 'expired';
2489
            }
2490
        }
2491
2492
        do_action( 'wpinv_subscription_pre_create', $args, $data, $this );
2493
        
2494
        if ( !empty( $args ) ) {
2495
            foreach ( $args as $key => $value ) {
2496
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2497
            }
2498
        }
2499
2500
        do_action( 'wpinv_subscription_post_create', $args, $data, $this );
2501
2502
        return true;
2503
    }
2504
    
2505
    public function update_subscription( $args = array() ) {
2506
        if ( empty( $this->ID ) ) {
2507
            return false;
2508
        }
2509
2510
        if ( !empty( $args['expiration'] ) && $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2511
            if ( !isset( $args['status'] ) || ( isset( $args['status'] ) && ( 'active' == $args['status'] || $args['status'] == 'trialing' ) ) ) {
2512
                $args['status'] = 'expired';
2513
            }
2514
        }
2515
2516
        if ( isset( $args['status'] ) && $args['status'] == 'cancelled' && empty( $args['cancelled_on'] ) ) {
2517
            $args['cancelled_on'] = date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) );
2518
        }
2519
2520
        do_action( 'wpinv_subscription_pre_update', $args, $this );
2521
        
2522
        if ( !empty( $args ) ) {
2523
            foreach ( $args as $key => $value ) {
2524
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2525
            }
2526
        }
2527
2528
        do_action( 'wpinv_subscription_post_update', $args, $this );
2529
2530
        return true;
2531
    }
2532
    
2533
    public function renew_subscription() {
2534
        $parent_invoice = $this->get_parent_payment();
2535
        $parent_invoice = empty( $parent_invoice ) ? $this : $parent_invoice;
2536
        
2537
        $current_time   = current_time( 'timestamp' );
2538
        $start          = $this->get_subscription_created();
2539
        $start          = $start ? strtotime( $start ) : $current_time;
2540
        $expires        = $this->get_expiration_time();
2541
        
2542
        if ( !$expires ) {
2543
            $expires    = strtotime( '+' . $parent_invoice->get_subscription_interval() . ' ' . $parent_invoice->get_subscription_period( true ), $start );
2544
        }
2545
        
2546
        $expiration     = date_i18n( 'Y-m-d 23:59:59', $expires );
2547
        $expiration     = apply_filters( 'wpinv_subscription_renewal_expiration', $expiration, $this->ID, $this );
2548
        $bill_times     = $parent_invoice->get_bill_times();
2549
        $times_billed   = $parent_invoice->get_total_payments();
2550
        
2551
        if ( $parent_invoice->get_subscription_status() == 'trialing' && ( $times_billed > 0 || strtotime( date_i18n( 'Y-m-d' ) ) < strtotime( $parent_invoice->get_trial_end_date( false ) ) ) ) {
2552
            $args = array(
2553
                'status'     => 'active',
2554
            );
2555
2556
            $parent_invoice->update_subscription( $args );
2557
        }
2558
        
2559
        do_action( 'wpinv_subscription_pre_renew', $this->ID, $expiration, $this );
2560
2561
        $status       = 'active';
2562
        if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2563
            $this->complete_subscription();
2564
            $status = 'completed';
2565
        }
2566
2567
        $args = array(
2568
            'expiration' => $expiration,
2569
            'status'     => $status,
2570
        );
2571
2572
        $this->update_subscription( $args );
2573
2574
        do_action( 'wpinv_subscription_post_renew', $this->ID, $expiration, $this );
2575
        do_action( 'wpinv_recurring_set_subscription_status', $this->ID, $status, $this );
2576
    }
2577
    
2578
    public function complete_subscription() {
2579
        $args = array(
2580
            'status' => 'completed'
2581
        );
2582
2583
        if ( $this->update_subscription( $args ) ) {
2584
            do_action( 'wpinv_subscription_completed', $this->ID, $this );
2585
        }
2586
    }
2587
    
2588
    public function expire_subscription() {
2589
        $args = array(
2590
            'status' => 'expired'
2591
        );
2592
2593
        if ( $this->update_subscription( $args ) ) {
2594
            do_action( 'wpinv_subscription_expired', $this->ID, $this );
2595
        }
2596
    }
2597
2598
    public function get_cancel_url() {
2599
        $url = wp_nonce_url( add_query_arg( array( 'wpi_action' => 'cancel_subscription', 'sub_id' => $this->ID ) ), 'wpinv-recurring-cancel' );
2600
2601
        return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
2602
    }
2603
2604
    public function can_update() {
2605
        return apply_filters( 'wpinv_subscription_can_update', false, $this );
2606
    }
2607
2608
    public function get_update_url() {
2609
        $url = add_query_arg( array( 'action' => 'update', 'sub_id' => $this->ID ) );
2610
2611
        return apply_filters( 'wpinv_subscription_update_url', $url, $this );
2612
    }
2613
2614
    public function is_parent() {
2615
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2616
2617
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2618
    }
2619
    
2620
    public function is_renewal() {
2621
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2622
2623
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2624
    }
2625
    
2626
    public function get_parent_payment() {
2627
        $parent_payment = NULL;
2628
        
2629
        if ( $this->is_renewal() ) {
2630
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2631
        }
2632
        
2633
        return $parent_payment;
2634
    }
2635
    
2636
    public function is_subscription_active() {
2637
        $ret = false;
2638
        
2639
        $subscription_status = $this->get_subscription_status();
2640
2641
        if( ! $this->is_subscription_expired() && ( $subscription_status == 'active' || $subscription_status == 'cancelled' || $subscription_status == 'trialing' ) ) {
2642
            $ret = true;
2643
        }
2644
2645
        return apply_filters( 'wpinv_subscription_is_active', $ret, $this->ID, $this );
2646
    }
2647
2648
    public function is_subscription_expired() {
2649
        $ret = false;
2650
        $subscription_status = $this->get_subscription_status();
2651
2652
        if ( $subscription_status == 'expired' ) {
2653
            $ret = true;
2654
        } else if ( 'active' === $subscription_status || 'cancelled' === $subscription_status || 'trialing' == $subscription_status ) {
2655
            $ret        = false;
2656
            $expiration = $this->get_expiration_time();
2657
2658
            if ( $expiration && strtotime( 'NOW', current_time( 'timestamp' ) ) > $expiration ) {
2659
                $ret = true;
2660
2661
                if ( 'active' === $subscription_status || 'trialing' === $subscription_status ) {
2662
                    $this->expire_subscription();
2663
                }
2664
            }
2665
        }
2666
2667
        return apply_filters( 'wpinv_subscription_is_expired', $ret, $this->ID, $this );
2668
    }
2669
    
2670
    public function get_new_expiration( $item_id = 0, $trial = true ) {
2671
        $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...
2672
        $interval = $item->get_recurring_interval();
2673
        $period = $item->get_recurring_period( true );
2674
        
2675
        $expiration_time = strtotime( '+' . $interval . ' ' . $period );
2676
        
2677
        if ( $trial && $this->is_free_trial() && $item->has_free_trial() ) {
2678
            $expiration_time += ( wpinv_period_in_days( $item->get_trial_interval(), $item->get_trial_period() ) * DAY_IN_SECONDS ) ;
2679
        }
2680
2681
        return date_i18n( 'Y-m-d 23:59:59', $expiration_time );
2682
    }
2683
    
2684
    public function get_subscription_data( $filed = '' ) {
2685
        $fields = array( 'item_id', 'status', 'period', 'initial_amount', 'recurring_amount', 'interval', 'bill_times', 'trial_period', 'trial_interval', 'expiration', 'profile_id', 'created', 'cancelled_on' );
2686
        
2687
        $subscription_meta = array();
2688
        foreach ( $fields as $field ) {
2689
            $subscription_meta[ $field ] = $this->get_meta( '_wpinv_subscr_' . $field );
2690
        }
2691
        
2692
        $item = $this->get_recurring( true );
2693
        
2694
        if ( !empty( $item ) ) {
2695
            if ( empty( $subscription_meta['item_id'] ) ) {
2696
                $subscription_meta['item_id'] = $item->ID;
2697
            }
2698
            if ( empty( $subscription_meta['period'] ) ) {
2699
                $subscription_meta['period'] = $item->get_recurring_period();
2700
            }
2701
            if ( empty( $subscription_meta['interval'] ) ) {
2702
                $subscription_meta['interval'] = $item->get_recurring_interval();
2703
            }
2704
            if ( $item->has_free_trial() ) {
2705
                if ( empty( $subscription_meta['trial_period'] ) ) {
2706
                    $subscription_meta['trial_period'] = $item->get_trial_period();
2707
                }
2708
                if ( empty( $subscription_meta['trial_interval'] ) ) {
2709
                    $subscription_meta['trial_interval'] = $item->get_trial_interval();
2710
                }
2711
            } else {
2712
                $subscription_meta['trial_period']      = '';
2713
                $subscription_meta['trial_interval']    = 0;
2714
            }
2715
            if ( !$subscription_meta['bill_times'] && $subscription_meta['bill_times'] !== 0 ) {
2716
                $subscription_meta['bill_times'] = $item->get_recurring_limit();
2717
            }
2718
            if ( $subscription_meta['initial_amount'] === '' || $subscription_meta['recurring_amount'] === '' ) {
2719
                $subscription_meta['initial_amount']    = wpinv_format_amount( $this->get_total() );
2720
                $subscription_meta['recurring_amount']  = wpinv_format_amount( $this->get_recurring_details( 'total' ) );
2721
            }
2722
        }
2723
        
2724
        if ( $filed === '' ) {
2725
            return apply_filters( 'wpinv_get_invoice_subscription_data', $subscription_meta, $this );
2726
        }
2727
        
2728
        $value = isset( $subscription_meta[$filed] ) ? $subscription_meta[$filed] : '';
2729
        
2730
        return apply_filters( 'wpinv_invoice_subscription_data_value', $value, $subscription_meta, $this );
2731
    }
2732
    
2733
    public function is_paid() {
2734
        if ( $this->has_status( array( 'publish', 'complete', 'processing', 'renewal' ) ) ) {
2735
            return true;
2736
        }
2737
        
2738
        return false;
2739
    }
2740
    
2741
    public function has_vat() {
2742
        global $wpinv_euvat, $wpi_country;
2743
        
2744
        $requires_vat = false;
2745
        
2746
        if ( $this->country ) {
2747
            $wpi_country        = $this->country;
2748
            
2749
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2750
        }
2751
        
2752
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2753
    }
2754
    
2755
    public function refresh_item_ids() {
2756
        $item_ids = array();
2757
        
2758
        if ( !empty( $this->cart_details ) ) {
2759
            foreach ( $this->cart_details as $key => $item ) {
2760
                if ( !empty( $item['id'] ) ) {
2761
                    $item_ids[] = $item['id'];
2762
                }
2763
            }
2764
        }
2765
        
2766
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2767
        
2768
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2769
    }
2770
}
2771