Passed
Pull Request — master (#33)
by Kiran
03:50
created

WPInv_Invoice::get_meta()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 15
nc 36
nop 2
dl 0
loc 29
rs 5.3846
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( '_wpinv_payment_meta', false );
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', true );
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', false );
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', true );
343
    }
344
345
    private function setup_gateway() {
346
        $gateway = $this->get_meta( '_wpinv_gateway', true );
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', true );
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', true );
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', true );
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...
378
        ///return $user_id;
379
    ///}
380
        
381
    private function setup_first_name() {
382
        $first_name = $this->get_meta( '_wpinv_first_name', true );
383
        return $first_name;
384
    }
385
    
386
    private function setup_last_name() {
387
        $last_name = $this->get_meta( '_wpinv_last_name', true );
388
        return $last_name;
389
    }
390
    
391
    private function setup_company() {
392
        $company = $this->get_meta( '_wpinv_company', true );
393
        return $company;
394
    }
395
    
396
    private function setup_vat_number() {
397
        $vat_number = $this->get_meta( '_wpinv_vat_number', true );
398
        return $vat_number;
399
    }
400
    
401
    private function setup_vat_rate() {
402
        $vat_rate = $this->get_meta( '_wpinv_vat_rate', true );
403
        return $vat_rate;
404
    }
405
    
406
    private function setup_adddress_confirmed() {
407
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed', true );
408
        return $adddress_confirmed;
409
    }
410
    
411
    private function setup_phone() {
412
        $phone = $this->get_meta( '_wpinv_phone', true );
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( '_wpinv_payment_meta', false );
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( '_wpinv_payment_meta', false );
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 = false ) {
1231
        if ( empty( $meta_key ) || $meta_key == 'payment_meta' ) {
1232
            $meta_key = '_wpinv_payment_meta';
1233
        }
1234
        
1235
        if ( $meta_key == '_wpinv_payment_meta' ) {
1236
            $single = false;
1237
        }
1238
        
1239
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1240
1241
        if ( $meta_key === '_wpinv_payment_meta' ) {
1242
            if ( !is_array( $meta ) ) {
1243
                $meta = array();
1244
            }
1245
            
1246
            if ( empty( $meta['key'] ) ) {
1247
                $meta['key'] = $this->setup_invoice_key();
1248
            }
1249
1250
            if ( empty( $meta['date'] ) ) {
1251
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1252
            }
1253
        }
1254
        
1255
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1256
1257
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1258
    }
1259
    
1260
    public function get_description() {
1261
        $post = get_post( $this->ID );
1262
        
1263
        $description = !empty( $post ) ? $post->post_content : '';
1264
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1265
    }
1266
    
1267
    public function get_status( $nicename = false ) {
1268
        if ( !$nicename ) {
1269
            $status = $this->status;
1270
        } else {
1271
            $status = $this->status_nicename;
1272
        }
1273
        
1274
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1275
    }
1276
    
1277
    public function get_cart_details() {
1278
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1279
    }
1280
    
1281 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...
1282
        $subtotal = wpinv_format_amount( $this->subtotal, NULL, !$currency );
1283
        
1284
        if ( $currency ) {
1285
            $subtotal = wpinv_price( $subtotal, $this->get_currency() );
1286
        }
1287
        
1288
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1289
    }
1290
    
1291
    public function get_total( $currency = false ) {        
1292
        if ( $this->is_free_trial() ) {
1293
            $total = wpinv_format_amount( 0, NULL, !$currency );
1294
        } else {
1295
            $total = wpinv_format_amount( $this->total, NULL, !$currency );
1296
        }
1297
        if ( $currency ) {
1298
            $total = wpinv_price( $total, $this->get_currency() );
1299
        }
1300
        
1301
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1302
    }
1303
    
1304
    public function get_recurring_details( $field = '', $currency = false ) {        
1305
        $data                 = array();
1306
        $data['cart_details'] = $this->cart_details;
1307
        $data['subtotal']     = $this->get_subtotal();
1308
        $data['discount']     = $this->get_discount();
1309
        $data['tax']          = $this->get_tax();
1310
        $data['total']        = $this->get_total();
1311
    
1312
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1313
            $is_free_trial = $this->is_free_trial();
1314
            $discounts = $this->get_discounts( true );
1315
            
1316
            if ( $is_free_trial || !empty( $discounts ) ) {
1317
                $first_use_only = false;
1318
                
1319
                if ( !empty( $discounts ) ) {
1320
                    foreach ( $discounts as $key => $code ) {
1321
                        if ( wpinv_discount_is_recurring( $code, true ) ) {
1322
                            $first_use_only = true;
1323
                            break;
1324
                        }
1325
                    }
1326
                }
1327
                    
1328
                if ( !$first_use_only ) {
1329
                    $data['subtotal'] = wpinv_format_amount( $this->subtotal, NULL, true );
1330
                    $data['discount'] = wpinv_format_amount( $this->discount, NULL, true );
1331
                    $data['tax']      = wpinv_format_amount( $this->tax, NULL, true );
1332
                    $data['total']    = wpinv_format_amount( $this->total, NULL, true );
1333
                } else {
1334
                    $cart_subtotal   = 0;
1335
                    $cart_discount   = 0;
1336
                    $cart_tax        = 0;
1337
1338
                    foreach ( $this->cart_details as $key => $item ) {
1339
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1340
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1341
                        $item_discount  = 0;
1342
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1343
                        
1344
                        if ( wpinv_prices_include_tax() ) {
1345
                            $item_subtotal -= wpinv_format_amount( $item_tax, NULL, true );
1346
                        }
1347
                        
1348
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1349
                        // Do not allow totals to go negative
1350
                        if ( $item_total < 0 ) {
1351
                            $item_total = 0;
1352
                        }
1353
                        
1354
                        $cart_subtotal  += (float)($item_subtotal);
1355
                        $cart_discount  += (float)($item_discount);
1356
                        $cart_tax       += (float)($item_tax);
1357
                        
1358
                        $data['cart_details'][$key]['discount']   = wpinv_format_amount( $item_discount, NULL, true );
1359
                        $data['cart_details'][$key]['tax']        = wpinv_format_amount( $item_tax, NULL, true );
1360
                        $data['cart_details'][$key]['price']      = wpinv_format_amount( $item_total, NULL, true );
1361
                    }
1362
                    
1363
                    $data['subtotal'] = wpinv_format_amount( $cart_subtotal, NULL, true );
1364
                    $data['discount'] = wpinv_format_amount( $cart_discount, NULL, true );
1365
                    $data['tax']      = wpinv_format_amount( $cart_tax, NULL, true );
1366
                    $data['total']    = wpinv_format_amount( ( $data['subtotal'] + $data['tax'] ), NULL, true );
1367
                }
1368
            }
1369
        }
