Passed
Pull Request — master (#34)
by Kiran
03:52
created

WPInv_Invoice::get_meta()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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