Passed
Push — master ( 0a9164...47344e )
by Brian
20:43
created

WPInv_Invoice::setup_is_taxable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
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 $disable_taxes = 0;
31
    public $tax = 0;
32
    public $fees = array();
33
    public $fees_total = 0;
34
    public $discounts = '';
35
    public $discount = 0;
36
    public $discount_code = 0;
37
    public $date = '';
38
    public $due_date = '';
39
    public $completed_date = '';
40
    public $status      = 'wpi-pending';
41
    public $post_status = 'wpi-pending';
42
    public $old_status = '';
43
    public $status_nicename = '';
44
    public $user_id = 0;
45
    public $first_name = '';
46
    public $last_name = '';
47
    public $email = '';
48
    public $phone = '';
49
    public $address = '';
50
    public $city = '';
51
    public $country = '';
52
    public $state = '';
53
    public $zip = '';
54
    public $transaction_id = '';
55
    public $ip = '';
56
    public $gateway = '';
57
    public $gateway_title = '';
58
    public $currency = '';
59
    public $cart_details = array();
60
    
61
    public $company = '';
62
    public $vat_number = '';
63
    public $vat_rate = '';
64
    public $adddress_confirmed = '';
65
    
66
    public $full_name = '';
67
    public $parent_invoice = 0;
68
    
69
    public function __construct( $invoice_id = false ) {
70
        if( empty( $invoice_id ) ) {
71
            return false;
72
        }
73
74
        $this->setup_invoice( $invoice_id );
75
    }
76
77
    public function get( $key ) {
78
        if ( method_exists( $this, 'get_' . $key ) ) {
79
            $value = call_user_func( array( $this, 'get_' . $key ) );
80
        } else {
81
            $value = $this->$key;
82
        }
83
84
        return $value;
85
    }
86
87
    public function set( $key, $value ) {
88
        $ignore = array( 'items', 'cart_details', 'fees', '_ID' );
89
90
        if ( $key === 'status' ) {
91
            $this->old_status = $this->status;
92
        }
93
94
        if ( ! in_array( $key, $ignore ) ) {
95
            $this->pending[ $key ] = $value;
96
        }
97
98
        if( '_ID' !== $key ) {
99
            $this->$key = $value;
100
        }
101
    }
102
103
    public function _isset( $name ) {
104
        if ( property_exists( $this, $name) ) {
105
            return false === empty( $this->$name );
106
        } else {
107
            return null;
108
        }
109
    }
110
111
    private function setup_invoice( $invoice_id ) {
112
        $this->pending = array();
113
114
        if ( empty( $invoice_id ) ) {
115
            return false;
116
        }
117
118
        $invoice = get_post( $invoice_id );
119
120
        if( !$invoice || is_wp_error( $invoice ) ) {
0 ignored issues
show
introduced by
$invoice is of type WP_Post, thus it always evaluated to true.
Loading history...
121
            return false;
122
        }
123
124
        if( !('wpi_invoice' == $invoice->post_type OR 'wpi_quote' == $invoice->post_type) ) {
125
            return false;
126
        }
127
128
        do_action( 'wpinv_pre_setup_invoice', $this, $invoice_id );
129
        
130
        // Primary Identifier
131
        $this->ID              = absint( $invoice_id );
132
        $this->post_type       = $invoice->post_type;
133
        
134
        // We have a payment, get the generic payment_meta item to reduce calls to it
135
        $this->payment_meta    = $this->get_meta();
136
        $this->date            = $invoice->post_date;
137
        $this->due_date        = $this->setup_due_date();
138
        $this->completed_date  = $this->setup_completed_date();
139
        $this->status          = $invoice->post_status;
140
        $this->post_status     = $this->status;
141
        $this->mode            = $this->setup_mode();
142
        $this->parent_invoice  = $invoice->post_parent;
143
        $this->post_name       = $this->setup_post_name( $invoice );
0 ignored issues
show
Bug Best Practice introduced by
The property post_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
Bug introduced by
Are you sure the assignment to $this->post_name is correct as $this->setup_post_name($invoice) targeting 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...
144
        $this->status_nicename = $this->setup_status_nicename($invoice->post_status);
145
146
        // Items
147
        $this->fees            = $this->setup_fees();
148
        $this->cart_details    = $this->setup_cart_details();
149
        $this->items           = $this->setup_items();
150
151
        // Currency Based
152
        $this->total           = $this->setup_total();
153
        $this->disable_taxes   = $this->setup_is_taxable();
154
        $this->tax             = $this->setup_tax();
155
        $this->fees_total      = $this->get_fees_total();
156
        $this->subtotal        = $this->setup_subtotal();
157
        $this->currency        = $this->setup_currency();
158
        
159
        // Gateway based
160
        $this->gateway         = $this->setup_gateway();
161
        $this->gateway_title   = $this->setup_gateway_title();
162
        $this->transaction_id  = $this->setup_transaction_id();
163
        
164
        // User based
165
        $this->ip              = $this->setup_ip();
166
        $this->user_id         = !empty( $invoice->post_author ) ? $invoice->post_author : get_current_user_id();///$this->setup_user_id();
167
        $this->email           = get_the_author_meta( 'email', $this->user_id );
0 ignored issues
show
Bug introduced by
It seems like $this->user_id can also be of type string; however, parameter $user_id of get_the_author_meta() does only seem to accept false|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
        $this->email           = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
168
        
169
        $this->user_info       = $this->setup_user_info();
170
                
171
        $this->first_name      = $this->user_info['first_name'];
172
        $this->last_name       = $this->user_info['last_name'];
173
        $this->company         = $this->user_info['company'];
174
        $this->vat_number      = $this->user_info['vat_number'];
175
        $this->vat_rate        = $this->user_info['vat_rate'];
176
        $this->adddress_confirmed  = $this->user_info['adddress_confirmed'];
177
        $this->address         = $this->user_info['address'];
178
        $this->city            = $this->user_info['city'];
179
        $this->country         = $this->user_info['country'];
180
        $this->state           = $this->user_info['state'];
181
        $this->zip             = $this->user_info['zip'];
182
        $this->phone           = $this->user_info['phone'];
183
        
184
        $this->discounts       = $this->user_info['discount'];
185
            $this->discount        = $this->setup_discount();
186
            $this->discount_code   = $this->setup_discount_code();
187
188
        // Other Identifiers
189
        $this->key             = $this->setup_invoice_key();
190
        $this->number          = $this->setup_invoice_number();
191
        $this->title           = !empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
192
        
193
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
194
        
195
        // Allow extensions to add items to this object via hook
196
        do_action( 'wpinv_setup_invoice', $this, $invoice_id );
197
198
        return true;
199
    }
200
201
    private function setup_status_nicename( $status ) {
202
        $all_invoice_statuses  = wpinv_get_invoice_statuses( true, true, $this );
203
204
        if ( $this->is_quote() && class_exists( 'Wpinv_Quotes_Shared' ) ) {
205
            $all_invoice_statuses  = Wpinv_Quotes_Shared::wpinv_get_quote_statuses();
0 ignored issues
show
Bug introduced by
The type Wpinv_Quotes_Shared was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
206
        }
207
        $status   = isset( $all_invoice_statuses[$status] ) ? $all_invoice_statuses[$status] : __( $status, 'invoicing' );
208
209
        return apply_filters( 'setup_status_nicename', $status );
210
    }
211
212
    private function setup_post_name( $post = NULL ) {
213
        global $wpdb;
214
        
215
        $post_name = '';
216
        
217
        if ( !empty( $post ) ) {
218
            if( !empty( $post->post_name ) ) {
219
                $post_name = $post->post_name;
220
            } else if ( !empty( $post->ID ) ) {
221
                $post_name = wpinv_generate_post_name( $post->ID );
222
223
                $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
224
            }
225
        }
226
227
        $this->post_name = $post_name;
0 ignored issues
show
Bug Best Practice introduced by
The property post_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
228
    }
229
    
230
    private function setup_due_date() {
231
        $due_date = $this->get_meta( '_wpinv_due_date' );
232
        
233
        if ( empty( $due_date ) ) {
234
            $overdue_time = strtotime( $this->date ) + ( DAY_IN_SECONDS * absint( wpinv_get_option( 'overdue_days' ) ) );
235
            $due_date = date_i18n( 'Y-m-d', $overdue_time );
236
        } else if ( $due_date == 'none' ) {
237
            $due_date = '';
238
        }
239
        
240
        return $due_date;
241
    }
242
    
243
    private function setup_completed_date() {
244
        $invoice = get_post( $this->ID );
245
246
        if ( 'wpi-pending' == $invoice->post_status || 'preapproved' == $invoice->post_status ) {
247
            return false; // This invoice was never paid
248
        }
249
250
        $date = ( $date = $this->get_meta( '_wpinv_completed_date', true ) ) ? $date : $invoice->modified_date;
251
252
        return $date;
253
    }
254
    
255
    private function setup_cart_details() {
256
        $cart_details = isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array();
257
        return $cart_details;
258
    }
259
    
260
    public function array_convert() {
261
        return get_object_vars( $this );
262
    }
263
    
264
    private function setup_items() {
265
        $items = isset( $this->payment_meta['items'] ) ? maybe_unserialize( $this->payment_meta['items'] ) : array();
266
        return $items;
267
    }
268
    
269
    private function setup_fees() {
270
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
271
        return $payment_fees;
272
    }
273
        
274
    private function setup_currency() {
275
        $currency = isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'wpinv_currency_default', wpinv_get_currency(), $this );
276
        return $currency;
277
    }
278
    
279
    private function setup_discount() {
280
        //$discount = $this->get_meta( '_wpinv_discount', true );
281
        $discount = (float)$this->subtotal - ( (float)$this->total - (float)$this->tax - (float)$this->fees_total );
282
        if ( $discount < 0 ) {
283
            $discount = 0;
284
        }
285
        $discount = wpinv_round_amount( $discount );
286
        
287
        return $discount;
288
    }