1370
        
1371
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1372
1373
        if ( isset( $data[$field] ) ) {
1374
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1375
        }
1376
        
1377
        return $data;
1378
    }
1379
    
1380 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...
1381
        $final_total = wpinv_format_amount( $this->tax, NULL, !$currency );
1382
        if ( $currency ) {
1383
            $final_total = wpinv_price( $final_total, $this->get_currency() );
1384
        }
1385
        
1386
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1387
    }
1388
    
1389
    public function get_discounts( $array = false ) {
1390
        $discounts = $this->discounts;
1391
        if ( $array && $discounts ) {
1392
            $discounts = explode( ',', $discounts );
1393
        }
1394
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1395
    }
1396
    
1397
    public function get_discount( $currency = false, $dash = false ) {
1398
        if ( !empty( $this->discounts ) ) {
1399
            global $ajax_cart_details;
1400
            $ajax_cart_details = $this->get_cart_details();
1401
1402
            $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...
1403
        }
1404
        $discount   = wpinv_format_amount( $this->discount, NULL, !$currency );
1405
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1406
        
1407
        if ( $currency ) {
1408
            $discount = wpinv_price( $discount, $this->get_currency() );
1409
        }
1410
        
1411
        $discount   = $dash . $discount;
1412
        
1413
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1414
    }
1415
    
1416
    public function get_discount_code() {
1417
        return $this->discount_code;
1418
    }
1419
    
1420 View Code Duplication
    public function get_tax( $currency = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
1588
            return false;
1589
        }
1590
        
1591
        $has_quantities = wpinv_item_quantities_enabled();
1592
1593
        // Set some defaults
1594
        $defaults = array(
1595
            'quantity'  => 1,
1596
            'id'        => false,
1597
            'name'      => $item->get_name(),
1598
            'item_price'=> false,
1599
            'discount'  => 0,
1600
            'tax'       => 0.00,
1601
            'meta'      => array(),
1602
            'fees'      => array()
1603
        );
1604
1605
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1606
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1607
1608
        $wpi_current_id         = $this->ID;
1609
        $wpi_item_id            = $item->ID;
1610
        $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...
1611
        
1612
        $_POST['wpinv_country'] = $this->country;
1613
        $_POST['wpinv_state']   = $this->state;
1614
        
1615
        $found_cart_key         = false;
1616
        
1617
        if ($has_quantities) {
1618
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1619
            
1620
            foreach ( $this->items as $key => $cart_item ) {
1621
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1622
                    continue;
1623
                }
1624
1625
                $this->items[ $key ]['quantity'] += $args['quantity'];
1626
                break;
1627
            }
1628
            
1629
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1630
                if ( $item_id != $cart_item['id'] ) {
1631
                    continue;
1632
                }
1633
1634
                $found_cart_key = $cart_key;
1635
                break;
1636
            }
1637
        }
1638
        
1639
        if ($has_quantities && $found_cart_key !== false) {
1640
            $cart_item          = $this->cart_details[$found_cart_key];
1641
            $item_price         = $cart_item['item_price'];
1642
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1643
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1644
            
1645
            $new_quantity       = $quantity + $args['quantity'];
1646
            $subtotal           = $item_price * $new_quantity;
1647
            
1648
            $args['quantity']   = $new_quantity;
1649
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1650
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1651
            
1652
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1653
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1654
            // The total increase equals the number removed * the item_price
1655
            $total_increased    = wpinv_format_amount( $item_price, NULL, true );
1656
            
1657
            if ( wpinv_prices_include_tax() ) {
1658
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1659
            }
1660
1661
            $total              = $subtotal - $discount + $tax;
1662
1663
            // Do not allow totals to go negative
1664
            if( $total < 0 ) {
1665
                $total = 0;
1666
            }
1667
            
1668
            $cart_item['quantity']  = $new_quantity;
1669
            $cart_item['subtotal']  = $subtotal;
1670
            $cart_item['discount']  = $discount;
1671
            $cart_item['tax']       = $tax;
1672
            $cart_item['price']     = $total;
1673
            
1674
            $subtotal               = $total_increased - $discount_increased;
1675
            $tax                    = $tax_increased;
1676
            
1677
            $this->cart_details[$found_cart_key] = $cart_item;
1678
        } else {
1679
            // Allow overriding the price
1680
            if( false !== $args['item_price'] ) {
1681
                $item_price = $args['item_price'];
1682
            } else {
1683
                $item_price = wpinv_get_item_price( $item->ID );
1684
            }
1685
1686
            // Sanitizing the price here so we don't have a dozen calls later
1687
            $item_price = wpinv_sanitize_amount( $item_price );
1688
            $subtotal   = wpinv_format_amount( $item_price * $args['quantity'], NULL, true );
1689
        
1690
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1691
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1692
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1693
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1694
1695
            // Setup the items meta item
1696
            $new_item = array(
1697
                'id'       => $item->ID,
1698
                'quantity' => $args['quantity'],
1699
            );
1700
1701
            $this->items[]  = $new_item;
1702
1703
            if ( wpinv_prices_include_tax() ) {
1704
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1705
            }
1706
1707
            $total      = $subtotal - $discount + $tax;
1708
1709
            // Do not allow totals to go negative
1710
            if( $total < 0 ) {
1711
                $total = 0;
1712
            }
1713
        
1714
            $this->cart_details[] = array(
1715
                'name'        => !empty($args['name']) ? $args['name'] : $item->get_name(),
1716
                'id'          => $item->ID,
1717
                'item_price'  => wpinv_format_amount( $item_price, NULL, true ),
1718
                'quantity'    => $args['quantity'],
1719
                'discount'    => $discount,
1720
                'subtotal'    => wpinv_format_amount( $subtotal, NULL, true ),
1721
                'tax'         => wpinv_format_amount( $tax, NULL, true ),
1722
                'price'       => wpinv_format_amount( $total, NULL, true ),
1723
                'vat_rate'    => $tax_rate,
1724
                'vat_class'   => $tax_class,
1725
                'meta'        => $args['meta'],
1726
                'fees'        => $args['fees'],
1727
            );
1728
                        
1729
            $subtotal = $subtotal - $discount;
1730
        }
