Passed
Pull Request — master (#47)
by Kiran
04:17
created

WPInv_Invoice::get_discount()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 24
nop 2
dl 0
loc 24
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * Contains functions related to Invoicing plugin.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
 
9
// MUST have WordPress.
10
if ( !defined( 'WPINC' ) ) {
11
    exit( 'Do NOT access this file directly: ' . basename( __FILE__ ) );
12
}
13
14
final class WPInv_Invoice {
15
    public $ID  = 0;
16
    public $title;
17
    public $post_type;
18
    
19
    public $pending;
20
    public $items = array();
21
    public $user_info = array();
22
    public $payment_meta = array();
23
    
24
    public $new = false;
25
    public $number = '';
26
    public $mode = 'live';
27
    public $key = '';
28
    public $total = 0.00;
29
    public $subtotal = 0;
30
    public $tax = 0;
31
    public $fees = array();
32
    public $fees_total = 0;
33
    public $discounts = '';
34
    public $discount = 0;
35
    public $discount_code = 0;
36
    public $date = '';
37
    public $due_date = '';
38
    public $completed_date = '';
39
    public $status      = 'pending';
40
    public $post_status = 'pending';
41
    public $old_status = '';
42
    public $status_nicename = '';
43
    public $user_id = 0;
44
    public $first_name = '';
45
    public $last_name = '';
46
    public $email = '';
47
    public $phone = '';
48
    public $address = '';
49
    public $city = '';
50
    public $country = '';
51
    public $state = '';
52
    public $zip = '';
53
    public $transaction_id = '';
54
    public $ip = '';
55
    public $gateway = '';
56
    public $gateway_title = '';
57
    public $currency = '';
58
    public $cart_details = array();
59
    
60
    public $company = '';
61
    public $vat_number = '';
62
    public $vat_rate = '';
63
    public $adddress_confirmed = '';
64
    
65
    public $full_name = '';
66
    public $parent_invoice = 0;
67
    
68
    public function __construct( $invoice_id = false ) {
69
        if( empty( $invoice_id ) ) {
70
            return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
71
        }
72
73
        $this->setup_invoice( $invoice_id );
74
    }
75
76
    public function get( $key ) {
77
        if ( method_exists( $this, 'get_' . $key ) ) {
78
            $value = call_user_func( array( $this, 'get_' . $key ) );
79
        } else {
80
            $value = $this->$key;
81
        }
82
83
        return $value;
84
    }
85
86
    public function set( $key, $value ) {
87
        $ignore = array( 'items', 'cart_details', 'fees', '_ID' );
88
89
        if ( $key === 'status' ) {
90
            $this->old_status = $this->status;
91
        }
92
93
        if ( ! in_array( $key, $ignore ) ) {
94
            $this->pending[ $key ] = $value;
95
        }
96
97
        if( '_ID' !== $key ) {
98
            $this->$key = $value;
99
        }
100
    }
101
102
    public function _isset( $name ) {
103
        if ( property_exists( $this, $name) ) {
104
            return false === empty( $this->$name );
105
        } else {
106
            return null;
107
        }
108
    }
109
110
    private function setup_invoice( $invoice_id ) {
111
        $this->pending = array();
112
113
        if ( empty( $invoice_id ) ) {
114
            return false;
115
        }
116
117
        $invoice = get_post( $invoice_id );
118
119
        if( !$invoice || is_wp_error( $invoice ) ) {
120
            return false;
121
        }
122
123
        if( !('wpi_invoice' == $invoice->post_type OR 'wpi_quote' == $invoice->post_type) ) {
124
            return false;
125
        }
126
127
        do_action( 'wpinv_pre_setup_invoice', $this, $invoice_id );
128
        
129
        // Primary Identifier
130
        $this->ID              = absint( $invoice_id );
131
        $this->post_type       = $invoice->post_type;
132
        
133
        // We have a payment, get the generic payment_meta item to reduce calls to it
134
        $this->payment_meta    = $this->get_meta();
135
        $this->date            = $invoice->post_date;
136
        $this->due_date        = $this->setup_due_date();
137
        $this->completed_date  = $this->setup_completed_date();
138
        $this->status          = $invoice->post_status;
139
        $this->post_status     = $this->status;
140
        $this->mode            = $this->setup_mode();
141
        $this->parent_invoice  = $invoice->post_parent;
142
        $this->post_name       = $this->setup_post_name( $invoice );
0 ignored issues
show
Bug introduced by
The property post_name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
Are you sure the assignment to $this->post_name is correct as $this->setup_post_name($invoice) (which targets WPInv_Invoice::setup_post_name()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
143
        $this->status_nicename = $this->setup_status_nicename($invoice->post_status);
144
145
        // Items
146
        $this->fees            = $this->setup_fees();
147
        $this->cart_details    = $this->setup_cart_details();
148
        $this->items           = $this->setup_items();
149
150
        // Currency Based
151
        $this->total           = $this->setup_total();
152
        $this->tax             = $this->setup_tax();
153
        $this->fees_total      = $this->get_fees_total();
154
        $this->subtotal        = $this->setup_subtotal();
155
        $this->currency        = $this->setup_currency();
156
        
157
        // Gateway based
158
        $this->gateway         = $this->setup_gateway();
159
        $this->gateway_title   = $this->setup_gateway_title();
160
        $this->transaction_id  = $this->setup_transaction_id();
161
        
162
        // User based
163
        $this->ip              = $this->setup_ip();
164
        $this->user_id         = !empty( $invoice->post_author ) ? $invoice->post_author : get_current_user_id();///$this->setup_user_id();
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
165
        $this->email           = get_the_author_meta( 'email', $this->user_id );
166
        
167
        $this->user_info       = $this->setup_user_info();
168
                
169
        $this->first_name      = $this->user_info['first_name'];
170
        $this->last_name       = $this->user_info['last_name'];
171
        $this->company         = $this->user_info['company'];
172
        $this->vat_number      = $this->user_info['vat_number'];
173
        $this->vat_rate        = $this->user_info['vat_rate'];
174
        $this->adddress_confirmed  = $this->user_info['adddress_confirmed'];
175
        $this->address         = $this->user_info['address'];
176
        $this->city            = $this->user_info['city'];
177
        $this->country         = $this->user_info['country'];
178
        $this->state           = $this->user_info['state'];
179
        $this->zip             = $this->user_info['zip'];
180
        $this->phone           = $this->user_info['phone'];
181
        
182
        $this->discounts       = $this->user_info['discount'];
183
            $this->discount        = $this->setup_discount();
184
            $this->discount_code   = $this->setup_discount_code();
185
186
        // Other Identifiers
187
        $this->key             = $this->setup_invoice_key();
188
        $this->number          = $this->setup_invoice_number();
189
        $this->title           = !empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
190
        
191
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
192
        
193
        // Allow extensions to add items to this object via hook
194
        do_action( 'wpinv_setup_invoice', $this, $invoice_id );
195
196
        return true;
197
    }
198
    
199
    private function setup_status_nicename($status) {
200
        $all_invoice_statuses  = wpinv_get_invoice_statuses();
201
        $status   = isset( $all_invoice_statuses[$status] ) ? $all_invoice_statuses[$status] : __( $status, 'invoicing' );
202
203
        return apply_filters( 'setup_status_nicename', $status );
204
    }
205
    
206
    private function setup_post_name( $post = NULL ) {
207
        $post_name = '';
208
        
209
        if ( !empty( $post ) ) {
210
            if( !empty( $post->post_name ) ) {
211
                $post_name = $post->post_name;
212
            } else if ( !empty( $post->ID ) && !empty( $post->post_title ) ) {
213
                $post_name = sanitize_title( $post->post_title );
214
                
215
                global $wpdb;
216
                $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
217
            }
218
        }
219
220
        $this->post_name   = $post_name;
221
    }
222
    
223
    private function setup_due_date() {
224
        $due_date = $this->get_meta( '_wpinv_due_date' );
225
        
226
        if ( empty( $due_date ) ) {
227
            $overdue_time = strtotime( $this->date ) + ( DAY_IN_SECONDS * absint( wpinv_get_option( 'overdue_days' ) ) );
228
            $due_date = date_i18n( 'Y-m-d', $overdue_time );
229
        } else if ( $due_date == 'none' ) {
230
            $due_date = '';
231
        }
232
233
        return $due_date;
234
    }
235
    
236
    private function setup_completed_date() {
237
        $invoice = get_post( $this->ID );
238
239
        if ( 'pending' == $invoice->post_status || 'preapproved' == $invoice->post_status ) {
240
            return false; // This invoice was never paid
241
        }
242
243
        $date = ( $date = $this->get_meta( '_wpinv_completed_date', true ) ) ? $date : $invoice->modified_date;
244
245
        return $date;
246
    }
247
    
248
    private function setup_cart_details() {
249
        $cart_details = isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
250
        return $cart_details;
251
    }
252
    
253
    public function array_convert() {
254
        return get_object_vars( $this );
255
    }
256
    
257
    private function setup_items() {
258
        $items = isset( $this->payment_meta['items'] ) ? maybe_unserialize( $this->payment_meta['items'] ) : array();
259
        return $items;
260
    }
261
    
262
    private function setup_fees() {
263
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
264
        return $payment_fees;
265
    }
266
        
267
    private function setup_currency() {
268
        $currency = isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'wpinv_currency_default', wpinv_get_currency(), $this );
269
        return $currency;
270
    }
271
    
272
    private function setup_discount() {
273
        //$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...
274
        $discount = (float)$this->subtotal - ( (float)$this->total - (float)$this->tax - (float)$this->fees_total );
275
        if ( $discount < 0 ) {
276
            $discount = 0;
277
        }
278
        $discount = wpinv_round_amount( $discount );
279
        
280
        return $discount;
281
    }
282
    
283
    private function setup_discount_code() {
284
        $discount_code = !empty( $this->discounts ) ? $this->discounts : $this->get_meta( '_wpinv_discount_code', true );
285
        return $discount_code;
286
    }
287
    
288
    private function setup_tax() {
289
        $tax = $this->get_meta( '_wpinv_tax', true );
290
291
        // We don't have tax as it's own meta and no meta was passed
292
        if ( '' === $tax ) {            
293
            $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
294
        }
295
296
        return $tax;
297
    }
298
299
    private function setup_subtotal() {
300
        $subtotal     = 0;
301
        $cart_details = $this->cart_details;
302
303
        if ( is_array( $cart_details ) ) {
304
            foreach ( $cart_details as $item ) {
305
                if ( isset( $item['subtotal'] ) ) {
306
                    $subtotal += $item['subtotal'];
307
                }
308
            }
309
        } else {
310
            $subtotal  = $this->total;
311
            $tax       = wpinv_use_taxes() ? $this->tax : 0;
312
            $subtotal -= $tax;
313
        }
314
315
        return $subtotal;
316
    }
317
    
318
    private function setup_discounts() {
319
        $discounts = ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
320
        return $discounts;
321
    }
322
    
323
    private function setup_total() {
324
        $amount = $this->get_meta( '_wpinv_total', true );
325
326
        if ( empty( $amount ) && '0.00' != $amount ) {
327
            $meta   = $this->get_meta( '_wpinv_payment_meta', true );
328
            $meta   = maybe_unserialize( $meta );
329
330
            if ( isset( $meta['amount'] ) ) {
331
                $amount = $meta['amount'];
332
            }
333
        }
334
335
        return $amount;
336
    }
337
    
338
    private function setup_mode() {
339
        return $this->get_meta( '_wpinv_mode' );
340
    }
341
342
    private function setup_gateway() {
343
        $gateway = $this->get_meta( '_wpinv_gateway' );
344
        
345
        if ( empty( $gateway ) && 'publish' === $this->status ) {
346
            $gateway = 'manual';
347
        }
348
        
349
        return $gateway;
350
    }
351
    
352
    private function setup_gateway_title() {
353
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
354
        return $gateway_title;
355
    }
356
357
    private function setup_transaction_id() {
358
        $transaction_id = $this->get_meta( '_wpinv_transaction_id' );
359
360
        if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
361
            $gateway        = $this->gateway;
362
            $transaction_id = apply_filters( 'wpinv_get_invoice_transaction_id-' . $gateway, $this->ID );
363
        }
364
365
        return $transaction_id;
366
    }
367
368
    private function setup_ip() {
369
        $ip = $this->get_meta( '_wpinv_user_ip' );
370
        return $ip;
371
    }
372
373
    ///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...
374
        ///$user_id = $this->get_meta( '_wpinv_user_id' );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
375
        ///return $user_id;
376
    ///}
377
        
378
    private function setup_first_name() {
379
        $first_name = $this->get_meta( '_wpinv_first_name' );
380
        return $first_name;
381
    }
382
    
383
    private function setup_last_name() {
384
        $last_name = $this->get_meta( '_wpinv_last_name' );
385
        return $last_name;
386
    }
387
    
388
    private function setup_company() {
389
        $company = $this->get_meta( '_wpinv_company' );
390
        return $company;
391
    }
392
    
393
    private function setup_vat_number() {
394
        $vat_number = $this->get_meta( '_wpinv_vat_number' );
395
        return $vat_number;
396
    }
397
    
398
    private function setup_vat_rate() {
399
        $vat_rate = $this->get_meta( '_wpinv_vat_rate' );
400
        return $vat_rate;
401
    }
402
    
403
    private function setup_adddress_confirmed() {
404
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed' );
405
        return $adddress_confirmed;
406
    }
407
    
408
    private function setup_phone() {
409
        $phone = $this->get_meta( '_wpinv_phone' );
410
        return $phone;
411
    }
412
    
413
    private function setup_address() {
414
        $address = $this->get_meta( '_wpinv_address', true );
415
        return $address;
416
    }
417
    
418
    private function setup_city() {
419
        $city = $this->get_meta( '_wpinv_city', true );
420
        return $city;
421
    }
422
    
423
    private function setup_country() {
424
        $country = $this->get_meta( '_wpinv_country', true );
425
        return $country;
426
    }
427
    
428
    private function setup_state() {
429
        $state = $this->get_meta( '_wpinv_state', true );
430
        return $state;
431
    }
432
    
433
    private function setup_zip() {
434
        $zip = $this->get_meta( '_wpinv_zip', true );
435
        return $zip;
436
    }
437
438
    private function setup_user_info() {
439
        $defaults = array(
440
            'user_id'        => $this->user_id,
441
            'first_name'     => $this->first_name,
442
            'last_name'      => $this->last_name,
443
            'email'          => get_the_author_meta( 'email', $this->user_id ),
444
            'phone'          => $this->phone,
445
            'address'        => $this->address,
446
            'city'           => $this->city,
447
            'country'        => $this->country,
448
            'state'          => $this->state,
449
            'zip'            => $this->zip,
450
            'company'        => $this->company,
451
            'vat_number'     => $this->vat_number,
452
            'vat_rate'       => $this->vat_rate,
453
            'adddress_confirmed' => $this->adddress_confirmed,
454
            'discount'       => $this->discounts,
455
        );
456
        
457
        $user_info = array();
458
        if ( isset( $this->payment_meta['user_info'] ) ) {
459
            $user_info = maybe_unserialize( $this->payment_meta['user_info'] );
460
            
461
            if ( !empty( $user_info ) && isset( $user_info['user_id'] ) && $post = get_post( $this->ID ) ) {
462
                $this->user_id = $post->post_author;
463
                $this->email = get_the_author_meta( 'email', $this->user_id );
464
                
465
                $user_info['user_id'] = $this->user_id;
466
                $user_info['email'] = $this->email;
467
                $this->payment_meta['user_id'] = $this->user_id;
468
                $this->payment_meta['email'] = $this->email;
469
            }
470
        }
471
        
472
        $user_info    = wp_parse_args( $user_info, $defaults );
473
        
474
        // Get the user, but only if it's been created
475
        $user = get_userdata( $this->user_id );
476
        
477
        if ( !empty( $user ) && $user->ID > 0 ) {
478
            if ( empty( $user_info ) ) {
479
                $user_info = array(
480
                    'user_id'    => $user->ID,
481
                    'first_name' => $user->first_name,
482
                    'last_name'  => $user->last_name,
483
                    'email'      => $user->user_email,
484
                    'discount'   => '',
485
                );
486
            } else {
487
                foreach ( $user_info as $key => $value ) {
488
                    if ( ! empty( $value ) ) {
489
                        continue;
490
                    }
491
492
                    switch( $key ) {
493
                        case 'user_id':
494
                            $user_info[ $key ] = $user->ID;
495
                            break;
496
                        case 'first_name':
497
                            $user_info[ $key ] = $user->first_name;
498
                            break;
499
                        case 'last_name':
500
                            $user_info[ $key ] = $user->last_name;
501
                            break;
502
                        case 'email':
503
                            $user_info[ $key ] = $user->user_email;
504
                            break;
505
                    }
506
                }
507
            }
508
        }
509
510
        return $user_info;
511
    }
512
513
    private function setup_invoice_key() {
514
        $key = $this->get_meta( '_wpinv_key', true );
515
        
516
        return $key;
517
    }
518
519
    private function setup_invoice_number() {
520
        $number = $this->get_meta( '_wpinv_number', true );
521
522
        if ( !$number ) {
523
            $number = wpinv_format_invoice_number( $this->ID );
524
        }
525
526
        return $number;
527
    }
528
    
529
    private function insert_invoice() {
530
        $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...
531
532
        if ($number = $this->get_number()) {
533
            $invoice_title = $number;
534
        } else if ( ! empty( $this->ID ) ) {
535
            $invoice_title = wpinv_format_invoice_number( $this->ID );
536
        } else {
537
            $invoice_title = wpinv_format_invoice_number( 0 );
538
        }
539
540 View Code Duplication
        if ( empty( $this->key ) ) {
541
            $this->key = self::generate_key();
542
            $this->pending['key'] = $this->key;
543
        }
544
545
        if ( empty( $this->ip ) ) {
546
            $this->ip = wpinv_get_ip();
547
            $this->pending['ip'] = $this->ip;
548
        }
549
        
550
        $payment_data = array(
551
            'price'        => $this->total,
552
            'date'         => $this->date,
553
            'user_email'   => $this->email,
554
            'invoice_key'  => $this->key,
555
            'currency'     => $this->currency,
556
            'items'        => $this->items,
557
            'user_info' => array(
558
                'user_id'    => $this->user_id,
559
                'email'      => $this->email,
560
                'first_name' => $this->first_name,
561
                'last_name'  => $this->last_name,
562
                'address'    => $this->address,
563
                'phone'      => $this->phone,
564
                'city'       => $this->city,
565
                'country'    => $this->country,
566
                'state'      => $this->state,
567
                'zip'        => $this->zip,
568
                'company'    => $this->company,
569
                'vat_number' => $this->vat_number,
570
                'discount'   => $this->discounts,
571
            ),
572
            'cart_details' => $this->cart_details,
573
            'status'       => $this->status,
574
            'fees'         => $this->fees,
575
        );
576
        
577
        $post_name      = sanitize_title( $invoice_title );
578
579
        $post_data = array(
580
                        'post_title'    => $invoice_title,
581
                        'post_status'   => $this->status,
582
                        'post_author'   => $this->user_id,
583
                        'post_type'     => $this->post_type,
584
                        'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
585
                        'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
586
                        'post_parent'   => $this->parent_invoice,
587
                    );
588
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
589
590
        // Create a blank invoice
591
        if ( !empty( $this->ID ) ) {
592
            $args['ID']         = $this->ID;
593
            $args['post_name']  = $post_name;
594
            
595
            $invoice_id = wp_update_post( $args );
596
        } else {
597
            $invoice_id = wp_insert_post( $args );
598
            
599
            $post_title = wpinv_format_invoice_number( $invoice_id );
600
            global $wpdb;
601
            $wpdb->update( $wpdb->posts, array( 'post_title' => $post_title, 'post_name' => sanitize_title( $post_title ) ), array( 'ID' => $invoice_id ) );
602
            clean_post_cache( $invoice_id );
603
        }
604
605
        if ( !empty( $invoice_id ) ) {             
606
            $this->ID  = $invoice_id;
607
            $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...
608
            
609
            ///$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...
610
            if ( isset( $this->pending['number'] ) ) {
611
                $this->pending['number'] = $post_name;
612
            }
613
            
614
            $this->payment_meta = apply_filters( 'wpinv_payment_meta', $this->payment_meta, $payment_data );
615
            if ( ! empty( $this->payment_meta['fees'] ) ) {
616
                $this->fees = array_merge( $this->fees, $this->payment_meta['fees'] );
617
                foreach( $this->fees as $fee ) {
618
                    $this->increase_fees( $fee['amount'] );
619
                }
620
            }
621
622
            $this->update_meta( '_wpinv_payment_meta', $this->payment_meta );            
623
            $this->new = true;
624
        }
625
626
        return $this->ID;
627
    }
628
629
    public function save( $setup = false ) {
630
        global $wpi_session;
631
        
632
        $saved = false;
633
        if ( empty( $this->items ) ) {
634
            return $saved; // Don't save empty invoice.
635
        }
636
        
637 View Code Duplication
        if ( empty( $this->key ) ) {
638
            $this->key = self::generate_key();
639
            $this->pending['key'] = $this->key;
640
        }
641
        
642
        if ( empty( $this->ID ) ) {
643
            $invoice_id = $this->insert_invoice();
644
645
            if ( false === $invoice_id ) {
646
                $saved = false;
647
            } else {
648
                $this->ID = $invoice_id;
649
            }
650
        }        
651
652
        // If we have something pending, let's save it
653
        if ( !empty( $this->pending ) ) {
654
            $total_increase = 0;
655
            $total_decrease = 0;
656
657
            foreach ( $this->pending as $key => $value ) {
658
                switch( $key ) {
659
                    case 'items':
660
                        // Update totals for pending items
661
                        foreach ( $this->pending[ $key ] as $item ) {
662
                            switch( $item['action'] ) {
663
                                case 'add':
664
                                    $price = $item['price'];
665
                                    $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...
666
667
                                    if ( 'publish' === $this->status ) {
668
                                        $total_increase += $price;
669
                                    }
670
                                    break;
671
672
                                case 'remove':
673
                                    if ( 'publish' === $this->status ) {
674
                                        $total_decrease += $item['price'];
675
                                    }
676
                                    break;
677
                            }
678
                        }
679
                        break;
680
                    case 'fees':
681
                        if ( 'publish' !== $this->status ) {
682
                            break;
683
                        }
684
685
                        if ( empty( $this->pending[ $key ] ) ) {
686
                            break;
687
                        }
688
689
                        foreach ( $this->pending[ $key ] as $fee ) {
690
                            switch( $fee['action'] ) {
691
                                case 'add':
692
                                    $total_increase += $fee['amount'];
693
                                    break;
694
695
                                case 'remove':
696
                                    $total_decrease += $fee['amount'];
697
                                    break;
698
                            }
699
                        }
700
                        break;
701
                    case 'status':
702
                        $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...
703
                        break;
704
                    case 'gateway':
705
                        $this->update_meta( '_wpinv_gateway', $this->gateway );
706
                        break;
707
                    case 'mode':
708
                        $this->update_meta( '_wpinv_mode', $this->mode );
709
                        break;
710
                    case 'transaction_id':
711
                        $this->update_meta( '_wpinv_transaction_id', $this->transaction_id );
712
                        break;
713
                    case 'ip':
714
                        $this->update_meta( '_wpinv_user_ip', $this->ip );
715
                        break;
716
                    ///case 'user_id':
717
                        ///$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...
718
                        ///$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...
719
                        ///break;
720
                    case 'first_name':
721
                        $this->update_meta( '_wpinv_first_name', $this->first_name );
722
                        $this->user_info['first_name'] = $this->first_name;
723
                        break;
724
                    case 'last_name':
725
                        $this->update_meta( '_wpinv_last_name', $this->last_name );
726
                        $this->user_info['last_name'] = $this->last_name;
727
                        break;
728
                    case 'phone':
729
                        $this->update_meta( '_wpinv_phone', $this->phone );
730
                        $this->user_info['phone'] = $this->phone;
731
                        break;
732
                    case 'address':
733
                        $this->update_meta( '_wpinv_address', $this->address );
734
                        $this->user_info['address'] = $this->address;
735
                        break;
736
                    case 'city':
737
                        $this->update_meta( '_wpinv_city', $this->city );
738
                        $this->user_info['city'] = $this->city;
739
                        break;
740
                    case 'country':
741
                        $this->update_meta( '_wpinv_country', $this->country );
742
                        $this->user_info['country'] = $this->country;
743
                        break;
744
                    case 'state':
745
                        $this->update_meta( '_wpinv_state', $this->state );
746
                        $this->user_info['state'] = $this->state;
747
                        break;
748
                    case 'zip':
749
                        $this->update_meta( '_wpinv_zip', $this->zip );
750
                        $this->user_info['zip'] = $this->zip;
751
                        break;
752
                    case 'company':
753
                        $this->update_meta( '_wpinv_company', $this->company );
754
                        $this->user_info['company'] = $this->company;
755
                        break;
756
                    case 'vat_number':
757
                        $this->update_meta( '_wpinv_vat_number', $this->vat_number );
758
                        $this->user_info['vat_number'] = $this->vat_number;
759
                        
760
                        $vat_info = $wpi_session->get( 'user_vat_data' );
761
                        if ( $this->vat_number && !empty( $vat_info ) && isset( $vat_info['number'] ) && isset( $vat_info['valid'] ) && $vat_info['number'] == $this->vat_number ) {
762
                            $adddress_confirmed = isset( $vat_info['adddress_confirmed'] ) ? $vat_info['adddress_confirmed'] : false;
763
                            $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...
764
                            $this->user_info['adddress_confirmed'] = (bool)$adddress_confirmed;
765
                        }
766
    
767
                        break;
768
                    case 'vat_rate':
769
                        $this->update_meta( '_wpinv_vat_rate', $this->vat_rate );
770
                        $this->user_info['vat_rate'] = $this->vat_rate;
771
                        break;
772
                    case 'adddress_confirmed':
773
                        $this->update_meta( '_wpinv_adddress_confirmed', $this->adddress_confirmed );
774
                        $this->user_info['adddress_confirmed'] = $this->adddress_confirmed;
775
                        break;
776
                    
777
                    case 'key':
778
                        $this->update_meta( '_wpinv_key', $this->key );
779
                        break;
780
                    case 'number':
781
                        $this->update_meta( '_wpinv_number', $this->number );
782
                        break;
783
                    case 'date':
784
                        $args = array(
785
                            'ID'        => $this->ID,
786
                            'post_date' => $this->date,
787
                            'edit_date' => true,
788
                        );
789
790
                        wp_update_post( $args );
791
                        break;
792
                    case 'due_date':
793
                        if ( empty( $this->due_date ) ) {
794
                            $this->due_date = 'none';
795
                        }
796
                        
797
                        $this->update_meta( '_wpinv_due_date', $this->due_date );
798
                        break;
799
                    case 'completed_date':
800
                        $this->update_meta( '_wpinv_completed_date', $this->completed_date );
801
                        break;
802
                    case 'discounts':
803
                        if ( ! is_array( $this->discounts ) ) {
804
                            $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...
805
                        }
806
807
                        $this->user_info['discount'] = implode( ',', $this->discounts );
808
                        break;
809
                        
810
                    //case 'tax':
811
                        //$this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
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...
812
                        //break;
813
                    case 'discount':
814
                        $this->update_meta( '_wpinv_discount', wpinv_round_amount( $this->discount ) );
815
                        break;
816
                    case 'discount_code':
817
                        $this->update_meta( '_wpinv_discount_code', $this->discount_code );
818
                        break;
819
                    //case 'fees':
820
                        //$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...
821
                        //break;
822
                    case 'parent_invoice':
823
                        $args = array(
824
                            'ID'          => $this->ID,
825
                            'post_parent' => $this->parent_invoice,
826
                        );
827
                        wp_update_post( $args );
828
                        break;
829
                    default:
830
                        do_action( 'wpinv_save', $this, $key );
831
                        break;
832
                }
833
            }       
834
835
            $this->update_meta( '_wpinv_subtotal', wpinv_round_amount( $this->subtotal ) );
836
            $this->update_meta( '_wpinv_total', wpinv_round_amount( $this->total ) );
837
            $this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
838
            
839
            $this->items    = array_values( $this->items );
840
            
841
            $new_meta = array(
842
                'items'         => $this->items,
843
                'cart_details'  => $this->cart_details,
844
                'fees'          => $this->fees,
845
                'currency'      => $this->currency,
846
                'user_info'     => $this->user_info,
847
            );
848
            
849
            $meta        = $this->get_meta();
850
            $merged_meta = array_merge( $meta, $new_meta );
851
852
            // Only save the payment meta if it's changed
853
            if ( md5( serialize( $meta ) ) !== md5( serialize( $merged_meta) ) ) {
854
                $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...
855
                if ( false !== $updated ) {
856
                    $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...
857
                }
858
            }
859
860
            $this->pending = array();
861
            $saved         = true;
862
        } else {
863
            $this->update_meta( '_wpinv_subtotal', wpinv_round_amount( $this->subtotal ) );
864
            $this->update_meta( '_wpinv_total', wpinv_round_amount( $this->total ) );
865
            $this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
866
        }
867
        
868
        do_action( 'wpinv_invoice_save', $this, $saved );
869
870
        if ( true === $saved || $setup ) {
871
            $this->setup_invoice( $this->ID );
872
        }
873
        
874
        $this->refresh_item_ids();
875
        
876
        return $saved;
877
    }
878
    
879
    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...
880
        $default_args = array(
881
            'label'       => '',
882
            'amount'      => 0,
883
            'type'        => 'fee',
884
            'id'          => '',
885
            'no_tax'      => false,
886
            'item_id'     => 0,
887
        );
888
889
        $fee = wp_parse_args( $args, $default_args );
890
        
891
        if ( !empty( $fee['label'] ) ) {
892
            return false;
893
        }
894
        
895
        $fee['id']  = sanitize_title( $fee['label'] );
896
        
897
        $this->fees[]               = $fee;
898
        
899
        $added_fee               = $fee;
900
        $added_fee['action']     = 'add';
901
        $this->pending['fees'][] = $added_fee;
902
        reset( $this->fees );
903
904
        $this->increase_fees( $fee['amount'] );
905
        return true;
906
    }