289
    
290
    private function setup_discount_code() {
291
        $discount_code = !empty( $this->discounts ) ? $this->discounts : $this->get_meta( '_wpinv_discount_code', true );
292
        return $discount_code;
293
    }
294
    
295
    private function setup_tax() {
296
297
        $tax = $this->get_meta( '_wpinv_tax', true );
298
299
        // We don't have tax as it's own meta and no meta was passed
300
        if ( '' === $tax ) {            
301
            $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0;
302
        }
303
        
304
        if ( $tax < 0 || ! $this->is_taxable() ) {
305
            $tax = 0;
306
        }
307
308
        return $tax;
309
    }
310
311
    /**
312
     * If taxes are enabled, allow users to enable/disable taxes per invoice.
313
     */
314
    private function setup_is_taxable() {
315
        return (int) $this->get_meta( '_wpinv_disable_taxes', true );
316
    }
317
318
    private function setup_subtotal() {
319
        $subtotal     = 0;
320
        $cart_details = $this->cart_details;
321
322
        if ( is_array( $cart_details ) ) {
0 ignored issues
show
introduced by
The condition is_array($cart_details) is always true.
Loading history...
323
            foreach ( $cart_details as $item ) {
324
                if ( isset( $item['subtotal'] ) ) {
325
                    $subtotal += $item['subtotal'];
326
                }
327
            }
328
        } else {
329
            $subtotal  = $this->total;
330
            $tax       = wpinv_use_taxes() ? $this->tax : 0;
331
            $subtotal -= $tax;
332
        }
333
334
        return $subtotal;
335
    }
336
337
    private function setup_discounts() {
338
        $discounts = ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array();
339
        return $discounts;
340
    }
341
    
342
    private function setup_total() {
343
        $amount = $this->get_meta( '_wpinv_total', true );
344
345
        if ( empty( $amount ) && '0.00' != $amount ) {
346
            $meta   = $this->get_meta( '_wpinv_payment_meta', true );
347
            $meta   = maybe_unserialize( $meta );
0 ignored issues
show
Bug introduced by
It seems like $meta can also be of type array and array; however, parameter $data of maybe_unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

347
            $meta   = maybe_unserialize( /** @scrutinizer ignore-type */ $meta );
Loading history...
348
349
            if ( isset( $meta['amount'] ) ) {
350
                $amount = $meta['amount'];
351
            }
352
        }
353
354
        if($amount < 0){
355
            $amount = 0;
356
        }
357
358
        return $amount;
359
    }
360
    
361
    private function setup_mode() {
362
        return $this->get_meta( '_wpinv_mode' );
363
    }
364
365
    private function setup_gateway() {
366
        $gateway = $this->get_meta( '_wpinv_gateway' );
367
        
368
        if ( empty( $gateway ) && 'publish' === $this->status ) {
369
            $gateway = 'manual';
370
        }
371
        
372
        return $gateway;
373
    }
374
375
    private function setup_gateway_title() {
376
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
377
        return $gateway_title;
378
    }
379
380
    private function setup_transaction_id() {
381
        $transaction_id = $this->get_meta( '_wpinv_transaction_id' );
382
383
        if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) {
384
            $gateway        = $this->gateway;
385
            $transaction_id = apply_filters( 'wpinv_get_invoice_transaction_id-' . $gateway, $this->ID );
386
        }
387
388
        return $transaction_id;
389
    }
390
391
    private function setup_ip() {
392
        $ip = $this->get_meta( '_wpinv_user_ip' );
393
        return $ip;
394
    }
395
396
    ///private function setup_user_id() {
397
        ///$user_id = $this->get_meta( '_wpinv_user_id' );
398
        ///return $user_id;
399
    ///}
400
        
401
    private function setup_first_name() {
402
        $first_name = $this->get_meta( '_wpinv_first_name' );
403
        return $first_name;
404
    }
405
    
406
    private function setup_last_name() {
407
        $last_name = $this->get_meta( '_wpinv_last_name' );
408
        return $last_name;
409
    }
410
    
411
    private function setup_company() {
412
        $company = $this->get_meta( '_wpinv_company' );
413
        return $company;
414
    }
415
    
416
    private function setup_vat_number() {
417
        $vat_number = $this->get_meta( '_wpinv_vat_number' );
418
        return $vat_number;
419
    }
420
    
421
    private function setup_vat_rate() {
422
        $vat_rate = $this->get_meta( '_wpinv_vat_rate' );
423
        return $vat_rate;
424
    }
425
    
426
    private function setup_adddress_confirmed() {
427
        $adddress_confirmed = $this->get_meta( '_wpinv_adddress_confirmed' );
428
        return $adddress_confirmed;
429
    }
430
    
431
    private function setup_phone() {
432
        $phone = $this->get_meta( '_wpinv_phone' );
433
        return $phone;
434
    }
435
    
436
    private function setup_address() {
437
        $address = $this->get_meta( '_wpinv_address', true );
438
        return $address;
439
    }
440
    
441
    private function setup_city() {
442
        $city = $this->get_meta( '_wpinv_city', true );
443
        return $city;
444
    }
445
    
446
    private function setup_country() {
447
        $country = $this->get_meta( '_wpinv_country', true );
448
        return $country;
449
    }
450
    
451
    private function setup_state() {
452
        $state = $this->get_meta( '_wpinv_state', true );
453
        return $state;
454
    }
455
    
456
    private function setup_zip() {
457
        $zip = $this->get_meta( '_wpinv_zip', true );
458
        return $zip;
459
    }