1731
        
1732
        $added_item = end( $this->cart_details );
1733
        $added_item['action']  = 'add';
1734
        
1735
        $this->pending['items'][] = $added_item;
1736
        
1737
        $this->increase_subtotal( $subtotal );
1738
        $this->increase_tax( $tax );
1739
1740
        return true;
1741
    }
1742
    
1743
    public function remove_item( $item_id, $args = array() ) {
1744
        // Set some defaults
1745
        $defaults = array(
1746
            'quantity'   => 1,
1747
            'item_price' => false,
1748
            'cart_index' => false,
1749
        );
1750
        $args = wp_parse_args( $args, $defaults );
1751
1752
        // Bail if this post isn't a item
1753
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1754
            return false;
1755
        }
1756
        
1757
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1758
1759
        foreach ( $this->items as $key => $item ) {
1760
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1761
                continue;
1762
            }
1763
1764
            if ( false !== $args['cart_index'] ) {
1765
                $cart_index = absint( $args['cart_index'] );
1766
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1767
1768
                if ( ! empty( $cart_item ) ) {
1769
                    // If the cart index item isn't the same item ID, don't remove it
1770
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1771
                        continue;
1772
                    }
1773
                }
1774
            }
1775
1776
            $item_quantity = $this->items[ $key ]['quantity'];
1777
            if ( $item_quantity > $args['quantity'] ) {
1778
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1779
                break;
1780
            } else {
1781
                unset( $this->items[ $key ] );
1782
                break;
1783
            }
1784
        }
1785
1786
        $found_cart_key = false;
1787
        if ( false === $args['cart_index'] ) {
1788
            foreach ( $this->cart_details as $cart_key => $item ) {
1789
                if ( $item_id != $item['id'] ) {
1790
                    continue;
1791
                }
1792
1793
                if ( false !== $args['item_price'] ) {
1794
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1795
                        continue;
1796
                    }
1797
                }
1798
1799
                $found_cart_key = $cart_key;
1800
                break;
1801
            }
1802
        } else {
1803
            $cart_index = absint( $args['cart_index'] );
1804
1805
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1806
                return false; // Invalid cart index passed.
1807
            }
1808
1809
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1810
                return false; // We still need the proper Item ID to be sure.
1811
            }
1812
1813
            $found_cart_key = $cart_index;
1814
        }
1815
        
1816
        $cart_item  = $this->cart_details[$found_cart_key];
1817
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1818
        
1819
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1820
            return false; // Invoice must contain at least one item.
1821
        }
1822
        
1823
        $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...
1824
        
1825
        if ( $quantity > $args['quantity'] ) {
1826
            $item_price         = $cart_item['item_price'];
1827
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1828
            
1829
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1830
            $subtotal           = $item_price * $new_quantity;
1831
            
1832
            $args['quantity']   = $new_quantity;
1833
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1834
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1835
            
1836
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_format_amount( ( (float)$cart_item['discount'] / $quantity ), NULL, true ) : 0;
1837
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1838
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_format_amount( ( (float)$cart_item['tax'] / $quantity ), NULL, true ) : 0;
1839
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1840
            
1841
            // The total increase equals the number removed * the item_price
1842
            $total_decrease     = wpinv_format_amount( $item_price, NULL, true );
1843
            
1844
            if ( wpinv_prices_include_tax() ) {
1845
                $subtotal -= wpinv_format_amount( $tax, NULL, true );
1846
            }
1847
1848
            $total              = $subtotal - $discount + $tax;
1849
1850
            // Do not allow totals to go negative
1851
            if( $total < 0 ) {
1852
                $total = 0;
1853
            }
1854
            
1855
            $cart_item['quantity']  = $new_quantity;
1856
            $cart_item['subtotal']  = $subtotal;
1857
            $cart_item['discount']  = $discount;
1858
            $cart_item['tax']       = $tax;
1859
            $cart_item['price']     = $total;
1860
            
1861
            $added_item             = $cart_item;
1862
            $added_item['id']       = $item_id;
1863
            $added_item['price']    = $total_decrease;
1864
            $added_item['quantity'] = $args['quantity'];
1865
            
1866
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1867
            
1868
            $this->cart_details[$found_cart_key] = $cart_item;
1869
            
1870
            $remove_item = end( $this->cart_details );
1871
        } else {
1872
            $item_price     = $cart_item['item_price'];
1873
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1874
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1875
        
1876
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1877
            $tax_decrease       = $tax;
1878
1879
            unset( $this->cart_details[$found_cart_key] );
1880
            
1881
            $remove_item             = $args;
1882
            $remove_item['id']       = $item_id;
1883
            $remove_item['price']    = $subtotal_decrease;
1884
            $remove_item['quantity'] = $args['quantity'];
1885
        }
1886
        
1887
        $remove_item['action']      = 'remove';
1888
        $this->pending['items'][]   = $remove_item;
1889
               
1890
        $this->decrease_subtotal( $subtotal_decrease );