907
908
    public function remove_fee( $key ) {
909
        $removed = false;
910
911
        if ( is_numeric( $key ) ) {
912
            $removed = $this->remove_fee_by( 'index', $key );
913
        }
914
915
        return $removed;
916
    }
917
918
    public function remove_fee_by( $key, $value, $global = false ) {
919
        $allowed_fee_keys = apply_filters( 'wpinv_fee_keys', array(
920
            'index', 'label', 'amount', 'type',
921
        ) );
922
923
        if ( ! in_array( $key, $allowed_fee_keys ) ) {
924
            return false;
925
        }
926
927
        $removed = false;
928
        if ( 'index' === $key && array_key_exists( $value, $this->fees ) ) {
929
            $removed_fee             = $this->fees[ $value ];
930
            $removed_fee['action']   = 'remove';
931
            $this->pending['fees'][] = $removed_fee;
932
933
            $this->decrease_fees( $removed_fee['amount'] );
934
935
            unset( $this->fees[ $value ] );
936
            $removed = true;
937
        } else if ( 'index' !== $key ) {
938
            foreach ( $this->fees as $index => $fee ) {
939
                if ( isset( $fee[ $key ] ) && $fee[ $key ] == $value ) {
940
                    $removed_fee             = $fee;
941
                    $removed_fee['action']   = 'remove';
942
                    $this->pending['fees'][] = $removed_fee;
943
944
                    $this->decrease_fees( $removed_fee['amount'] );
945
946
                    unset( $this->fees[ $index ] );
947
                    $removed = true;
948
949
                    if ( false === $global ) {
950
                        break;
951
                    }
952
                }
953
            }
954
        }
955
956
        if ( true === $removed ) {
957
            $this->fees = array_values( $this->fees );
958
        }
959
960
        return $removed;
961
    }
