Passed
Pull Request — master (#57)
by Kiran
04:16
created

WPInv_Invoice::setup_post_name()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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

class MyClass { }

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

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

class MyClass {
    public $foo;
}

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

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

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

}

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

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

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

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

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

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

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

Loading history...
165
        $this->email           = get_the_author_meta( 'email', $this->user_id );
166
        
167
        $this->user_info       = $this->setup_user_info();
168
                
169
        $this->first_name      = $this->user_info['first_name'];
170
        $this->last_name       = $this->user_info['last_name'];
171
        $this->company         = $this->user_info['company'];
172
        $this->vat_number      = $this->user_info['vat_number'];
173
        $this->vat_rate        = $this->user_info['vat_rate'];
174
        $this->adddress_confirmed  = $this->user_info['adddress_confirmed'];
175
        $this->address         = $this->user_info['address'];
176
        $this->city            = $this->user_info['city'];
177
        $this->country         = $this->user_info['country'];
178
        $this->state           = $this->user_info['state'];
179
        $this->zip             = $this->user_info['zip'];
180
        $this->phone           = $this->user_info['phone'];
181
        
182
        $this->discounts       = $this->user_info['discount'];
183
            $this->discount        = $this->setup_discount();
184
            $this->discount_code   = $this->setup_discount_code();
185
186
        // Other Identifiers
187
        $this->key             = $this->setup_invoice_key();
188
        $this->number          = $this->setup_invoice_number();
189
        $this->title           = !empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
190
        
191
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
192
        
193
        // Allow extensions to add items to this object via hook
194
        do_action( 'wpinv_setup_invoice', $this, $invoice_id );
195
196
        return true;
197
    }
198
    
199
    private function setup_status_nicename($status) {
200
        $all_invoice_statuses  = wpinv_get_invoice_statuses();
201
        $status   = isset( $all_invoice_statuses[$status] ) ? $all_invoice_statuses[$status] : __( $status, 'invoicing' );
202
203
        return apply_filters( 'setup_status_nicename', $status );
204
    }
205
    
206
    private function setup_post_name( $post = NULL ) {
207
        global $wpdb;
208
        
209
        $post_name = '';
210
        
211
        if ( !empty( $post ) ) {
212
            if( !empty( $post->post_name ) ) {
213
                $post_name = $post->post_name;
214
            } else if ( !empty( $post->ID ) ) {
215
                $post_name = 'inv-' . $post->ID;
216
217
                $wpdb->update( $wpdb->posts, array( 'post_name' => 'inv-' . $post->ID ), array( 'ID' => $post->ID ) );
218
            }
219
        }
220
221
        $this->post_name = $post_name;
222
    }
223
    
224
    private function setup_due_date() {
225
        $due_date = $this->get_meta( '_wpinv_due_date' );
226
        
227
        if ( empty( $due_date ) ) {
228
            $overdue_time = strtotime( $this->date ) + ( DAY_IN_SECONDS * absint( wpinv_get_option( 'overdue_days' ) ) );
229
            $due_date = date_i18n( 'Y-m-d', $overdue_time );
230
        } else if ( $due_date == 'none' ) {
231
            $due_date = '';
232
        }
233
        
234
        return $due_date;
235
    }
236
    
237
    private function setup_completed_date() {
238
        $invoice = get_post( $this->ID );
239
240
        if ( 'pending' == $invoice->post_status || 'preapproved' == $invoice->post_status ) {
241
            return false; // This invoice was never paid
242
        }
243
244
        $date = ( $date = $this->get_meta( '_wpinv_completed_date', true ) ) ? $date : $invoice->modified_date;
245
246
        return $date;
247
    }
248
    
249
    private function setup_cart_details() {
250
        $cart_details = isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
251
        return $cart_details;
252
    }
253
    
254
    public function array_convert() {
255
        return get_object_vars( $this );
256
    }
257
    
258
    private function setup_items() {
259
        $items = isset( $this->payment_meta['items'] ) ? maybe_unserialize( $this->payment_meta['items'] ) : array();
260
        return $items;
261
    }
262
    
263
    private function setup_fees() {
264
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
265
        return $payment_fees;
266
    }
267
        
268
    private function setup_currency() {
269
        $currency = isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'wpinv_currency_default', wpinv_get_currency(), $this );
270
        return $currency;
271
    }
272
    
273
    private function setup_discount() {
274
        //$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...
275
        $discount = (float)$this->subtotal - ( (float)$this->total - (float)$this->tax - (float)$this->fees_total );
276
        if ( $discount < 0 ) {
277
            $discount = 0;
278
        }
279
        $discount = wpinv_round_amount( $discount );
280
        
281
        return $discount;
282
    }
283
    
284
    private function setup_discount_code() {
285
        $discount_code = !empty( $this->discounts ) ? $this->discounts : $this->get_meta( '_wpinv_discount_code', true );
286
        return $discount_code;
287
    }
288
    
289
    private function setup_tax() {
290
        $tax = $this->get_meta( '_wpinv_tax', true );
291
292
        // We don't have tax as it's own meta and no meta was passed
293
        if ( '' === $tax ) {            
294
            $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
295
        }
296
297
        return $tax;
298
    }