1891
        $this->decrease_tax( $tax_decrease );
1892
        
1893
        return true;
1894
    }
1895
    
1896
    public function update_items($temp = false) {
1897
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1898
        
1899
        if ( !empty( $this->cart_details ) ) {
1900
            $wpi_nosave             = $temp;
1901
            $cart_subtotal          = 0;
1902
            $cart_discount          = 0;
1903
            $cart_tax               = 0;
1904
            $cart_details           = array();
1905
            
1906
            $_POST['wpinv_country'] = $this->country;
1907
            $_POST['wpinv_state']   = $this->state;
1908
            
1909
            foreach ( $this->cart_details as $key => $item ) {
1910
                $item_price = $item['item_price'];
1911
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1912
                $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...
1913
                $subtotal   = $item_price * $quantity;
1914
                
1915
                $wpi_current_id         = $this->ID;
1916
                $wpi_item_id            = $item['id'];
1917
                
1918
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1919
                
1920
                $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...
1921
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1922
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1923
1924
                if ( wpinv_prices_include_tax() ) {
1925
                    $subtotal -= wpinv_format_amount( $tax, NULL, true );
1926
                }
1927
1928
                $total      = $subtotal - $discount + $tax;
1929
1930
                // Do not allow totals to go negative
1931
                if( $total < 0 ) {
1932
                    $total = 0;
1933
                }
1934
1935
                $cart_details[] = array(
1936
                    'id'          => $item['id'],
1937
                    'name'        => $item['name'],
1938
                    'item_price'  => wpinv_format_amount( $item_price, NULL, true ),
1939
                    'quantity'    => $quantity,
1940
                    'discount'    => $discount,
1941
                    'subtotal'    => wpinv_format_amount( $subtotal, NULL, true ),
1942
                    'tax'         => wpinv_format_amount( $tax, NULL, true ),
1943
                    'price'       => wpinv_format_amount( $total, NULL, true ),
1944
                    'vat_rate'    => $tax_rate,
1945
                    'vat_class'   => $tax_class,
1946
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1947
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1948
                );
1949
                
1950
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1951
                $cart_discount  += (float)($discount);
1952
                $cart_tax       += (float)($tax);
1953
            }
1954
            $this->subtotal = wpinv_format_amount( $cart_subtotal, NULL, true );
1955
            $this->tax      = wpinv_format_amount( $cart_tax, NULL, true );
1956
            $this->discount = wpinv_format_amount( $cart_discount, NULL, true );
1957
            
1958
            $this->recalculate_total();
1959
            
1960
            $this->cart_details = $cart_details;
1961
        }
1962
1963
        return $this;
1964
    }
1965
    
1966
    public function recalculate_totals($temp = false) {        
1967
        $this->update_items($temp);
1968
        $this->save( true );
1969
        
1970
        return $this;
1971
    }
1972
    
1973
    public function needs_payment() {
1974
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'pending' ), $this );
1975
1976
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() ) ) {
1977
            $needs_payment = true;
1978
        } else {
1979
            $needs_payment = false;
1980
        }
1981
1982
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1983
    }
1984
    
1985
    public function get_checkout_payment_url( $on_checkout = false, $secret = false ) {
1986
        $pay_url = wpinv_get_checkout_uri();
1987
1988
        if ( is_ssl() ) {
1989
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
1990
        }
1991
        
1992
        $key = $this->get_key();
1993
1994
        if ( $on_checkout ) {
1995
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
1996
        } else {
1997
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
1998
        }
1999
        
2000 View Code Duplication
        if ( $secret ) {
2001
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
2002
        }
2003
2004
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this );
2005
    }
2006
    
2007
    public function get_view_url( $secret = false ) {
2008
        $print_url = get_permalink( $this->ID );
2009
        
2010 View Code Duplication
        if ( $secret ) {
2011
            $print_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $this->get_key() ) ), $print_url );
2012
        }
2013
2014
        return apply_filters( 'wpinv_get_view_url', $print_url, $this );
2015
    }
2016
    
2017
    public function generate_key( $string = '' ) {
2018
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2019
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2020
    }
2021
    
2022
    public function is_recurring() {
2023
        if ( empty( $this->cart_details ) ) {
2024
            return false;
2025
        }
2026
        
2027
        $has_subscription = false;
2028 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2029
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2030
                $has_subscription = true;
2031
                break;
2032
            }
2033
        }
2034
        
2035
        if ( count( $this->cart_details ) > 1 ) {
2036
            $has_subscription = false;
2037
        }
2038
2039
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2040
    }
2041
    
2042
    public function is_free_trial() {
2043
        $is_free_trial = false;
2044
        
2045
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2046
            if ( !empty( $item ) && $item->has_free_trial() ) {
2047
                $is_free_trial = true;
2048
            }
2049
        }
2050
2051
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details );
2052
    }
2053
    
2054
    public function get_recurring( $object = false ) {
2055
        $item = NULL;
2056
        
2057
        if ( empty( $this->cart_details ) ) {
2058
            return $item;
2059
        }
2060
        
2061 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2062
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2063
                $item = $cart_item['id'];
2064
                break;
2065
            }
2066
        }
2067
        
2068
        if ( $object ) {
2069
            $item = $item ? new WPInv_Item( $item ) : NULL;
2070
            
2071
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2072
        }
2073
2074
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2075
    }
2076
    
2077
    public function get_subscription_name() {
2078
        $item = $this->get_recurring( true );
2079
        
2080
        if ( empty( $item ) ) {
2081
            return NULL;
2082
        }
2083
        
2084
        if ( !($name = $item->get_name()) ) {
2085
            $name = $item->post_name;
2086
        }
2087
2088
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2089
    }
2090
        
2091
    public function get_expiration() {
2092
        $expiration = $this->get_meta( '_wpinv_subscr_expiration', true );
2093
        return $expiration;
2094
    }
2095
    