962
963
    
964
965
    public function add_note( $note = '', $customer_type = false, $added_by_user = false, $system = false ) {
966
        // Bail if no note specified
967
        if( !$note ) {
968
            return false;
969
        }
970
971
        if ( empty( $this->ID ) )
972
            return false;
973
        
974
        if ( ( ( is_user_logged_in() && current_user_can( 'manage_options' ) ) || $added_by_user ) && !$system ) {
975
            $user                 = get_user_by( 'id', get_current_user_id() );
976
            $comment_author       = $user->display_name;
977
            $comment_author_email = $user->user_email;
978
        } else {
979
            $comment_author       = __( 'System', 'invoicing' );
980
            $comment_author_email = strtolower( __( 'System', 'invoicing' ) ) . '@';
981
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
982
            $comment_author_email = sanitize_email( $comment_author_email );
983
        }
984
985
        do_action( 'wpinv_pre_insert_invoice_note', $this->ID, $note, $customer_type );
986
987
        $note_id = wp_insert_comment( wp_filter_comment( array(
988
            'comment_post_ID'      => $this->ID,
989
            'comment_content'      => $note,
990
            'comment_agent'        => 'GeoDirectory',
991
            'user_id'              => is_admin() ? get_current_user_id() : 0,
992
            'comment_date'         => current_time( 'mysql' ),
993
            'comment_date_gmt'     => current_time( 'mysql', 1 ),
994
            'comment_approved'     => 1,
995
            'comment_parent'       => 0,
996
            'comment_author'       => $comment_author,
997
            'comment_author_IP'    => wpinv_get_ip(),
998
            'comment_author_url'   => '',
999
            'comment_author_email' => $comment_author_email,
1000
            'comment_type'         => 'wpinv_note'
1001
        ) ) );
1002
1003
        do_action( 'wpinv_insert_payment_note', $note_id, $this->ID, $note );
1004
        
1005
        if ( $customer_type ) {
1006
            add_comment_meta( $note_id, '_wpi_customer_note', 1 );
1007
1008
            do_action( 'wpinv_new_customer_note', array( 'invoice_id' => $this->ID, 'user_note' => $note ) );
1009
        }
1010
1011
        return $note_id;
1012
    }