299
300
    private function setup_subtotal() {
301
        $subtotal     = 0;
302
        $cart_details = $this->cart_details;
303
304
        if ( is_array( $cart_details ) ) {
305
            foreach ( $cart_details as $item ) {
306
                if ( isset( $item['subtotal'] ) ) {
307
                    $subtotal += $item['subtotal'];
308
                }
309
            }
310
        } else {
311
            $subtotal  = $this->total;
312
            $tax       = wpinv_use_taxes() ? $this->tax : 0;
313
            $subtotal -= $tax;
314
        }
315
316
        return $subtotal;
317
    }
318
    
319
    private function setup_discounts() {
320
        $discounts = ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
321
        return $discounts;
322
    }
323
    
324
    private function setup_total() {
325
        $amount = $this->get_meta( '_wpinv_total', true );
326
327
        if ( empty( $amount ) && '0.00' != $amount ) {
328
            $meta   = $this->get_meta( '_wpinv_payment_meta', true );
329
            $meta   = maybe_unserialize( $meta );
330
331
            if ( isset( $meta['amount'] ) ) {
332
                $amount = $meta['amount'];
333
            }
334
        }
335
336
        return $amount;
337
    }
338
    
339
    private function setup_mode() {
340
        return $this->get_meta( '_wpinv_mode' );
341
    }
342
343
    private function setup_gateway() {
344
        $gateway = $this->get_meta( '_wpinv_gateway' );
345
        
346
        if ( empty( $gateway ) && 'publish' === $this->status ) {
347
            $gateway = 'manual';
348
        }
349
        
350
        return $gateway;
351
    }
352
    
353
    private function setup_gateway_title() {
354
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
355
        return $gateway_title;
356
    }
357
358
    private function setup_transaction_id() {
359
        $transaction_id = $this->get_meta( '_wpinv_transaction_id' );
360
361
        if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
362
            $gateway        = $this->gateway;
363
            $transaction_id = apply_filters( 'wpinv_get_invoice_transaction_id-' . $gateway, $this->ID );
364
        }
365
366
        return $transaction_id;
367
    }
368
369
    private function setup_ip() {
370
        $ip = $this->get_meta( '_wpinv_user_ip' );
371
        return $ip;
372
    }
373
374
    ///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...
375
        ///$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...
376
        ///return $user_id;
377
    ///}
378
        
379
    private function setup_first_name() {
380
        $first_name = $this->get_meta( '_wpinv_first_name' );
381
        return $first_name;
382
    }
383
    
384
    private function setup_last_name() {
385
        $last_name = $this->get_meta( '_wpinv_last_name' );
386
        return $last_name;
387
    }
388
    
389
    private function setup_company() {
390
        $company = $this->get_meta( '_wpinv_company' );
391
        return $company;
392
    }
393
    
394
    private function setup_vat_number() {
395
        $vat_number = $this->get_meta( '_wpinv_vat_number' );
396
        return $vat_number;
397
    }
398
    
399
    private function setup_vat_rate() {
400
        $vat_rate = $this->get_meta( '_wpinv_vat_rate' );
401
        return $vat_rate;
402
    }
403
    
404
    private function setup_adddress_confirmed() {
405
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed' );
406
        return $adddress_confirmed;
407
    }
408
    
409
    private function setup_phone() {
410
        $phone = $this->get_meta( '_wpinv_phone' );
411
        return $phone;
412
    }
413
    
414
    private function setup_address() {
415
        $address = $this->get_meta( '_wpinv_address', true );
416
        return $address;
417
    }
418
    
419
    private function setup_city() {
420
        $city = $this->get_meta( '_wpinv_city', true );
421
        return $city;
422
    }
423
    
424
    private function setup_country() {
425
        $country = $this->get_meta( '_wpinv_country', true );
426
        return $country;
427
    }
428
    
429
    private function setup_state() {
430
        $state = $this->get_meta( '_wpinv_state', true );
431
        return $state;
432
    }
433
    
434
    private function setup_zip() {
435
        $zip = $this->get_meta( '_wpinv_zip', true );
436
        return $zip;
437
    }
