Passed
Pull Request — master (#147)
by
unknown
03:47
created

WPInv_Invoice::setup_invoice()   D

Complexity

Conditions 10
Paths 15

Size

Total Lines 96
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 64
nc 15
nop 1
dl 0
loc 96
rs 4.9494
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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      = 'wpi-pending';
40
    public $post_status = 'wpi-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
	    if( wpinv_get_option( 'invoice_number_only_on_payment' ) ) {
92
			if($value === 'wpi-pending') {
93
				$this->number = $this->ID;
0 ignored issues
show
Documentation Bug introduced by
The property $number was declared of type string, but $this->ID is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

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