1013
1014
    private function increase_subtotal( $amount = 0.00 ) {
1015
        $amount          = (float) $amount;
1016
        $this->subtotal += $amount;
1017
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
1018
1019
        $this->recalculate_total();
1020
    }
1021
1022 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...
1023
        $amount          = (float) $amount;
1024
        $this->subtotal -= $amount;
1025
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
1026
1027
        if ( $this->subtotal < 0 ) {
1028
            $this->subtotal = 0;
1029
        }
1030
1031
        $this->recalculate_total();
1032
    }
1033
1034
    private function increase_fees( $amount = 0.00 ) {
1035
        $amount            = (float)$amount;
1036
        $this->fees_total += $amount;
1037
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
1038
1039
        $this->recalculate_total();
1040
    }
1041
1042 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...
1043
        $amount            = (float) $amount;
1044
        $this->fees_total -= $amount;
1045
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
1046
1047
        if ( $this->fees_total < 0 ) {
1048
            $this->fees_total = 0;
1049
        }
1050
1051
        $this->recalculate_total();
1052
    }
1053
1054
    public function recalculate_total() {
1055
        global $wpi_nosave;
1056
        
1057
        $this->total = $this->subtotal + $this->tax + $this->fees_total;
1058
        $this->total = wpinv_round_amount( $this->total );
1059
        
1060
        do_action( 'wpinv_invoice_recalculate_total', $this, $wpi_nosave );
1061
    }
1062
    
1063
    public function increase_tax( $amount = 0.00 ) {
1064
        $amount       = (float) $amount;
1065
        $this->tax   += $amount;
1066
1067
        $this->recalculate_total();
1068
    }
1069
1070
    public function decrease_tax( $amount = 0.00 ) {
1071
        $amount     = (float) $amount;
1072
        $this->tax -= $amount;
1073
1074
        if ( $this->tax < 0 ) {
1075
            $this->tax = 0;
1076
        }
1077
1078
        $this->recalculate_total();
1079
    }
1080
1081
    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...
1082
        $old_status = ! empty( $this->old_status ) ? $this->old_status : get_post_status( $this->ID );
1083
        
1084
        if ( $old_status === $new_status && in_array( $new_status, array_keys( wpinv_get_invoice_statuses() ) ) ) {
1085
            return false; // Don't permit status changes that aren't changes
1086
        }
1087
1088
        $do_change = apply_filters( 'wpinv_should_update_invoice_status', true, $this->ID, $new_status, $old_status );
1089
        $updated = false;
1090
1091
        if ( $do_change ) {
1092
            do_action( 'wpinv_before_invoice_status_change', $this->ID, $new_status, $old_status );
1093
1094
            $update_post_data                   = array();
1095
            $update_post_data['ID']             = $this->ID;
1096
            $update_post_data['post_status']    = $new_status;
1097
            $update_post_data['edit_date']      = current_time( 'mysql', 0 );
1098
            $update_post_data['edit_date_gmt']  = current_time( 'mysql', 1 );
1099
            
1100
            $update_post_data = apply_filters( 'wpinv_update_invoice_status_fields', $update_post_data, $this->ID );
1101
1102
            $updated = wp_update_post( $update_post_data );     
1103
           
1104
            // Process any specific status functions
1105
            switch( $new_status ) {
1106
                case 'wpi-refunded':
1107
                    $this->process_refund();
1108
                    break;
1109
                case 'wpi-failed':
1110
                    $this->process_failure();
1111
                    break;
1112
                case 'pending':
1113
                    $this->process_pending();
1114
                    break;
1115
            }
1116
            
1117
            // Status was changed.
1118
            do_action( 'wpinv_status_' . $new_status, $this->ID, $old_status );
1119
            do_action( 'wpinv_status_' . $old_status . '_to_' . $new_status, $this->ID, $old_status );
1120
            do_action( 'wpinv_update_status', $this->ID, $new_status, $old_status );
1121
        }
1122
1123
        return $updated;
1124
    }
1125
1126
    public function refund() {
1127
        $this->old_status        = $this->status;
1128
        $this->status            = 'wpi-refunded';
1129
        $this->pending['status'] = $this->status;
1130
1131
        $this->save();
1132
    }
1133
1134
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
1135
        if ( empty( $meta_key ) ) {
1136
            return false;
1137
        }
1138
1139
        if ( $meta_key == 'key' || $meta_key == 'date' ) {
1140
            $current_meta = $this->get_meta();
1141
            $current_meta[ $meta_key ] = $meta_value;
1142
1143
            $meta_key     = '_wpinv_payment_meta';
1144
            $meta_value   = $current_meta;
1145
        }
1146
1147
        $meta_value = apply_filters( 'wpinv_update_payment_meta_' . $meta_key, $meta_value, $this->ID );
1148
        
1149
        if ( $meta_key == '_wpinv_completed_date' && !empty( $meta_value ) ) {
1150
            $args = array(
1151
                'ID'                => $this->ID,
1152
                'post_date'         => $meta_value,
1153
                'edit_date'         => true,
1154
                'post_date_gmt'     => get_gmt_from_date( $meta_value ),
1155
                'post_modified'     => $meta_value,
1156
                'post_modified_gmt' => get_gmt_from_date( $meta_value )
1157
            );
1158
            wp_update_post( $args );
1159
        }
1160
        
1161
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
1162
    }
1163
1164
    private function process_refund() {
1165
        $process_refund = true;
1166
1167
        // If the payment was not in publish, don't decrement stats as they were never incremented
1168
        if ( 'publish' != $this->old_status || 'wpi-refunded' != $this->status ) {
1169
            $process_refund = false;
1170
        }
1171
1172
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1173
        $process_refund = apply_filters( 'wpinv_should_process_refund', $process_refund, $this );
1174
1175
        if ( false === $process_refund ) {
1176
            return;
1177
        }
1178
1179
        do_action( 'wpinv_pre_refund_invoice', $this );
1180
        
1181
        $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...
1182
        $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...
1183
        $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...
1184
        
1185
        do_action( 'wpinv_post_refund_invoice', $this );
1186
    }
1187
1188
    private function process_failure() {
1189
        $discounts = $this->discounts;
1190
        if ( empty( $discounts ) ) {
1191
            return;
1192
        }
1193
1194
        if ( ! is_array( $discounts ) ) {
1195
            $discounts = array_map( 'trim', explode( ',', $discounts ) );
1196
        }
1197
1198
        foreach ( $discounts as $discount ) {
1199
            wpinv_decrease_discount_usage( $discount );
1200
        }
1201
    }
1202
    
1203
    private function process_pending() {
1204
        $process_pending = true;
1205
1206
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
1207
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'pending' != $this->status ) {
1208
            $process_pending = false;
1209
        }
1210
1211
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1212
        $process_pending = apply_filters( 'wpinv_should_process_pending', $process_pending, $this );
1213
1214
        if ( false === $process_pending ) {
1215
            return;
1216
        }
1217
1218
        $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...
1219
        $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...
1220
        $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...
1221
1222
        $this->completed_date = '';
1223
        $this->update_meta( '_wpinv_completed_date', '' );
1224
    }
1225
    
1226
    // get data
1227
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
1228
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1229
1230
        if ( $meta_key === '_wpinv_payment_meta' ) {
1231
1232
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
1233
1234
            if ( empty( $meta['key'] ) ) {
1235
                $meta['key'] = $this->setup_invoice_key();
1236
            }
1237
1238
            if ( empty( $meta['date'] ) ) {
1239
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1240
            }
1241
        }
1242
1243
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1244
1245
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1246
    }
1247
    
1248
    public function get_description() {
1249
        $post = get_post( $this->ID );
1250
        
1251
        $description = !empty( $post ) ? $post->post_content : '';
1252
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1253
    }
1254
    
1255
    public function get_status( $nicename = false ) {
1256
        if ( !$nicename ) {
1257
            $status = $this->status;
1258
        } else {
1259
            $status = $this->status_nicename;
1260
        }
1261
        
1262
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1263
    }
1264
    
1265
    public function get_cart_details() {
1266
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1267
    }
1268
    
1269 View Code Duplication
    public function get_subtotal( $currency = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1270
        $subtotal = wpinv_round_amount( $this->subtotal );
1271
        
1272
        if ( $currency ) {
1273
            $subtotal = wpinv_price( wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
1274
        }
1275
        
1276
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1277
    }
1278
    
1279 View Code Duplication
    public function get_total( $currency = false ) {        
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1280
        if ( $this->is_free_trial() ) {
1281
            $total = wpinv_round_amount( 0 );
1282
        } else {
1283
            $total = wpinv_round_amount( $this->total );
1284
        }
1285
        if ( $currency ) {
1286
            $total = wpinv_price( wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
1287
        }
1288
        
1289
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1290
    }
1291
    
1292
    public function get_recurring_details( $field = '', $currency = false ) {        
1293
        $data                 = array();
1294
        $data['cart_details'] = $this->cart_details;
1295
        $data['subtotal']     = $this->get_subtotal();
1296
        $data['discount']     = $this->get_discount();
1297
        $data['tax']          = $this->get_tax();
1298
        $data['total']        = $this->get_total();
1299
    
1300
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1301
            $is_free_trial = $this->is_free_trial();
1302
            $discounts = $this->get_discounts( true );
1303
            
1304
            if ( $is_free_trial || !empty( $discounts ) ) {
1305
                $first_use_only = false;
1306
                
1307
                if ( !empty( $discounts ) ) {
1308
                    foreach ( $discounts as $key => $code ) {
1309
                        if ( wpinv_discount_is_recurring( $code, true ) ) {
1310
                            $first_use_only = true;
1311
                            break;
1312
                        }
1313
                    }
1314
                }
1315
                    
1316
                if ( !$first_use_only ) {
1317
                    $data['subtotal'] = wpinv_round_amount( $this->subtotal );
1318
                    $data['discount'] = wpinv_round_amount( $this->discount );
1319
                    $data['tax']      = wpinv_round_amount( $this->tax );
1320
                    $data['total']    = wpinv_round_amount( $this->total );
1321
                } else {
1322
                    $cart_subtotal   = 0;
1323
                    $cart_discount   = 0;
1324
                    $cart_tax        = 0;
1325
1326
                    foreach ( $this->cart_details as $key => $item ) {
1327
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1328
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1329
                        $item_discount  = 0;
1330
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1331
                        
1332
                        if ( wpinv_prices_include_tax() ) {
1333
                            $item_subtotal -= wpinv_round_amount( $item_tax );
1334
                        }
1335
                        
1336
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1337
                        // Do not allow totals to go negative
1338
                        if ( $item_total < 0 ) {
1339
                            $item_total = 0;
1340
                        }
1341
                        
1342
                        $cart_subtotal  += (float)($item_subtotal);
1343
                        $cart_discount  += (float)($item_discount);
1344
                        $cart_tax       += (float)($item_tax);
1345
                        
1346
                        $data['cart_details'][$key]['discount']   = wpinv_round_amount( $item_discount );
1347
                        $data['cart_details'][$key]['tax']        = wpinv_round_amount( $item_tax );
1348
                        $data['cart_details'][$key]['price']      = wpinv_round_amount( $item_total );
1349
                    }
1350
                    
1351
                    $data['subtotal'] = wpinv_round_amount( $cart_subtotal );
1352
                    $data['discount'] = wpinv_round_amount( $cart_discount );
1353
                    $data['tax']      = wpinv_round_amount( $cart_tax );
1354
                    $data['total']    = wpinv_round_amount( $data['subtotal'] + $data['tax'] );
1355
                }
1356
            }
1357
        }
1358
        
1359
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1360
1361
        if ( isset( $data[$field] ) ) {
1362
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1363
        }
1364
        
1365
        return $data;
1366
    }
1367
    
1368
    public function get_final_tax( $currency = false ) {        
1369
        $final_total = wpinv_round_amount( $this->tax );
1370
        if ( $currency ) {
1371
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
1372
        }
1373
        
1374
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1375
    }
1376
    
1377
    public function get_discounts( $array = false ) {
1378
        $discounts = $this->discounts;
1379
        if ( $array && $discounts ) {
1380
            $discounts = explode( ',', $discounts );
1381
        }
1382
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1383
    }
1384
    
1385
    public function get_discount( $currency = false, $dash = false ) {
1386
        if ( !empty( $this->discounts ) ) {
1387
            global $ajax_cart_details;
1388
            $ajax_cart_details = $this->get_cart_details();
1389
            
1390
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
1391
                $cart_items = $ajax_cart_details;
1392
            } else {
1393
                $cart_items = $this->items;
1394
            }
1395
1396
            $this->discount = wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
0 ignored issues
show
Documentation introduced by
$this->discounts is of type string, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1397
        }
1398
        $discount   = wpinv_round_amount( $this->discount );
1399
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1400
        
1401
        if ( $currency ) {
1402
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
1403
        }
1404
        
1405
        $discount   = $dash . $discount;
1406
        
1407
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1408
    }