2096
    public function get_cancelled_date( $formatted = true ) {
2097
        $cancelled_date = $this->get_subscription_status() == 'cancelled' ? $this->get_meta( '_wpinv_subscr_cancelled_on', true ) : '';
2098
        
2099
        if ( $formatted && $cancelled_date ) {
2100
            $cancelled_date = date_i18n( get_option( 'date_format' ), strtotime( $cancelled_date ) );
2101
        }
2102
        
2103
        return $cancelled_date;
2104
    }
2105
    
2106
    public function get_trial_end_date( $formatted = true ) {
2107
        if ( !$this->is_free_trial() || !$this->is_paid() ) {
2108
            return NULL;
2109
        }
2110
        
2111
        $trial_end_date = $this->get_subscription_status() == 'trialing' ? $this->get_meta( '_wpinv_subscr_trial_end', true ) : '';
2112
        
2113
        if ( empty( $trial_end_date ) ) {
2114
            $trial_start_time = strtotime( $this->get_subscription_start() );
2115
            $trial_start_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2116
            
2117
            $trial_end_date = date_i18n( 'Y-m-d H:i:s', $trial_start_time );
2118
        }
2119
        
2120
        if ( $formatted && $trial_end_date ) {
2121
            $trial_end_date = date_i18n( get_option( 'date_format' ), strtotime( $trial_end_date ) );
2122
        }
2123
        
2124
        return $trial_end_date;
2125
    }
2126
    
2127
    public function get_subscription_created( $default = true ) {
2128
        $created = $this->get_meta( '_wpinv_subscr_created', true );
2129
        
2130
        if ( empty( $created ) && $default ) {
2131
            $created = $this->date;
2132
        }
2133
        return $created;
2134
    }
2135
    
2136
    public function get_subscription_start( $formatted = true ) {
2137
        if ( !$this->is_paid() ) {
2138
            return '-';
2139
        }
2140
        $start   = $this->get_subscription_created();
2141
        
2142
        if ( $formatted ) {
2143
            $date = date_i18n( get_option( 'date_format' ), strtotime( $start ) );
2144
        } else {
2145
            $date = date_i18n( 'Y-m-d H:i:s', strtotime( $start ) );
2146
        }
2147
2148
        return $date;
2149
    }
2150
    
2151
    public function get_subscription_end( $formatted = true ) {
2152
        if ( !$this->is_paid() ) {
2153
            return '-';
2154
        }
2155
        $start          = $this->get_subscription_created();
2156
        $interval       = $this->get_subscription_interval();
2157
        $period         = $this->get_subscription_period( true );
2158
        $bill_times     = (int)$this->get_bill_times();
2159
        
2160
        if ( $bill_times == 0 ) {
2161
            return $formatted ? __( 'Until cancelled', 'invoicing' ) : $bill_times;
2162
        }
2163
        
2164
        $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...
2165
        
2166
        $end_time = strtotime( $start . '+' . ( $interval * $bill_times ) . ' ' . $period );
2167
        
2168
        if ( $this->is_free_trial() ) {
2169
            $end_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2170
        }
2171
        
2172
        if ( $formatted ) {
2173
            $date = date_i18n( get_option( 'date_format' ), $end_time );
2174
        } else {
2175
            $date = date_i18n( 'Y-m-d H:i:s', $end_time );
2176
        }
2177
2178
        return $date;
2179
    }
2180
    
2181
    public function get_expiration_time() {
2182
        return strtotime( $this->get_expiration(), current_time( 'timestamp' ) );
2183
    }
2184
    
2185
    public function get_original_invoice_id() {        
2186
        return $this->parent_invoice;
2187
    }
2188
    
2189
    public function get_bill_times() {
2190
        $subscription_data = $this->get_subscription_data();
2191
        return $subscription_data['bill_times'];
2192
    }
2193
2194
    public function get_child_payments( $self = false ) {
2195
        $invoices = get_posts( array(
2196
            'post_type'         => 'wpi_invoice',
2197
            'post_parent'       => (int)$this->ID,
2198
            'posts_per_page'    => '999',
2199
            'post_status'       => array( 'publish', 'complete', 'processing', 'renewal' ),
2200
            'orderby'           => 'ID',
2201
            'order'             => 'DESC',
2202
            'fields'            => 'ids'
2203
        ) );
2204
        
2205
        if ( $this->is_free_trial() ) {
2206
            $self = false;
2207
        }
2208
        
2209
        if ( $self && $this->is_paid() ) {
2210
            if ( !empty( $invoices ) ) {
2211
                $invoices[] = (int)$this->ID;
2212
            } else {
2213
                $invoices = array( $this->ID );
2214
            }
2215
            
2216
            $invoices = array_unique( $invoices );
2217
        }
2218
2219
        return $invoices;
2220
    }
2221
2222
    public function get_total_payments( $self = true ) {
2223
        return count( $this->get_child_payments( $self ) );
2224
    }
2225
    
2226
    public function get_subscriptions( $limit = -1 ) {
2227
        $subscriptions = wpinv_get_subscriptions( array( 'parent_invoice_id' => $this->ID, 'numberposts' => $limit ) );
2228
2229
        return $subscriptions;
2230
    }
2231
    
2232
    public function get_subscription_id() {
2233
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2234
        
2235
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2236
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2237
            
2238
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2239
        }
2240
        
2241
        return $subscription_id;
2242
    }
2243
    
2244
    public function get_subscription_status() {
2245
        $subscription_status = $this->get_meta( '_wpinv_subscr_status', true );
2246
2247
        if ( empty( $subscription_status ) ) {
2248
            $status = 'pending';
2249
            
2250
            if ( $this->is_paid() ) {        
2251
                $bill_times   = (int)$this->get_bill_times();
2252
                $times_billed = (int)$this->get_total_payments();
2253
                $expiration = $this->get_subscription_end( false );
2254
                $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;
2255
                
2256
                if ( (int)$bill_times == 0 ) {
2257
                    $status = $expired ? 'expired' : 'active';
2258
                } else if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2259
                    $status = 'completed';
2260
                } else if ( $expired ) {
2261
                    $status = 'expired';
2262
                } else if ( $bill_times > 0 ) {
2263
                    $status = 'active';
2264
                } else {
2265
                    $status = 'pending';
2266
                }
2267
            }
2268
            
2269
            if ( $status && $status != $subscription_status ) {
2270
                $subscription_status = $status;
2271
                
2272
                $this->update_meta( '_wpinv_subscr_status', $status );
2273
            }
2274
        }