460
461
    private function setup_user_info() {
462
        $defaults = array(
463
            'user_id'        => $this->user_id,
464
            'first_name'     => $this->first_name,
465
            'last_name'      => $this->last_name,
466
            'email'          => get_the_author_meta( 'email', $this->user_id ),
467
            'phone'          => $this->phone,
468
            'address'        => $this->address,
469
            'city'           => $this->city,
470
            'country'        => $this->country,
471
            'state'          => $this->state,
472
            'zip'            => $this->zip,
473
            'company'        => $this->company,
474
            'vat_number'     => $this->vat_number,
475
            'vat_rate'       => $this->vat_rate,
476
            'adddress_confirmed' => $this->adddress_confirmed,
477
            'discount'       => $this->discounts,
478
        );
479
        
480
        $user_info = array();
481
        if ( isset( $this->payment_meta['user_info'] ) ) {
482
            $user_info = maybe_unserialize( $this->payment_meta['user_info'] );
483
            
484
            if ( !empty( $user_info ) && isset( $user_info['user_id'] ) && $post = get_post( $this->ID ) ) {
485
                $this->user_id = $post->post_author;
486
                $this->email = get_the_author_meta( 'email', $this->user_id );
0 ignored issues
show
Bug introduced by
$this->user_id of type string is incompatible with the type false|integer expected by parameter $user_id of get_the_author_meta(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

486
                $this->email = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
487
                
488
                $user_info['user_id'] = $this->user_id;
489
                $user_info['email'] = $this->email;
490
                $this->payment_meta['user_id'] = $this->user_id;
491
                $this->payment_meta['email'] = $this->email;
492
            }
493
        }
494
        
495
        $user_info    = wp_parse_args( $user_info, $defaults );
496
        
497
        // Get the user, but only if it's been created
498
        $user = get_userdata( $this->user_id );
499
        
500
        if ( !empty( $user ) && $user->ID > 0 ) {
501
            if ( empty( $user_info ) ) {
502
                $user_info = array(
503
                    'user_id'    => $user->ID,
504
                    'first_name' => $user->first_name,
505
                    'last_name'  => $user->last_name,
506
                    'email'      => $user->user_email,
507
                    'discount'   => '',
508
                );
509
            } else {
510
                foreach ( $user_info as $key => $value ) {
511
                    if ( ! empty( $value ) ) {
512
                        continue;
513
                    }
514
515
                    switch( $key ) {
516
                        case 'user_id':
517
                            $user_info[ $key ] = $user->ID;
518
                            break;
519
                        case 'first_name':
520
                            $user_info[ $key ] = $user->first_name;
521
                            break;
522
                        case 'last_name':
523
                            $user_info[ $key ] = $user->last_name;
524
                            break;
525
                        case 'email':
526
                            $user_info[ $key ] = $user->user_email;
527
                            break;
528
                    }
529
                }
530
            }
531
        }
532
533
        return $user_info;
534
    }
535
536
    private function setup_invoice_key() {
537
        $key = $this->get_meta( '_wpinv_key', true );
538
        
539
        return $key;
540
    }
541
542
    private function setup_invoice_number() {
543
        $number = $this->get_meta( '_wpinv_number', true );
544
545
        if ( !$number ) {
546
            $number = $this->ID;
547
548
            if ( $this->status == 'auto-draft' ) {
549
                if ( wpinv_sequential_number_active( $this->post_type ) ) {
550
                    $next_number = wpinv_get_next_invoice_number( $this->post_type );
551
                    $number      = $next_number;
552
                }
553
            }
554
            
555
            $number = wpinv_format_invoice_number( $number, $this->post_type );
556
        }
557
558
        return $number;
559
    }
560
    
561
    private function insert_invoice() {
562
        global $wpdb;
563
564
        if ( empty( $this->post_type ) ) {
565
            if ( !empty( $this->ID ) && $post_type = get_post_type( $this->ID ) ) {
566
                $this->post_type = $post_type;
567
            } else if ( !empty( $this->parent_invoice ) && $post_type = get_post_type( $this->parent_invoice ) ) {
568
                $this->post_type = $post_type;
569
            } else {
570
                $this->post_type = 'wpi_invoice';
571
            }
572
        }
573
574
        $invoice_number = $this->ID;
575
        if ( $number = $this->get_meta( '_wpinv_number', true ) ) {
576
            $invoice_number = $number;
577
        }
578
579
        if ( empty( $this->key ) ) {
580
            $this->key = self::generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_Invoice::generate_key() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

580
            /** @scrutinizer ignore-call */ 
581
            $this->key = self::generate_key();
Loading history...
581
            $this->pending['key'] = $this->key;
582
        }
583
584
        if ( empty( $this->ip ) ) {
585
            $this->ip = wpinv_get_ip();
586
            $this->pending['ip'] = $this->ip;
587
        }
588
        
589
        $payment_data = array(
590
            'price'        => $this->total,
591
            'date'         => $this->date,
592
            'user_email'   => $this->email,
593
            'invoice_key'  => $this->key,
594
            'currency'     => $this->currency,
595
            'items'        => $this->items,
596
            'user_info' => array(
597
                'user_id'    => $this->user_id,
598
                'email'      => $this->email,
599
                'first_name' => $this->first_name,
600
                'last_name'  => $this->last_name,
601
                'address'    => $this->address,
602
                'phone'      => $this->phone,
603
                'city'       => $this->city,
604
                'country'    => $this->country,
605
                'state'      => $this->state,
606
                'zip'        => $this->zip,
607
                'company'    => $this->company,
608
                'vat_number' => $this->vat_number,
609
                'discount'   => $this->discounts,
610
            ),
611
            'cart_details' => $this->cart_details,
612
            'status'       => $this->status,
613
            'fees'         => $this->fees,
614
        );
615
616
        $post_data = array(
617
                        'post_title'    => $invoice_number,
618
                        'post_status'   => $this->status,
619
                        'post_author'   => $this->user_id,
620
                        'post_type'     => $this->post_type,
621
                        'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
622
                        'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
623
                        'post_parent'   => $this->parent_invoice,
624
                    );
625
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
626
627
        // Create a blank invoice
628
        if ( !empty( $this->ID ) ) {
629
            $args['ID']         = $this->ID;
630
631
            $invoice_id = wp_update_post( $args, true );
632
        } else {
633
            $invoice_id = wp_insert_post( $args, true );
634
        }
635
636
        if ( is_wp_error( $invoice_id ) ) {
637
            return false;
638
        }
639
640
        if ( !empty( $invoice_id ) ) {
641
            $this->ID  = $invoice_id;
642
            $this->_ID = $invoice_id;
0 ignored issues
show
Bug Best Practice introduced by
The property _ID does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
643
644
            $this->payment_meta = apply_filters( 'wpinv_payment_meta', $this->payment_meta, $payment_data );
645
            if ( ! empty( $this->payment_meta['fees'] ) ) {
646
                $this->fees = array_merge( $this->fees, $this->payment_meta['fees'] );
647
                foreach( $this->fees as $fee ) {
648
                    $this->increase_fees( $fee['amount'] );
649
                }
650
            }
651
652
            $this->update_meta( '_wpinv_payment_meta', $this->payment_meta );    
653
            $this->new = true;
654
        }
655
656
        return $this->ID;
657
    }
658
659
    public function save( $setup = false ) {
660
        global $wpi_session;
661
        
662
        $saved = false;
663
        if ( empty( $this->items ) ) {
664
            return $saved; // Don't save empty invoice.
665
        }
666
        
667
        if ( empty( $this->key ) ) {
668
            $this->key = self::generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_Invoice::generate_key() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

668
            /** @scrutinizer ignore-call */ 
669
            $this->key = self::generate_key();
Loading history...
669
            $this->pending['key'] = $this->key;
670
        }
671
        
672
        if ( empty( $this->ID ) ) {
673
            $invoice_id = $this->insert_invoice();
674
675
            if ( false === $invoice_id ) {
676
                $saved = false;
677
            } else {
678
                $this->ID = $invoice_id;
679
            }
680
        }
681
682
        // If we have something pending, let's save it
683
        if ( !empty( $this->pending ) ) {
684
            $total_increase = 0;
685
            $total_decrease = 0;
686
687
            foreach ( $this->pending as $key => $value ) {
688
                switch( $key ) {
689
                    case 'items':
690
                        // Update totals for pending items
691
                        foreach ( $this->pending[ $key ] as $item ) {
692
                            switch( $item['action'] ) {
693
                                case 'add':
694
                                    $price = $item['price'];
695
                                    $taxes = $item['tax'];
0 ignored issues
show
Unused Code introduced by
The assignment to $taxes is dead and can be removed.
Loading history...
696
697
                                    if ( 'publish' === $this->status ) {
698
                                        $total_increase += $price;
699
                                    }
700
                                    break;
701
702
                                case 'remove':
703
                                    if ( 'publish' === $this->status ) {
704
                                        $total_decrease += $item['price'];
705
                                    }
706
                                    break;
707
                            }
708
                        }
709
                        break;
710
                    case 'fees':
711
                        if ( 'publish' !== $this->status ) {
712
                            break;
713
                        }
714
715
                        if ( empty( $this->pending[ $key ] ) ) {
716
                            break;
717
                        }
718
719
                        foreach ( $this->pending[ $key ] as $fee ) {
720
                            switch( $fee['action'] ) {
721
                                case 'add':
722
                                    $total_increase += $fee['amount'];
723
                                    break;
724
725
                                case 'remove':
726
                                    $total_decrease += $fee['amount'];
727
                                    break;
728
                            }
729
                        }
730
                        break;
731
                    case 'status':
732
                        $this->update_status( $this->status );
733
                        break;
734
                    case 'gateway':
735
                        $this->update_meta( '_wpinv_gateway', $this->gateway );
736
                        break;
737
                    case 'mode':
738
                        $this->update_meta( '_wpinv_mode', $this->mode );
739
                        break;
740
                    case 'transaction_id':
741
                        $this->update_meta( '_wpinv_transaction_id', $this->transaction_id );
742
                        break;
743
                    case 'ip':
744
                        $this->update_meta( '_wpinv_user_ip', $this->ip );
745
                        break;
746
                    ///case 'user_id':
747
                        ///$this->update_meta( '_wpinv_user_id', $this->user_id );
748
                        ///$this->user_info['user_id'] = $this->user_id;
749
                        ///break;
750
                    case 'first_name':
751
                        $this->update_meta( '_wpinv_first_name', $this->first_name );
752
                        $this->user_info['first_name'] = $this->first_name;
753
                        break;
754
                    case 'last_name':
755
                        $this->update_meta( '_wpinv_last_name', $this->last_name );
756
                        $this->user_info['last_name'] = $this->last_name;
757
                        break;
758
                    case 'phone':
759
                        $this->update_meta( '_wpinv_phone', $this->phone );
760
                        $this->user_info['phone'] = $this->phone;
761
                        break;
762
                    case 'address':
763
                        $this->update_meta( '_wpinv_address', $this->address );
764
                        $this->user_info['address'] = $this->address;
765
                        break;
766
                    case 'city':
767
                        $this->update_meta( '_wpinv_city', $this->city );
768
                        $this->user_info['city'] = $this->city;
769
                        break;
770
                    case 'country':
771
                        $this->update_meta( '_wpinv_country', $this->country );
772
                        $this->user_info['country'] = $this->country;
773
                        break;
774
                    case 'state':
775
                        $this->update_meta( '_wpinv_state', $this->state );
776
                        $this->user_info['state'] = $this->state;
777
                        break;
778
                    case 'zip':
779
                        $this->update_meta( '_wpinv_zip', $this->zip );
780
                        $this->user_info['zip'] = $this->zip;
781
                        break;
782
                    case 'company':
783
                        $this->update_meta( '_wpinv_company', $this->company );
784
                        $this->user_info['company'] = $this->company;
785
                        break;
786
                    case 'vat_number':
787
                        $this->update_meta( '_wpinv_vat_number', $this->vat_number );
788
                        $this->user_info['vat_number'] = $this->vat_number;
789
                        
790
                        $vat_info = $wpi_session->get( 'user_vat_data' );
791
                        if ( $this->vat_number && !empty( $vat_info ) && isset( $vat_info['number'] ) && isset( $vat_info['valid'] ) && $vat_info['number'] == $this->vat_number ) {
792
                            $adddress_confirmed = isset( $vat_info['adddress_confirmed'] ) ? $vat_info['adddress_confirmed'] : false;
793
                            $this->update_meta( '_wpinv_adddress_confirmed', (bool)$adddress_confirmed );
794
                            $this->user_info['adddress_confirmed'] = (bool)$adddress_confirmed;
795
                        }
796
    
797
                        break;
798
                    case 'vat_rate':
799
                        $this->update_meta( '_wpinv_vat_rate', $this->vat_rate );
800
                        $this->user_info['vat_rate'] = $this->vat_rate;
801
                        break;
802
                    case 'adddress_confirmed':
803
                        $this->update_meta( '_wpinv_adddress_confirmed', $this->adddress_confirmed );
804
                        $this->user_info['adddress_confirmed'] = $this->adddress_confirmed;
805
                        break;
806
                    
807
                    case 'key':
808
                        $this->update_meta( '_wpinv_key', $this->key );
809
                        break;
810
                    case 'disable_taxes':
811
                        $this->update_meta( '_wpinv_disable_taxes', $this->disable_taxes );
812
                        break;
813
                    case 'date':
814
                        $args = array(
815
                            'ID'        => $this->ID,
816
                            'post_date' => $this->date,
817
                            'edit_date' => true,
818
                        );
819
820
                        wp_update_post( $args );
821
                        break;
822
                    case 'due_date':
823
                        if ( empty( $this->due_date ) ) {
824
                            $this->due_date = 'none';
825
                        }
826
                        
827
                        $this->update_meta( '_wpinv_due_date', $this->due_date );
828
                        break;
829
                    case 'completed_date':
830
                        $this->update_meta( '_wpinv_completed_date', $this->completed_date );
831
                        break;
832
                    case 'discounts':
833
                        if ( ! is_array( $this->discounts ) ) {
834
                            $this->discounts = explode( ',', $this->discounts );
835
                        }
836
837
                        $this->user_info['discount'] = implode( ',', $this->discounts );
838
                        break;
839
                    case 'discount':
840
                        $this->update_meta( '_wpinv_discount', wpinv_round_amount( $this->discount ) );
841
                        break;
842
                    case 'discount_code':
843
                        $this->update_meta( '_wpinv_discount_code', $this->discount_code );
844
                        break;
845
                    case 'parent_invoice':
846
                        $args = array(
847
                            'ID'          => $this->ID,
848
                            'post_parent' => $this->parent_invoice,
849
                        );
850
                        wp_update_post( $args );
851
                        break;
852
                    default:
853
                        do_action( 'wpinv_save', $this, $key );
854
                        break;
855
                }
856
            }
857
858
            $this->update_meta( '_wpinv_subtotal', wpinv_round_amount( $this->subtotal ) );
859
            $this->update_meta( '_wpinv_total', wpinv_round_amount( $this->total ) );
860
            $this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
861
            
862
            $this->items    = array_values( $this->items );
863
            
864
            $new_meta = array(
865
                'items'         => $this->items,
866
                'cart_details'  => $this->cart_details,
867
                'fees'          => $this->fees,
868
                'currency'      => $this->currency,
869
                'user_info'     => $this->user_info,
870
            );
871
            
872
            $meta        = $this->get_meta();
873
            $merged_meta = array_merge( $meta, $new_meta );
874
875
            // Only save the payment meta if it's changed
876
            if ( md5( serialize( $meta ) ) !== md5( serialize( $merged_meta) ) ) {
877
                $updated     = $this->update_meta( '_wpinv_payment_meta', $merged_meta );
878
                if ( false !== $updated ) {
879
                    $saved = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $saved is dead and can be removed.
Loading history...
880
                }
881
            }
882
883
            $this->pending = array();
884
            $saved         = true;
885
        } else {
886
            $this->update_meta( '_wpinv_subtotal', wpinv_round_amount( $this->subtotal ) );
887
            $this->update_meta( '_wpinv_total', wpinv_round_amount( $this->total ) );
888
            $this->update_meta( '_wpinv_tax', wpinv_round_amount( $this->tax ) );
889
        }
890
        
891
        do_action( 'wpinv_invoice_save', $this, $saved );
892
893
        if ( true === $saved || $setup ) {
894
            $this->setup_invoice( $this->ID );
895
        }
896
        
897
        $this->refresh_item_ids();
898
        
899
        return $saved;
900
    }
901
    
902
    public function add_fee( $args, $global = true ) {
903
        $default_args = array(
904
            'label'       => '',
905
            'amount'      => 0,
906
            'type'        => 'fee',
907
            'id'          => '',
908
            'no_tax'      => false,
909
            'item_id'     => 0,
910
        );
911
912
        $fee = wp_parse_args( $args, $default_args );
913
        
914
        if ( empty( $fee['label'] ) ) {
915
            return false;
916
        }
917
        
918
        $fee['id']  = sanitize_title( $fee['label'] );
919
        
920
        $this->fees[]               = $fee;
921
        
922
        $added_fee               = $fee;
923
        $added_fee['action']     = 'add';
924
        $this->pending['fees'][] = $added_fee;
925
        reset( $this->fees );
926
927
        $this->increase_fees( $fee['amount'] );
928
        return true;
929
    }
930
931
    public function remove_fee( $key ) {
932
        $removed = false;
933
934
        if ( is_numeric( $key ) ) {
935
            $removed = $this->remove_fee_by( 'index', $key );
936
        }
937
938
        return $removed;
939
    }
940
941
    public function remove_fee_by( $key, $value, $global = false ) {
942
        $allowed_fee_keys = apply_filters( 'wpinv_fee_keys', array(
943
            'index', 'label', 'amount', 'type',
944
        ) );
945
946
        if ( ! in_array( $key, $allowed_fee_keys ) ) {
947
            return false;
948
        }
949
950
        $removed = false;
951
        if ( 'index' === $key && array_key_exists( $value, $this->fees ) ) {
952
            $removed_fee             = $this->fees[ $value ];
953
            $removed_fee['action']   = 'remove';
954
            $this->pending['fees'][] = $removed_fee;
955
956
            $this->decrease_fees( $removed_fee['amount'] );
957
958
            unset( $this->fees[ $value ] );
959
            $removed = true;
960
        } else if ( 'index' !== $key ) {
961
            foreach ( $this->fees as $index => $fee ) {
962
                if ( isset( $fee[ $key ] ) && $fee[ $key ] == $value ) {
963
                    $removed_fee             = $fee;
964
                    $removed_fee['action']   = 'remove';
965
                    $this->pending['fees'][] = $removed_fee;
966
967
                    $this->decrease_fees( $removed_fee['amount'] );
968
969
                    unset( $this->fees[ $index ] );
970
                    $removed = true;
971
972
                    if ( false === $global ) {
973
                        break;
974
                    }
975
                }
976
            }
977
        }
978
979
        if ( true === $removed ) {
980
            $this->fees = array_values( $this->fees );
981
        }
982
983
        return $removed;
984
    }
985
986
    
987
988
    public function add_note( $note = '', $customer_type = false, $added_by_user = false, $system = false ) {
989
        // Bail if no note specified
990
        if( !$note ) {
991
            return false;
992
        }
993
994
        if ( empty( $this->ID ) )
995
            return false;
996
        
997
        if ( ( ( is_user_logged_in() && wpinv_current_user_can_manage_invoicing() ) || $added_by_user ) && !$system ) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (is_user_logged_in() && ...d_by_user) && ! $system, Probably Intended Meaning: is_user_logged_in() && w...d_by_user && ! $system)
Loading history...
998
            $user                 = get_user_by( 'id', get_current_user_id() );
999
            $comment_author       = $user->display_name;
1000
            $comment_author_email = $user->user_email;
1001
        } else {
1002
            $comment_author       = 'System';
1003
            $comment_author_email = 'system@';
1004
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
1005
            $comment_author_email = sanitize_email( $comment_author_email );
1006
        }