1409
    
1410
    public function get_discount_code() {
1411
        return $this->discount_code;
1412
    }
1413
    
1414 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...
1415
        $tax = wpinv_round_amount( $this->tax );
1416
        
1417
        if ( $currency ) {
1418
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
1419
        }
1420
        
1421
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1422
    }
1423
    
1424
    public function get_fees( $type = 'all' ) {
1425
        $fees    = array();
1426
1427
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1428
            foreach ( $this->fees as $fee ) {
1429 View Code Duplication
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1430
                    continue;
1431
                }
1432
1433
                $fee['label'] = stripslashes( $fee['label'] );
1434
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1435
                $fees[]    = $fee;
1436
            }
1437
        }
1438
1439
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1440
    }
1441
    
1442
    public function get_fees_total( $type = 'all' ) {
1443
        $fees_total = (float) 0.00;
1444
1445
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1446
        if ( ! empty( $payment_fees ) ) {
1447
            foreach ( $payment_fees as $fee ) {
1448
                $fees_total += (float) $fee['amount'];
1449
            }
1450
        }
1451
1452
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1453
        /*
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...
1454
        $fees = $this->get_fees( $type );
1455
1456
        $fees_total = 0;
1457
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1458
            foreach ( $fees as $fee_id => $fee ) {
1459
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1460
                    continue;
1461
                }
1462
1463
                $fees_total += $fee['amount'];
1464
            }
1465
        }
1466
1467
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1468
        */
1469
    }
1470
1471
    public function get_user_id() {
1472
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1473
    }
1474
    
1475
    public function get_first_name() {
1476
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1477
    }
1478
    
1479
    public function get_last_name() {
1480
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1481
    }
1482
    
1483
    public function get_user_full_name() {
1484
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1485
    }
1486
    
1487
    public function get_user_info() {
1488
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1489
    }
1490
    
1491
    public function get_email() {
1492
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1493
    }
1494
    
1495
    public function get_address() {
1496
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1497
    }
1498
    
1499
    public function get_phone() {
1500
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1501
    }
1502
    
1503
    public function get_number() {
1504
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1505
    }
1506
    
1507
    public function get_items() {
1508
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1509
    }
1510
    
1511
    public function get_key() {
1512
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1513
    }
1514
    
1515
    public function get_transaction_id() {
1516
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1517
    }
1518
    
1519
    public function get_gateway() {
1520
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1521
    }
1522
    
1523
    public function get_gateway_title() {
1524
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1525
        
1526
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1527
    }
1528
    
1529
    public function get_currency() {
1530
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1531
    }
1532
    
1533
    public function get_created_date() {
1534
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1535
    }
1536
    
1537
    public function get_due_date( $display = false ) {
1538
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1539
        
1540
        if ( !$display || empty( $due_date ) ) {
1541
            return $due_date;
1542
        }
1543
        
1544
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
1545
    }
1546
    
1547
    public function get_completed_date() {
1548
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1549
    }
1550
    
1551
    public function get_invoice_date( $formatted = true ) {
1552
        $date_completed = $this->completed_date;
1553
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1554
        
1555
        if ( $invoice_date == '' ) {
1556
            $date_created   = $this->date;
1557
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1558
        }
1559
        
1560
        if ( $formatted && $invoice_date ) {
1561
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
1562
        }
1563
1564
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1565
    }
1566
    
1567
    public function get_ip() {
1568
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1569
    }
1570
        
1571
    public function has_status( $status ) {
1572
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1573
    }
1574
    
1575
    public function add_item( $item_id = 0, $args = array() ) {
1576
        global $wpi_current_id, $wpi_item_id;
1577
        
1578
        $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...
1579
1580
        // Bail if this post isn't a item
1581
        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...
1582
            return false;
1583
        }
1584
        
1585
        $has_quantities = wpinv_item_quantities_enabled();
1586
1587
        // Set some defaults
1588
        $defaults = array(
1589
            'quantity'      => 1,
1590
            'id'            => false,
1591
            'name'          => $item->get_name(),
1592
            'item_price'    => false,
1593
            'custom_price'  => '',
1594
            'discount'      => 0,
1595
            'tax'           => 0.00,
1596
            'meta'          => array(),
1597
            'fees'          => array()
1598
        );
1599
1600
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1601
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1602
1603
        $wpi_current_id         = $this->ID;
1604
        $wpi_item_id            = $item->ID;
1605
        $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...
1606
        
1607
        $_POST['wpinv_country'] = $this->country;
1608
        $_POST['wpinv_state']   = $this->state;
1609
        
1610
        $found_cart_key         = false;
1611
        
1612
        if ($has_quantities) {
1613
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1614
            
1615
            foreach ( $this->items as $key => $cart_item ) {
1616
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1617
                    continue;
1618
                }
1619
1620
                $this->items[ $key ]['quantity'] += $args['quantity'];
1621
                break;
1622
            }
1623
            
1624
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1625
                if ( $item_id != $cart_item['id'] ) {
1626
                    continue;
1627
                }
1628
1629
                $found_cart_key = $cart_key;
1630
                break;
1631
            }
1632
        }
1633
        
1634
        if ($has_quantities && $found_cart_key !== false) {
1635
            $cart_item          = $this->cart_details[$found_cart_key];
1636
            $item_price         = $cart_item['item_price'];
1637
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1638
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1639
            
1640
            $new_quantity       = $quantity + $args['quantity'];
1641
            $subtotal           = $item_price * $new_quantity;
1642
            
1643
            $args['quantity']   = $new_quantity;
1644
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1645
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1646
            
1647
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1648
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1649
            // The total increase equals the number removed * the item_price
1650
            $total_increased    = wpinv_round_amount( $item_price );
1651
            
1652
            if ( wpinv_prices_include_tax() ) {
1653
                $subtotal -= wpinv_round_amount( $tax );
1654
            }
1655
1656
            $total              = $subtotal - $discount + $tax;
1657
1658
            // Do not allow totals to go negative
1659
            if( $total < 0 ) {
1660
                $total = 0;
1661
            }
1662
            
1663
            $cart_item['quantity']  = $new_quantity;
1664
            $cart_item['subtotal']  = $subtotal;
1665
            $cart_item['discount']  = $discount;
1666
            $cart_item['tax']       = $tax;
1667
            $cart_item['price']     = $total;
1668
            
1669
            $subtotal               = $total_increased - $discount_increased;
1670
            $tax                    = $tax_increased;
1671
            
1672
            $this->cart_details[$found_cart_key] = $cart_item;
1673
        } else {
1674
            // Set custom price.
1675
            if ( $args['custom_price'] !== '' ) {
1676
                $item_price = $args['custom_price'];
1677
            } else {
1678
                // Allow overriding the price
1679
                if ( false !== $args['item_price'] ) {
1680
                    $item_price = $args['item_price'];
1681
                } else {
1682
                    $item_price = wpinv_get_item_price( $item->ID );
1683
                }
1684
            }
1685
1686
            // Sanitizing the price here so we don't have a dozen calls later
1687
            $item_price = wpinv_sanitize_amount( $item_price );
1688
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
1689
        
1690
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1691
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1692
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1693
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1694
1695
            // Setup the items meta item
1696
            $new_item = array(
1697
                'id'       => $item->ID,
1698
                'quantity' => $args['quantity'],
1699
            );
1700
1701
            $this->items[]  = $new_item;
1702
1703
            if ( wpinv_prices_include_tax() ) {
1704
                $subtotal -= wpinv_round_amount( $tax );
1705
            }
1706
1707
            $total      = $subtotal - $discount + $tax;
1708
1709
            // Do not allow totals to go negative
1710
            if( $total < 0 ) {
1711
                $total = 0;
1712
            }
1713
        
1714
            $this->cart_details[] = array(
1715
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
1716
                'id'            => $item->ID,
1717
                'item_price'    => wpinv_round_amount( $item_price ),
1718
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
1719
                'quantity'      => $args['quantity'],
1720
                'discount'      => $discount,
1721
                'subtotal'      => wpinv_round_amount( $subtotal ),
1722
                'tax'           => wpinv_round_amount( $tax ),
1723
                'price'         => wpinv_round_amount( $total ),
1724
                'vat_rate'      => $tax_rate,
1725
                'vat_class'     => $tax_class,
1726
                'meta'          => $args['meta'],
1727
                'fees'          => $args['fees'],
1728
            );
1729
                        
1730
            $subtotal = $subtotal - $discount;
1731
        }
1732
        
1733
        $added_item = end( $this->cart_details );
1734
        $added_item['action']  = 'add';
1735
        
1736
        $this->pending['items'][] = $added_item;
1737
        
1738
        $this->increase_subtotal( $subtotal );
1739
        $this->increase_tax( $tax );
1740
1741
        return true;
1742
    }
1743
    
1744
    public function remove_item( $item_id, $args = array() ) {
1745
        // Set some defaults
1746
        $defaults = array(
1747
            'quantity'      => 1,
1748
            'item_price'    => false,
1749
            'custom_price'  => '',
1750
            'cart_index'    => false,
1751
        );
1752
        $args = wp_parse_args( $args, $defaults );
1753
1754
        // Bail if this post isn't a item
1755
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1756
            return false;
1757
        }
1758
        
1759
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1760
1761
        foreach ( $this->items as $key => $item ) {
1762
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1763
                continue;
1764
            }
1765
1766
            if ( false !== $args['cart_index'] ) {
1767
                $cart_index = absint( $args['cart_index'] );
1768
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1769
1770
                if ( ! empty( $cart_item ) ) {
1771
                    // If the cart index item isn't the same item ID, don't remove it
1772
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1773
                        continue;
1774
                    }
1775
                }
1776
            }
1777
1778
            $item_quantity = $this->items[ $key ]['quantity'];
1779
            if ( $item_quantity > $args['quantity'] ) {
1780
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1781
                break;
1782
            } else {
1783
                unset( $this->items[ $key ] );
1784
                break;
1785
            }
1786
        }
1787
1788
        $found_cart_key = false;