2275
        
2276
        return $subscription_status;
2277
    }
2278
    
2279
    public function get_subscription_status_label( $status = '' ) {
2280
        $status = !empty( $status ) ? $status : $this->get_subscription_status();
2281
2282
        switch( $status ) {
2283
            case 'active' :
2284
                $status_label = __( 'Active', 'invoicing' );
2285
                break;
2286
2287
            case 'cancelled' :
2288
                $status_label = __( 'Cancelled', 'invoicing' );
2289
                break;
2290
                
2291
            case 'completed' :
2292
                $status_label = __( 'Completed', 'invoicing' );
2293
                break;
2294
2295
            case 'expired' :
2296
                $status_label = __( 'Expired', 'invoicing' );
2297
                break;
2298
2299
            case 'pending' :
2300
                $status_label = __( 'Pending', 'invoicing' );
2301
                break;
2302
2303
            case 'failing' :
2304
                $status_label = __( 'Failing', 'invoicing' );
2305
                break;
2306
                
2307
            case 'stopped' :
2308
                $status_label = __( 'Stopped', 'invoicing' );
2309
                break;
2310
                
2311
            case 'trialing' :
2312
                $status_label = __( 'Trialing', 'invoicing' );
2313
                break;
2314
2315
            default:
2316
                $status_label = $status;
2317
                break;
2318
        }
2319
2320
        return $status_label;
2321
    }
2322
    
2323 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...
2324
        $period = $this->get_meta( '_wpinv_subscr_period', true );
2325
        
2326
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2327
            $period = 'D';
2328
        }
2329
        
2330
        if ( $full ) {
2331
            switch( $period ) {
2332
                case 'D':
2333
                    $period = 'day';
2334
                break;
2335
                case 'W':
2336
                    $period = 'week';
2337
                break;
2338
                case 'M':
2339
                    $period = 'month';
2340
                break;
2341
                case 'Y':
2342
                    $period = 'year';
2343
                break;
2344
            }
2345
        }
2346
        
2347
        return $period;
2348
    }
2349
    
2350
    public function get_subscription_interval() {
2351
        $interval = (int)$this->get_meta( '_wpinv_subscr_interval', true );
2352
        
2353
        if ( !$interval > 0 ) {
2354
            $interval = 1;
2355
        }
2356
        
2357
        return $interval;
2358
    }
2359
    
2360 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...
2361
        if ( !$this->is_free_trial() ) {
2362
            return '';
2363
        }
2364
        
2365
        $period = $this->get_meta( '_wpinv_subscr_trial_period', true );
2366
        
2367
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2368
            $period = 'D';
2369
        }
2370
        
2371
        if ( $full ) {
2372
            switch( $period ) {
2373
                case 'D':
2374
                    $period = 'day';
2375
                break;
2376
                case 'W':
2377
                    $period = 'week';
2378
                break;
2379
                case 'M':
2380
                    $period = 'month';
2381
                break;
2382
                case 'Y':
2383
                    $period = 'year';
2384
                break;
2385
            }
2386
        }
2387
        
2388
        return $period;
2389
    }
2390
    
2391
    public function get_subscription_trial_interval() {
2392
        if ( !$this->is_free_trial() ) {
2393
            return 0;
2394
        }
2395
        
2396
        $interval = (int)$this->get_meta( '_wpinv_subscr_trial_interval', true );
2397
        
2398
        if ( !$interval > 0 ) {
2399
            $interval = 1;
2400
        }
2401
        
2402
        return $interval;
2403
    }
2404
    
2405 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...
2406
        $args = array(
2407
            'status' => 'failing'
2408
        );
2409
2410
        if ( $this->update_subscription( $args ) ) {
2411
            do_action( 'wpinv_subscription_failing', $this->ID, $this );
2412
            return true;
2413
        }
2414
2415
        return false;
2416
    }
2417
    
2418 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...
2419
        $args = array(
2420
            'status' => 'stopped'
2421
        );
2422
2423
        if ( $this->update_subscription( $args ) ) {
2424
            do_action( 'wpinv_subscription_stopped', $this->ID, $this );
2425
            return true;
2426
        }
2427
2428
        return false;
2429
    }
2430
    
2431 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...
2432
        $args = array(
2433
            'status' => 'active'
2434
        );
2435
2436
        if ( $this->update_subscription( $args ) ) {
2437
            do_action( 'wpinv_subscription_restarted', $this->ID, $this );
2438
            return true;
2439
        }
2440
2441
        return false;
2442
    }
2443
2444
    public function cancel_subscription() {
2445
        $args = array(
2446
            'status' => 'cancelled'
2447
        );
2448
2449
        if ( $this->update_subscription( $args ) ) {
2450
            if ( is_user_logged_in() ) {
2451
                $userdata = get_userdata( get_current_user_id() );
2452
                $user     = $userdata->user_login;
2453
            } else {
2454
                $user = __( 'gateway', 'invoicing' );
2455
            }
2456
            
2457
            $subscription_id = $this->get_subscription_id();
2458
            if ( !$subscription_id ) {
2459
                $subscription_id = $this->ID;
2460
            }
2461
2462
            $note = sprintf( __( 'Subscription %s has been cancelled by %s', 'invoicing' ), $subscription_id, $user );
2463
            $this->add_note( $note );
2464
2465
            do_action( 'wpinv_subscription_cancelled', $this->ID, $this );
2466
            return true;
2467
        }
2468
2469
        return false;
2470
    }
2471
2472
    public function can_cancel() {
2473
        return apply_filters( 'wpinv_subscription_can_cancel', false, $this );
2474
    }