1007
1008
        do_action( 'wpinv_pre_insert_invoice_note', $this->ID, $note, $customer_type );
1009
1010
        $note_id = wp_insert_comment( wp_filter_comment( array(
1011
            'comment_post_ID'      => $this->ID,
1012
            'comment_content'      => $note,
1013
            'comment_agent'        => 'WPInvoicing',
1014
            'user_id'              => is_admin() ? get_current_user_id() : 0,
1015
            'comment_date'         => current_time( 'mysql' ),
1016
            'comment_date_gmt'     => current_time( 'mysql', 1 ),
1017
            'comment_approved'     => 1,
1018
            'comment_parent'       => 0,
1019
            'comment_author'       => $comment_author,
1020
            'comment_author_IP'    => wpinv_get_ip(),
1021
            'comment_author_url'   => '',
1022
            'comment_author_email' => $comment_author_email,
1023
            'comment_type'         => 'wpinv_note'
1024
        ) ) );
1025
1026
        do_action( 'wpinv_insert_payment_note', $note_id, $this->ID, $note );
1027
        
1028
        if ( $customer_type ) {
1029
            add_comment_meta( $note_id, '_wpi_customer_note', 1 );
0 ignored issues
show
Bug introduced by
$note_id of type false is incompatible with the type integer expected by parameter $comment_id of add_comment_meta(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1029
            add_comment_meta( /** @scrutinizer ignore-type */ $note_id, '_wpi_customer_note', 1 );
Loading history...
1030
1031
            do_action( 'wpinv_new_customer_note', array( 'invoice_id' => $this->ID, 'user_note' => $note ) );
1032
        }
1033
1034
        return $note_id;
1035
    }
1036
1037
    private function increase_subtotal( $amount = 0.00 ) {
1038
        $amount          = (float) $amount;
1039
        $this->subtotal += $amount;
1040
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
1041
1042
        $this->recalculate_total();
1043
    }
1044
1045
    private function decrease_subtotal( $amount = 0.00 ) {
1046
        $amount          = (float) $amount;
1047
        $this->subtotal -= $amount;
1048
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
1049
1050
        if ( $this->subtotal < 0 ) {
1051
            $this->subtotal = 0;
1052
        }
1053
1054
        $this->recalculate_total();
1055
    }
1056
1057
    private function increase_fees( $amount = 0.00 ) {
1058
        $amount            = (float)$amount;
1059
        $this->fees_total += $amount;
1060
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
1061
1062
        $this->recalculate_total();
1063
    }
1064
1065
    private function decrease_fees( $amount = 0.00 ) {
1066
        $amount            = (float) $amount;
1067
        $this->fees_total -= $amount;
1068
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
1069
1070
        if ( $this->fees_total < 0 ) {
1071
            $this->fees_total = 0;
1072
        }
1073
1074
        $this->recalculate_total();
1075
    }
1076
1077
    public function recalculate_total() {
1078
        global $wpi_nosave;
1079
        
1080
        $this->total = $this->subtotal + $this->tax + $this->fees_total;
1081
        $this->total = wpinv_round_amount( $this->total );
1082
        
1083
        do_action( 'wpinv_invoice_recalculate_total', $this, $wpi_nosave );
1084
    }
1085
    
1086
    public function increase_tax( $amount = 0.00 ) {
1087
        $amount       = (float) $amount;
1088
        $this->tax   += $amount;
1089
1090
        $this->recalculate_total();
1091
    }