1789
        if ( false === $args['cart_index'] ) {
1790
            foreach ( $this->cart_details as $cart_key => $item ) {
1791
                if ( $item_id != $item['id'] ) {
1792
                    continue;
1793
                }
1794
1795
                if ( false !== $args['item_price'] ) {
1796
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1797
                        continue;
1798
                    }
1799
                }
1800
1801
                $found_cart_key = $cart_key;
1802
                break;
1803
            }
1804
        } else {
1805
            $cart_index = absint( $args['cart_index'] );
1806
1807
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1808
                return false; // Invalid cart index passed.
1809
            }
1810
1811
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1812
                return false; // We still need the proper Item ID to be sure.
1813
            }
1814
1815
            $found_cart_key = $cart_index;
1816
        }
1817
        
1818
        $cart_item  = $this->cart_details[$found_cart_key];
1819
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1820
        
1821
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1822
            return false; // Invoice must contain at least one item.
1823
        }
1824
        
1825
        $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...
1826
        
1827
        if ( $quantity > $args['quantity'] ) {
1828
            $item_price         = $cart_item['item_price'];
1829
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1830
            
1831
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1832
            $subtotal           = $item_price * $new_quantity;
1833
            
1834
            $args['quantity']   = $new_quantity;
1835
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1836
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1837
            
1838
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
1839
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1840
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
1841
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1842
            
1843
            // The total increase equals the number removed * the item_price
1844
            $total_decrease     = wpinv_round_amount( $item_price );
1845
            
1846
            if ( wpinv_prices_include_tax() ) {
1847
                $subtotal -= wpinv_round_amount( $tax );
1848
            }
1849
1850
            $total              = $subtotal - $discount + $tax;
1851
1852
            // Do not allow totals to go negative
1853
            if( $total < 0 ) {
1854
                $total = 0;
1855
            }
1856
            
1857
            $cart_item['quantity']  = $new_quantity;
1858
            $cart_item['subtotal']  = $subtotal;
1859
            $cart_item['discount']  = $discount;
1860
            $cart_item['tax']       = $tax;
1861
            $cart_item['price']     = $total;
1862
            
1863
            $added_item             = $cart_item;
1864
            $added_item['id']       = $item_id;
1865
            $added_item['price']    = $total_decrease;
1866
            $added_item['quantity'] = $args['quantity'];
1867
            
1868
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1869
            
1870
            $this->cart_details[$found_cart_key] = $cart_item;
1871
            
1872
            $remove_item = end( $this->cart_details );
1873
        } else {
1874
            $item_price     = $cart_item['item_price'];
1875
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1876
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1877
        
1878
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1879
            $tax_decrease       = $tax;
1880
1881
            unset( $this->cart_details[$found_cart_key] );
1882
            
1883
            $remove_item             = $args;
1884
            $remove_item['id']       = $item_id;
1885
            $remove_item['price']    = $subtotal_decrease;
1886
            $remove_item['quantity'] = $args['quantity'];
1887
        }
1888
        
1889
        $remove_item['action']      = 'remove';
1890
        $this->pending['items'][]   = $remove_item;
1891
               
1892
        $this->decrease_subtotal( $subtotal_decrease );
1893
        $this->decrease_tax( $tax_decrease );
1894
        
1895
        return true;
1896
    }
1897
    
1898
    public function update_items($temp = false) {
1899
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1900
        
1901
        if ( !empty( $this->cart_details ) ) {
1902
            $wpi_nosave             = $temp;
1903
            $cart_subtotal          = 0;
1904
            $cart_discount          = 0;
1905
            $cart_tax               = 0;
1906
            $cart_details           = array();
1907
            
1908
            $_POST['wpinv_country'] = $this->country;
1909
            $_POST['wpinv_state']   = $this->state;
1910
            
1911
            foreach ( $this->cart_details as $key => $item ) {
1912
                //$item['item_price'] = $item['custom_price'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
1913
                wpinv_error_log( $item, 'item', __FILE__, __LINE__ );
1914
                $item_price = $item['item_price'];
1915
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1916
                $amount     = wpinv_round_amount( $item_price * $quantity );
0 ignored issues
show
Unused Code introduced by
$amount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1917
                $subtotal   = $item_price * $quantity;
1918
                
1919
                $wpi_current_id         = $this->ID;
1920
                $wpi_item_id            = $item['id'];
1921
                
1922
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1923
                wpinv_error_log( $discount, 'discount', __FILE__, __LINE__ );
1924
                
1925
                $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...
1926
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1927
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1928
                wpinv_error_log( $tax, 'tax', __FILE__, __LINE__ );
1929
1930
                if ( wpinv_prices_include_tax() ) {
1931
                    $subtotal -= wpinv_round_amount( $tax );
1932
                }
1933
1934
                $total      = $subtotal - $discount + $tax;
1935
1936
                // Do not allow totals to go negative
1937
                if( $total < 0 ) {
1938
                    $total = 0;
1939
                }
1940
1941
                $cart_details[] = array(
1942
                    'id'          => $item['id'],
1943
                    'name'        => $item['name'],
1944
                    'item_price'  => wpinv_round_amount( $item_price ),
1945
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
1946
                    'quantity'    => $quantity,
1947
                    'discount'    => $discount,
1948
                    'subtotal'    => wpinv_round_amount( $subtotal ),
1949
                    'tax'         => wpinv_round_amount( $tax ),
1950
                    'price'       => wpinv_round_amount( $total ),
1951
                    'vat_rate'    => $tax_rate,
1952
                    'vat_class'   => $tax_class,
1953
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1954
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1955
                );
1956
                
1957
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1958
                $cart_discount  += (float)($discount);
1959
                $cart_tax       += (float)($tax);
1960
            }
1961
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
1962
            $this->tax      = wpinv_round_amount( $cart_tax );
1963
            $this->discount = wpinv_round_amount( $cart_discount );
1964
            
1965
            $this->recalculate_total();
1966
            
1967
            $this->cart_details = $cart_details;
1968
        }
1969
1970
        return $this;
1971
    }
1972
    
1973
    public function recalculate_totals($temp = false) {        
1974
        $this->update_items($temp);
1975
        $this->save( true );
1976
        
1977
        return $this;
1978
    }
1979
    
1980
    public function needs_payment() {
1981
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'pending' ), $this );
1982
1983
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() ) ) {
1984
            $needs_payment = true;
1985
        } else {
1986
            $needs_payment = false;
1987
        }
1988
1989
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
1990
    }
1991
    
1992
    public function get_checkout_payment_url( $on_checkout = false, $secret = false ) {
1993
        $pay_url = wpinv_get_checkout_uri();
1994
1995
        if ( is_ssl() ) {
1996
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
1997
        }
1998
        
1999
        $key = $this->get_key();
2000
2001
        if ( $on_checkout ) {
2002
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
2003
        } else {
2004
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
2005
        }
2006
        
2007 View Code Duplication
        if ( $secret ) {
2008
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
2009
        }
2010
2011
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this );
2012
    }
2013
    
2014
    public function get_view_url( $secret = false ) {
2015
        $print_url = get_permalink( $this->ID );
2016
        
2017 View Code Duplication
        if ( $secret ) {
2018
            $print_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $this->get_key() ) ), $print_url );
2019
        }
2020
2021
        return apply_filters( 'wpinv_get_view_url', $print_url, $this );
2022
    }
2023
    
2024
    public function generate_key( $string = '' ) {
2025
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2026
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2027
    }
2028
    
2029
    public function is_recurring() {
2030
        if ( empty( $this->cart_details ) ) {
2031
            return false;
2032
        }
2033
        
2034
        $has_subscription = false;
2035 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2036
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2037
                $has_subscription = true;
2038
                break;
2039
            }
2040
        }
2041
        
2042
        if ( count( $this->cart_details ) > 1 ) {
2043
            $has_subscription = false;
2044
        }
2045
2046
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2047
    }
2048
    
2049
    public function is_free_trial() {
2050
        $is_free_trial = false;
2051
        
2052
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2053
            if ( !empty( $item ) && $item->has_free_trial() ) {
2054
                $is_free_trial = true;
2055
            }
2056
        }
2057
2058
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details );
2059
    }
2060
    
2061
    public function get_recurring( $object = false ) {
2062
        $item = NULL;
2063
        
2064
        if ( empty( $this->cart_details ) ) {
2065
            return $item;
2066
        }
2067
        
2068 View Code Duplication
        foreach( $this->cart_details as $cart_item ) {
2069
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2070
                $item = $cart_item['id'];
2071
                break;
2072
            }
2073
        }
2074
        
2075
        if ( $object ) {
2076
            $item = $item ? new WPInv_Item( $item ) : NULL;
2077
            
2078
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2079
        }
2080
2081
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2082
    }
2083
    
2084
    public function get_subscription_name() {
2085
        $item = $this->get_recurring( true );
2086
        
2087
        if ( empty( $item ) ) {
2088
            return NULL;
2089
        }
2090
        
2091
        if ( !($name = $item->get_name()) ) {
2092
            $name = $item->post_name;
2093
        }
2094
2095
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2096
    }
2097
        
2098
    public function get_expiration() {
2099
        $expiration = $this->get_meta( '_wpinv_subscr_expiration', true );
2100
        return $expiration;
2101
    }
2102
    
2103
    public function get_cancelled_date( $formatted = true ) {
2104
        $cancelled_date = $this->get_subscription_status() == 'cancelled' ? $this->get_meta( '_wpinv_subscr_cancelled_on', true ) : '';
2105
        
2106
        if ( $formatted && $cancelled_date ) {
2107
            $cancelled_date = date_i18n( get_option( 'date_format' ), strtotime( $cancelled_date ) );
2108
        }
2109
        
2110
        return $cancelled_date;
2111
    }
2112
    
2113
    public function get_trial_end_date( $formatted = true ) {
2114
        if ( !$this->is_free_trial() || !$this->is_paid() ) {
2115
            return NULL;
2116
        }
2117
        
2118
        $trial_end_date = $this->get_subscription_status() == 'trialing' ? $this->get_meta( '_wpinv_subscr_trial_end', true ) : '';
2119
        
2120
        if ( empty( $trial_end_date ) ) {
2121
            $trial_start_time = strtotime( $this->get_subscription_start() );
2122
            $trial_start_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2123
            
2124
            $trial_end_date = date_i18n( 'Y-m-d H:i:s', $trial_start_time );
2125
        }
2126
        
2127
        if ( $formatted && $trial_end_date ) {
2128
            $trial_end_date = date_i18n( get_option( 'date_format' ), strtotime( $trial_end_date ) );
2129
        }
2130
        
2131
        return $trial_end_date;
2132
    }
2133
    
2134
    public function get_subscription_created( $default = true ) {
2135
        $created = $this->get_meta( '_wpinv_subscr_created', true );
2136
        
2137
        if ( empty( $created ) && $default ) {
2138
            $created = $this->date;
2139
        }
2140
        return $created;
2141
    }
2142
    
2143
    public function get_subscription_start( $formatted = true ) {
2144
        if ( !$this->is_paid() ) {
2145
            return '-';
2146
        }
2147
        $start   = $this->get_subscription_created();
2148
        
2149
        if ( $formatted ) {
2150
            $date = date_i18n( get_option( 'date_format' ), strtotime( $start ) );
2151
        } else {
2152
            $date = date_i18n( 'Y-m-d H:i:s', strtotime( $start ) );
2153
        }
2154
2155
        return $date;
2156
    }
2157
    