2475
    
2476
    public function add_subscription( $data = array() ) {
2477
        if ( empty( $this->ID ) ) {
2478
            return false;
2479
        }
2480
2481
        $defaults = array(
2482
            'period'            => '',
2483
            'initial_amount'    => '',
2484
            'recurring_amount'  => '',
2485
            'interval'          => 0,
2486
            'trial_interval'    => 0,
2487
            'trial_period'      => '',
2488
            'bill_times'        => 0,
2489
            'item_id'           => 0,
2490
            'created'           => '',
2491
            'expiration'        => '',
2492
            'status'            => '',
2493
            'profile_id'        => '',
2494
        );
2495
2496
        $args = wp_parse_args( $data, $defaults );
2497
2498
        if ( $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2499
            if ( 'active' == $args['status'] || $args['status'] == 'trialing' ) {
2500
                $args['status'] = 'expired';
2501
            }
2502
        }
2503
2504
        do_action( 'wpinv_subscription_pre_create', $args, $data, $this );
2505
        
2506
        if ( !empty( $args ) ) {
2507
            foreach ( $args as $key => $value ) {
2508
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2509
            }
2510
        }
2511
2512
        do_action( 'wpinv_subscription_post_create', $args, $data, $this );
2513
2514
        return true;
2515
    }
2516
    
2517
    public function update_subscription( $args = array() ) {
2518
        if ( empty( $this->ID ) ) {
2519
            return false;
2520
        }
2521
2522
        if ( !empty( $args['expiration'] ) && $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2523
            if ( !isset( $args['status'] ) || ( isset( $args['status'] ) && ( 'active' == $args['status'] || $args['status'] == 'trialing' ) ) ) {
2524
                $args['status'] = 'expired';
2525
            }
2526
        }
2527
2528
        if ( isset( $args['status'] ) && $args['status'] == 'cancelled' && empty( $args['cancelled_on'] ) ) {
2529
            $args['cancelled_on'] = date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) );
2530
        }
2531
2532
        do_action( 'wpinv_subscription_pre_update', $args, $this );
2533
        
2534
        if ( !empty( $args ) ) {
2535
            foreach ( $args as $key => $value ) {
2536
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2537
            }
2538
        }
2539
2540
        do_action( 'wpinv_subscription_post_update', $args, $this );
2541
2542
        return true;
2543
    }
2544
    
2545
    public function renew_subscription() {
2546
        $parent_invoice = $this->get_parent_payment();
2547
        $parent_invoice = empty( $parent_invoice ) ? $this : $parent_invoice;
2548
        
2549
        $current_time   = current_time( 'timestamp' );
2550
        $start          = $this->get_subscription_created();
2551
        $start          = $start ? strtotime( $start ) : $current_time;
2552
        $expires        = $this->get_expiration_time();
2553
        
2554
        if ( !$expires ) {
2555
            $expires    = strtotime( '+' . $parent_invoice->get_subscription_interval() . ' ' . $parent_invoice->get_subscription_period( true ), $start );
2556
        }
2557
        
2558
        $expiration     = date_i18n( 'Y-m-d 23:59:59', $expires );
2559
        $expiration     = apply_filters( 'wpinv_subscription_renewal_expiration', $expiration, $this->ID, $this );
2560
        $bill_times     = $parent_invoice->get_bill_times();
2561
        $times_billed   = $parent_invoice->get_total_payments();
2562
        
2563
        if ( $parent_invoice->get_subscription_status() == 'trialing' && ( $times_billed > 0 || strtotime( date_i18n( 'Y-m-d' ) ) < strtotime( $parent_invoice->get_trial_end_date( false ) ) ) ) {
2564
            $args = array(
2565
                'status'     => 'active',
2566
            );
2567
2568
            $parent_invoice->update_subscription( $args );
2569
        }
2570
        
2571
        do_action( 'wpinv_subscription_pre_renew', $this->ID, $expiration, $this );
2572
2573
        $status       = 'active';
2574
        if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2575
            $this->complete_subscription();
2576
            $status = 'completed';
2577
        }
2578
2579
        $args = array(
2580
            'expiration' => $expiration,
2581
            'status'     => $status,
2582
        );
2583
2584
        $this->update_subscription( $args );
2585
2586
        do_action( 'wpinv_subscription_post_renew', $this->ID, $expiration, $this );
2587
        do_action( 'wpinv_recurring_set_subscription_status', $this->ID, $status, $this );
2588
    }
2589
    
2590
    public function complete_subscription() {
2591
        $args = array(
2592
            'status' => 'completed'
2593
        );
2594
2595
        if ( $this->update_subscription( $args ) ) {
2596
            do_action( 'wpinv_subscription_completed', $this->ID, $this );
2597
        }
2598
    }
2599
    
2600
    public function expire_subscription() {
2601
        $args = array(
2602
            'status' => 'expired'
2603
        );
2604
2605
        if ( $this->update_subscription( $args ) ) {
2606
            do_action( 'wpinv_subscription_expired', $this->ID, $this );
2607
        }
2608
    }
2609
2610
    public function get_cancel_url() {
2611
        $url = wp_nonce_url( add_query_arg( array( 'wpi_action' => 'cancel_subscription', 'sub_id' => $this->ID ) ), 'wpinv-recurring-cancel' );
2612
2613
        return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
2614
    }
2615
2616
    public function can_update() {
2617
        return apply_filters( 'wpinv_subscription_can_update', false, $this );
2618
    }
2619
2620
    public function get_update_url() {
2621
        $url = add_query_arg( array( 'action' => 'update', 'sub_id' => $this->ID ) );
2622
2623
        return apply_filters( 'wpinv_subscription_update_url', $url, $this );
2624
    }
2625
2626
    public function is_parent() {
2627
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2628
2629
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2630
    }
2631
    
2632
    public function is_renewal() {
2633
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2634
2635
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2636
    }
2637
    