1092
1093
    public function decrease_tax( $amount = 0.00 ) {
1094
        $amount     = (float) $amount;
1095
        $this->tax -= $amount;
1096
1097
        if ( $this->tax < 0 ) {
1098
            $this->tax = 0;
1099
        }
1100
1101
        $this->recalculate_total();
1102
    }
1103
1104
    public function update_status( $new_status = false, $note = '', $manual = false ) {
1105
        $old_status = ! empty( $this->old_status ) ? $this->old_status : get_post_status( $this->ID );
1106
1107
        if ( $old_status === $new_status && in_array( $new_status, array_keys( wpinv_get_invoice_statuses( true ) ) ) ) {
1108
            return false; // Don't permit status changes that aren't changes
1109
        }
1110
1111
        $do_change = apply_filters( 'wpinv_should_update_invoice_status', true, $this->ID, $new_status, $old_status );
1112
        $updated = false;
1113
1114
        if ( $do_change ) {
1115
            do_action( 'wpinv_before_invoice_status_change', $this->ID, $new_status, $old_status );
1116
1117
            $update_post_data                   = array();
1118
            $update_post_data['ID']             = $this->ID;
1119
            $update_post_data['post_status']    = $new_status;
1120
            $update_post_data['edit_date']      = current_time( 'mysql', 0 );
1121
            $update_post_data['edit_date_gmt']  = current_time( 'mysql', 1 );
1122
            
1123
            $update_post_data = apply_filters( 'wpinv_update_invoice_status_fields', $update_post_data, $this->ID );
1124
1125
            $updated = wp_update_post( $update_post_data );     
1126
           
1127
            // Process any specific status functions
1128
            switch( $new_status ) {
1129
                case 'wpi-refunded':
1130
                    $this->process_refund();
1131
                    break;
1132
                case 'wpi-failed':
1133
                    $this->process_failure();
1134
                    break;
1135
                case 'wpi-pending':
1136
                    $this->process_pending();
1137
                    break;
1138
            }
1139
            
1140
            // Status was changed.
1141
            do_action( 'wpinv_status_' . $new_status, $this->ID, $old_status );
1142
            do_action( 'wpinv_status_' . $old_status . '_to_' . $new_status, $this->ID, $old_status );
0 ignored issues
show
Bug introduced by
Are you sure $old_status of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1142
            do_action( 'wpinv_status_' . /** @scrutinizer ignore-type */ $old_status . '_to_' . $new_status, $this->ID, $old_status );
Loading history...
1143
            do_action( 'wpinv_update_status', $this->ID, $new_status, $old_status );
1144
        }
1145
1146
        return $updated;
1147
    }
1148
1149
    public function refund() {
1150
        $this->old_status        = $this->status;
1151
        $this->status            = 'wpi-refunded';
1152
        $this->pending['status'] = $this->status;
1153
1154
        $this->save();
1155
    }
1156
1157
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
1158
        if ( empty( $meta_key ) ) {
1159
            return false;
1160
        }
1161
1162
        if ( $meta_key == 'key' || $meta_key == 'date' ) {
1163
            $current_meta = $this->get_meta();
1164
            $current_meta[ $meta_key ] = $meta_value;
1165
1166
            $meta_key     = '_wpinv_payment_meta';
1167
            $meta_value   = $current_meta;
1168
        }
1169
1170
        $meta_value = apply_filters( 'wpinv_update_payment_meta_' . $meta_key, $meta_value, $this->ID );
1171
        
1172
        // Do not update created date on invoice marked as paid.
1173
        /*if ( $meta_key == '_wpinv_completed_date' && !empty( $meta_value ) ) {
1174
            $args = array(
1175
                'ID'                => $this->ID,
1176
                'post_date'         => $meta_value,
1177
                'edit_date'         => true,
1178
                'post_date_gmt'     => get_gmt_from_date( $meta_value ),
1179
                'post_modified'     => $meta_value,
1180
                'post_modified_gmt' => get_gmt_from_date( $meta_value )
1181
            );
1182
            wp_update_post( $args );
1183
        }*/
1184
        
1185
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
1186
    }