438
439
    private function setup_user_info() {
440
        $defaults = array(
441
            'user_id'        => $this->user_id,
442
            'first_name'     => $this->first_name,
443
            'last_name'      => $this->last_name,
444
            'email'          => get_the_author_meta( 'email', $this->user_id ),
445
            'phone'          => $this->phone,
446
            'address'        => $this->address,
447
            'city'           => $this->city,
448
            'country'        => $this->country,
449
            'state'          => $this->state,
450
            'zip'            => $this->zip,
451
            'company'        => $this->company,
452
            'vat_number'     => $this->vat_number,
453
            'vat_rate'       => $this->vat_rate,
454
            'adddress_confirmed' => $this->adddress_confirmed,
455
            'discount'       => $this->discounts,
456
        );
457
        
458
        $user_info = array();
459
        if ( isset( $this->payment_meta['user_info'] ) ) {
460
            $user_info = maybe_unserialize( $this->payment_meta['user_info'] );
461
            
462
            if ( !empty( $user_info ) && isset( $user_info['user_id'] ) && $post = get_post( $this->ID ) ) {
463
                $this->user_id = $post->post_author;
464
                $this->email = get_the_author_meta( 'email', $this->user_id );
465
                
466
                $user_info['user_id'] = $this->user_id;
467
                $user_info['email'] = $this->email;
468
                $this->payment_meta['user_id'] = $this->user_id;
469
                $this->payment_meta['email'] = $this->email;
470
            }
471
        }
472
        
473
        $user_info    = wp_parse_args( $user_info, $defaults );
474
        
475
        // Get the user, but only if it's been created
476
        $user = get_userdata( $this->user_id );
477
        
478
        if ( !empty( $user ) && $user->ID > 0 ) {
479
            if ( empty( $user_info ) ) {
480
                $user_info = array(
481
                    'user_id'    => $user->ID,
482
                    'first_name' => $user->first_name,
483
                    'last_name'  => $user->last_name,
484
                    'email'      => $user->user_email,
485
                    'discount'   => '',
486
                );
487
            } else {
488
                foreach ( $user_info as $key => $value ) {
489
                    if ( ! empty( $value ) ) {
490
                        continue;
491
                    }
492
493
                    switch( $key ) {
494
                        case 'user_id':
495
                            $user_info[ $key ] = $user->ID;
496
                            break;
497
                        case 'first_name':
498
                            $user_info[ $key ] = $user->first_name;
499
                            break;
500
                        case 'last_name':
501
                            $user_info[ $key ] = $user->last_name;
502
                            break;
503
                        case 'email':
504
                            $user_info[ $key ] = $user->user_email;
505
                            break;
506
                    }
507
                }
508
            }
509
        }
510
511
        return $user_info;
512
    }
513
514
    private function setup_invoice_key() {
515
        $key = $this->get_meta( '_wpinv_key', true );
516
        
517
        return $key;
518
    }
519
520
    private function setup_invoice_number() {
521
        $number = $this->get_meta( '_wpinv_number', true );
522
523
        if ( !$number ) {
524
            $number = $this->ID;
525
526
            if ( $this->status == 'auto-draft' ) {
527
                if ( wpinv_get_option( 'sequential_invoice_number' ) ) {
528
                    $next_number = wpinv_get_next_invoice_number();
529
                    $number      = $next_number;
530
                }
531
            }
532
            
533
            $number = wpinv_format_invoice_number( $number );
534
        }
535
536
        return $number;
537
    }
538
    
539
    private function insert_invoice() {
540
        global $wpdb;
541
542
        $invoice_number = $this->ID;
543
        if ( $number = $this->get_meta( '_wpinv_number', true ) ) {
544
            $invoice_number = $number;
545
        }
546
547 View Code Duplication
        if ( empty( $this->key ) ) {
548
            $this->key = self::generate_key();
549
            $this->pending['key'] = $this->key;
550
        }
551
552
        if ( empty( $this->ip ) ) {
553
            $this->ip = wpinv_get_ip();
554
            $this->pending['ip'] = $this->ip;
555
        }
556
        
557
        $payment_data = array(
558
            'price'        => $this->total,
559
            'date'         => $this->date,
560
            'user_email'   => $this->email,
561
            'invoice_key'  => $this->key,
562
            'currency'     => $this->currency,
563
            'items'        => $this->items,
564
            'user_info' => array(
565
                'user_id'    => $this->user_id,
566
                'email'      => $this->email,
567
                'first_name' => $this->first_name,
568
                'last_name'  => $this->last_name,
569
                'address'    => $this->address,
570
                'phone'      => $this->phone,
571
                'city'       => $this->city,
572
                'country'    => $this->country,
573
                'state'      => $this->state,
574
                'zip'        => $this->zip,
575
                'company'    => $this->company,
576
                'vat_number' => $this->vat_number,
577
                'discount'   => $this->discounts,
578
            ),
579
            'cart_details' => $this->cart_details,
580
            'status'       => $this->status,
581
            'fees'         => $this->fees,
582
        );
583
584
        $post_data = array(
585
                        'post_title'    => $invoice_number,
586
                        'post_status'   => $this->status,
587
                        'post_author'   => $this->user_id,
588
                        'post_type'     => $this->post_type,
589
                        'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
590
                        'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
591
                        'post_parent'   => $this->parent_invoice,
592
                    );
593
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
594
595
        // Create a blank invoice
596
        if ( !empty( $this->ID ) ) {
597
            $args['ID']         = $this->ID;
598
599
            $invoice_id = wp_update_post( $args, true );
600
        } else {
601
            $invoice_id = wp_insert_post( $args, true );
602
        }
603
604
        if ( is_wp_error( $invoice_id ) ) {
605
            return false;
606
        }
607
608
        if ( !empty( $invoice_id ) ) {
609
            $this->ID  = $invoice_id;
610
            $this->_ID = $invoice_id;
0 ignored issues
show
Bug introduced by
The property _ID does not exist. Did you maybe forget to declare it?

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

class MyClass { }

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

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

class MyClass {
    public $foo;
}

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