2638
    public function get_parent_payment() {
2639
        $parent_payment = NULL;
2640
        
2641
        if ( $this->is_renewal() ) {
2642
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2643
        }
2644
        
2645
        return $parent_payment;
2646
    }
2647
    
2648
    public function is_subscription_active() {
2649
        $ret = false;
2650
        
2651
        $subscription_status = $this->get_subscription_status();
2652
2653
        if( ! $this->is_subscription_expired() && ( $subscription_status == 'active' || $subscription_status == 'cancelled' || $subscription_status == 'trialing' ) ) {
2654
            $ret = true;
2655
        }
2656
2657
        return apply_filters( 'wpinv_subscription_is_active', $ret, $this->ID, $this );
2658
    }
2659
2660
    public function is_subscription_expired() {
2661
        $ret = false;
2662
        $subscription_status = $this->get_subscription_status();
2663
2664
        if ( $subscription_status == 'expired' ) {
2665
            $ret = true;
2666
        } else if ( 'active' === $subscription_status || 'cancelled' === $subscription_status || 'trialing' == $subscription_status ) {
2667
            $ret        = false;
2668
            $expiration = $this->get_expiration_time();
2669
2670
            if ( $expiration && strtotime( 'NOW', current_time( 'timestamp' ) ) > $expiration ) {
2671
                $ret = true;
2672
2673
                if ( 'active' === $subscription_status || 'trialing' === $subscription_status ) {
2674
                    $this->expire_subscription();
2675
                }
2676
            }
2677
        }
2678
2679
        return apply_filters( 'wpinv_subscription_is_expired', $ret, $this->ID, $this );
2680
    }
2681
    
2682
    public function get_new_expiration( $item_id = 0, $trial = true ) {
2683
        $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...
2684
        $interval = $item->get_recurring_interval();
2685
        $period = $item->get_recurring_period( true );
2686
        
2687
        $expiration_time = strtotime( '+' . $interval . ' ' . $period );
2688
        
2689
        if ( $trial && $this->is_free_trial() && $item->has_free_trial() ) {
2690
            $expiration_time += ( wpinv_period_in_days( $item->get_trial_interval(), $item->get_trial_period() ) * DAY_IN_SECONDS ) ;
2691
        }
2692
2693
        return date_i18n( 'Y-m-d 23:59:59', $expiration_time );
2694
    }
2695
    
2696
    public function get_subscription_data( $filed = '' ) {
2697
        $fields = array( 'item_id', 'status', 'period', 'initial_amount', 'recurring_amount', 'interval', 'bill_times', 'trial_period', 'trial_interval', 'expiration', 'profile_id', 'created', 'cancelled_on' );
2698
        
2699
        $subscription_meta = array();
2700
        foreach ( $fields as $field ) {
2701
            $subscription_meta[ $field ] = $this->get_meta( '_wpinv_subscr_' . $field, true );
2702
        }
2703
        
2704
        $item = $this->get_recurring( true );
2705
        
2706
        if ( !empty( $item ) ) {
2707
            if ( empty( $subscription_meta['item_id'] ) ) {
2708
                $subscription_meta['item_id'] = $item->ID;
2709
            }
2710
            if ( empty( $subscription_meta['period'] ) ) {
2711
                $subscription_meta['period'] = $item->get_recurring_period();
2712
            }
2713
            if ( empty( $subscription_meta['interval'] ) ) {
2714
                $subscription_meta['interval'] = $item->get_recurring_interval();
2715
            }
2716
            if ( $item->has_free_trial() ) {
2717
                if ( empty( $subscription_meta['trial_period'] ) ) {
2718
                    $subscription_meta['trial_period'] = $item->get_trial_period();
2719
                }
2720
                if ( empty( $subscription_meta['trial_interval'] ) ) {
2721
                    $subscription_meta['trial_interval'] = $item->get_trial_interval();
2722
                }
2723
            } else {
2724
                $subscription_meta['trial_period']      = '';
2725
                $subscription_meta['trial_interval']    = 0;
2726
            }
2727
            if ( !$subscription_meta['bill_times'] && $subscription_meta['bill_times'] !== 0 ) {
2728
                $subscription_meta['bill_times'] = $item->get_recurring_limit();
2729
            }
2730
            if ( $subscription_meta['initial_amount'] === '' || $subscription_meta['recurring_amount'] === '' ) {
2731
                $subscription_meta['initial_amount']    = wpinv_format_amount( $this->get_total() );
2732
                $subscription_meta['recurring_amount']  = wpinv_format_amount( $this->get_recurring_details( 'total' ) );
2733
            }
2734
        }
2735
        
2736
        if ( $filed === '' ) {
2737
            return apply_filters( 'wpinv_get_invoice_subscription_data', $subscription_meta, $this );
2738
        }
2739
        
2740
        $value = isset( $subscription_meta[$filed] ) ? $subscription_meta[$filed] : '';
2741
        
2742
        return apply_filters( 'wpinv_invoice_subscription_data_value', $value, $subscription_meta, $this );
2743
    }
2744
    
2745
    public function is_paid() {
2746
        if ( $this->has_status( array( 'publish', 'complete', 'processing', 'renewal' ) ) ) {
2747
            return true;
2748
        }
2749
        
2750
        return false;
2751
    }
2752
    
2753
    public function has_vat() {
2754
        global $wpinv_euvat, $wpi_country;
2755
        
2756
        $requires_vat = false;
2757
        
2758
        if ( $this->country ) {
2759
            $wpi_country        = $this->country;
2760
            
2761
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2762
        }
2763
        
2764
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2765
    }
2766
    
2767
    public function refresh_item_ids() {
2768
        $item_ids = array();
2769
        
2770
        if ( !empty( $this->cart_details ) ) {
2771
            foreach ( $this->cart_details as $key => $item ) {
2772
                if ( !empty( $item['id'] ) ) {
2773
                    $item_ids[] = $item['id'];
2774
                }
2775
            }
2776
        }
2777
        
2778
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2779
        
2780
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2781
    }
2782
}
2783