1187
1188
    private function process_refund() {
1189
        $process_refund = true;
1190
1191
        // If the payment was not in publish, don't decrement stats as they were never incremented
1192
        if ( 'publish' != $this->old_status || 'wpi-refunded' != $this->status ) {
1193
            $process_refund = false;
1194
        }
1195
1196
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1197
        $process_refund = apply_filters( 'wpinv_should_process_refund', $process_refund, $this );
1198
1199
        if ( false === $process_refund ) {
1200
            return;
1201
        }
1202
1203
        do_action( 'wpinv_pre_refund_invoice', $this );
1204
        
1205
        $decrease_store_earnings = apply_filters( 'wpinv_decrease_store_earnings_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_store_earnings is dead and can be removed.
Loading history...
1206
        $decrease_customer_value = apply_filters( 'wpinv_decrease_customer_value_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_customer_value is dead and can be removed.
Loading history...
1207
        $decrease_purchase_count = apply_filters( 'wpinv_decrease_customer_purchase_count_on_refund', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_purchase_count is dead and can be removed.
Loading history...
1208
        
1209
        do_action( 'wpinv_post_refund_invoice', $this );
1210
    }
1211
1212
    private function process_failure() {
1213
        $discounts = $this->discounts;
1214
        if ( empty( $discounts ) ) {
1215
            return;
1216
        }
1217
1218
        if ( ! is_array( $discounts ) ) {
0 ignored issues
show
introduced by
The condition is_array($discounts) is always false.
Loading history...
1219
            $discounts = array_map( 'trim', explode( ',', $discounts ) );
1220
        }
1221
1222
        foreach ( $discounts as $discount ) {
1223
            wpinv_decrease_discount_usage( $discount );
1224
        }
1225
    }
1226
    
1227
    private function process_pending() {
1228
        $process_pending = true;
1229
1230
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
1231
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'wpi-pending' != $this->status ) {
1232
            $process_pending = false;
1233
        }
1234
1235
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
1236
        $process_pending = apply_filters( 'wpinv_should_process_pending', $process_pending, $this );
1237
1238
        if ( false === $process_pending ) {
1239
            return;
1240
        }
1241
1242
        $decrease_store_earnings = apply_filters( 'wpinv_decrease_store_earnings_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_store_earnings is dead and can be removed.
Loading history...
1243
        $decrease_customer_value = apply_filters( 'wpinv_decrease_customer_value_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_customer_value is dead and can be removed.
Loading history...
1244
        $decrease_purchase_count = apply_filters( 'wpinv_decrease_customer_purchase_count_on_pending', true, $this );
0 ignored issues
show
Unused Code introduced by
The assignment to $decrease_purchase_count is dead and can be removed.
Loading history...
1245
1246
        $this->completed_date = '';
1247
        $this->update_meta( '_wpinv_completed_date', '' );
1248
    }
1249
    
1250
    // get data
1251
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
1252
        $meta = get_post_meta( $this->ID, $meta_key, $single );
1253
1254
        if ( $meta_key === '_wpinv_payment_meta' ) {
1255
1256
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
1257
1258
            if ( empty( $meta['key'] ) ) {
1259
                $meta['key'] = $this->setup_invoice_key();
1260
            }
1261
1262
            if ( empty( $meta['date'] ) ) {
1263
                $meta['date'] = get_post_field( 'post_date', $this->ID );
1264
            }
1265
        }
1266
1267
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
1268
1269
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
1270
    }
1271
    
1272
    public function get_description() {
1273
        $post = get_post( $this->ID );
1274
        
1275
        $description = !empty( $post ) ? $post->post_content : '';
1276
        return apply_filters( 'wpinv_get_description', $description, $this->ID, $this );
1277
    }
1278
    
1279
    public function get_status( $nicename = false ) {
1280
        if ( !$nicename ) {
1281
            $status = $this->status;
1282
        } else {
1283
            $status = $this->status_nicename;
1284
        }
1285
        
1286
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
1287
    }
1288
    
1289
    public function get_cart_details() {
1290
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
1291
    }
1292
    
1293
    public function get_subtotal( $currency = false ) {
1294
        $subtotal = wpinv_round_amount( $this->subtotal );
1295
        
1296
        if ( $currency ) {
1297
            $subtotal = wpinv_price( wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
1298
        }
1299
        
1300
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
1301
    }
1302
    
1303
    public function get_total( $currency = false ) {        
1304
        if ( $this->is_free_trial() ) {
1305
            $total = wpinv_round_amount( 0 );
1306
        } else {
1307
            $total = wpinv_round_amount( $this->total );
1308
        }
1309
        if ( $currency ) {
1310
            $total = wpinv_price( wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
1311
        }
1312
        
1313
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
1314
    }
1315
    
1316
    public function get_recurring_details( $field = '', $currency = false ) {        
1317
        $data                 = array();
1318
        $data['cart_details'] = $this->cart_details;
1319
        $data['subtotal']     = $this->get_subtotal();
1320
        $data['discount']     = $this->get_discount();
1321
        $data['tax']          = $this->get_tax();
1322
        $data['total']        = $this->get_total();
1323
    
1324
        if ( !empty( $this->cart_details ) && ( $this->is_parent() || $this->is_renewal() ) ) {
1325
            $is_free_trial = $this->is_free_trial();
1326
            $discounts = $this->get_discounts( true );
1327
            
1328
            if ( $is_free_trial || !empty( $discounts ) ) {
1329
                $first_use_only = false;
1330
                
1331
                if ( !empty( $discounts ) ) {
1332
                    foreach ( $discounts as $key => $code ) {
1333
                        if ( wpinv_discount_is_recurring( $code, true ) && !$this->is_renewal() ) {
1334
                            $first_use_only = true;
1335
                            break;
1336
                        }
1337
                    }
1338
                }
1339
                    
1340
                if ( !$first_use_only ) {
1341
                    $data['subtotal'] = wpinv_round_amount( $this->subtotal );
1342
                    $data['discount'] = wpinv_round_amount( $this->discount );
1343
                    $data['tax']      = wpinv_round_amount( $this->tax );
1344
                    $data['total']    = wpinv_round_amount( $this->total );
1345
                } else {
1346
                    $cart_subtotal   = 0;
1347
                    $cart_discount   = $this->discount;
1348
                    $cart_tax        = 0;
1349
1350
                    foreach ( $this->cart_details as $key => $item ) {
1351
                        $item_quantity  = $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1352
                        $item_subtotal  = !empty( $item['subtotal'] ) ? $item['subtotal'] : $item['item_price'] * $item_quantity;
1353
                        $item_discount  = 0;
1354
                        $item_tax       = $item_subtotal > 0 && !empty( $item['vat_rate'] ) ? ( $item_subtotal * 0.01 * (float)$item['vat_rate'] ) : 0;
1355
                        
1356
                        if ( wpinv_prices_include_tax() ) {
1357
                            $item_subtotal -= wpinv_round_amount( $item_tax );
1358
                        }
1359
                        
1360
                        $item_total     = $item_subtotal - $item_discount + $item_tax;
1361
                        // Do not allow totals to go negative
1362
                        if ( $item_total < 0 ) {
1363
                            $item_total = 0;
1364
                        }
1365
                        
1366
                        $cart_subtotal  += (float)($item_subtotal);
1367
                        $cart_discount  += (float)($item_discount);
1368
                        $cart_tax       += (float)($item_tax);
1369
                        
1370
                        $data['cart_details'][$key]['discount']   = wpinv_round_amount( $item_discount );
1371
                        $data['cart_details'][$key]['tax']        = wpinv_round_amount( $item_tax );
1372
                        $data['cart_details'][$key]['price']      = wpinv_round_amount( $item_total );
1373
                    }
1374
1375
	                $total = $data['subtotal'] - $data['discount'] + $data['tax'];
1376
	                if ( $total < 0 ) {
1377
		                $total = 0;
1378
	                }
1379
1380
                    $data['subtotal'] = wpinv_round_amount( $cart_subtotal );
1381
                    $data['discount'] = wpinv_round_amount( $cart_discount );
1382
                    $data['tax']      = wpinv_round_amount( $cart_tax );
1383
                    $data['total']    = wpinv_round_amount( $total );
1384
                }
1385
            }
1386
        }
1387
        
1388
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
1389
1390
        if ( isset( $data[$field] ) ) {
1391
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
1392
        }
1393
        
1394
        return $data;
1395
    }
1396
    
1397
    public function get_final_tax( $currency = false ) {        
1398
        $final_total = wpinv_round_amount( $this->tax );
1399
        if ( $currency ) {
1400
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
1401
        }
1402
        
1403
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
1404
    }
1405
    
1406
    public function get_discounts( $array = false ) {
1407
        $discounts = $this->discounts;
1408
        if ( $array && $discounts ) {
1409
            $discounts = explode( ',', $discounts );
1410
        }
1411
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
1412
    }
1413
    
1414
    public function get_discount( $currency = false, $dash = false ) {
1415
        if ( !empty( $this->discounts ) ) {
1416
            global $ajax_cart_details;
1417
            $ajax_cart_details = $this->get_cart_details();
1418
            
1419
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
1420
                $cart_items = $ajax_cart_details;
1421
            } else {
1422
                $cart_items = $this->items;
1423
            }
1424
1425
            $this->discount = wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
1426
        }
1427
        $discount   = wpinv_round_amount( $this->discount );
1428
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
1429
        
1430
        if ( $currency ) {
1431
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
1432
        }
1433
        
1434
        $discount   = $dash . $discount;
1435
        
1436
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
1437
    }
1438
    
1439
    public function get_discount_code() {
1440
        return $this->discount_code;
1441
    }
1442
1443
    // Checks if the invoice is taxable. Does not check if taxes are enabled on the site.
1444
    public function is_taxable() {
1445
        return (int) $this->disable_taxes === 0;
1446
    }
1447
1448
    public function get_tax( $currency = false ) {
1449
        $tax = wpinv_round_amount( $this->tax );
1450
1451
        if ( $currency ) {
1452
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
1453
        }
1454
1455
        if ( ! $this->is_taxable() ) {
1456
            $tax = wpinv_round_amount( 0.00 );
1457
        }
1458
1459
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
1460
    }
1461
    
1462
    public function get_fees( $type = 'all' ) {
1463
        $fees    = array();
1464
1465
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
1466
            foreach ( $this->fees as $fee ) {
1467
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
1468
                    continue;
1469
                }
1470
1471
                $fee['label'] = stripslashes( $fee['label'] );
1472
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
1473
                $fees[]    = $fee;
1474
            }
1475
        }
1476
1477
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
1478
    }
1479
    
1480
    public function get_fees_total( $type = 'all' ) {
1481
        $fees_total = (float) 0.00;
1482
1483
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1484
        if ( ! empty( $payment_fees ) ) {
1485
            foreach ( $payment_fees as $fee ) {
1486
                $fees_total += (float) $fee['amount'];
1487
            }
1488
        }
1489
1490
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1491
        /*
1492
        $fees = $this->get_fees( $type );
1493
1494
        $fees_total = 0;
1495
        if ( ! empty( $fees ) && is_array( $fees ) ) {
1496
            foreach ( $fees as $fee_id => $fee ) {
1497
                if( 'all' != $type && !empty( $fee['type'] ) && $type != $fee['type'] ) {
1498
                    continue;
1499
                }
1500
1501
                $fees_total += $fee['amount'];
1502
            }
1503
        }
1504
1505
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
1506
        */
1507
    }
1508
1509
    public function get_user_id() {
1510
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
1511
    }
1512
    
1513
    public function get_first_name() {
1514
        return apply_filters( 'wpinv_first_name', $this->first_name, $this->ID, $this );
1515
    }
1516
    
1517
    public function get_last_name() {
1518
        return apply_filters( 'wpinv_last_name', $this->last_name, $this->ID, $this );
1519
    }
1520
    
1521
    public function get_user_full_name() {
1522
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
1523
    }
1524
    
1525
    public function get_user_info() {
1526
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
1527
    }
1528
    
1529
    public function get_email() {
1530
        return apply_filters( 'wpinv_user_email', $this->email, $this->ID, $this );
1531
    }
1532
    
1533
    public function get_address() {
1534
        return apply_filters( 'wpinv_address', $this->address, $this->ID, $this );
1535
    }
1536
    
1537
    public function get_phone() {
1538
        return apply_filters( 'wpinv_phone', $this->phone, $this->ID, $this );
1539
    }
1540
    
1541
    public function get_number() {
1542
        return apply_filters( 'wpinv_number', $this->number, $this->ID, $this );
1543
    }
1544
    
1545
    public function get_items() {
1546
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
1547
    }
1548
    
1549
    public function get_key() {
1550
        return apply_filters( 'wpinv_key', $this->key, $this->ID, $this );
1551
    }
1552
    
1553
    public function get_transaction_id() {
1554
        return apply_filters( 'wpinv_get_invoice_transaction_id', $this->transaction_id, $this->ID, $this );
1555
    }
1556
    
1557
    public function get_gateway() {
1558
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
1559
    }
1560
    
1561
    public function get_gateway_title() {
1562
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
1563
        
1564
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
1565
    }
1566
    
1567
    public function get_currency() {
1568
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
1569
    }
1570
    
1571
    public function get_created_date() {
1572
        return apply_filters( 'wpinv_created_date', $this->date, $this->ID, $this );
1573
    }
1574
    
1575
    public function get_due_date( $display = false ) {
1576
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
1577
        
1578
        if ( !$display || empty( $due_date ) ) {
1579
            return $due_date;
1580
        }
1581
        
1582
        return date_i18n( get_option( 'date_format' ), strtotime( $due_date ) );
0 ignored issues
show
Bug introduced by
It seems like get_option('date_format') can also be of type false; however, parameter $format of date_i18n() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1582
        return date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $due_date ) );
Loading history...
1583
    }
1584
    
1585
    public function get_completed_date() {
1586
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
1587
    }
1588
    
1589
    public function get_invoice_date( $formatted = true ) {
1590
        $date_completed = $this->completed_date;
1591
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
1592
        
1593
        if ( $invoice_date == '' ) {
1594
            $date_created   = $this->date;
1595
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
1596
        }
1597
        
1598
        if ( $formatted && $invoice_date ) {
1599
            $invoice_date   = date_i18n( get_option( 'date_format' ), strtotime( $invoice_date ) );
0 ignored issues
show
Bug introduced by
It seems like get_option('date_format') can also be of type false; however, parameter $format of date_i18n() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1599
            $invoice_date   = date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $invoice_date ) );
Loading history...
1600
        }
1601
1602
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
1603
    }
1604
    
1605
    public function get_ip() {
1606
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
1607
    }
1608
        