2158
    public function get_subscription_end( $formatted = true ) {
2159
        if ( !$this->is_paid() ) {
2160
            return '-';
2161
        }
2162
        $start          = $this->get_subscription_created();
2163
        $interval       = $this->get_subscription_interval();
2164
        $period         = $this->get_subscription_period( true );
2165
        $bill_times     = (int)$this->get_bill_times();
2166
        
2167
        if ( $bill_times == 0 ) {
2168
            return $formatted ? __( 'Until cancelled', 'invoicing' ) : $bill_times;
2169
        }
2170
        
2171
        $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...
2172
        
2173
        $end_time = strtotime( $start . '+' . ( $interval * $bill_times ) . ' ' . $period );
2174
        
2175
        if ( $this->is_free_trial() ) {
2176
            $end_time += ( wpinv_period_in_days( $this->get_subscription_trial_interval(), $this->get_subscription_trial_period() ) * DAY_IN_SECONDS ) ;
2177
        }
2178
        
2179
        if ( $formatted ) {
2180
            $date = date_i18n( get_option( 'date_format' ), $end_time );
2181
        } else {
2182
            $date = date_i18n( 'Y-m-d H:i:s', $end_time );
2183
        }
2184
2185
        return $date;
2186
    }
2187
    
2188
    public function get_expiration_time() {
2189
        return strtotime( $this->get_expiration(), current_time( 'timestamp' ) );
2190
    }
2191
    
2192
    public function get_original_invoice_id() {        
2193
        return $this->parent_invoice;
2194
    }
2195
    
2196
    public function get_bill_times() {
2197
        $subscription_data = $this->get_subscription_data();
2198
        return $subscription_data['bill_times'];
2199
    }
2200
2201
    public function get_child_payments( $self = false ) {
2202
        $invoices = get_posts( array(
2203
            'post_type'         => $this->post_type,
2204
            'post_parent'       => (int)$this->ID,
2205
            'posts_per_page'    => '999',
2206
            'post_status'       => array( 'publish', 'wpi-processing', 'wpi-renewal' ),
2207
            'orderby'           => 'ID',
2208
            'order'             => 'DESC',
2209
            'fields'            => 'ids'
2210
        ) );
2211
        
2212
        if ( $this->is_free_trial() ) {
2213
            $self = false;
2214
        }
2215
        
2216
        if ( $self && $this->is_paid() ) {
2217
            if ( !empty( $invoices ) ) {
2218
                $invoices[] = (int)$this->ID;
2219
            } else {
2220
                $invoices = array( $this->ID );
2221
            }
2222
            
2223
            $invoices = array_unique( $invoices );
2224
        }
2225
2226
        return $invoices;
2227
    }
2228
2229
    public function get_total_payments( $self = true ) {
2230
        return count( $this->get_child_payments( $self ) );
2231
    }
2232
    
2233
    public function get_subscriptions( $limit = -1 ) {
2234
        $subscriptions = wpinv_get_subscriptions( array( 'parent_invoice_id' => $this->ID, 'numberposts' => $limit ) );
2235
2236
        return $subscriptions;
2237
    }
2238
    
2239
    public function get_subscription_id() {
2240
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2241
        
2242
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2243
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2244
            
2245
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2246
        }
2247
        
2248
        return $subscription_id;
2249
    }
2250
    
2251
    public function get_subscription_status() {
2252
        $subscription_status = $this->get_meta( '_wpinv_subscr_status', true );
2253
2254
        if ( empty( $subscription_status ) ) {
2255
            $status = 'pending';
2256
            
2257
            if ( $this->is_paid() ) {        
2258
                $bill_times   = (int)$this->get_bill_times();
2259
                $times_billed = (int)$this->get_total_payments();
2260
                $expiration = $this->get_subscription_end( false );
2261
                $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;
2262
                
2263
                if ( (int)$bill_times == 0 ) {
2264
                    $status = $expired ? 'expired' : 'active';
2265
                } else if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2266
                    $status = 'completed';
2267
                } else if ( $expired ) {
2268
                    $status = 'expired';
2269
                } else if ( $bill_times > 0 ) {
2270
                    $status = 'active';
2271
                } else {
2272
                    $status = 'pending';
2273
                }
2274
            }
2275
            
2276
            if ( $status && $status != $subscription_status ) {
2277
                $subscription_status = $status;
2278
                
2279
                $this->update_meta( '_wpinv_subscr_status', $status );
2280
            }
2281
        }
2282
        
2283
        return $subscription_status;
2284
    }
2285
    
2286
    public function get_subscription_status_label( $status = '' ) {
2287
        $status = !empty( $status ) ? $status : $this->get_subscription_status();
2288
2289
        switch( $status ) {
2290
            case 'active' :
2291
                $status_label = __( 'Active', 'invoicing' );
2292
                break;
2293
2294
            case 'cancelled' :
2295
                $status_label = __( 'Cancelled', 'invoicing' );
2296
                break;
2297
                
2298
            case 'completed' :
2299
                $status_label = __( 'Completed', 'invoicing' );
2300
                break;
2301
2302
            case 'expired' :
2303
                $status_label = __( 'Expired', 'invoicing' );
2304
                break;
2305
2306
            case 'pending' :
2307
                $status_label = __( 'Pending', 'invoicing' );
2308
                break;
2309
2310
            case 'failing' :
2311
                $status_label = __( 'Failing', 'invoicing' );
2312
                break;
2313
                
2314
            case 'stopped' :
2315
                $status_label = __( 'Stopped', 'invoicing' );
2316
                break;
2317
                
2318
            case 'trialing' :
2319
                $status_label = __( 'Trialing', 'invoicing' );
2320
                break;
2321
2322
            default:
2323
                $status_label = $status;
2324
                break;
2325
        }
2326
2327
        return $status_label;
2328
    }
2329
    
2330 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...
2331
        $period = $this->get_meta( '_wpinv_subscr_period', true );
2332
        
2333
        // Fix period for old invoices
2334
        if ( $period == 'day' ) {
2335
            $period = 'D';
2336
        } else if ( $period == 'week' ) {
2337
            $period = 'W';
2338
        } else if ( $period == 'month' ) {
2339
            $period = 'M';
2340
        } else if ( $period == 'year' ) {
2341
            $period = 'Y';
2342
        }
2343
        
2344
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2345
            $period = 'D';
2346
        }
2347
        
2348
        if ( $full ) {
2349
            switch( $period ) {
2350
                case 'D':
2351
                    $period = 'day';
2352
                break;
2353
                case 'W':
2354
                    $period = 'week';
2355
                break;
2356
                case 'M':
2357
                    $period = 'month';
2358
                break;
2359
                case 'Y':
2360
                    $period = 'year';
2361
                break;
2362
            }
2363
        }
2364
        
2365
        return $period;
2366
    }
2367
    
2368
    public function get_subscription_interval() {
2369
        $interval = (int)$this->get_meta( '_wpinv_subscr_interval', true );
2370
        
2371
        if ( !$interval > 0 ) {
2372
            $interval = 1;
2373
        }
2374
        
2375
        return $interval;
2376
    }
2377
    
2378 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...
2379
        if ( !$this->is_free_trial() ) {
2380
            return '';
2381
        }
2382
        
2383
        $period = $this->get_meta( '_wpinv_subscr_trial_period', true );
2384
        
2385
        // Fix period for old invoices
2386
        if ( $period == 'day' ) {
2387
            $period = 'D';
2388
        } else if ( $period == 'week' ) {
2389
            $period = 'W';
2390
        } else if ( $period == 'month' ) {
2391
            $period = 'M';
2392
        } else if ( $period == 'year' ) {
2393
            $period = 'Y';
2394
        }
2395
        
2396
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
2397
            $period = 'D';
2398
        }
2399
        
2400
        if ( $full ) {
2401
            switch( $period ) {
2402
                case 'D':
2403
                    $period = 'day';
2404
                break;
2405
                case 'W':
2406
                    $period = 'week';
2407
                break;
2408
                case 'M':
2409
                    $period = 'month';
2410
                break;
2411
                case 'Y':
2412
                    $period = 'year';
2413
                break;
2414
            }
2415
        }
2416
        
2417
        return $period;
2418
    }
2419
    
2420
    public function get_subscription_trial_interval() {
2421
        if ( !$this->is_free_trial() ) {
2422
            return 0;
2423
        }
2424
        
2425
        $interval = (int)$this->get_meta( '_wpinv_subscr_trial_interval', true );
2426
        
2427
        if ( !$interval > 0 ) {
2428
            $interval = 1;
2429
        }
2430
        
2431
        return $interval;
2432
    }
2433
    
2434 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...
2435
        $args = array(
2436
            'status' => 'failing'
2437
        );
2438
2439
        if ( $this->update_subscription( $args ) ) {
2440
            do_action( 'wpinv_subscription_failing', $this->ID, $this );
2441
            return true;
2442
        }
2443
2444
        return false;
2445
    }
2446
    
2447 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...
2448
        $args = array(
2449
            'status' => 'stopped'
2450
        );
2451
2452
        if ( $this->update_subscription( $args ) ) {
2453
            do_action( 'wpinv_subscription_stopped', $this->ID, $this );
2454
            return true;
2455
        }
2456
2457
        return false;
2458
    }
2459
    
2460 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...
2461
        $args = array(
2462
            'status' => 'active'
2463
        );
2464
2465
        if ( $this->update_subscription( $args ) ) {
2466
            do_action( 'wpinv_subscription_restarted', $this->ID, $this );
2467
            return true;
2468
        }
2469
2470
        return false;
2471
    }
2472
2473
    public function cancel_subscription() {
2474
        $args = array(
2475
            'status' => 'cancelled'
2476
        );
2477
2478
        if ( $this->update_subscription( $args ) ) {
2479
            if ( is_user_logged_in() ) {
2480
                $userdata = get_userdata( get_current_user_id() );
2481
                $user     = $userdata->user_login;
2482
            } else {
2483
                $user = __( 'gateway', 'invoicing' );
2484
            }
2485
            
2486
            $subscription_id = $this->get_subscription_id();
2487
            if ( !$subscription_id ) {
2488
                $subscription_id = $this->ID;
2489
            }
2490
2491
            $note = sprintf( __( 'Subscription %s has been cancelled by %s', 'invoicing' ), $subscription_id, $user );
2492
            $this->add_note( $note );
2493
2494
            do_action( 'wpinv_subscription_cancelled', $this->ID, $this );
2495
            return true;
2496
        }
2497
2498
        return false;
2499
    }
2500
2501
    public function can_cancel() {
2502
        return apply_filters( 'wpinv_subscription_can_cancel', false, $this );
2503
    }
2504
    
2505
    public function add_subscription( $data = array() ) {
2506
        if ( empty( $this->ID ) ) {
2507
            return false;
2508
        }
2509
2510
        $defaults = array(
2511
            'period'            => '',
2512
            'initial_amount'    => '',
2513
            'recurring_amount'  => '',
2514
            'interval'          => 0,
2515
            'trial_interval'    => 0,
2516
            'trial_period'      => '',
2517
            'bill_times'        => 0,
2518
            'item_id'           => 0,
2519
            'created'           => '',
2520
            'expiration'        => '',
2521
            'status'            => '',
2522
            'profile_id'        => '',
2523
        );
2524
2525
        $args = wp_parse_args( $data, $defaults );
2526
2527
        if ( $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2528
            if ( 'active' == $args['status'] || $args['status'] == 'trialing' ) {
2529
                $args['status'] = 'expired';
2530
            }
2531
        }
2532
2533
        do_action( 'wpinv_subscription_pre_create', $args, $data, $this );
2534
        
2535
        if ( !empty( $args ) ) {
2536
            foreach ( $args as $key => $value ) {
2537
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2538
            }
2539
        }
2540
2541
        do_action( 'wpinv_subscription_post_create', $args, $data, $this );
2542
2543
        return true;
2544
    }