1609
    public function has_status( $status ) {
1610
        return apply_filters( 'wpinv_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1611
    }
1612
    
1613
    public function add_item( $item_id = 0, $args = array() ) {
1614
        global $wpi_current_id, $wpi_item_id;
1615
        
1616
        $item = new WPInv_Item( $item_id );
1617
1618
        // Bail if this post isn't a item
1619
        if( !$item || $item->post_type !== 'wpi_item' ) {
0 ignored issues
show
Bug Best Practice introduced by
The property post_type does not exist on WPInv_Item. Since you implemented __get, consider adding a @property annotation.
Loading history...
introduced by
$item is of type WPInv_Item, thus it always evaluated to true.
Loading history...
1620
            return false;
1621
        }
1622
        
1623
        $has_quantities = wpinv_item_quantities_enabled();
1624
1625
        // Set some defaults
1626
        $defaults = array(
1627
            'quantity'      => 1,
1628
            'id'            => false,
1629
            'name'          => $item->get_name(),
1630
            'item_price'    => false,
1631
            'custom_price'  => '',
1632
            'discount'      => 0,
1633
            'tax'           => 0.00,
1634
            'meta'          => array(),
1635
            'fees'          => array()
1636
        );
1637
1638
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
1639
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
1640
1641
        $wpi_current_id         = $this->ID;
1642
        $wpi_item_id            = $item->ID;
1643
        $discounts              = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
1644
        
1645
        $_POST['wpinv_country'] = $this->country;
1646
        $_POST['wpinv_state']   = $this->state;
1647
        
1648
        $found_cart_key         = false;
1649
        
1650
        if ($has_quantities) {
1651
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1652
            
1653
            foreach ( $this->items as $key => $cart_item ) {
1654
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
1655
                    continue;
1656
                }
1657
1658
                $this->items[ $key ]['quantity'] += $args['quantity'];
1659
                break;
1660
            }
1661
            
1662
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
1663
                if ( $item_id != $cart_item['id'] ) {
1664
                    continue;
1665
                }
1666
1667
                $found_cart_key = $cart_key;
1668
                break;
1669
            }
1670
        }
1671
        
1672
        if ($has_quantities && $found_cart_key !== false) {
1673
            $cart_item          = $this->cart_details[$found_cart_key];
1674
            $item_price         = $cart_item['item_price'];
1675
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1676
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1677
            
1678
            $new_quantity       = $quantity + $args['quantity'];
1679
            $subtotal           = $item_price * $new_quantity;
1680
            
1681
            $args['quantity']   = $new_quantity;
1682
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
1683
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1684
            
1685
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
1686
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
1687
            // The total increase equals the number removed * the item_price
1688
            $total_increased    = wpinv_round_amount( $item_price );
1689
            
1690
            if ( wpinv_prices_include_tax() ) {
1691
                $subtotal -= wpinv_round_amount( $tax );
1692
            }
1693
1694
            $total              = $subtotal - $discount + $tax;
1695
1696
            // Do not allow totals to go negative
1697
            if( $total < 0 ) {
1698
                $total = 0;
1699
            }
1700
            
1701
            $cart_item['quantity']  = $new_quantity;
1702
            $cart_item['subtotal']  = $subtotal;
1703
            $cart_item['discount']  = $discount;
1704
            $cart_item['tax']       = $tax;
1705
            $cart_item['price']     = $total;
1706
            
1707
            $subtotal               = $total_increased - $discount_increased;
1708
            $tax                    = $tax_increased;
1709
            
1710
            $this->cart_details[$found_cart_key] = $cart_item;
1711
        } else {
1712
            // Set custom price.
1713
            if ( $args['custom_price'] !== '' ) {
1714
                $item_price = $args['custom_price'];
1715
            } else {
1716
                // Allow overriding the price
1717
                if ( false !== $args['item_price'] ) {
1718
                    $item_price = $args['item_price'];
1719
                } else {
1720
                    $item_price = wpinv_get_item_price( $item->ID );
1721
                }
1722
            }
1723
1724
            // Sanitizing the price here so we don't have a dozen calls later
1725
            $item_price = wpinv_sanitize_amount( $item_price );
1726
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
1727
        
1728
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
1729
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
1730
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
1731
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1732
1733
            // Setup the items meta item
1734
            $new_item = array(
1735
                'id'       => $item->ID,
1736
                'quantity' => $args['quantity'],
1737
            );
1738
1739
            $this->items[]  = $new_item;
1740
1741
            if ( wpinv_prices_include_tax() ) {
1742
                $subtotal -= wpinv_round_amount( $tax );
1743
            }
1744
1745
            $total      = $subtotal - $discount + $tax;
1746
1747
            // Do not allow totals to go negative
1748
            if( $total < 0 ) {
1749
                $total = 0;
1750
            }
1751
        
1752
            $this->cart_details[] = array(
1753
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
1754
                'id'            => $item->ID,
1755
                'item_price'    => wpinv_round_amount( $item_price ),
1756
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
1757
                'quantity'      => $args['quantity'],
1758
                'discount'      => $discount,
1759
                'subtotal'      => wpinv_round_amount( $subtotal ),
1760
                'tax'           => wpinv_round_amount( $tax ),
1761
                'price'         => wpinv_round_amount( $total ),
1762
                'vat_rate'      => $tax_rate,
1763
                'vat_class'     => $tax_class,
1764
                'meta'          => $args['meta'],
1765
                'fees'          => $args['fees'],
1766
            );
1767
                        
1768
            $subtotal = $subtotal - $discount;
1769
        }
1770
        
1771
        $added_item = end( $this->cart_details );
1772
        $added_item['action']  = 'add';
1773
        
1774
        $this->pending['items'][] = $added_item;
1775
        
1776
        $this->increase_subtotal( $subtotal );
1777
        $this->increase_tax( $tax );
1778
1779
        return true;
1780
    }
1781
    
1782
    public function remove_item( $item_id, $args = array() ) {
1783
        // Set some defaults
1784
        $defaults = array(
1785
            'quantity'      => 1,
1786
            'item_price'    => false,
1787
            'custom_price'  => '',
1788
            'cart_index'    => false,
1789
        );
1790
        $args = wp_parse_args( $args, $defaults );
1791
1792
        // Bail if this post isn't a item
1793
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
1794
            return false;
1795
        }
1796
        
1797
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
1798
1799
        foreach ( $this->items as $key => $item ) {
1800
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
1801
                continue;
1802
            }
1803
1804
            if ( false !== $args['cart_index'] ) {
1805
                $cart_index = absint( $args['cart_index'] );
1806
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
1807
1808
                if ( ! empty( $cart_item ) ) {
1809
                    // If the cart index item isn't the same item ID, don't remove it
1810
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
1811
                        continue;
1812
                    }
1813
                }
1814
            }
1815
1816
            $item_quantity = $this->items[ $key ]['quantity'];
1817
            if ( $item_quantity > $args['quantity'] ) {
1818
                $this->items[ $key ]['quantity'] -= $args['quantity'];
1819
                break;
1820
            } else {
1821
                unset( $this->items[ $key ] );
1822
                break;
1823
            }
1824
        }
1825
1826
        $found_cart_key = false;
1827
        if ( false === $args['cart_index'] ) {
1828
            foreach ( $this->cart_details as $cart_key => $item ) {
1829
                if ( $item_id != $item['id'] ) {
1830
                    continue;
1831
                }
1832
1833
                if ( false !== $args['item_price'] ) {
1834
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
1835
                        continue;
1836
                    }
1837
                }
1838
1839
                $found_cart_key = $cart_key;
1840
                break;
1841
            }
1842
        } else {
1843
            $cart_index = absint( $args['cart_index'] );
1844
1845
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
1846
                return false; // Invalid cart index passed.
1847
            }
1848
1849
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
1850
                return false; // We still need the proper Item ID to be sure.
1851
            }
1852
1853
            $found_cart_key = $cart_index;
1854
        }
1855
        
1856
        $cart_item  = $this->cart_details[$found_cart_key];
1857
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
1858
        
1859
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
1860
            //return false; // Invoice must contain at least one item.
1861
        }
1862
        
1863
        $discounts  = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
1864
        
1865
        if ( $quantity > $args['quantity'] ) {
1866
            $item_price         = $cart_item['item_price'];
1867
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
1868
            
1869
            $new_quantity       = max( $quantity - $args['quantity'], 1);
1870
            $subtotal           = $item_price * $new_quantity;
1871
            
1872
            $args['quantity']   = $new_quantity;
1873
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1874
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1875
            
1876
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
1877
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
1878
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
1879
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
1880
            
1881
            // The total increase equals the number removed * the item_price
1882
            $total_decrease     = wpinv_round_amount( $item_price );
1883
            
1884
            if ( wpinv_prices_include_tax() ) {
1885
                $subtotal -= wpinv_round_amount( $tax );
1886
            }
1887
1888
            $total              = $subtotal - $discount + $tax;
1889
1890
            // Do not allow totals to go negative
1891
            if( $total < 0 ) {
1892
                $total = 0;
1893
            }
1894
            
1895
            $cart_item['quantity']  = $new_quantity;
1896
            $cart_item['subtotal']  = $subtotal;
1897
            $cart_item['discount']  = $discount;
1898
            $cart_item['tax']       = $tax;
1899
            $cart_item['price']     = $total;
1900
            
1901
            $added_item             = $cart_item;
1902
            $added_item['id']       = $item_id;
1903
            $added_item['price']    = $total_decrease;
1904
            $added_item['quantity'] = $args['quantity'];
1905
            
1906
            $subtotal_decrease      = $total_decrease - $discount_decrease;
1907
            
1908
            $this->cart_details[$found_cart_key] = $cart_item;
1909
            
1910
            $remove_item = end( $this->cart_details );
1911
        } else {
1912
            $item_price     = $cart_item['item_price'];
1913
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
1914
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
1915
        
1916
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
1917
            $tax_decrease       = $tax;
1918
1919
            unset( $this->cart_details[$found_cart_key] );
1920
            
1921
            $remove_item             = $args;
1922
            $remove_item['id']       = $item_id;
1923
            $remove_item['price']    = $subtotal_decrease;
1924
            $remove_item['quantity'] = $args['quantity'];
1925
        }
1926
        
1927
        $remove_item['action']      = 'remove';
1928
        $this->pending['items'][]   = $remove_item;
1929
               
1930
        $this->decrease_subtotal( $subtotal_decrease );
1931
        $this->decrease_tax( $tax_decrease );
1932
        
1933
        return true;
1934
    }
1935
    
1936
    public function update_items($temp = false) {
1937
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
1938
        
1939
        if ( !empty( $this->cart_details ) ) {
1940
            $wpi_nosave             = $temp;
1941
            $cart_subtotal          = 0;
1942
            $cart_discount          = 0;
1943
            $cart_tax               = 0;
1944
            $cart_details           = array();
1945
1946
            $_POST['wpinv_country'] = $this->country;
1947
            $_POST['wpinv_state']   = $this->state;
1948
1949
            foreach ( $this->cart_details as $key => $item ) {
1950
                $item_price = $item['item_price'];
1951
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
1952
                $amount     = wpinv_round_amount( $item_price * $quantity );
0 ignored issues
show
Unused Code introduced by
The assignment to $amount is dead and can be removed.
Loading history...
1953
                $subtotal   = $item_price * $quantity;
1954
                
1955
                $wpi_current_id         = $this->ID;
1956
                $wpi_item_id            = $item['id'];
1957
                
1958
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
1959
                
1960
                $tax_rate   = wpinv_get_tax_rate( $this->country, $this->state, $wpi_item_id );
1961
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
1962
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
1963
1964
                if ( ! $this->is_taxable() ) {
1965
                    $tax = 0;
1966
                }
1967
1968
                if ( wpinv_prices_include_tax() ) {
1969
                    $subtotal -= wpinv_round_amount( $tax );
1970
                }
1971
1972
                $total      = $subtotal - $discount + $tax;
1973
1974
                // Do not allow totals to go negative
1975
                if( $total < 0 ) {
1976
                    $total = 0;
1977
                }
1978
1979
                $cart_details[] = array(
1980
                    'id'          => $item['id'],
1981
                    'name'        => $item['name'],
1982
                    'item_price'  => wpinv_round_amount( $item_price ),
1983
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
1984
                    'quantity'    => $quantity,
1985
                    'discount'    => $discount,
1986
                    'subtotal'    => wpinv_round_amount( $subtotal ),
1987
                    'tax'         => wpinv_round_amount( $tax ),
1988
                    'price'       => wpinv_round_amount( $total ),
1989
                    'vat_rate'    => $tax_rate,
1990
                    'vat_class'   => $tax_class,
1991
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
1992
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
1993
                );
1994
1995
                $cart_subtotal  += (float)($subtotal - $discount); // TODO
1996
                $cart_discount  += (float)($discount);
1997
                $cart_tax       += (float)($tax);
1998
            }
1999
            if ( $cart_subtotal < 0 ) {
2000
                $cart_subtotal = 0;
2001
            }
2002
            if ( $cart_tax < 0 ) {
2003
                $cart_tax = 0;
2004
            }
2005
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
2006
            $this->tax      = wpinv_round_amount( $cart_tax );
2007
            $this->discount = wpinv_round_amount( $cart_discount );
2008
            
2009
            $this->recalculate_total();
2010
            
2011
            $this->cart_details = $cart_details;
2012
        }
2013
2014
        return $this;
2015
    }
2016
    
2017
    public function recalculate_totals($temp = false) {        
2018
        $this->update_items($temp);
2019
        $this->save( true );
2020
        
2021
        return $this;
2022
    }
2023
    
2024
    public function needs_payment() {
2025
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'wpi-pending' ), $this );
2026
2027
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() || $this->is_initial_free() ) ) {
2028
            $needs_payment = true;
2029
        } else {
2030
            $needs_payment = false;
2031
        }
2032
2033
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
2034
    }
2035
    
2036
    public function get_checkout_payment_url( $with_key = false, $secret = false ) {
2037
        $pay_url = wpinv_get_checkout_uri();
2038
2039
        if ( is_ssl() ) {
2040
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
2041
        }
2042
        
2043
        $key = $this->get_key();
2044
2045
        if ( $with_key ) {
2046
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
2047
        } else {
2048
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
2049
        }
2050
        
2051
        if ( $secret ) {
2052
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
2053
        }
2054
2055
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this, $with_key, $secret );
2056
    }
2057
    
2058
    public function get_view_url( $with_key = false ) {
2059
        $invoice_url = get_permalink( $this->ID );
2060
2061
        if ( $with_key ) {
2062
            $invoice_url = add_query_arg( 'invoice_key', $this->get_key(), $invoice_url );
2063
        }
2064
2065
        return apply_filters( 'wpinv_get_view_url', $invoice_url, $this, $with_key );
2066
    }
2067
    
2068
    public function generate_key( $string = '' ) {
2069
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2070
        return strtolower( md5( $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) ) );  // Unique key
2071
    }
2072
    
2073
    public function is_recurring() {
2074
        if ( empty( $this->cart_details ) ) {
2075
            return false;
2076
        }
2077
        
2078
        $has_subscription = false;
2079
        foreach( $this->cart_details as $cart_item ) {
2080
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2081
                $has_subscription = true;
2082
                break;
2083
            }
2084
        }
2085
        
2086
        if ( count( $this->cart_details ) > 1 ) {
2087
            $has_subscription = false;
2088
        }
2089
2090
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
2091
    }
2092
2093
    public function is_free_trial() {
2094
        $is_free_trial = false;
2095
        
2096
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
2097
            if ( !empty( $item ) && $item->has_free_trial() ) {
2098
                $is_free_trial = true;
2099
            }
2100
        }
2101
2102
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details, $this );
2103
    }
2104
2105
    public function is_initial_free() {
2106
        $is_initial_free = false;
2107
        
2108
        if ( ! ( (float)wpinv_round_amount( $this->get_total() ) > 0 ) && $this->is_parent() && $this->is_recurring() && ! $this->is_free_trial() && ! $this->is_free() ) {
2109
            $is_initial_free = true;
2110
        }
2111
2112
        return apply_filters( 'wpinv_invoice_is_initial_free', $is_initial_free, $this->cart_details );
2113
    }
2114
    
2115
    public function get_recurring( $object = false ) {
2116
        $item = NULL;
2117
        
2118
        if ( empty( $this->cart_details ) ) {
2119
            return $item;
2120
        }
2121
        
2122
        foreach( $this->cart_details as $cart_item ) {
2123
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
2124
                $item = $cart_item['id'];
2125
                break;
2126
            }
2127
        }
2128
        
2129
        if ( $object ) {
2130
            $item = $item ? new WPInv_Item( $item ) : NULL;
2131
            
2132
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
2133
        }
2134
2135
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
2136
    }
2137
    
2138
    public function get_subscription_name() {
2139
        $item = $this->get_recurring( true );
2140
        
2141
        if ( empty( $item ) ) {
2142
            return NULL;
2143
        }
2144
        
2145
        if ( !($name = $item->get_name()) ) {
2146
            $name = $item->post_name;
2147
        }
2148
2149
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
2150
    }
2151
    
2152
    public function get_subscription_id() {
2153
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
2154
        
2155
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
2156
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
2157
            
2158
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
2159
        }
2160
        
2161
        return $subscription_id;
2162
    }
2163
    
2164
    public function is_parent() {
2165
        $is_parent = empty( $this->parent_invoice ) ? true : false;
2166
2167
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
2168
    }
2169
    
2170
    public function is_renewal() {
2171
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
2172
2173
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
2174
    }
2175
    
2176
    public function get_parent_payment() {
2177
        $parent_payment = NULL;
2178
        
2179
        if ( $this->is_renewal() ) {
2180
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
2181
        }
2182
        
2183
        return $parent_payment;
2184
    }
2185
    
2186
    public function is_paid() {
2187
        $is_paid = $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) );
2188
2189
        return apply_filters( 'wpinv_invoice_is_paid', $is_paid, $this );
2190
    }
2191
2192
    /**
2193
     * Checks if this is a quote object.
2194
     * 
2195
     * @since 1.0.15
2196
     */
2197
    public function is_quote() {
2198
        return 'wpi_quote' === $this->post_type;
2199
    }
2200
    
2201
    public function is_refunded() {
2202
        $is_refunded = $this->has_status( array( 'wpi-refunded' ) );
2203
2204
        return apply_filters( 'wpinv_invoice_is_refunded', $is_refunded, $this );
2205
    }
2206
    
2207
    public function is_free() {
2208
        $is_free = false;
2209
        
2210
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
2211
            if ( $this->is_parent() && $this->is_recurring() ) {
2212
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
2213
            } else {
2214
                $is_free = true;
2215
            }
2216
        }
2217
        
2218
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
2219
    }
2220
    
2221
    public function has_vat() {
2222
        global $wpinv_euvat, $wpi_country;
2223
        
2224
        $requires_vat = false;
2225
        
2226
        if ( $this->country ) {
2227
            $wpi_country        = $this->country;
2228
            
2229
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
2230
        }
2231
        
2232
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
2233
    }
2234
    
2235
    public function refresh_item_ids() {
2236
        $item_ids = array();
2237
        
2238
        if ( !empty( $this->cart_details ) ) {
2239
            foreach ( $this->cart_details as $key => $item ) {
2240
                if ( !empty( $item['id'] ) ) {
2241
                    $item_ids[] = $item['id'];
2242
                }
2243
            }
2244
        }
2245
        
2246
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
2247
        
2248
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
2249
    }
2250
    
2251
    public function get_invoice_quote_type( $post_id ) {
2252
        if ( empty( $post_id ) ) {
2253
            return '';
2254
        }
2255
2256
        $type = get_post_type( $post_id );
2257
2258
        if ( 'wpi_invoice' === $type ) {
2259
            $post_type = __('Invoice', 'invoicing');
2260
        } else{
2261
            $post_type = __('Quote', 'invoicing');
2262
        }
2263
2264
        return apply_filters('get_invoice_type_label', $post_type, $post_id);
2265
    }
2266
}
2267