2545
    
2546
    public function update_subscription( $args = array() ) {
2547
        if ( empty( $this->ID ) ) {
2548
            return false;
2549
        }
2550
2551
        if ( !empty( $args['expiration'] ) && $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
2552
            if ( !isset( $args['status'] ) || ( isset( $args['status'] ) && ( 'active' == $args['status'] || $args['status'] == 'trialing' ) ) ) {
2553
                $args['status'] = 'expired';
2554
            }
2555
        }
2556
2557
        if ( isset( $args['status'] ) && $args['status'] == 'cancelled' && empty( $args['cancelled_on'] ) ) {
2558
            $args['cancelled_on'] = date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) );
2559
        }
2560
2561
        do_action( 'wpinv_subscription_pre_update', $args, $this );
2562
        
2563
        if ( !empty( $args ) ) {
2564
            foreach ( $args as $key => $value ) {
2565
                $this->update_meta( '_wpinv_subscr_' . $key, $value );
2566
            }
2567
        }
2568
2569
        do_action( 'wpinv_subscription_post_update', $args, $this );
2570
2571
        return true;
2572
    }
2573
    
2574
    public function renew_subscription() {
2575
        $parent_invoice = $this->get_parent_payment();
2576
        $parent_invoice = empty( $parent_invoice ) ? $this : $parent_invoice;
2577
        
2578
        $current_time   = current_time( 'timestamp' );
2579
        $start          = $this->get_subscription_created();
2580
        $start          = $start ? strtotime( $start ) : $current_time;
2581
        $expires        = $this->get_expiration_time();
2582
        
2583
        if ( !$expires ) {
2584
            $expires    = strtotime( '+' . $parent_invoice->get_subscription_interval() . ' ' . $parent_invoice->get_subscription_period( true ), $start );
2585
        }
2586
        
2587
        $expiration     = date_i18n( 'Y-m-d 23:59:59', $expires );
2588
        $expiration     = apply_filters( 'wpinv_subscription_renewal_expiration', $expiration, $this->ID, $this );
2589
        $bill_times     = $parent_invoice->get_bill_times();
2590
        $times_billed   = $parent_invoice->get_total_payments();
2591
        
2592
        if ( $parent_invoice->get_subscription_status() == 'trialing' && ( $times_billed > 0 || strtotime( date_i18n( 'Y-m-d' ) ) < strtotime( $parent_invoice->get_trial_end_date( false ) ) ) ) {
2593
            $args = array(
2594
                'status'     => 'active',
2595
            );
2596
2597
            $parent_invoice->update_subscription( $args );
2598
        }
2599
        
2600
        do_action( 'wpinv_subscription_pre_renew', $this->ID, $expiration, $this );
2601
2602
        $status       = 'active';
2603
        if ( $bill_times > 0 && $times_billed >= $bill_times ) {
2604
            $this->complete_subscription();
2605
            $status = 'completed';
2606
        }
2607
2608
        $args = array(
2609
            'expiration' => $expiration,
2610
            'status'     => $status,
2611
        );
2612
2613
        $this->update_subscription( $args );
2614
2615
        do_action( 'wpinv_subscription_post_renew', $this->ID, $expiration, $this );
2616
        do_action( 'wpinv_recurring_set_subscription_status', $this->ID, $status, $this );
2617
    }
2618
    
2619
    public function complete_subscription() {
2620
        $args = array(
2621
            'status' => 'completed'
2622
        );
2623
2624
        if ( $this->update_subscription( $args ) ) {
2625
            do_action( 'wpinv_subscription_completed', $this->ID, $this );
2626
        }
2627
    }
2628
    
2629
    public function expire_subscription() {
2630
        $args = array(
2631
            'status' => 'expired'
2632
        );
2633
2634
        if ( $this->update_subscription( $args ) ) {
2635
            do_action( 'wpinv_subscription_expired', $this->ID, $this );
2636
        }
2637
    }
2638
2639
    public function get_cancel_url() {
2640
        $url = wp_nonce_url( add_query_arg( array( 'wpi_action' => 'cancel_subscription', 'sub_id' => $this->ID ) ), 'wpinv-recurring-cancel' );
2641
2642
        return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
2643
    }
2644
2645
    public function can_update() {
2646
        return apply_filters( 'wpinv_subscription_can_update', false, $this );
2647
    }
2648
2649
    public function get_update_url() {
2650
        $url = add_query_arg( array( 'action' => 'update', 'sub_id' => $this->ID ) );
2651
2652
        return apply_filters( 'wpinv_subscription_update_url', $url, $this );
2653
    }
2654
2655
    public function is_parent() {
2656
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2657
2658
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2659
    }
2660
    
2661
    public function is_renewal() {
2662
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2663
2664
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2665
    }
2666
    
2667
    public function get_parent_payment() {
2668
        $parent_payment = NULL;
2669
        
2670
        if ( $this->is_renewal() ) {
2671
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2672
        }
2673
        
2674
        return $parent_payment;
2675
    }
2676
    
2677
    public function is_subscription_active() {
2678
        $ret = false;
2679
        
2680
        $subscription_status = $this->get_subscription_status();
2681
2682
        if( ! $this->is_subscription_expired() && ( $subscription_status == 'active' || $subscription_status == 'cancelled' || $subscription_status == 'trialing' ) ) {
2683
            $ret = true;
2684
        }
2685
2686
        return apply_filters( 'wpinv_subscription_is_active', $ret, $this->ID, $this );
2687
    }
2688
2689
    public function is_subscription_expired() {
2690
        $ret = false;
2691
        $subscription_status = $this->get_subscription_status();
2692
2693
        if ( $subscription_status == 'expired' ) {
2694
            $ret = true;
2695
        } else if ( 'active' === $subscription_status || 'cancelled' === $subscription_status || 'trialing' == $subscription_status ) {
2696
            $ret        = false;
2697
            $expiration = $this->get_expiration_time();
2698
2699
            if ( $expiration && strtotime( 'NOW', current_time( 'timestamp' ) ) > $expiration ) {
2700
                $ret = true;
2701
2702
                if ( 'active' === $subscription_status || 'trialing' === $subscription_status ) {
2703
                    $this->expire_subscription();
2704
                }
2705
            }
2706
        }
2707
2708
        return apply_filters( 'wpinv_subscription_is_expired', $ret, $this->ID, $this );
2709
    }
2710
    
2711
    public function get_new_expiration( $item_id = 0, $trial = true ) {
2712
        $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...
2713
        $interval = $item->get_recurring_interval();
2714
        $period = $item->get_recurring_period( true );
2715
        
2716
        $expiration_time = strtotime( '+' . $interval . ' ' . $period );
2717
        
2718
        if ( $trial && $this->is_free_trial() && $item->has_free_trial() ) {
2719
            $expiration_time += ( wpinv_period_in_days( $item->get_trial_interval(), $item->get_trial_period() ) * DAY_IN_SECONDS ) ;
2720
        }
2721
2722
        return date_i18n( 'Y-m-d 23:59:59', $expiration_time );
2723
    }
2724
    
2725
    public function get_subscription_data( $filed = '' ) {
2726
        $fields = array( 'item_id', 'status', 'period', 'initial_amount', 'recurring_amount', 'interval', 'bill_times', 'trial_period', 'trial_interval', 'expiration', 'profile_id', 'created', 'cancelled_on' );
2727
        
2728
        $subscription_meta = array();
2729
        foreach ( $fields as $field ) {
2730
            $subscription_meta[ $field ] = $this->get_meta( '_wpinv_subscr_' . $field );
2731
        }
2732
        
2733
        $item = $this->get_recurring( true );
2734
        
2735
        if ( !empty( $item ) ) {
2736
            if ( empty( $subscription_meta['item_id'] ) ) {
2737
                $subscription_meta['item_id'] = $item->ID;
2738
            }
2739
            if ( empty( $subscription_meta['period'] ) ) {
2740
                $subscription_meta['period'] = $item->get_recurring_period();
2741
            }
2742
            if ( empty( $subscription_meta['interval'] ) ) {
2743
                $subscription_meta['interval'] = $item->get_recurring_interval();
2744
            }
2745
            if ( $item->has_free_trial() ) {
2746
                if ( empty( $subscription_meta['trial_period'] ) ) {
2747
                    $subscription_meta['trial_period'] = $item->get_trial_period();
2748
                }
2749
                if ( empty( $subscription_meta['trial_interval'] ) ) {
2750
                    $subscription_meta['trial_interval'] = $item->get_trial_interval();
2751
                }
2752
            } else {
2753
                $subscription_meta['trial_period']      = '';
2754
                $subscription_meta['trial_interval']    = 0;
2755
            }
2756
            if ( !$subscription_meta['bill_times'] && $subscription_meta['bill_times'] !== 0 ) {
2757
                $subscription_meta['bill_times'] = $item->get_recurring_limit();
2758
            }
2759
            if ( $subscription_meta['initial_amount'] === '' || $subscription_meta['recurring_amount'] === '' ) {
2760
                $subscription_meta['initial_amount']    = wpinv_round_amount( $this->get_total() );
2761
                $subscription_meta['recurring_amount']  = wpinv_round_amount( $this->get_recurring_details( 'total' ) );
2762
            }
2763
        }
2764
        
2765
        if ( $filed === '' ) {
2766
            return apply_filters( 'wpinv_get_invoice_subscription_data', $subscription_meta, $this );
2767
        }
2768
        
2769
        $value = isset( $subscription_meta[$filed] ) ? $subscription_meta[$filed] : '';
2770
        
2771
        return apply_filters( 'wpinv_invoice_subscription_data_value', $value, $subscription_meta, $this );
2772
    }
2773
    
2774
    public function is_paid() {
2775
        if ( $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) ) ) {
2776
            return true;
2777
        }
2778
        
2779
        return false;
2780
    }
2781
    
2782
    public function is_free() {
2783
        $is_free = false;
2784
        
2785
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
2786
            if ( $this->is_parent() && $this->is_recurring() ) {
2787
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
2788
            } else {
2789
                $is_free = true;
2790
            }
2791
        }
2792
        
2793
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
2794
    }
2795
    
2796
    public function has_vat() {
2797
        global $wpinv_euvat, $wpi_country;
2798
        
2799
        $requires_vat = false;
2800
        
2801
        if ( $this->country ) {
2802
            $wpi_country        = $this->country;
2803
            
2804
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2805
        }
2806
        
2807
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2808
    }
2809
    
2810
    public function refresh_item_ids() {
2811
        $item_ids = array();
2812
        
2813
        if ( !empty( $this->cart_details ) ) {
2814
            foreach ( $this->cart_details as $key => $item ) {
2815
                if ( !empty( $item['id'] ) ) {
2816
                    $item_ids[] = $item['id'];
2817
                }
2818
            }
2819
        }
2820
        
2821
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2822
        
2823
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2824
    }
2825
2826
    public function get_invoice_quote_type($post_id) {
2827
        if ( empty( $post_id ) ) {
2828
            return '';
2829
        }
2830
        $post_type = '';
0 ignored issues
show
Unused Code introduced by
$post_type 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...
2831
        $type = get_post_type($post_id);
2832
        if("wpi_invoice" === $type){
2833
            $post_type = __('Invoice', 'invoicing');
2834
        } else{
2835
            $post_type = __('Quote', 'invoicing');
2836
        }
2837
2838
        return $post_type;
2839
    }
2840
}
2841