Passed
Pull Request — master (#377)
by Brian
83:02 queued 40:35
created

WPInv_Invoice::get_total_fees()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 1
1
<?php
2
 
3
// MUST have WordPress.
4
if ( !defined( 'WPINC' ) ) {
5
    exit( 'Do NOT access this file directly: ' . basename( __FILE__ ) );
6
}
7
8
/**
9
 * Invoice class.
10
 */
11
class WPInv_Invoice extends GetPaid_Data {
12
13
    /**
14
	 * Which data store to load.
15
	 *
16
	 * @var string
17
	 */
18
    protected $data_store_name = 'invoice';
19
20
    /**
21
	 * This is the name of this object type.
22
	 *
23
	 * @var string
24
	 */
25
    protected $object_type = 'invoice';
26
27
    /**
28
	 * Item Data array. This is the core item data exposed in APIs.
29
	 *
30
	 * @since 1.0.19
31
	 * @var array
32
	 */
33
	protected $data = array(
34
		'parent_id'            => 0,
35
		'status'               => 'wpi-pending',
36
		'version'              => '',
37
		'date_created'         => null,
38
        'date_modified'        => null,
39
        'due_date'             => null,
40
        'completed_date'       => null,
41
        'number'               => '',
42
        'title'                => '',
43
        'key'                  => '',
44
        'description'          => '',
45
        'author'               => 1,
46
        'type'                 => 'invoice',
47
        'mode'                 => 'live',
48
        'user_ip'              => null,
49
        'first_name'           => null,
50
        'last_name'            => null,
51
        'phone'                => null,
52
        'email'                => null,
53
        'country'              => null,
54
        'city'                 => null,
55
        'state'                => null,
56
        'zip'                  => null,
57
        'company'              => null,
58
        'vat_number'           => null,
59
        'vat_rate'             => null,
60
        'address'              => null,
61
        'address_confirmed'    => false,
62
        'subtotal'             => 0,
63
        'total_discount'       => 0,
64
        'total_tax'            => 0,
65
        'total_fees'           => 0,
66
        'fees'                 => array(),
67
        'discounts'            => array(),
68
        'taxes'                => array(),
69
        'items'                => array(),
70
        'payment_form'         => 1,
71
        'submission_id'        => null,
72
        'discount_code'        => null,
73
        'gateway'              => 'none',
74
        'transaction_id'       => '',
75
        'currency'             => '',
76
        'disable_taxes'        => 0,
77
    );
78
79
    // user_info, payment_meta, cart_details, full_name
80
81
    /**
82
	 * Stores meta in cache for future reads.
83
	 *
84
	 * A group must be set to to enable caching.
85
	 *
86
	 * @var string
87
	 */
88
	protected $cache_group = 'getpaid_invoices';
89
90
    /**
91
     * Stores a reference to the original WP_Post object
92
     * 
93
     * @var WP_Post
94
     */
95
    protected $post = null;
96
97
    /**
98
	 * Get the invoice if ID is passed, otherwise the invoice is new and empty.
99
	 *
100
	 * @param  int/string|object|WPInv_Invoice|WPInv_Legacy_Invoice|WP_Post $invoice Invoice id, key, number or object to read.
0 ignored issues
show
Documentation Bug introduced by
The doc comment int/string|object|WPInv_..._Legacy_Invoice|WP_Post at position 0 could not be parsed: Unknown type name 'int/string' at position 0 in int/string|object|WPInv_Invoice|WPInv_Legacy_Invoice|WP_Post.
Loading history...
101
	 */
102
    public function __construct( $invoice = false ) {
103
104
        parent::__construct( $invoice );
105
106
		if ( is_numeric( $invoice ) && getpaid_is_invoice_post_type( get_post_type( $invoice ) ) ) {
107
			$this->set_id( $invoice );
108
		} elseif ( $invoice instanceof self ) {
109
			$this->set_id( $invoice->get_id() );
110
		} elseif ( ! empty( $invoice->ID ) ) {
111
			$this->set_id( $invoice->ID );
112
		} elseif ( is_array( $invoice ) ) {
113
			$this->set_props( $invoice );
114
115
			if ( isset( $invoice['ID'] ) ) {
116
				$this->set_id( $invoice['ID'] );
117
			}
118
119
		} elseif ( is_scalar( $invoice ) && $invoice_id = self::get_discount_id_by_code( $invoice, 'key' ) ) {
120
			$this->set_id( $invoice_id );
121
		} elseif ( is_scalar( $invoice ) && $invoice_id = self::get_discount_id_by_code( $invoice, 'number' ) ) {
122
			$this->set_id( $invoice_id );
123
		} else {
124
			$this->set_object_read( true );
125
		}
126
127
        // Load the datastore.
128
		$this->data_store = GetPaid_Data_Store::load( $this->data_store_name );
129
130
		if ( $this->get_id() > 0 ) {
131
            $this->post = get_post( $this->get_id() );
132
            $this->ID   = $this->get_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...
133
			$this->data_store->read( $this );
134
        }
135
136
    }
137
138
    /**
139
	 * Given a discount code, it returns a discount id.
140
	 *
141
	 *
142
	 * @static
143
	 * @param string $discount_code
144
	 * @since 1.0.15
145
	 * @return int
146
	 */
147
	public static function get_discount_id_by_code( $invoice_key_or_number, $field = 'key' ) {
148
        global $wpdb;
149
150
		// Trim the code.
151
        $key = trim( $invoice_key_or_number );
152
        
153
        // Valid fields.
154
        $fields = array( 'key', 'number' );
155
156
		// Ensure a value has been passed.
157
		if ( empty( $key ) || ! in_array( $field, $fields ) ) {
158
			return 0;
159
		}
160
161
		// Maybe retrieve from the cache.
162
		$invoice_id   = wp_cache_get( $key, 'getpaid_invoice_keys_' . $field );
163
		if ( ! empty( $invoice_id ) ) {
164
			return $invoice_id;
165
		}
166
167
        // Fetch from the db.
168
        $table       = $wpdb->prefix . 'getpaid_invoices';
169
        $invoice_id  = $wpdb->get_var(
170
            $wpdb->prepare( "SELECT `post_id` FROM $table WHERE $field=%s LIMIT 1", $key )
171
        );
172
173
		if ( empty( $invoice_id ) ) {
174
			return 0;
175
		}
176
177
		// Update the cache with our data
178
		wp_cache_add( $key, $invoice_id, 'getpaid_invoice_keys_' . $field );
179
180
		return $invoice_id;
181
    }
182
    
183
    /*
184
	|--------------------------------------------------------------------------
185
	| CRUD methods
186
	|--------------------------------------------------------------------------
187
	|
188
	| Methods which create, read, update and delete items from the database.
189
	|
190
    */
191
192
    /*
193
	|--------------------------------------------------------------------------
194
	| Getters
195
	|--------------------------------------------------------------------------
196
    */
197
198
    /**
199
	 * Get parent invoice ID.
200
	 *
201
	 * @since 1.0.19
202
	 * @param  string $context View or edit context.
203
	 * @return int
204
	 */
205
	public function get_parent_id( $context = 'view' ) {
206
		return (int) $this->get_prop( 'parent_id', $context );
207
    }
208
209
    /**
210
	 * Get invoice status.
211
	 *
212
	 * @since 1.0.19
213
	 * @param  string $context View or edit context.
214
	 * @return string
215
	 */
216
	public function get_status( $context = 'view' ) {
217
		return $this->get_prop( 'status', $context );
218
    }
219
220
    /**
221
	 * Get plugin version when the invoice was created.
222
	 *
223
	 * @since 1.0.19
224
	 * @param  string $context View or edit context.
225
	 * @return string
226
	 */
227
	public function get_version( $context = 'view' ) {
228
		return $this->get_prop( 'version', $context );
229
    }
230
231
    /**
232
	 * Get date when the invoice was created.
233
	 *
234
	 * @since 1.0.19
235
	 * @param  string $context View or edit context.
236
	 * @return string
237
	 */
238
	public function get_date_created( $context = 'view' ) {
239
		return $this->get_prop( 'date_created', $context );
240
    }
241
242
    /**
243
	 * Get GMT date when the invoice was created.
244
	 *
245
	 * @since 1.0.19
246
	 * @param  string $context View or edit context.
247
	 * @return string
248
	 */
249
	public function get_date_created_gmt( $context = 'view' ) {
250
        $date = $this->get_date_created( $context );
251
252
        if ( $date ) {
253
            $date = get_gmt_from_date( $date );
254
        }
255
		return $date;
256
    }
257
258
    /**
259
	 * Get date when the invoice was last modified.
260
	 *
261
	 * @since 1.0.19
262
	 * @param  string $context View or edit context.
263
	 * @return string
264
	 */
265
	public function get_date_modified( $context = 'view' ) {
266
		return $this->get_prop( 'date_modified', $context );
267
    }
268
269
    /**
270
	 * Get GMT date when the invoice was last modified.
271
	 *
272
	 * @since 1.0.19
273
	 * @param  string $context View or edit context.
274
	 * @return string
275
	 */
276
	public function get_date_modified_gmt( $context = 'view' ) {
277
        $date = $this->get_date_modified( $context );
278
279
        if ( $date ) {
280
            $date = get_gmt_from_date( $date );
281
        }
282
		return $date;
283
    }
284
285
    /**
286
	 * Get the invoice due date.
287
	 *
288
	 * @since 1.0.19
289
	 * @param  string $context View or edit context.
290
	 * @return string
291
	 */
292
	public function get_due_date( $context = 'view' ) {
293
		return $this->get_prop( 'due_date', $context );
294
    }
295
296
    /**
297
	 * Alias for self::get_due_date().
298
	 *
299
	 * @since 1.0.19
300
	 * @param  string $context View or edit context.
301
	 * @return string
302
	 */
303
	public function get_date_due( $context = 'view' ) {
304
		return $this->get_due_date( $context );
305
    }
306
307
    /**
308
	 * Get the invoice GMT due date.
309
	 *
310
	 * @since 1.0.19
311
	 * @param  string $context View or edit context.
312
	 * @return string
313
	 */
314
	public function get_due_date_gmt( $context = 'view' ) {
315
        $date = $this->get_due_date( $context );
316
317
        if ( $date ) {
318
            $date = get_gmt_from_date( $date );
319
        }
320
		return $date;
321
    }
322
323
    /**
324
	 * Alias for self::get_due_date_gmt().
325
	 *
326
	 * @since 1.0.19
327
	 * @param  string $context View or edit context.
328
	 * @return string
329
	 */
330
	public function get_gmt_date_due( $context = 'view' ) {
331
		return $this->get_due_date_gmt( $context );
332
    }
333
334
    /**
335
	 * Get date when the invoice was completed.
336
	 *
337
	 * @since 1.0.19
338
	 * @param  string $context View or edit context.
339
	 * @return string
340
	 */
341
	public function get_completed_date( $context = 'view' ) {
342
		return $this->get_prop( 'completed_date', $context );
343
    }
344
345
    /**
346
	 * Alias for self::get_completed_date().
347
	 *
348
	 * @since 1.0.19
349
	 * @param  string $context View or edit context.
350
	 * @return string
351
	 */
352
	public function get_date_completed( $context = 'view' ) {
353
		return $this->get_completed_date( $context );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_completed_date() has too many arguments starting with $context. ( Ignorable by Annotation )

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

353
		return $this->/** @scrutinizer ignore-call */ get_completed_date( $context );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
354
    }
355
356
    /**
357
	 * Get GMT date when the invoice was was completed.
358
	 *
359
	 * @since 1.0.19
360
	 * @param  string $context View or edit context.
361
	 * @return string
362
	 */
363
	public function get_completed_date_gmt( $context = 'view' ) {
364
        $date = $this->get_completed_date( $context );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_completed_date() has too many arguments starting with $context. ( Ignorable by Annotation )

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

364
        /** @scrutinizer ignore-call */ 
365
        $date = $this->get_completed_date( $context );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
365
366
        if ( $date ) {
367
            $date = get_gmt_from_date( $date );
368
        }
369
		return $date;
370
    }
371
372
    /**
373
	 * Alias for self::get_completed_date_gmt().
374
	 *
375
	 * @since 1.0.19
376
	 * @param  string $context View or edit context.
377
	 * @return string
378
	 */
379
	public function get_gmt_completed_date( $context = 'view' ) {
380
		return $this->get_completed_date_gmt( $context );
381
    }
382
383
    /**
384
	 * Get the invoice number.
385
	 *
386
	 * @since 1.0.19
387
	 * @param  string $context View or edit context.
388
	 * @return string
389
	 */
390
	public function get_number( $context = 'view' ) {
391
        $number = $this->get_prop( 'number', $context );
392
393
        if ( empty( $number ) ) {
394
            $number = $this->generate_number();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $number is correct as $this->generate_number() targeting WPInv_Invoice::generate_number() 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...
395
            $this->set_number( $number );
0 ignored issues
show
Bug introduced by
The method set_number() does not exist on WPInv_Invoice. ( Ignorable by Annotation )

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

395
            $this->/** @scrutinizer ignore-call */ 
396
                   set_number( $number );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
396
        }
397
398
		return $number;
399
    }
400
401
    /**
402
	 * Get the invoice key.
403
	 *
404
	 * @since 1.0.19
405
	 * @param  string $context View or edit context.
406
	 * @return string
407
	 */
408
	public function get_key( $context = 'view' ) {
409
        $key = $this->get_prop( 'key', $context );
410
411
        if ( empty( $key ) ) {
412
            $key = $this->generate_key( $this->post_type );
413
            $this->set_key( $key );
0 ignored issues
show
Bug introduced by
The method set_key() does not exist on WPInv_Invoice. ( Ignorable by Annotation )

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

413
            $this->/** @scrutinizer ignore-call */ 
414
                   set_key( $key );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
414
        }
415
416
		return $key;
417
    }
418
419
    /**
420
	 * Get the invoice type.
421
	 *
422
	 * @since 1.0.19
423
	 * @param  string $context View or edit context.
424
	 * @return string
425
	 */
426
	public function get_type( $context = 'view' ) {
427
        return $this->get_prop( 'type', $context );
428
    }
429
430
    /**
431
	 * Get the invoice mode.
432
	 *
433
	 * @since 1.0.19
434
	 * @param  string $context View or edit context.
435
	 * @return string
436
	 */
437
	public function get_mode( $context = 'view' ) {
438
        return $this->get_prop( 'mode', $context );
439
    }
440
441
    /**
442
	 * Get the invoice name/title.
443
	 *
444
	 * @since 1.0.19
445
	 * @param  string $context View or edit context.
446
	 * @return string
447
	 */
448
	public function get_name( $context = 'view' ) {
449
        $name = $this->get_prop( 'title', $context );
450
451
		return empty( $name ) ? $this->get_number( $context ) : $name;
452
    }
453
454
    /**
455
	 * Alias of self::get_name().
456
	 *
457
	 * @since 1.0.19
458
	 * @param  string $context View or edit context.
459
	 * @return string
460
	 */
461
	public function get_title( $context = 'view' ) {
462
		return $this->get_name( $context );
463
    }
464
465
    /**
466
	 * Get the invoice description.
467
	 *
468
	 * @since 1.0.19
469
	 * @param  string $context View or edit context.
470
	 * @return string
471
	 */
472
	public function get_description( $context = 'view' ) {
473
		return $this->get_prop( 'description', $context );
474
    }
475
476
    /**
477
	 * Alias of self::get_description().
478
	 *
479
	 * @since 1.0.19
480
	 * @param  string $context View or edit context.
481
	 * @return string
482
	 */
483
	public function get_excerpt( $context = 'view' ) {
484
		return $this->get_description( $context );
485
    }
486
487
    /**
488
	 * Alias of self::get_description().
489
	 *
490
	 * @since 1.0.19
491
	 * @param  string $context View or edit context.
492
	 * @return string
493
	 */
494
	public function get_summary( $context = 'view' ) {
495
		return $this->get_description( $context );
496
    }
497
498
    /**
499
	 * Get the customer id.
500
	 *
501
	 * @since 1.0.19
502
	 * @param  string $context View or edit context.
503
	 * @return int
504
	 */
505
	public function get_author( $context = 'view' ) {
506
		return (int) $this->get_prop( 'author', $context );
507
    }
508
509
    /**
510
	 * Alias of self::get_author().
511
	 *
512
	 * @since 1.0.19
513
	 * @param  string $context View or edit context.
514
	 * @return int
515
	 */
516
	public function get_user_id( $context = 'view' ) {
517
		return $this->get_author( $context );
518
    }
519
520
     /**
521
	 * Alias of self::get_author().
522
	 *
523
	 * @since 1.0.19
524
	 * @param  string $context View or edit context.
525
	 * @return int
526
	 */
527
	public function get_customer_id( $context = 'view' ) {
528
		return $this->get_author( $context );
529
    }
530
531
    /**
532
	 * Get the customer's ip.
533
	 *
534
	 * @since 1.0.19
535
	 * @param  string $context View or edit context.
536
	 * @return string
537
	 */
538
	public function get_ip( $context = 'view' ) {
539
		return $this->get_prop( 'user_ip', $context );
540
    }
541
542
    /**
543
	 * Alias of self::get_ip().
544
	 *
545
	 * @since 1.0.19
546
	 * @param  string $context View or edit context.
547
	 * @return int
548
	 */
549
	public function get_user_ip( $context = 'view' ) {
550
		return $this->get_ip( $context );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_ip() has too many arguments starting with $context. ( Ignorable by Annotation )

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

550
		return $this->/** @scrutinizer ignore-call */ get_ip( $context );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
551
    }
552
553
     /**
554
	 * Alias of self::get_ip().
555
	 *
556
	 * @since 1.0.19
557
	 * @param  string $context View or edit context.
558
	 * @return int
559
	 */
560
	public function get_customer_ip( $context = 'view' ) {
561
		return $this->get_ip( $context );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_ip() has too many arguments starting with $context. ( Ignorable by Annotation )

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

561
		return $this->/** @scrutinizer ignore-call */ get_ip( $context );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
562
    }
563
564
    /**
565
	 * Get the customer's first name.
566
	 *
567
	 * @since 1.0.19
568
	 * @param  string $context View or edit context.
569
	 * @return string
570
	 */
571
	public function get_first_name( $context = 'view' ) {
572
		return $this->get_prop( 'first_name', $context );
573
    }
574
575
    /**
576
	 * Alias of self::get_first_name().
577
	 *
578
	 * @since 1.0.19
579
	 * @param  string $context View or edit context.
580
	 * @return int
581
	 */
582
	public function get_user_first_name( $context = 'view' ) {
583
		return $this->get_first_name( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_first_name($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
584
    }
585
586
     /**
587
	 * Alias of self::get_first_name().
588
	 *
589
	 * @since 1.0.19
590
	 * @param  string $context View or edit context.
591
	 * @return int
592
	 */
593
	public function get_customer_first_name( $context = 'view' ) {
594
		return $this->get_first_name( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_first_name($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
595
    }
596
597
    /**
598
	 * Get the customer's last name.
599
	 *
600
	 * @since 1.0.19
601
	 * @param  string $context View or edit context.
602
	 * @return string
603
	 */
604
	public function get_last_name( $context = 'view' ) {
605
		return $this->get_prop( 'last_name', $context );
606
    }
607
    
608
    /**
609
	 * Alias of self::get_last_name().
610
	 *
611
	 * @since 1.0.19
612
	 * @param  string $context View or edit context.
613
	 * @return int
614
	 */
615
	public function get_user_last_name( $context = 'view' ) {
616
		return $this->get_last_name( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_last_name($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
617
    }
618
619
    /**
620
	 * Alias of self::get_last_name().
621
	 *
622
	 * @since 1.0.19
623
	 * @param  string $context View or edit context.
624
	 * @return int
625
	 */
626
	public function get_customer_last_name( $context = 'view' ) {
627
		return $this->get_last_name( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_last_name($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
628
    }
629
630
    /**
631
	 * Get the customer's full name.
632
	 *
633
	 * @since 1.0.19
634
	 * @param  string $context View or edit context.
635
	 * @return string
636
	 */
637
	public function get_full_name( $context = 'view' ) {
638
		return trim( $this->get_first_name( $context ) . ' ' . $this->get_last_name( $context ) );
639
    }
640
641
    /**
642
	 * Alias of self::get_full_name().
643
	 *
644
	 * @since 1.0.19
645
	 * @param  string $context View or edit context.
646
	 * @return int
647
	 */
648
	public function get_user_full_name( $context = 'view' ) {
649
		return $this->get_full_name( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_full_name($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
650
    }
651
652
    /**
653
	 * Alias of self::get_full_name().
654
	 *
655
	 * @since 1.0.19
656
	 * @param  string $context View or edit context.
657
	 * @return int
658
	 */
659
	public function get_customer_full_name( $context = 'view' ) {
660
		return $this->get_full_name( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_full_name($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
661
    }
662
663
    /**
664
	 * Get the customer's phone number.
665
	 *
666
	 * @since 1.0.19
667
	 * @param  string $context View or edit context.
668
	 * @return string
669
	 */
670
	public function get_phone( $context = 'view' ) {
671
		return $this->get_prop( 'phone', $context );
672
    }
673
674
    /**
675
	 * Alias of self::get_phone().
676
	 *
677
	 * @since 1.0.19
678
	 * @param  string $context View or edit context.
679
	 * @return int
680
	 */
681
	public function get_phone_number( $context = 'view' ) {
682
		return $this->get_phone( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_phone($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
683
    }
684
685
    /**
686
	 * Alias of self::get_phone().
687
	 *
688
	 * @since 1.0.19
689
	 * @param  string $context View or edit context.
690
	 * @return int
691
	 */
692
	public function get_user_phone( $context = 'view' ) {
693
		return $this->get_phone( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_phone($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
694
    }
695
696
    /**
697
	 * Alias of self::get_phone().
698
	 *
699
	 * @since 1.0.19
700
	 * @param  string $context View or edit context.
701
	 * @return int
702
	 */
703
	public function get_customer_phone( $context = 'view' ) {
704
		return $this->get_phone( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_phone($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
705
    }
706
707
    /**
708
	 * Get the customer's email address.
709
	 *
710
	 * @since 1.0.19
711
	 * @param  string $context View or edit context.
712
	 * @return string
713
	 */
714
	public function get_email( $context = 'view' ) {
715
		return $this->get_prop( 'email', $context );
716
    }
717
718
    /**
719
	 * Alias of self::get_email().
720
	 *
721
	 * @since 1.0.19
722
	 * @param  string $context View or edit context.
723
	 * @return string
724
	 */
725
	public function get_email_address( $context = 'view' ) {
726
		return $this->get_email( $context );
727
    }
728
729
    /**
730
	 * Alias of self::get_email().
731
	 *
732
	 * @since 1.0.19
733
	 * @param  string $context View or edit context.
734
	 * @return int
735
	 */
736
	public function get_user_email( $context = 'view' ) {
737
		return $this->get_email( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_email($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
738
    }
739
740
    /**
741
	 * Alias of self::get_email().
742
	 *
743
	 * @since 1.0.19
744
	 * @param  string $context View or edit context.
745
	 * @return int
746
	 */
747
	public function get_customer_email( $context = 'view' ) {
748
		return $this->get_email( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_email($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
749
    }
750
751
    /**
752
	 * Get the customer's country.
753
	 *
754
	 * @since 1.0.19
755
	 * @param  string $context View or edit context.
756
	 * @return string
757
	 */
758
	public function get_country( $context = 'view' ) {
759
		return $this->get_prop( 'country', $context );
760
    }
761
762
    /**
763
	 * Alias of self::get_country().
764
	 *
765
	 * @since 1.0.19
766
	 * @param  string $context View or edit context.
767
	 * @return int
768
	 */
769
	public function get_user_country( $context = 'view' ) {
770
		return $this->get_country( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_country($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
771
    }
772
773
    /**
774
	 * Alias of self::get_country().
775
	 *
776
	 * @since 1.0.19
777
	 * @param  string $context View or edit context.
778
	 * @return int
779
	 */
780
	public function get_customer_country( $context = 'view' ) {
781
		return $this->get_country( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_country($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
782
    }
783
784
    /**
785
	 * Get the customer's state.
786
	 *
787
	 * @since 1.0.19
788
	 * @param  string $context View or edit context.
789
	 * @return string
790
	 */
791
	public function get_state( $context = 'view' ) {
792
		return $this->get_prop( 'state', $context );
793
    }
794
795
    /**
796
	 * Alias of self::get_state().
797
	 *
798
	 * @since 1.0.19
799
	 * @param  string $context View or edit context.
800
	 * @return int
801
	 */
802
	public function get_user_state( $context = 'view' ) {
803
		return $this->get_state( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_state($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
804
    }
805
806
    /**
807
	 * Alias of self::get_state().
808
	 *
809
	 * @since 1.0.19
810
	 * @param  string $context View or edit context.
811
	 * @return int
812
	 */
813
	public function get_customer_state( $context = 'view' ) {
814
		return $this->get_state( $context );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_state($context) returns the type string which is incompatible with the documented return type integer.
Loading history...
815
    }
816
817
    /**
818
	 * Get the customer's city.
819
	 *
820
	 * @since 1.0.19
821
	 * @param  string $context View or edit context.
822
	 * @return string
823
	 */
824
	public function get_city( $context = 'view' ) {
825
		return $this->get_prop( 'city', $context );
826
    }
827
828
    /**
829
	 * Alias of self::get_city().
830
	 *
831
	 * @since 1.0.19
832
	 * @param  string $context View or edit context.
833
	 * @return string
834
	 */
835
	public function get_user_city( $context = 'view' ) {
836
		return $this->get_city( $context );
837
    }
838
839
    /**
840
	 * Alias of self::get_city().
841
	 *
842
	 * @since 1.0.19
843
	 * @param  string $context View or edit context.
844
	 * @return string
845
	 */
846
	public function get_customer_city( $context = 'view' ) {
847
		return $this->get_city( $context );
848
    }
849
850
    /**
851
	 * Get the customer's zip.
852
	 *
853
	 * @since 1.0.19
854
	 * @param  string $context View or edit context.
855
	 * @return string
856
	 */
857
	public function get_zip( $context = 'view' ) {
858
		return $this->get_prop( 'zip', $context );
859
    }
860
861
    /**
862
	 * Alias of self::get_zip().
863
	 *
864
	 * @since 1.0.19
865
	 * @param  string $context View or edit context.
866
	 * @return string
867
	 */
868
	public function get_user_zip( $context = 'view' ) {
869
		return $this->get_zip( $context );
870
    }
871
872
    /**
873
	 * Alias of self::get_zip().
874
	 *
875
	 * @since 1.0.19
876
	 * @param  string $context View or edit context.
877
	 * @return string
878
	 */
879
	public function get_customer_zip( $context = 'view' ) {
880
		return $this->get_zip( $context );
881
    }
882
883
    /**
884
	 * Get the customer's company.
885
	 *
886
	 * @since 1.0.19
887
	 * @param  string $context View or edit context.
888
	 * @return string
889
	 */
890
	public function get_company( $context = 'view' ) {
891
		return $this->get_prop( 'company', $context );
892
    }
893
894
    /**
895
	 * Alias of self::get_company().
896
	 *
897
	 * @since 1.0.19
898
	 * @param  string $context View or edit context.
899
	 * @return string
900
	 */
901
	public function get_user_company( $context = 'view' ) {
902
		return $this->get_company( $context );
903
    }
904
905
    /**
906
	 * Alias of self::get_company().
907
	 *
908
	 * @since 1.0.19
909
	 * @param  string $context View or edit context.
910
	 * @return string
911
	 */
912
	public function get_customer_company( $context = 'view' ) {
913
		return $this->get_company( $context );
914
    }
915
916
    /**
917
	 * Get the customer's vat number.
918
	 *
919
	 * @since 1.0.19
920
	 * @param  string $context View or edit context.
921
	 * @return string
922
	 */
923
	public function get_vat_number( $context = 'view' ) {
924
		return $this->get_prop( 'vat_number', $context );
925
    }
926
927
    /**
928
	 * Alias of self::get_vat_number().
929
	 *
930
	 * @since 1.0.19
931
	 * @param  string $context View or edit context.
932
	 * @return string
933
	 */
934
	public function get_user_vat_number( $context = 'view' ) {
935
		return $this->get_vat_number( $context );
936
    }
937
938
    /**
939
	 * Alias of self::get_vat_number().
940
	 *
941
	 * @since 1.0.19
942
	 * @param  string $context View or edit context.
943
	 * @return string
944
	 */
945
	public function get_customer_vat_number( $context = 'view' ) {
946
		return $this->get_vat_number( $context );
947
    }
948
949
    /**
950
	 * Get the customer's vat rate.
951
	 *
952
	 * @since 1.0.19
953
	 * @param  string $context View or edit context.
954
	 * @return string
955
	 */
956
	public function get_vat_rate( $context = 'view' ) {
957
		return $this->get_prop( 'vat_rate', $context );
958
    }
959
960
    /**
961
	 * Alias of self::get_vat_rate().
962
	 *
963
	 * @since 1.0.19
964
	 * @param  string $context View or edit context.
965
	 * @return string
966
	 */
967
	public function get_user_vat_rate( $context = 'view' ) {
968
		return $this->get_vat_rate( $context );
969
    }
970
971
    /**
972
	 * Alias of self::get_vat_rate().
973
	 *
974
	 * @since 1.0.19
975
	 * @param  string $context View or edit context.
976
	 * @return string
977
	 */
978
	public function get_customer_vat_rate( $context = 'view' ) {
979
		return $this->get_vat_rate( $context );
980
    }
981
982
    /**
983
	 * Get the customer's address.
984
	 *
985
	 * @since 1.0.19
986
	 * @param  string $context View or edit context.
987
	 * @return string
988
	 */
989
	public function get_address( $context = 'view' ) {
990
		return $this->get_prop( 'address', $context );
991
    }
992
993
    /**
994
	 * Alias of self::get_address().
995
	 *
996
	 * @since 1.0.19
997
	 * @param  string $context View or edit context.
998
	 * @return string
999
	 */
1000
	public function get_user_address( $context = 'view' ) {
1001
		return $this->get_address( $context );
1002
    }
1003
1004
    /**
1005
	 * Alias of self::get_address().
1006
	 *
1007
	 * @since 1.0.19
1008
	 * @param  string $context View or edit context.
1009
	 * @return string
1010
	 */
1011
	public function get_customer_address( $context = 'view' ) {
1012
		return $this->get_address( $context );
1013
    }
1014
1015
    /**
1016
	 * Get whether the customer has confirmed their address.
1017
	 *
1018
	 * @since 1.0.19
1019
	 * @param  string $context View or edit context.
1020
	 * @return bool
1021
	 */
1022
	public function get_address_confirmed( $context = 'view' ) {
1023
		return (bool) $this->get_prop( 'address_confirmed', $context );
1024
    }
1025
1026
    /**
1027
	 * Alias of self::get_address_confirmed().
1028
	 *
1029
	 * @since 1.0.19
1030
	 * @param  string $context View or edit context.
1031
	 * @return bool
1032
	 */
1033
	public function get_user_address_confirmed( $context = 'view' ) {
1034
		return $this->get_address_confirmed( $context );
1035
    }
1036
1037
    /**
1038
	 * Alias of self::get_address().
1039
	 *
1040
	 * @since 1.0.19
1041
	 * @param  string $context View or edit context.
1042
	 * @return bool
1043
	 */
1044
	public function get_customer_address_confirmed( $context = 'view' ) {
1045
		return $this->get_address_confirmed( $context );
1046
    }
1047
1048
    /**
1049
	 * Get the invoice subtotal.
1050
	 *
1051
	 * @since 1.0.19
1052
	 * @param  string $context View or edit context.
1053
	 * @return float
1054
	 */
1055
	public function get_subtotal( $context = 'view' ) {
1056
		return (float) $this->get_prop( 'subtotal', $context );
1057
    }
1058
1059
    /**
1060
	 * Get the invoice discount total.
1061
	 *
1062
	 * @since 1.0.19
1063
	 * @param  string $context View or edit context.
1064
	 * @return float
1065
	 */
1066
	public function get_total_discount( $context = 'view' ) {
1067
		return (float) $this->get_prop( 'total_discount', $context );
1068
    }
1069
1070
    /**
1071
	 * Get the invoice tax total.
1072
	 *
1073
	 * @since 1.0.19
1074
	 * @param  string $context View or edit context.
1075
	 * @return float
1076
	 */
1077
	public function get_total_tax( $context = 'view' ) {
1078
		return (float) $this->get_prop( 'total_tax', $context );
1079
    }
1080
1081
    /**
1082
	 * Get the invoice fees total.
1083
	 *
1084
	 * @since 1.0.19
1085
	 * @param  string $context View or edit context.
1086
	 * @return float
1087
	 */
1088
	public function get_total_fees( $context = 'view' ) {
1089
		return (float) $this->get_prop( 'total_fees', $context );
1090
    }
1091
1092
    /**
1093
	 * Get the invoice total.
1094
	 *
1095
	 * @since 1.0.19
1096
     * @param  string $context View or edit context.
1097
     * @return float
1098
	 */
1099
	public function get_total( $context = 'view' ) {
1100
		$total = $this->get_subtotal( $context ) + $this->get_total_fees( $context ) - $this->get_total_discount(  $context  ) + $this->get_total_tax(  $context  );
1101
		$total = apply_filters( 'getpaid_get_invoice_total_amount', $total, $this  );
1102
		return (float) wpinv_sanitize_amount( $total );
1103
    }
1104
    
1105
    /**
1106
	 * Get the invoice fees.
1107
	 *
1108
	 * @since 1.0.19
1109
	 * @param  string $context View or edit context.
1110
	 * @return array
1111
	 */
1112
	public function get_fees( $context = 'view' ) {
1113
		return wpinv_parse_list( $this->get_prop( 'fees', $context ) );
1114
    }
1115
1116
    /**
1117
	 * Get the invoice discounts.
1118
	 *
1119
	 * @since 1.0.19
1120
	 * @param  string $context View or edit context.
1121
	 * @return array
1122
	 */
1123
	public function get_discounts( $context = 'view' ) {
1124
		return wpinv_parse_list( $this->get_prop( 'discounts', $context ) );
1125
    }
1126
1127
    /**
1128
	 * Get the invoice taxes.
1129
	 *
1130
	 * @since 1.0.19
1131
	 * @param  string $context View or edit context.
1132
	 * @return array
1133
	 */
1134
	public function get_taxes( $context = 'view' ) {
1135
		return wpinv_parse_list( $this->get_prop( 'taxes', $context ) );
1136
    }
1137
1138
    /**
1139
	 * Get the invoice items.
1140
	 *
1141
	 * @since 1.0.19
1142
	 * @param  string $context View or edit context.
1143
	 * @return array
1144
	 */
1145
	public function get_items( $context = 'view' ) {
1146
		return wpinv_parse_list( $this->get_prop( 'items', $context ) );
1147
    }
1148
1149
    /**
1150
	 * Get the invoice's payment form.
1151
	 *
1152
	 * @since 1.0.19
1153
	 * @param  string $context View or edit context.
1154
	 * @return int
1155
	 */
1156
	public function get_payment_form( $context = 'view' ) {
1157
		return intval( $this->get_prop( 'payment_form', $context ) );
1158
    }
1159
1160
    /**
1161
	 * Get the invoice's submission id.
1162
	 *
1163
	 * @since 1.0.19
1164
	 * @param  string $context View or edit context.
1165
	 * @return string
1166
	 */
1167
	public function get_submission_id( $context = 'view' ) {
1168
		return $this->get_prop( 'submission_id', $context );
1169
    }
1170
1171
    /**
1172
	 * Get the invoice's discount code.
1173
	 *
1174
	 * @since 1.0.19
1175
	 * @param  string $context View or edit context.
1176
	 * @return string
1177
	 */
1178
	public function get_discount_code( $context = 'view' ) {
1179
		return $this->get_prop( 'discount_code', $context );
1180
    }
1181
1182
    /**
1183
	 * Get the invoice's gateway.
1184
	 *
1185
	 * @since 1.0.19
1186
	 * @param  string $context View or edit context.
1187
	 * @return string
1188
	 */
1189
	public function get_gateway( $context = 'view' ) {
1190
		return $this->get_prop( 'gateway', $context );
1191
    }
1192
1193
    /**
1194
	 * Get the invoice's transaction id.
1195
	 *
1196
	 * @since 1.0.19
1197
	 * @param  string $context View or edit context.
1198
	 * @return string
1199
	 */
1200
	public function get_transaction_id( $context = 'view' ) {
1201
		return $this->get_prop( 'transaction_id', $context );
1202
    }
1203
1204
    /**
1205
	 * Get the invoice's currency.
1206
	 *
1207
	 * @since 1.0.19
1208
	 * @param  string $context View or edit context.
1209
	 * @return string
1210
	 */
1211
	public function get_currency( $context = 'view' ) {
1212
        $currency = $this->get_prop( 'currency', $context );
1213
        return empty( $currency ) ? wpinv_get_currency() : $currency;
1214
    }
1215
1216
    /**
1217
	 * Checks if we are charging taxes for this invoice.
1218
	 *
1219
	 * @since 1.0.19
1220
	 * @param  string $context View or edit context.
1221
	 * @return bool
1222
	 */
1223
	public function get_disable_taxes( $context = 'view' ) {
1224
        return (bool) $this->get_prop( 'disable_taxes', $context );
1225
    }
1226
1227
    /**
1228
     * Retrieves an invoice key.
1229
     */
1230
    public function get( $key ) {
1231
        if ( method_exists( $this, 'get_' . $key ) ) {
1232
            $value = call_user_func( array( $this, 'get_' . $key ) );
1233
        } else {
1234
            $value = $this->$key;
1235
        }
1236
1237
        return $value;
1238
    }
1239
1240
     /**
1241
     * Sets an invoice key.
1242
     */
1243
    public function set( $key, $value ) {
1244
        $ignore = array( 'items', 'cart_details', 'fees', '_ID' );
1245
1246
        if ( $key === 'status' ) {
1247
            $this->old_status = $this->status;
0 ignored issues
show
Bug Best Practice introduced by
The property old_status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1248
        }
1249
1250
        if ( ! in_array( $key, $ignore ) ) {
1251
            $this->pending[ $key ] = $value;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1252
        }
1253
1254
        if( '_ID' !== $key ) {
1255
            $this->$key = $value;
1256
        }
1257
    }
1258
1259
    /**
1260
     * Checks if an invoice key is set.
1261
     */
1262
    public function _isset( $name ) {
1263
        if ( property_exists( $this, $name) ) {
1264
            return false === empty( $this->$name );
1265
        } else {
1266
            return null;
1267
        }
1268
    }
1269
1270
    /**
1271
     * @param int|WPInv_Invoice|WP_Post $invoice The invoice.
1272
     */
1273
    private function setup_invoice( $invoice ) {
1274
        global $wpdb;
1275
        $this->pending = array();
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1276
1277
        if ( empty( $invoice ) ) {
1278
            return false;
1279
        }
1280
1281
        if ( is_a( $invoice, 'WPInv_Invoice' ) ) {
1282
            foreach ( get_object_vars( $invoice ) as $prop => $value ) {
1283
                $this->$prop = $value;
1284
            }
1285
            return true;
1286
        }
1287
1288
        // Retrieve post object.
1289
        $invoice      = get_post( $invoice );
1290
1291
        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...
1292
            return false;
1293
        }
1294
1295
        if( ! ( 'wpi_invoice' == $invoice->post_type OR 'wpi_quote' == $invoice->post_type ) ) {
1296
            return false;
1297
        }
1298
1299
        // Retrieve post data.
1300
        $table = $wpdb->prefix . 'getpaid_invoices';
1301
        $data  = $wpdb->get_row(
1302
            $wpdb->prepare( "SELECT * FROM $table WHERE post_id=%d", $invoice->ID )
1303
        );
1304
1305
        do_action( 'wpinv_pre_setup_invoice', $this, $invoice->ID, $data );
1306
1307
        // Primary Identifier
1308
        $this->ID              = absint( $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...
1309
        $this->post_type       = $invoice->post_type;
0 ignored issues
show
Bug Best Practice introduced by
The property post_type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1310
1311
        $this->date            = $invoice->post_date;
0 ignored issues
show
Bug Best Practice introduced by
The property date does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1312
        $this->status          = $invoice->post_status;
0 ignored issues
show
Bug Best Practice introduced by
The property status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1313
1314
        if ( 'future' == $this->status ) {
1315
            $this->status = 'publish';
1316
        }
1317
1318
        $this->post_status     = $this->status;
0 ignored issues
show
Bug Best Practice introduced by
The property post_status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1319
        $this->description     = $invoice->post_excerpt;
0 ignored issues
show
Bug Best Practice introduced by
The property description does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1320
        $this->parent_invoice  = $invoice->post_parent;
0 ignored issues
show
Bug Best Practice introduced by
The property parent_invoice does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1321
        $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...
1322
        $this->status_nicename = $this->setup_status_nicename( $invoice->post_status );
0 ignored issues
show
Bug Best Practice introduced by
The property status_nicename does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1323
1324
        $this->user_id         = ! empty( $invoice->post_author ) ? $invoice->post_author : get_current_user_id();
0 ignored issues
show
Bug Best Practice introduced by
The property user_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1325
        $this->email           = get_the_author_meta( 'email', $this->user_id );
0 ignored issues
show
Bug Best Practice introduced by
The property email does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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

1325
        $this->email           = get_the_author_meta( 'email', /** @scrutinizer ignore-type */ $this->user_id );
Loading history...
1326
        $this->currency        = wpinv_get_currency();
0 ignored issues
show
Bug Best Practice introduced by
The property currency does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1327
        $this->setup_invoice_data( $data );
1328
1329
        // Other Identifiers
1330
        $this->title           = ! empty( $invoice->post_title ) ? $invoice->post_title : $this->number;
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1331
1332
        // Allow extensions to add items to this object via hook
1333
        do_action( 'wpinv_setup_invoice', $this, $invoice->ID, $data );
1334
1335
        return true;
1336
    }
1337
1338
    /**
1339
     * @param stdClass $data The invoice data.
1340
     */
1341
    private function setup_invoice_data( $data ) {
1342
1343
        if ( empty( $data ) ) {
1344
            $this->number = $this->setup_invoice_number( $data );
0 ignored issues
show
Bug Best Practice introduced by
The property number does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1345
            return;
1346
        }
1347
1348
        $data = map_deep( $data, 'maybe_unserialize' );
1349
1350
        $this->payment_meta    = is_array( $data->custom_meta ) ? $data->custom_meta : array();
0 ignored issues
show
Bug Best Practice introduced by
The property payment_meta does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1351
        $this->due_date        = $data->due_date;
0 ignored issues
show
Bug Best Practice introduced by
The property due_date does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1352
        $this->completed_date  = $data->completed_date;
0 ignored issues
show
Bug Best Practice introduced by
The property completed_date does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1353
        $this->mode            = $data->mode;
0 ignored issues
show
Bug Best Practice introduced by
The property mode does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1354
1355
        // Items
1356
        $this->fees            = $this->setup_fees();
0 ignored issues
show
Bug Best Practice introduced by
The property fees does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1357
        $this->cart_details    = $this->setup_cart_details();
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1358
        $this->items           = ! empty( $this->payment_meta['items'] ) ? $this->payment_meta['items'] : array();
0 ignored issues
show
Bug Best Practice introduced by
The property items does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1359
1360
        // Currency Based
1361
        $this->total           = $data->total;
0 ignored issues
show
Bug Best Practice introduced by
The property total does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1362
        $this->disable_taxes   = (int) $data->disable_taxes;
0 ignored issues
show
Bug Best Practice introduced by
The property disable_taxes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1363
        $this->tax             = $data->tax;
0 ignored issues
show
Bug Best Practice introduced by
The property tax does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1364
        $this->fees_total      = $data->fees_total;
0 ignored issues
show
Bug Best Practice introduced by
The property fees_total does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1365
        $this->subtotal        = $data->subtotal;
0 ignored issues
show
Bug Best Practice introduced by
The property subtotal does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1366
        $this->currency        = empty( $data->currency ) ? wpinv_get_currency() : $data->currency ;
0 ignored issues
show
Bug Best Practice introduced by
The property currency does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1367
1368
        // Gateway based
1369
        $this->gateway         = $data->gateway;
0 ignored issues
show
Bug Best Practice introduced by
The property gateway does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1370
        $this->gateway_title   = $this->setup_gateway_title();
0 ignored issues
show
Bug Best Practice introduced by
The property gateway_title does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1371
        $this->transaction_id  = $data->transaction_id;
0 ignored issues
show
Bug Best Practice introduced by
The property transaction_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1372
1373
        // User based
1374
        $this->ip              = $data->user_ip;
0 ignored issues
show
Bug Best Practice introduced by
The property ip does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1375
        $this->user_info       = ! empty( $this->payment_meta['user_info'] ) ? $this->payment_meta['user_info'] : array();
0 ignored issues
show
Bug Best Practice introduced by
The property user_info does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1376
1377
        $this->first_name      = $data->first_name;
0 ignored issues
show
Bug Best Practice introduced by
The property first_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1378
        $this->last_name       = $data->last_name;
0 ignored issues
show
Bug Best Practice introduced by
The property last_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1379
        $this->company         = $data->company;
0 ignored issues
show
Bug Best Practice introduced by
The property company does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1380
        $this->vat_number      = $data->vat_number;
0 ignored issues
show
Bug Best Practice introduced by
The property vat_number does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1381
        $this->vat_rate        = $data->vat_rate;
0 ignored issues
show
Bug Best Practice introduced by
The property vat_rate does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1382
        $this->adddress_confirmed  = (int) $data->adddress_confirmed;
0 ignored issues
show
Bug Best Practice introduced by
The property adddress_confirmed does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1383
        $this->address         = $data->address;
0 ignored issues
show
Bug Best Practice introduced by
The property address does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1384
        $this->city            = $data->city;
0 ignored issues
show
Bug Best Practice introduced by
The property city does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1385
        $this->country         = $data->country;
0 ignored issues
show
Bug Best Practice introduced by
The property country does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1386
        $this->state           = $data->state;
0 ignored issues
show
Bug Best Practice introduced by
The property state does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1387
        $this->zip             = $data->zip;
0 ignored issues
show
Bug Best Practice introduced by
The property zip does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1388
        $this->phone           = ! empty( $this->user_info['phone'] ) ? $this->user_info['phone'] : '';
0 ignored issues
show
Bug Best Practice introduced by
The property phone does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1389
1390
        $this->discounts       = ! empty( $this->user_info['discount'] ) ? $this->user_info['discount'] : '';
0 ignored issues
show
Bug Best Practice introduced by
The property discounts does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1391
        $this->discount        = $data->discount;
0 ignored issues
show
Bug Best Practice introduced by
The property discount does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1392
        $this->discount_code   = $data->discount_code;
0 ignored issues
show
Bug Best Practice introduced by
The property discount_code does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1393
1394
        // Other Identifiers
1395
        $this->key             = $data->key;
0 ignored issues
show
Bug Best Practice introduced by
The property key does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1396
        $this->number          = $this->setup_invoice_number( $data );
1397
1398
        $this->full_name       = trim( $this->first_name . ' '. $this->last_name );
0 ignored issues
show
Bug Best Practice introduced by
The property full_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1399
1400
1401
        return true;
1402
    }
1403
1404
1405
    /**
1406
     * Sets up the status nice name.
1407
     */
1408
    private function setup_status_nicename( $status ) {
1409
        $all_invoice_statuses  = wpinv_get_invoice_statuses( true, true, $this );
1410
1411
        if ( $this->is_quote() && class_exists( 'Wpinv_Quotes_Shared' ) ) {
1412
            $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...
1413
        }
1414
        $status   = isset( $all_invoice_statuses[$status] ) ? $all_invoice_statuses[$status] : __( $status, 'invoicing' );
1415
1416
        return apply_filters( 'setup_status_nicename', $status );
1417
    }
1418
1419
    /**
1420
     * Set's up the invoice number.
1421
     */
1422
    private function setup_invoice_number( $data ) {
1423
1424
        if ( ! empty( $data ) && ! empty( $data->number ) ) {
1425
            return $data->number;
1426
        }
1427
1428
        $number = $this->ID;
1429
1430
        if ( $this->status == 'auto-draft' && wpinv_sequential_number_active( $this->post_type ) ) {
1431
            $next_number = wpinv_get_next_invoice_number( $this->post_type );
1432
            $number      = $next_number;
1433
        }
1434
        
1435
        return wpinv_format_invoice_number( $number, $this->post_type );
1436
1437
    }
1438
1439
    /**
1440
     * Invoice's post name.
1441
     */
1442
    private function setup_post_name( $post = NULL ) {
1443
        global $wpdb;
1444
        
1445
        $post_name = '';
1446
1447
        if ( !empty( $post ) ) {
1448
            if( !empty( $post->post_name ) ) {
1449
                $post_name = $post->post_name;
1450
            } else if ( !empty( $post->ID ) ) {
1451
                $post_name = wpinv_generate_post_name( $post->ID );
1452
1453
                $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
1454
            }
1455
        }
1456
1457
        $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...
1458
    }
1459
1460
    /**
1461
     * Set's up the cart details.
1462
     */
1463
    public function setup_cart_details() {
1464
        global $wpdb;
1465
1466
        $table =  $wpdb->prefix . 'getpaid_invoice_items';
1467
        $items = $wpdb->get_results(
1468
            $wpdb->prepare( "SELECT * FROM $table WHERE `post_id`=%d", $this->ID )
1469
        );
1470
1471
        if ( empty( $items ) ) {
1472
            return array();
1473
        }
1474
1475
        $details = array();
1476
1477
        foreach ( $items as $item ) {
1478
            $item = (array) $item;
1479
            $details[] = array(
1480
                'name'          => $item['item_name'],
1481
                'id'            => $item['item_id'],
1482
                'item_price'    => $item['item_price'],
1483
                'custom_price'  => $item['custom_price'],
1484
                'quantity'      => $item['quantity'],
1485
                'discount'      => $item['discount'],
1486
                'subtotal'      => $item['subtotal'],
1487
                'tax'           => $item['tax'],
1488
                'price'         => $item['price'],
1489
                'vat_rate'      => $item['vat_rate'],
1490
                'vat_class'     => $item['vat_class'],
1491
                'meta'          => $item['meta'],
1492
                'fees'          => $item['fees'],
1493
            );
1494
        }
1495
1496
        return map_deep( $details, 'maybe_unserialize' );
1497
1498
    }
1499
1500
    /**
1501
     * Convert this to an array.
1502
     */
1503
    public function array_convert() {
1504
        return get_object_vars( $this );
1505
    }
1506
    
1507
    private function setup_fees() {
1508
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
1509
        return $payment_fees;
1510
    }
1511
1512
    private function setup_gateway_title() {
1513
        $gateway_title = wpinv_get_gateway_checkout_label( $this->gateway );
1514
        return $gateway_title;
1515
    }
1516
    
1517
    /**
1518
     * Refreshes payment data.
1519
     */
1520
    private function refresh_payment_data() {
1521
1522
        $payment_data = array(
1523
            'price'        => $this->total,
1524
            'date'         => $this->date,
1525
            'user_email'   => $this->email,
1526
            'invoice_key'  => $this->key,
1527
            'currency'     => $this->currency,
1528
            'items'        => $this->items,
1529
            'user_info' => array(
1530
                'user_id'    => $this->user_id,
1531
                'email'      => $this->email,
1532
                'first_name' => $this->first_name,
1533
                'last_name'  => $this->last_name,
1534
                'address'    => $this->address,
1535
                'phone'      => $this->phone,
1536
                'city'       => $this->city,
1537
                'country'    => $this->country,
1538
                'state'      => $this->state,
1539
                'zip'        => $this->zip,
1540
                'company'    => $this->company,
1541
                'vat_number' => $this->vat_number,
1542
                'discount'   => $this->discounts,
1543
            ),
1544
            'cart_details' => $this->cart_details,
1545
            'status'       => $this->status,
1546
            'fees'         => $this->fees,
1547
        );
1548
1549
        $this->payment_meta = array_merge( $this->payment_meta, $payment_data );
0 ignored issues
show
Bug Best Practice introduced by
The property payment_meta does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1550
1551
    }
1552
1553
    private function insert_invoice() {
1554
1555
        if ( empty( $this->post_type ) ) {
1556
            if ( !empty( $this->ID ) && $post_type = get_post_type( $this->ID ) ) {
1557
                $this->post_type = $post_type;
0 ignored issues
show
Bug Best Practice introduced by
The property post_type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1558
            } else if ( !empty( $this->parent_invoice ) && $post_type = get_post_type( $this->parent_invoice ) ) {
1559
                $this->post_type = $post_type;
1560
            } else {
1561
                $this->post_type = 'wpi_invoice';
1562
            }
1563
        }
1564
1565
        $invoice_number = $this->ID;
1566
        if ( $number = $this->number ) {
1567
            $invoice_number = $number;
1568
        }
1569
1570
        if ( empty( $this->key ) ) {
1571
            $this->key = $this->generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The property key does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1572
            $this->pending['key'] = $this->key;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1573
        }
1574
1575
        if ( empty( $this->ip ) ) {
1576
            $this->ip = wpinv_get_ip();
0 ignored issues
show
Bug Best Practice introduced by
The property ip does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1577
            $this->pending['ip'] = $this->ip;
1578
        }
1579
1580
        $payment_data = array(
1581
            'price'        => $this->total,
1582
            'date'         => $this->date,
1583
            'user_email'   => $this->email,
1584
            'invoice_key'  => $this->key,
1585
            'currency'     => $this->currency,
1586
            'items'        => $this->items,
1587
            'user_info' => array(
1588
                'user_id'    => $this->user_id,
1589
                'email'      => $this->email,
1590
                'first_name' => $this->first_name,
1591
                'last_name'  => $this->last_name,
1592
                'address'    => $this->address,
1593
                'phone'      => $this->phone,
1594
                'city'       => $this->city,
1595
                'country'    => $this->country,
1596
                'state'      => $this->state,
1597
                'zip'        => $this->zip,
1598
                'company'    => $this->company,
1599
                'vat_number' => $this->vat_number,
1600
                'discount'   => $this->discounts,
1601
            ),
1602
            'cart_details' => $this->cart_details,
1603
            'status'       => $this->status,
1604
            'fees'         => $this->fees,
1605
        );
1606
1607
        $post_data = array(
1608
            'post_title'    => $invoice_number,
1609
            'post_status'   => $this->status,
1610
            'post_author'   => $this->user_id,
1611
            'post_type'     => $this->post_type,
1612
            'post_excerpt'  => $this->description,
1613
            'post_date'     => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? $this->date : current_time( 'mysql' ),
1614
            'post_date_gmt' => ! empty( $this->date ) && $this->date != '0000-00-00 00:00:00' ? get_gmt_from_date( $this->date ) : current_time( 'mysql', 1 ),
1615
            'post_parent'   => $this->parent_invoice,
1616
        );
1617
        $args = apply_filters( 'wpinv_insert_invoice_args', $post_data, $this );
1618
1619
        // Create a blank invoice
1620
        if ( !empty( $this->ID ) ) {
1621
            $args['ID']         = $this->ID;
1622
            $invoice_id = wp_update_post( $args, true );
1623
        } else {
1624
            $invoice_id = wp_insert_post( $args, true );
1625
        }
1626
1627
        if ( is_wp_error( $invoice_id ) ) {
1628
            return false;
1629
        }
1630
1631
        if ( ! empty( $invoice_id ) ) {
1632
            $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...
1633
            $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...
1634
1635
            $this->payment_meta = array_merge( $this->payment_meta, $payment_data );
0 ignored issues
show
Bug Best Practice introduced by
The property payment_meta does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1636
1637
            if ( ! empty( $this->payment_meta['fees'] ) ) {
1638
                $this->fees = array_merge( $this->fees, $this->payment_meta['fees'] );
0 ignored issues
show
Bug Best Practice introduced by
The property fees does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1639
                foreach( $this->fees as $fee ) {
1640
                    $this->increase_fees( $fee['amount'] );
1641
                }
1642
            }
1643
1644
            $this->pending['payment_meta'] = $this->payment_meta;
1645
            $this->save();
1646
        }
1647
1648
        return $this->ID;
1649
    }
1650
1651
    /**
1652
     * Saves special fields in our custom table.
1653
     */
1654
    public function get_special_fields() {
1655
1656
        return array (
1657
            'post_id'        => $this->ID,
1658
            'number'         => $this->get_number(),
1659
            'key'            => $this->get_key(),
1660
            'type'           => str_replace( 'wpi_', '', $this->post_type ),
1661
            'mode'           => $this->mode,
1662
            'user_ip'        => $this->get_ip(),
1663
            'first_name'     => $this->get_first_name(),
1664
            'last_name'      => $this->get_last_name(),
1665
            'address'        => $this->get_address(),
1666
            'city'           => $this->city,
1667
            'state'          => $this->state,
1668
            'country'        => $this->country,
1669
            'zip'            => $this->zip,
1670
            'adddress_confirmed' => (int) $this->adddress_confirmed,
1671
            'gateway'        => $this->get_gateway(),
1672
            'transaction_id' => $this->get_transaction_id(),
1673
            'currency'       => $this->get_currency(),
1674
            'subtotal'       => $this->get_subtotal(),
1675
            'tax'            => $this->get_tax(),
1676
            'fees_total'     => $this->get_fees_total(),
1677
            'total'          => $this->get_total(),
1678
            'discount'       => $this->get_discount(),
1679
            'discount_code'  => $this->get_discount_code(),
1680
            'disable_taxes'  => $this->disable_taxes,
1681
            'due_date'       => $this->get_due_date(),
1682
            'completed_date' => $this->get_completed_date(),
1683
            'company'        => $this->company,
1684
            'vat_number'     => $this->vat_number,
1685
            'vat_rate'       => $this->vat_rate,
1686
            'custom_meta'    => $this->payment_meta
1687
        );
1688
1689
    }
1690
1691
    /**
1692
     * Saves special fields in our custom table.
1693
     */
1694
    public function save_special() {
1695
        global $wpdb;
1696
1697
        $this->refresh_payment_data();
1698
1699
        $fields = $this->get_special_fields();
1700
        $fields = array_map( 'maybe_serialize', $fields );
1701
1702
        $table =  $wpdb->prefix . 'getpaid_invoices';
1703
1704
        $id = (int) $this->ID;
1705
1706
        if ( empty( $id ) ) {
1707
            return;
1708
        }
1709
1710
        if ( $wpdb->get_var( "SELECT `post_id` FROM $table WHERE `post_id`=$id" ) ) {
1711
1712
            $wpdb->update( $table, $fields, array( 'post_id' => $id ) );
1713
1714
        } else {
1715
1716
            $wpdb->insert( $table, $fields );
1717
1718
        }
1719
1720
        $table =  $wpdb->prefix . 'getpaid_invoice_items';
1721
        $wpdb->delete( $table, array( 'post_id' => $this->ID ) );
1722
1723
        foreach ( $this->get_cart_details() as $details ) {
1724
            $fields = array(
1725
                'post_id'          => $this->ID,
1726
                'item_id'          => $details['id'],
1727
                'item_name'        => $details['name'],
1728
                'item_description' => empty( $details['meta']['description'] ) ? '' : $details['meta']['description'],
1729
                'vat_rate'         => $details['vat_rate'],
1730
                'vat_class'        => empty( $details['vat_class'] ) ? '_standard' : $details['vat_class'],
1731
                'tax'              => $details['tax'],
1732
                'item_price'       => $details['item_price'],
1733
                'custom_price'     => $details['custom_price'],
1734
                'quantity'         => $details['quantity'],
1735
                'discount'         => $details['discount'],
1736
                'subtotal'         => $details['subtotal'],
1737
                'price'            => $details['price'],
1738
                'meta'             => $details['meta'],
1739
                'fees'             => $details['fees'],
1740
            );
1741
1742
            $item_columns = array_keys ( $fields );
1743
1744
            foreach ( $fields as $key => $val ) {
1745
                if ( is_null( $val ) ) {
1746
                    $val = '';
1747
                }
1748
                $val = maybe_serialize( $val );
1749
                $fields[ $key ] = $wpdb->prepare( '%s', $val );
1750
            }
1751
1752
            $fields = implode( ', ', $fields );
1753
            $item_rows[] = "($fields)";
1754
        }
1755
1756
        $item_rows    = implode( ', ', $item_rows );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_rows seems to be defined by a foreach iteration on line 1723. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1757
        $item_columns = implode( ', ', $item_columns );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_columns seems to be defined by a foreach iteration on line 1723. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1758
        $wpdb->query( "INSERT INTO $table ($item_columns) VALUES $item_rows" );
1759
    }
1760
1761
    public function save( $setup = false ) {
1762
        global $wpi_session;
1763
1764
        $saved = false;
1765
        if ( empty( $this->items ) ) {
1766
            return $saved;
1767
        }
1768
1769
        if ( empty( $this->key ) ) {
1770
            $this->key = $this->generate_key();
0 ignored issues
show
Bug Best Practice introduced by
The property key does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1771
        }
1772
1773
        if ( empty( $this->ID ) ) {
1774
            $invoice_id = $this->insert_invoice();
1775
1776
            if ( false === $invoice_id ) {
1777
                $saved = false;
1778
            } else {
1779
                $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...
1780
            }
1781
        }
1782
1783
        // If we have something pending, let's save it
1784
        if ( ! empty( $this->pending ) ) {
1785
            $total_increase = 0;
1786
            $total_decrease = 0;
1787
1788
            foreach ( $this->pending as $key => $value ) {
1789
1790
                switch( $key ) {
1791
                    case 'items':
1792
                        // Update totals for pending items
1793
                        foreach ( $this->pending[ $key ] as $item ) {
1794
                            switch( $item['action'] ) {
1795
                                case 'add':
1796
                                    $price = $item['price'];
1797
                                    $taxes = $item['tax'];
0 ignored issues
show
Unused Code introduced by
The assignment to $taxes is dead and can be removed.
Loading history...
1798
1799
                                    if ( 'publish' === $this->status ) {
1800
                                        $total_increase += $price;
1801
                                    }
1802
                                    break;
1803
1804
                                case 'remove':
1805
                                    if ( 'publish' === $this->status ) {
1806
                                        $total_decrease += $item['price'];
1807
                                    }
1808
                                    break;
1809
                            }
1810
                        }
1811
                        break;
1812
                    case 'fees':
1813
                        if ( 'publish' !== $this->status ) {
1814
                            break;
1815
                        }
1816
1817
                        if ( empty( $this->pending[ $key ] ) ) {
1818
                            break;
1819
                        }
1820
1821
                        foreach ( $this->pending[ $key ] as $fee ) {
1822
                            switch( $fee['action'] ) {
1823
                                case 'add':
1824
                                    $total_increase += $fee['amount'];
1825
                                    break;
1826
1827
                                case 'remove':
1828
                                    $total_decrease += $fee['amount'];
1829
                                    break;
1830
                            }
1831
                        }
1832
                        break;
1833
                    case 'status':
1834
                        $this->update_status( $this->status );
1835
                        break;
1836
                    case 'first_name':
1837
                        $this->user_info['first_name'] = $this->first_name;
0 ignored issues
show
Bug Best Practice introduced by
The property user_info does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1838
                        break;
1839
                    case 'last_name':
1840
                        $this->user_info['last_name'] = $this->last_name;
1841
                        break;
1842
                    case 'phone':
1843
                        $this->user_info['phone'] = $this->phone;
1844
                        break;
1845
                    case 'address':
1846
                        $this->user_info['address'] = $this->address;
1847
                        break;
1848
                    case 'city':
1849
                        $this->user_info['city'] = $this->city;
1850
                        break;
1851
                    case 'country':
1852
                        $this->user_info['country'] = $this->country;
1853
                        break;
1854
                    case 'state':
1855
                        $this->user_info['state'] = $this->state;
1856
                        break;
1857
                    case 'zip':
1858
                        $this->user_info['zip'] = $this->zip;
1859
                        break;
1860
                    case 'company':
1861
                        $this->user_info['company'] = $this->company;
1862
                        break;
1863
                    case 'vat_number':
1864
                        $this->user_info['vat_number'] = $this->vat_number;
1865
                        
1866
                        $vat_info = $wpi_session->get( 'user_vat_data' );
1867
                        if ( $this->vat_number && !empty( $vat_info ) && isset( $vat_info['number'] ) && isset( $vat_info['valid'] ) && $vat_info['number'] == $this->vat_number ) {
1868
                            $adddress_confirmed = isset( $vat_info['adddress_confirmed'] ) ? $vat_info['adddress_confirmed'] : false;
1869
                            $this->update_meta( '_wpinv_adddress_confirmed', (bool)$adddress_confirmed );
1870
                            $this->user_info['adddress_confirmed'] = (bool)$adddress_confirmed;
1871
                            $this->adddress_confirmed = (bool)$adddress_confirmed;
0 ignored issues
show
Bug Best Practice introduced by
The property adddress_confirmed does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1872
                        }
1873
    
1874
                        break;
1875
                    case 'vat_rate':
1876
                        $this->user_info['vat_rate'] = $this->vat_rate;
1877
                        break;
1878
                    case 'adddress_confirmed':
1879
                        $this->user_info['adddress_confirmed'] = $this->adddress_confirmed;
1880
                        break;
1881
                    case 'date':
1882
                        $args = array(
1883
                            'ID'        => $this->ID,
1884
                            'post_date' => $this->date,
1885
                            'edit_date' => true,
1886
                        );
1887
1888
                        wp_update_post( $args );
1889
                        break;
1890
                    case 'due_date':
1891
                        if ( empty( $this->due_date ) ) {
1892
                            $this->due_date = 'none';
0 ignored issues
show
Bug Best Practice introduced by
The property due_date does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1893
                        }
1894
                        break;
1895
                    case 'discounts':
1896
                        if ( ! is_array( $this->discounts ) ) {
1897
                            $this->discounts = explode( ',', $this->discounts );
0 ignored issues
show
Bug Best Practice introduced by
The property discounts does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1898
                        }
1899
1900
                        $this->user_info['discount'] = implode( ',', $this->discounts );
1901
                        break;
1902
                    case 'parent_invoice':
1903
                        $args = array(
1904
                            'ID'          => $this->ID,
1905
                            'post_parent' => $this->parent_invoice,
1906
                        );
1907
                        wp_update_post( $args );
1908
                        break;
1909
                    default:
1910
                        do_action( 'wpinv_save', $this, $key );
1911
                        break;
1912
                }
1913
            }
1914
1915
            $this->items    = array_values( $this->items );
0 ignored issues
show
Bug Best Practice introduced by
The property items does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1916
1917
            $this->pending      = array();
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1918
            $saved              = true;
1919
        }
1920
1921
        $new_meta = array(
1922
            'items'         => $this->items,
1923
            'cart_details'  => $this->cart_details,
1924
            'fees'          => $this->fees,
1925
            'currency'      => $this->currency,
1926
            'user_info'     => $this->user_info,
1927
        );
1928
        $this->payment_meta = array_merge( $this->payment_meta, $new_meta );
0 ignored issues
show
Bug Best Practice introduced by
The property payment_meta does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1929
        $this->update_items();
1930
1931
        $this->save_special();
1932
        do_action( 'wpinv_invoice_save', $this, $saved );
1933
1934
        if ( true === $saved || $setup ) {
1935
            $this->setup_invoice( $this->ID );
1936
        }
1937
1938
        $this->refresh_item_ids();
1939
1940
        return $saved;
1941
    }
1942
    
1943
    public function add_fee( $args, $global = true ) {
1944
        $default_args = array(
1945
            'label'       => '',
1946
            'amount'      => 0,
1947
            'type'        => 'fee',
1948
            'id'          => '',
1949
            'no_tax'      => false,
1950
            'item_id'     => 0,
1951
        );
1952
1953
        $fee = wp_parse_args( $args, $default_args );
1954
        
1955
        if ( empty( $fee['label'] ) ) {
1956
            return false;
1957
        }
1958
        
1959
        $fee['id']  = sanitize_title( $fee['label'] );
1960
        
1961
        $this->fees[]               = $fee;
0 ignored issues
show
Bug Best Practice introduced by
The property fees does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1962
        
1963
        $added_fee               = $fee;
1964
        $added_fee['action']     = 'add';
1965
        $this->pending['fees'][] = $added_fee;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1966
        reset( $this->fees );
1967
1968
        $this->increase_fees( $fee['amount'] );
1969
        return true;
1970
    }
1971
1972
    public function remove_fee( $key ) {
1973
        $removed = false;
1974
1975
        if ( is_numeric( $key ) ) {
1976
            $removed = $this->remove_fee_by( 'index', $key );
1977
        }
1978
1979
        return $removed;
1980
    }
1981
1982
    public function remove_fee_by( $key, $value, $global = false ) {
1983
        $allowed_fee_keys = apply_filters( 'wpinv_fee_keys', array(
1984
            'index', 'label', 'amount', 'type',
1985
        ) );
1986
1987
        if ( ! in_array( $key, $allowed_fee_keys ) ) {
1988
            return false;
1989
        }
1990
1991
        $removed = false;
1992
        if ( 'index' === $key && array_key_exists( $value, $this->fees ) ) {
1993
            $removed_fee             = $this->fees[ $value ];
1994
            $removed_fee['action']   = 'remove';
1995
            $this->pending['fees'][] = $removed_fee;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
1996
1997
            $this->decrease_fees( $removed_fee['amount'] );
1998
1999
            unset( $this->fees[ $value ] );
2000
            $removed = true;
2001
        } else if ( 'index' !== $key ) {
2002
            foreach ( $this->fees as $index => $fee ) {
2003
                if ( isset( $fee[ $key ] ) && $fee[ $key ] == $value ) {
2004
                    $removed_fee             = $fee;
2005
                    $removed_fee['action']   = 'remove';
2006
                    $this->pending['fees'][] = $removed_fee;
2007
2008
                    $this->decrease_fees( $removed_fee['amount'] );
2009
2010
                    unset( $this->fees[ $index ] );
2011
                    $removed = true;
2012
2013
                    if ( false === $global ) {
2014
                        break;
2015
                    }
2016
                }
2017
            }
2018
        }
2019
2020
        if ( true === $removed ) {
2021
            $this->fees = array_values( $this->fees );
0 ignored issues
show
Bug Best Practice introduced by
The property fees does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2022
        }
2023
2024
        return $removed;
2025
    }
2026
2027
    
2028
2029
    public function add_note( $note = '', $customer_type = false, $added_by_user = false, $system = false ) {
2030
        // Bail if no note specified
2031
        if( !$note ) {
2032
            return false;
2033
        }
2034
2035
        if ( empty( $this->ID ) )
2036
            return false;
2037
        
2038
        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...
2039
            $user                 = get_user_by( 'id', get_current_user_id() );
2040
            $comment_author       = $user->display_name;
2041
            $comment_author_email = $user->user_email;
2042
        } else {
2043
            $comment_author       = 'System';
2044
            $comment_author_email = 'system@';
2045
            $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
2046
            $comment_author_email = sanitize_email( $comment_author_email );
2047
        }
2048
2049
        do_action( 'wpinv_pre_insert_invoice_note', $this->ID, $note, $customer_type );
2050
2051
        $note_id = wp_insert_comment( wp_filter_comment( array(
2052
            'comment_post_ID'      => $this->ID,
2053
            'comment_content'      => $note,
2054
            'comment_agent'        => 'WPInvoicing',
2055
            'user_id'              => is_admin() ? get_current_user_id() : 0,
2056
            'comment_date'         => current_time( 'mysql' ),
2057
            'comment_date_gmt'     => current_time( 'mysql', 1 ),
2058
            'comment_approved'     => 1,
2059
            'comment_parent'       => 0,
2060
            'comment_author'       => $comment_author,
2061
            'comment_author_IP'    => wpinv_get_ip(),
2062
            'comment_author_url'   => '',
2063
            'comment_author_email' => $comment_author_email,
2064
            'comment_type'         => 'wpinv_note'
2065
        ) ) );
2066
2067
        do_action( 'wpinv_insert_payment_note', $note_id, $this->ID, $note );
2068
        
2069
        if ( $customer_type ) {
2070
            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

2070
            add_comment_meta( /** @scrutinizer ignore-type */ $note_id, '_wpi_customer_note', 1 );
Loading history...
2071
2072
            do_action( 'wpinv_new_customer_note', array( 'invoice_id' => $this->ID, 'user_note' => $note ) );
2073
        }
2074
2075
        return $note_id;
2076
    }
2077
2078
    private function increase_subtotal( $amount = 0.00 ) {
2079
        $amount          = (float) $amount;
2080
        $this->subtotal += $amount;
2081
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
0 ignored issues
show
Bug Best Practice introduced by
The property subtotal does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2082
2083
        $this->recalculate_total();
2084
    }
2085
2086
    private function decrease_subtotal( $amount = 0.00 ) {
2087
        $amount          = (float) $amount;
2088
        $this->subtotal -= $amount;
2089
        $this->subtotal  = wpinv_round_amount( $this->subtotal );
0 ignored issues
show
Bug Best Practice introduced by
The property subtotal does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2090
2091
        if ( $this->subtotal < 0 ) {
2092
            $this->subtotal = 0;
2093
        }
2094
2095
        $this->recalculate_total();
2096
    }
2097
2098
    private function increase_fees( $amount = 0.00 ) {
2099
        $amount            = (float)$amount;
2100
        $this->fees_total += $amount;
2101
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
0 ignored issues
show
Bug Best Practice introduced by
The property fees_total does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2102
2103
        $this->recalculate_total();
2104
    }
2105
2106
    private function decrease_fees( $amount = 0.00 ) {
2107
        $amount            = (float) $amount;
2108
        $this->fees_total -= $amount;
2109
        $this->fees_total  = wpinv_round_amount( $this->fees_total );
0 ignored issues
show
Bug Best Practice introduced by
The property fees_total does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2110
2111
        if ( $this->fees_total < 0 ) {
2112
            $this->fees_total = 0;
2113
        }
2114
2115
        $this->recalculate_total();
2116
    }
2117
2118
    public function recalculate_total() {
2119
        global $wpi_nosave;
2120
        
2121
        $this->total = $this->subtotal + $this->tax + $this->fees_total - $this->discount;
0 ignored issues
show
Bug Best Practice introduced by
The property total does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2122
        $this->total = wpinv_round_amount( $this->total );
2123
        
2124
        do_action( 'wpinv_invoice_recalculate_total', $this, $wpi_nosave );
2125
    }
2126
    
2127
    public function increase_tax( $amount = 0.00 ) {
2128
        $amount       = (float) $amount;
2129
        $this->tax   += $amount;
2130
2131
        $this->recalculate_total();
2132
    }
2133
2134
    public function decrease_tax( $amount = 0.00 ) {
2135
        $amount     = (float) $amount;
2136
        $this->tax -= $amount;
2137
2138
        if ( $this->tax < 0 ) {
2139
            $this->tax = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property tax does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2140
        }
2141
2142
        $this->recalculate_total();
2143
    }
2144
2145
    public function update_status( $new_status = false, $note = '', $manual = false ) {
2146
        $old_status = ! empty( $this->old_status ) ? $this->old_status : get_post_status( $this->ID );
2147
2148
        if ( $old_status === $new_status && in_array( $new_status, array_keys( wpinv_get_invoice_statuses( true ) ) ) ) {
2149
            return false; // Don't permit status changes that aren't changes
2150
        }
2151
2152
        $do_change = apply_filters( 'wpinv_should_update_invoice_status', true, $this->ID, $new_status, $old_status );
2153
        $updated = false;
2154
2155
        if ( $do_change ) {
2156
            do_action( 'wpinv_before_invoice_status_change', $this->ID, $new_status, $old_status );
2157
2158
            $update_post_data                   = array();
2159
            $update_post_data['ID']             = $this->ID;
2160
            $update_post_data['post_status']    = $new_status;
2161
            $update_post_data['edit_date']      = current_time( 'mysql', 0 );
2162
            $update_post_data['edit_date_gmt']  = current_time( 'mysql', 1 );
2163
            
2164
            $update_post_data = apply_filters( 'wpinv_update_invoice_status_fields', $update_post_data, $this->ID );
2165
2166
            $updated = wp_update_post( $update_post_data );     
2167
           
2168
            // Process any specific status functions
2169
            switch( $new_status ) {
2170
                case 'wpi-refunded':
2171
                    $this->process_refund();
2172
                    break;
2173
                case 'wpi-failed':
2174
                    $this->process_failure();
2175
                    break;
2176
                case 'wpi-pending':
2177
                    $this->process_pending();
2178
                    break;
2179
            }
2180
            
2181
            // Status was changed.
2182
            do_action( 'wpinv_status_' . $new_status, $this->ID, $old_status );
2183
            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|mixed|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

2183
            do_action( 'wpinv_status_' . /** @scrutinizer ignore-type */ $old_status . '_to_' . $new_status, $this->ID, $old_status );
Loading history...
2184
            do_action( 'wpinv_update_status', $this->ID, $new_status, $old_status );
2185
        }
2186
2187
        return $updated;
2188
    }
2189
2190
    public function refund() {
2191
        $this->old_status        = $this->status;
0 ignored issues
show
Bug Best Practice introduced by
The property old_status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2192
        $this->status            = 'wpi-refunded';
0 ignored issues
show
Bug Best Practice introduced by
The property status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2193
        $this->pending['status'] = $this->status;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2194
2195
        $this->save();
2196
    }
2197
2198
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
2199
        if ( empty( $meta_key ) ) {
2200
            return false;
2201
        }
2202
2203
        if ( $meta_key == 'key' || $meta_key == 'date' ) {
2204
            $current_meta = $this->get_meta();
2205
            $current_meta[ $meta_key ] = $meta_value;
2206
2207
            $meta_key     = '_wpinv_payment_meta';
2208
            $meta_value   = $current_meta;
2209
        }
2210
2211
        $key  = str_ireplace( '_wpinv_', '', $meta_key );
2212
        $this->$key = $meta_value;
2213
2214
        $special = array_keys( $this->get_special_fields() );
2215
        if ( in_array( $key, $special ) ) {
2216
            $this->save_special();
2217
        } else {
2218
            $meta_value = apply_filters( 'wpinv_update_payment_meta_' . $meta_key, $meta_value, $this->ID );
2219
        }
2220
2221
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
2222
    }
2223
2224
    private function process_refund() {
2225
        $process_refund = true;
2226
2227
        // If the payment was not in publish, don't decrement stats as they were never incremented
2228
        if ( 'publish' != $this->old_status || 'wpi-refunded' != $this->status ) {
2229
            $process_refund = false;
2230
        }
2231
2232
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
2233
        $process_refund = apply_filters( 'wpinv_should_process_refund', $process_refund, $this );
2234
2235
        if ( false === $process_refund ) {
2236
            return;
2237
        }
2238
2239
        do_action( 'wpinv_pre_refund_invoice', $this );
2240
        
2241
        $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...
2242
        $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...
2243
        $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...
2244
        
2245
        do_action( 'wpinv_post_refund_invoice', $this );
2246
    }
2247
2248
    private function process_failure() {
2249
        $discounts = $this->discounts;
2250
        if ( empty( $discounts ) ) {
2251
            return;
2252
        }
2253
2254
        if ( ! is_array( $discounts ) ) {
2255
            $discounts = array_map( 'trim', explode( ',', $discounts ) );
2256
        }
2257
2258
        foreach ( $discounts as $discount ) {
2259
            wpinv_decrease_discount_usage( $discount );
2260
        }
2261
    }
2262
    
2263
    private function process_pending() {
2264
        $process_pending = true;
2265
2266
        // If the payment was not in publish or revoked status, don't decrement stats as they were never incremented
2267
        if ( ( 'publish' != $this->old_status && 'revoked' != $this->old_status ) || 'wpi-pending' != $this->status ) {
2268
            $process_pending = false;
2269
        }
2270
2271
        // Allow extensions to filter for their own payment types, Example: Recurring Payments
2272
        $process_pending = apply_filters( 'wpinv_should_process_pending', $process_pending, $this );
2273
2274
        if ( false === $process_pending ) {
2275
            return;
2276
        }
2277
2278
        $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...
2279
        $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...
2280
        $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...
2281
2282
        $this->completed_date = '';
0 ignored issues
show
Bug Best Practice introduced by
The property completed_date does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2283
        $this->update_meta( '_wpinv_completed_date', '' );
2284
    }
2285
    
2286
    // get data
2287
    public function get_meta( $meta_key = '_wpinv_payment_meta', $single = true ) {
2288
        $meta = get_post_meta( $this->ID, $meta_key, $single );
2289
2290
        if ( $meta_key === '_wpinv_payment_meta' ) {
2291
2292
            if(!is_array($meta)){$meta = array();} // we need this to be an array so make sure it is.
2293
2294
            if ( empty( $meta['key'] ) ) {
2295
                $meta['key'] = $this->key;
2296
            }
2297
2298
            if ( empty( $meta['date'] ) ) {
2299
                $meta['date'] = get_post_field( 'post_date', $this->ID );
2300
            }
2301
        }
2302
2303
        $meta = apply_filters( 'wpinv_get_invoice_meta_' . $meta_key, $meta, $this->ID );
2304
2305
        return apply_filters( 'wpinv_get_invoice_meta', $meta, $this->ID, $meta_key );
2306
    }
2307
    
2308
    public function get_status( $nicename = false ) {
2309
        if ( !$nicename ) {
2310
            $status = $this->status;
2311
        } else {
2312
            $status = $this->status_nicename;
2313
        }
2314
        
2315
        return apply_filters( 'wpinv_get_status', $status, $nicename, $this->ID, $this );
2316
    }
2317
2318
    public function get_cart_details() {
2319
        return apply_filters( 'wpinv_cart_details', $this->cart_details, $this->ID, $this );
2320
    }
2321
    
2322
    public function get_subtotal( $currency = false ) {
2323
        $subtotal = wpinv_round_amount( $this->subtotal );
2324
        
2325
        if ( $currency ) {
2326
            $subtotal = wpinv_price( wpinv_format_amount( $subtotal, NULL, !$currency ), $this->get_currency() );
2327
        }
2328
        
2329
        return apply_filters( 'wpinv_get_invoice_subtotal', $subtotal, $this->ID, $this, $currency );
2330
    }
2331
    
2332
    public function get_total( $currency = false ) {        
2333
        if ( $this->is_free_trial() ) {
2334
            $total = wpinv_round_amount( 0 );
2335
        } else {
2336
            $total = wpinv_round_amount( $this->total );
2337
        }
2338
        if ( $currency ) {
2339
            $total = wpinv_price( wpinv_format_amount( $total, NULL, !$currency ), $this->get_currency() );
2340
        }
2341
        
2342
        return apply_filters( 'wpinv_get_invoice_total', $total, $this->ID, $this, $currency );
2343
    }
2344
2345
    /**
2346
     * Returns recurring payment details.
2347
     */
2348
    public function get_recurring_details( $field = '', $currency = false ) {        
2349
        $data                 = array();
2350
        $data['cart_details'] = $this->cart_details;
2351
        $data['subtotal']     = $this->get_subtotal();
2352
        $data['discount']     = $this->get_discount();
2353
        $data['tax']          = $this->get_tax();
2354
        $data['total']        = $this->get_total();
2355
2356
        if ( $this->is_parent() || $this->is_renewal() ) {
2357
2358
            // Use the parent to calculate recurring details.
2359
            if ( $this->is_renewal() ){
2360
                $parent = $this->get_parent_payment();
2361
            } else {
2362
                $parent = $this;
2363
            }
2364
2365
            if ( empty( $parent ) ) {
2366
                $parent = $this;
2367
            }
2368
2369
            // Subtotal.
2370
            $data['subtotal'] = wpinv_round_amount( $parent->subtotal );
2371
            $data['tax']      = wpinv_round_amount( $parent->tax );
2372
            $data['discount'] = wpinv_round_amount( $parent->discount );
2373
2374
            if ( $data['discount'] > 0 && $parent->discount_first_payment_only() ) {
2375
                $data['discount'] = wpinv_round_amount( 0 );
2376
            }
2377
2378
            $data['total'] = wpinv_round_amount( $data['subtotal'] + $data['tax'] - $data['discount'] );
2379
2380
        }
2381
        
2382
        $data = apply_filters( 'wpinv_get_invoice_recurring_details', $data, $this, $field, $currency );
2383
2384
        if ( $data['total'] < 0 ) {
2385
            $data['total'] = 0;
2386
        }
2387
2388
        if ( isset( $data[$field] ) ) {
2389
            return ( $currency ? wpinv_price( $data[$field], $this->get_currency() ) : $data[$field] );
2390
        }
2391
        
2392
        return $data;
2393
    }
2394
    
2395
    public function get_final_tax( $currency = false ) {        
2396
        $final_total = wpinv_round_amount( $this->tax );
2397
        if ( $currency ) {
2398
            $final_total = wpinv_price( wpinv_format_amount( $final_total, NULL, !$currency ), $this->get_currency() );
2399
        }
2400
        
2401
        return apply_filters( 'wpinv_get_invoice_final_total', $final_total, $this, $currency );
2402
    }
2403
    
2404
    public function get_discounts( $array = false ) {
2405
        $discounts = $this->discounts;
2406
2407
        if ( ! is_array( $discounts ) ) {
2408
            $discounts = explode( ',', $discounts );
2409
        }
2410
2411
        $discounts = array_filter( $discounts );
2412
2413
        if ( ! $array ) {
2414
            $discounts = implode( ',', $discounts );
2415
        }
2416
2417
        return apply_filters( 'wpinv_payment_discounts', $discounts, $this->ID, $this, $array );
2418
    }
2419
    
2420
    public function get_discount( $currency = false, $dash = false ) {
2421
        if ( !empty( $this->discounts ) ) {
2422
            global $ajax_cart_details;
2423
            $ajax_cart_details = $this->get_cart_details();
2424
            
2425
            if ( !empty( $ajax_cart_details ) && count( $ajax_cart_details ) == count( $this->items ) ) {
2426
                $cart_items = $ajax_cart_details;
2427
            } else {
2428
                $cart_items = $this->items;
2429
            }
2430
2431
            $this->discount = wpinv_get_cart_items_discount_amount( $cart_items , $this->discounts );
0 ignored issues
show
Bug Best Practice introduced by
The property discount does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2432
        }
2433
        $discount   = wpinv_round_amount( $this->discount );
2434
        $dash       = $dash && $discount > 0 ? '&ndash;' : '';
2435
        
2436
        if ( $currency ) {
2437
            $discount = wpinv_price( wpinv_format_amount( $discount, NULL, !$currency ), $this->get_currency() );
2438
        }
2439
        
2440
        $discount   = $dash . $discount;
2441
        
2442
        return apply_filters( 'wpinv_get_invoice_discount', $discount, $this->ID, $this, $currency, $dash );
2443
    }
2444
    
2445
    public function get_discount_code() {
2446
        return $this->discount_code;
2447
    }
2448
2449
    // Checks if the invoice is taxable. Does not check if taxes are enabled on the site.
2450
    public function is_taxable() {
2451
        return (int) $this->disable_taxes === 0;
2452
    }
2453
2454
    public function get_tax( $currency = false ) {
2455
        $tax = wpinv_round_amount( $this->tax );
2456
2457
        if ( $currency ) {
2458
            $tax = wpinv_price( wpinv_format_amount( $tax, NULL, !$currency ), $this->get_currency() );
2459
        }
2460
2461
        if ( ! $this->is_taxable() ) {
2462
            $tax = wpinv_round_amount( 0.00 );
2463
        }
2464
2465
        return apply_filters( 'wpinv_get_invoice_tax', $tax, $this->ID, $this, $currency );
2466
    }
2467
    
2468
    public function get_fees( $type = 'all' ) {
2469
        $fees    = array();
2470
2471
        if ( ! empty( $this->fees ) && is_array( $this->fees ) ) {
2472
            foreach ( $this->fees as $fee ) {
2473
                if( 'all' != $type && ! empty( $fee['type'] ) && $type != $fee['type'] ) {
2474
                    continue;
2475
                }
2476
2477
                $fee['label'] = stripslashes( $fee['label'] );
2478
                $fee['amount_display'] = wpinv_price( $fee['amount'], $this->get_currency() );
2479
                $fees[]    = $fee;
2480
            }
2481
        }
2482
2483
        return apply_filters( 'wpinv_get_invoice_fees', $fees, $this->ID, $this );
2484
    }
2485
    
2486
    public function get_fees_total( $type = 'all' ) {
2487
        $fees_total = (float) 0.00;
2488
2489
        $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array();
2490
        if ( ! empty( $payment_fees ) ) {
2491
            foreach ( $payment_fees as $fee ) {
2492
                $fees_total += (float) $fee['amount'];
2493
            }
2494
        }
2495
2496
        return apply_filters( 'wpinv_get_invoice_fees_total', $fees_total, $this->ID, $this );
2497
2498
    }
2499
2500
    public function get_user_id() {
2501
        return apply_filters( 'wpinv_user_id', $this->user_id, $this->ID, $this );
2502
    }
2503
    
2504
    public function get_user_full_name() {
2505
        return apply_filters( 'wpinv_user_full_name', $this->full_name, $this->ID, $this );
2506
    }
2507
    
2508
    public function get_user_info() {
2509
        return apply_filters( 'wpinv_user_info', $this->user_info, $this->ID, $this );
2510
    }
2511
    
2512
    public function get_items() {
2513
        return apply_filters( 'wpinv_payment_meta_items', $this->items, $this->ID, $this );
2514
    }
2515
    
2516
    public function get_gateway() {
2517
        return apply_filters( 'wpinv_gateway', $this->gateway, $this->ID, $this );
2518
    }
2519
    
2520
    public function get_gateway_title() {
2521
        $this->gateway_title = !empty( $this->gateway_title ) ? $this->gateway_title : wpinv_get_gateway_checkout_label( $this->gateway );
0 ignored issues
show
Bug Best Practice introduced by
The property gateway_title does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2522
        
2523
        return apply_filters( 'wpinv_gateway_title', $this->gateway_title, $this->ID, $this );
2524
    }
2525
    
2526
    public function get_currency() {
2527
        return apply_filters( 'wpinv_currency_code', $this->currency, $this->ID, $this );
2528
    }
2529
    
2530
    public function get_due_date( $display = false ) {
2531
        $due_date = apply_filters( 'wpinv_due_date', $this->due_date, $this->ID, $this );
2532
        
2533
        if ( !$display || empty( $due_date ) ) {
2534
            return $due_date;
2535
        }
2536
        
2537
        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

2537
        return date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $due_date ) );
Loading history...
2538
    }
2539
    
2540
    public function get_completed_date() {
2541
        return apply_filters( 'wpinv_completed_date', $this->completed_date, $this->ID, $this );
2542
    }
2543
    
2544
    public function get_invoice_date( $formatted = true ) {
2545
        $date_completed = $this->completed_date;
2546
        $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? $date_completed : '';
2547
        
2548
        if ( $invoice_date == '' ) {
2549
            $date_created   = $this->date;
2550
            $invoice_date   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? $date_created : '';
2551
        }
2552
        
2553
        if ( $formatted && $invoice_date ) {
2554
            $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

2554
            $invoice_date   = date_i18n( /** @scrutinizer ignore-type */ get_option( 'date_format' ), strtotime( $invoice_date ) );
Loading history...
2555
        }
2556
2557
        return apply_filters( 'wpinv_get_invoice_date', $invoice_date, $formatted, $this->ID, $this );
2558
    }
2559
    
2560
    public function get_ip() {
2561
        return apply_filters( 'wpinv_user_ip', $this->ip, $this->ID, $this );
2562
    }
2563
2564
    /**
2565
     * Checks if the invoice has a given status.
2566
     */
2567
    public function has_status( $status ) {
2568
        $status = wpinv_parse_list( $status );
2569
        return apply_filters( 'wpinv_has_status', in_array( $this->get_status(), $status ), $status );
2570
    }
2571
    
2572
    public function add_item( $item_id = 0, $args = array() ) {
2573
        global $wpi_current_id, $wpi_item_id;
2574
    
2575
        $item = new WPInv_Item( $item_id );
2576
2577
        // Bail if this post isn't a item
2578
        if( !$item || $item->post_type !== 'wpi_item' ) {
0 ignored issues
show
introduced by
$item is of type WPInv_Item, thus it always evaluated to true.
Loading history...
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...
2579
            return false;
2580
        }
2581
        
2582
        $has_quantities = wpinv_item_quantities_enabled();
2583
2584
        // Set some defaults
2585
        $defaults = array(
2586
            'quantity'      => 1,
2587
            'id'            => false,
2588
            'name'          => $item->get_name(),
2589
            'item_price'    => false,
2590
            'custom_price'  => '',
2591
            'discount'      => 0,
2592
            'tax'           => 0.00,
2593
            'meta'          => array(),
2594
            'fees'          => array()
2595
        );
2596
2597
        $args = wp_parse_args( apply_filters( 'wpinv_add_item_args', $args, $item->ID ), $defaults );
2598
        $args['quantity']   = $has_quantities && $args['quantity'] > 0 ? absint( $args['quantity'] ) : 1;
2599
2600
        $wpi_current_id         = $this->ID;
2601
        $wpi_item_id            = $item->ID;
2602
        $discounts              = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
2603
        
2604
        $_POST['wpinv_country'] = $this->country;
2605
        $_POST['wpinv_state']   = $this->state;
2606
        
2607
        $found_cart_key         = false;
2608
        
2609
        if ($has_quantities) {
2610
            $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2611
            
2612
            foreach ( $this->items as $key => $cart_item ) {
2613
                if ( (int)$item_id !== (int)$cart_item['id'] ) {
2614
                    continue;
2615
                }
2616
2617
                $this->items[ $key ]['quantity'] += $args['quantity'];
2618
                break;
2619
            }
2620
            
2621
            foreach ( $this->cart_details as $cart_key => $cart_item ) {
2622
                if ( $item_id != $cart_item['id'] ) {
2623
                    continue;
2624
                }
2625
2626
                $found_cart_key = $cart_key;
2627
                break;
2628
            }
2629
        }
2630
        
2631
        if ($has_quantities && $found_cart_key !== false) {
2632
            $cart_item          = $this->cart_details[$found_cart_key];
2633
            $item_price         = $cart_item['item_price'];
2634
            $quantity           = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
2635
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
2636
            
2637
            $new_quantity       = $quantity + $args['quantity'];
2638
            $subtotal           = $item_price * $new_quantity;
2639
            
2640
            $args['quantity']   = $new_quantity;
2641
            $discount           = !empty( $args['discount'] ) ? $args['discount'] : 0;
2642
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
2643
            
2644
            $discount_increased = $discount > 0 && $subtotal > 0 && $discount > (float)$cart_item['discount'] ? $discount - (float)$cart_item['discount'] : 0;
2645
            $tax_increased      = $tax > 0 && $subtotal > 0 && $tax > (float)$cart_item['tax'] ? $tax - (float)$cart_item['tax'] : 0;
2646
            // The total increase equals the number removed * the item_price
2647
            $total_increased    = wpinv_round_amount( $item_price );
2648
            
2649
            if ( wpinv_prices_include_tax() ) {
2650
                $subtotal -= wpinv_round_amount( $tax );
2651
            }
2652
2653
            $total              = $subtotal - $discount + $tax;
2654
2655
            // Do not allow totals to go negative
2656
            if( $total < 0 ) {
2657
                $total = 0;
2658
            }
2659
            
2660
            $cart_item['quantity']  = $new_quantity;
2661
            $cart_item['subtotal']  = $subtotal;
2662
            $cart_item['discount']  = $discount;
2663
            $cart_item['tax']       = $tax;
2664
            $cart_item['price']     = $total;
2665
            
2666
            $subtotal               = $total_increased - $discount_increased;
2667
            $tax                    = $tax_increased;
2668
            
2669
            $this->cart_details[$found_cart_key] = $cart_item;
2670
        } else {
2671
            // Set custom price.
2672
            if ( $args['custom_price'] !== '' ) {
2673
                $item_price = $args['custom_price'];
2674
            } else {
2675
                // Allow overriding the price
2676
                if ( false !== $args['item_price'] ) {
2677
                    $item_price = $args['item_price'];
2678
                } else {
2679
                    $item_price = wpinv_get_item_price( $item->ID );
2680
                }
2681
            }
2682
2683
            // Sanitizing the price here so we don't have a dozen calls later
2684
            $item_price = wpinv_sanitize_amount( $item_price );
2685
            $subtotal   = wpinv_round_amount( $item_price * $args['quantity'] );
2686
        
2687
            $discount   = !empty( $args['discount'] ) ? $args['discount'] : 0;
2688
            $tax_class  = !empty( $args['vat_class'] ) ? $args['vat_class'] : '';
2689
            $tax_rate   = !empty( $args['vat_rate'] ) ? $args['vat_rate'] : 0;
2690
            $tax        = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
2691
2692
            // Setup the items meta item
2693
            $new_item = array(
2694
                'id'       => $item->ID,
2695
                'quantity' => $args['quantity'],
2696
            );
2697
2698
            $this->items[]  = $new_item;
0 ignored issues
show
Bug Best Practice introduced by
The property items does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2699
2700
            if ( wpinv_prices_include_tax() ) {
2701
                $subtotal -= wpinv_round_amount( $tax );
2702
            }
2703
2704
            $total      = $subtotal - $discount + $tax;
2705
2706
            // Do not allow totals to go negative
2707
            if( $total < 0 ) {
2708
                $total = 0;
2709
            }
2710
        
2711
            $this->cart_details[] = array(
2712
                'name'          => !empty($args['name']) ? $args['name'] : $item->get_name(),
2713
                'id'            => $item->ID,
2714
                'item_price'    => wpinv_round_amount( $item_price ),
2715
                'custom_price'  => ( $args['custom_price'] !== '' ? wpinv_round_amount( $args['custom_price'] ) : '' ),
2716
                'quantity'      => $args['quantity'],
2717
                'discount'      => $discount,
2718
                'subtotal'      => wpinv_round_amount( $subtotal ),
2719
                'tax'           => wpinv_round_amount( $tax ),
2720
                'price'         => wpinv_round_amount( $total ),
2721
                'vat_rate'      => $tax_rate,
2722
                'vat_class'     => $tax_class,
2723
                'meta'          => $args['meta'],
2724
                'fees'          => $args['fees'],
2725
            );
2726
   
2727
            $subtotal = $subtotal - $discount;
2728
        }
2729
        
2730
        $added_item = end( $this->cart_details );
2731
        $added_item['action']  = 'add';
2732
        
2733
        $this->pending['items'][] = $added_item;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2734
        
2735
        $this->increase_subtotal( $subtotal );
2736
        $this->increase_tax( $tax );
2737
2738
        return true;
2739
    }
2740
2741
    public function remove_item( $item_id, $args = array() ) {
2742
2743
        // Set some defaults
2744
        $defaults = array(
2745
            'quantity'      => 1,
2746
            'item_price'    => false,
2747
            'custom_price'  => '',
2748
            'cart_index'    => false,
2749
        );
2750
        $args = wp_parse_args( $args, $defaults );
2751
2752
        // Bail if this post isn't a item
2753
        if ( get_post_type( $item_id ) !== 'wpi_item' ) {
2754
            return false;
2755
        }
2756
        
2757
        $this->cart_details = !empty( $this->cart_details ) ? array_values( $this->cart_details ) : $this->cart_details;
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2758
2759
        foreach ( $this->items as $key => $item ) {
2760
            if ( !empty($item['id']) && (int)$item_id !== (int)$item['id'] ) {
2761
                continue;
2762
            }
2763
2764
            if ( false !== $args['cart_index'] ) {
2765
                $cart_index = absint( $args['cart_index'] );
2766
                $cart_item  = ! empty( $this->cart_details[ $cart_index ] ) ? $this->cart_details[ $cart_index ] : false;
2767
2768
                if ( ! empty( $cart_item ) ) {
2769
                    // If the cart index item isn't the same item ID, don't remove it
2770
                    if ( !empty($cart_item['id']) && $cart_item['id'] != $item['id'] ) {
2771
                        continue;
2772
                    }
2773
                }
2774
            }
2775
2776
            $item_quantity = $this->items[ $key ]['quantity'];
2777
            if ( $item_quantity > $args['quantity'] ) {
2778
                $this->items[ $key ]['quantity'] -= $args['quantity'];
2779
                break;
2780
            } else {
2781
                unset( $this->items[ $key ] );
2782
                break;
2783
            }
2784
        }
2785
2786
        $found_cart_key = false;
2787
        if ( false === $args['cart_index'] ) {
2788
            foreach ( $this->cart_details as $cart_key => $item ) {
2789
                if ( $item_id != $item['id'] ) {
2790
                    continue;
2791
                }
2792
2793
                if ( false !== $args['item_price'] ) {
2794
                    if ( isset( $item['item_price'] ) && (float) $args['item_price'] != (float) $item['item_price'] ) {
2795
                        continue;
2796
                    }
2797
                }
2798
2799
                $found_cart_key = $cart_key;
2800
                break;
2801
            }
2802
        } else {
2803
            $cart_index = absint( $args['cart_index'] );
2804
2805
            if ( ! array_key_exists( $cart_index, $this->cart_details ) ) {
2806
                return false; // Invalid cart index passed.
2807
            }
2808
2809
            if ( (int) $this->cart_details[ $cart_index ]['id'] > 0 && (int) $this->cart_details[ $cart_index ]['id'] !== (int) $item_id ) {
2810
                return false; // We still need the proper Item ID to be sure.
2811
            }
2812
2813
            $found_cart_key = $cart_index;
2814
        }
2815
        
2816
        $cart_item  = $this->cart_details[$found_cart_key];
2817
        $quantity   = !empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1;
2818
        
2819
        if ( count( $this->cart_details ) == 1 && ( $quantity - $args['quantity'] ) < 1 ) {
2820
            //return false; // Invoice must contain at least one item.
2821
        }
2822
        
2823
        $discounts  = $this->get_discounts();
0 ignored issues
show
Unused Code introduced by
The assignment to $discounts is dead and can be removed.
Loading history...
2824
        
2825
        if ( $quantity > $args['quantity'] ) {
2826
            $item_price         = $cart_item['item_price'];
2827
            $tax_rate           = !empty( $cart_item['vat_rate'] ) ? $cart_item['vat_rate'] : 0;
2828
            
2829
            $new_quantity       = max( $quantity - $args['quantity'], 1);
2830
            $subtotal           = $item_price * $new_quantity;
2831
            
2832
            $args['quantity']   = $new_quantity;
2833
            $discount           = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
2834
            $tax                = $subtotal > 0 && $tax_rate > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
2835
            
2836
            $discount_decrease  = (float)$cart_item['discount'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['discount'] / $quantity ) ) : 0;
2837
            $discount_decrease  = $discount > 0 && $subtotal > 0 && (float)$cart_item['discount'] > $discount ? (float)$cart_item['discount'] - $discount : $discount_decrease; 
2838
            $tax_decrease       = (float)$cart_item['tax'] > 0 && $quantity > 0 ? wpinv_round_amount( ( (float)$cart_item['tax'] / $quantity ) ) : 0;
2839
            $tax_decrease       = $tax > 0 && $subtotal > 0 && (float)$cart_item['tax'] > $tax ? (float)$cart_item['tax'] - $tax : $tax_decrease;
2840
            
2841
            // The total increase equals the number removed * the item_price
2842
            $total_decrease     = wpinv_round_amount( $item_price );
2843
            
2844
            if ( wpinv_prices_include_tax() ) {
2845
                $subtotal -= wpinv_round_amount( $tax );
2846
            }
2847
2848
            $total              = $subtotal - $discount + $tax;
2849
2850
            // Do not allow totals to go negative
2851
            if( $total < 0 ) {
2852
                $total = 0;
2853
            }
2854
            
2855
            $cart_item['quantity']  = $new_quantity;
2856
            $cart_item['subtotal']  = $subtotal;
2857
            $cart_item['discount']  = $discount;
2858
            $cart_item['tax']       = $tax;
2859
            $cart_item['price']     = $total;
2860
            
2861
            $added_item             = $cart_item;
2862
            $added_item['id']       = $item_id;
2863
            $added_item['price']    = $total_decrease;
2864
            $added_item['quantity'] = $args['quantity'];
2865
            
2866
            $subtotal_decrease      = $total_decrease - $discount_decrease;
2867
            
2868
            $this->cart_details[$found_cart_key] = $cart_item;
2869
            
2870
            $remove_item = end( $this->cart_details );
2871
        } else {
2872
            $item_price     = $cart_item['item_price'];
2873
            $discount       = !empty( $cart_item['discount'] ) ? $cart_item['discount'] : 0;
2874
            $tax            = !empty( $cart_item['tax'] ) ? $cart_item['tax'] : 0;
2875
        
2876
            $subtotal_decrease  = ( $item_price * $quantity ) - $discount;
2877
            $tax_decrease       = $tax;
2878
2879
            unset( $this->cart_details[$found_cart_key] );
2880
            
2881
            $remove_item             = $args;
2882
            $remove_item['id']       = $item_id;
2883
            $remove_item['price']    = $subtotal_decrease;
2884
            $remove_item['quantity'] = $args['quantity'];
2885
        }
2886
        
2887
        $remove_item['action']      = 'remove';
2888
        $this->pending['items'][]   = $remove_item;
0 ignored issues
show
Bug Best Practice introduced by
The property pending does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2889
               
2890
        $this->decrease_subtotal( $subtotal_decrease );
2891
        $this->decrease_tax( $tax_decrease );
2892
        
2893
        return true;
2894
    }
2895
    
2896
    public function update_items($temp = false) {
2897
        global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpi_nosave;
2898
2899
        if ( ! empty( $this->cart_details ) ) {
2900
            $wpi_nosave             = $temp;
2901
            $cart_subtotal          = 0;
2902
            $cart_discount          = 0;
2903
            $cart_tax               = 0;
2904
            $cart_details           = array();
2905
2906
            $_POST['wpinv_country'] = $this->country;
2907
            $_POST['wpinv_state']   = $this->state;
2908
2909
            foreach ( $this->cart_details as $item ) {
2910
                $item_price = $item['item_price'];
2911
                $quantity   = wpinv_item_quantities_enabled() && $item['quantity'] > 0 ? absint( $item['quantity'] ) : 1;
2912
                $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...
2913
                $subtotal   = $item_price * $quantity;
2914
2915
                $wpi_current_id         = $this->ID;
2916
                $wpi_item_id            = $item['id'];
2917
2918
                $discount   = wpinv_get_cart_item_discount_amount( $item, $this->get_discounts() );
2919
2920
                $tax_rate   = wpinv_get_tax_rate( $this->country, $this->state, $wpi_item_id );
2921
                $tax_class  = $wpinv_euvat->get_item_class( $wpi_item_id );
2922
                $tax        = $item_price > 0 ? ( ( $subtotal - $discount ) * 0.01 * (float)$tax_rate ) : 0;
2923
2924
                if ( ! $this->is_taxable() ) {
2925
                    $tax = 0;
2926
                }
2927
2928
                if ( wpinv_prices_include_tax() ) {
2929
                    $subtotal -= wpinv_round_amount( $tax );
2930
                }
2931
2932
                $total      = $subtotal - $discount + $tax;
2933
2934
                // Do not allow totals to go negative
2935
                if( $total < 0 ) {
2936
                    $total = 0;
2937
                }
2938
2939
                $cart_details[] = array(
2940
                    'id'          => $item['id'],
2941
                    'name'        => $item['name'],
2942
                    'item_price'  => wpinv_round_amount( $item_price ),
2943
                    'custom_price'=> ( isset( $item['custom_price'] ) ? $item['custom_price'] : '' ),
2944
                    'quantity'    => $quantity,
2945
                    'discount'    => $discount,
2946
                    'subtotal'    => wpinv_round_amount( $subtotal ),
2947
                    'tax'         => wpinv_round_amount( $tax ),
2948
                    'price'       => wpinv_round_amount( $total ),
2949
                    'vat_rate'    => $tax_rate,
2950
                    'vat_class'   => $tax_class,
2951
                    'meta'        => isset($item['meta']) ? $item['meta'] : array(),
2952
                    'fees'        => isset($item['fees']) ? $item['fees'] : array(),
2953
                );
2954
2955
                $cart_subtotal  += (float) $subtotal;
2956
                $cart_discount  += (float) $discount;
2957
                $cart_tax       += (float) $tax;
2958
            }
2959
2960
            if ( $cart_subtotal < 0 ) {
2961
                $cart_subtotal = 0;
2962
            }
2963
2964
            if ( $cart_discount < 0 ) {
2965
                $cart_discount = 0;
2966
            }
2967
2968
            if ( $cart_tax < 0 ) {
2969
                $cart_tax = 0;
2970
            }
2971
2972
            $this->subtotal = wpinv_round_amount( $cart_subtotal );
0 ignored issues
show
Bug Best Practice introduced by
The property subtotal does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2973
            $this->tax      = wpinv_round_amount( $cart_tax );
0 ignored issues
show
Bug Best Practice introduced by
The property tax does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2974
            $this->discount = wpinv_round_amount( $cart_discount );
0 ignored issues
show
Bug Best Practice introduced by
The property discount does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2975
2976
            $this->recalculate_total();
2977
            
2978
            $this->cart_details = $cart_details;
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
2979
        }
2980
2981
        return $this;
2982
    }
2983
2984
    /**
2985
     * Validates a whether the discount is valid.
2986
     */
2987
    public function validate_discount() {    
2988
        
2989
        $discounts = $this->get_discounts( true );
2990
2991
        if ( empty( $discounts ) ) {
2992
            return false;
2993
        }
2994
2995
        $discount = wpinv_get_discount_obj( $discounts[0] );
2996
2997
        // Ensure it is active.
2998
        return $discount->exists();
2999
3000
    }
3001
    
3002
    public function recalculate_totals($temp = false) {        
3003
        $this->update_items($temp);
3004
        $this->save( true );
3005
        
3006
        return $this;
3007
    }
3008
    
3009
    public function needs_payment() {
3010
        $valid_invoice_statuses = apply_filters( 'wpinv_valid_invoice_statuses_for_payment', array( 'wpi-pending' ), $this );
3011
3012
        if ( $this->has_status( $valid_invoice_statuses ) && ( $this->get_total() > 0 || $this->is_free_trial() || $this->is_free() || $this->is_initial_free() ) ) {
3013
            $needs_payment = true;
3014
        } else {
3015
            $needs_payment = false;
3016
        }
3017
3018
        return apply_filters( 'wpinv_needs_payment', $needs_payment, $this, $valid_invoice_statuses );
3019
    }
3020
    
3021
    public function get_checkout_payment_url( $with_key = false, $secret = false ) {
3022
        $pay_url = wpinv_get_checkout_uri();
3023
3024
        if ( is_ssl() ) {
3025
            $pay_url = str_replace( 'http:', 'https:', $pay_url );
3026
        }
3027
        
3028
        $key = $this->get_key();
3029
3030
        if ( $with_key ) {
3031
            $pay_url = add_query_arg( 'invoice_key', $key, $pay_url );
3032
        } else {
3033
            $pay_url = add_query_arg( array( 'wpi_action' => 'pay_for_invoice', 'invoice_key' => $key ), $pay_url );
3034
        }
3035
        
3036
        if ( $secret ) {
3037
            $pay_url = add_query_arg( array( '_wpipay' => md5( $this->get_user_id() . '::' . $this->get_email() . '::' . $key ) ), $pay_url );
3038
        }
3039
3040
        return apply_filters( 'wpinv_get_checkout_payment_url', $pay_url, $this, $with_key, $secret );
3041
    }
3042
    
3043
    public function get_view_url( $with_key = false ) {
3044
        $invoice_url = get_permalink( $this->ID );
3045
3046
        if ( $with_key ) {
3047
            $invoice_url = add_query_arg( 'invoice_key', $this->get_key(), $invoice_url );
3048
        }
3049
3050
        return apply_filters( 'wpinv_get_view_url', $invoice_url, $this, $with_key );
3051
    }
3052
3053
    /**
3054
     * Generates a unique key for the invoice.
3055
     */
3056
    public function generate_key( $string = '' ) {
3057
        $auth_key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
3058
        return strtolower(
3059
            md5( $this->get_id() . $string . date( 'Y-m-d H:i:s' ) . $auth_key . uniqid( 'wpinv', true ) )
3060
        );
3061
    }
3062
3063
    /**
3064
     * Generates a new number for the invoice.
3065
     */
3066
    public function generate_number() {
3067
        $number = $this->get_id();
3068
3069
        if ( $this->has_status( 'auto-draft' ) && wpinv_sequential_number_active( $this->post_type ) ) {
3070
            $next_number = wpinv_get_next_invoice_number( $this->post_type );
3071
            $number      = $next_number;
3072
        }
3073
3074
        $number = wpinv_format_invoice_number( $number, $this->post_type );
0 ignored issues
show
Unused Code introduced by
The assignment to $number is dead and can be removed.
Loading history...
3075
    }
3076
3077
    public function is_recurring() {
3078
        if ( empty( $this->cart_details ) ) {
3079
            return false;
3080
        }
3081
        
3082
        $has_subscription = false;
3083
        foreach( $this->cart_details as $cart_item ) {
3084
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
3085
                $has_subscription = true;
3086
                break;
3087
            }
3088
        }
3089
        
3090
        if ( count( $this->cart_details ) > 1 ) {
3091
            $has_subscription = false;
3092
        }
3093
3094
        return apply_filters( 'wpinv_invoice_has_recurring_item', $has_subscription, $this->cart_details );
3095
    }
3096
3097
    /**
3098
     * Check if we are offering a free trial.
3099
     * 
3100
     * Returns true if it has a 100% discount for the first period.
3101
     */
3102
    public function is_free_trial() {
3103
        $is_free_trial = false;
3104
3105
        if ( $this->is_parent() && $item = $this->get_recurring( true ) ) {
3106
            if ( ! empty( $item ) && ( $item->has_free_trial() || ( $this->total == 0 && $this->discount_first_payment_only() ) ) ) {
3107
                $is_free_trial = true;
3108
            }
3109
        }
3110
3111
        return apply_filters( 'wpinv_invoice_is_free_trial', $is_free_trial, $this->cart_details, $this );
3112
    }
3113
3114
    /**
3115
     * Check if the free trial is a result of a discount.
3116
     */
3117
    public function is_free_trial_from_discount() {
3118
3119
        $parent = $this;
3120
3121
        if ( $this->is_renewal() ) {
3122
            $parent = $this->get_parent_payment();
3123
        }
3124
    
3125
        if ( $parent && $item = $parent->get_recurring( true ) ) {
3126
            return ! ( ! empty( $item ) && $item->has_free_trial() );
3127
        }
3128
        return false;
3129
3130
    }
3131
3132
    /**
3133
     * Check if a discount is only applicable to the first payment.
3134
     */
3135
    public function discount_first_payment_only() {
3136
3137
        if ( empty( $this->discounts ) || ! $this->is_recurring() ) {
3138
            return true;
3139
        }
3140
3141
        $discount = wpinv_get_discount_obj( $this->discounts[0] );
3142
3143
        if ( ! $discount || ! $discount->exists() ) {
0 ignored issues
show
introduced by
$discount is of type WPInv_Discount, thus it always evaluated to true.
Loading history...
3144
            return true;
3145
        }
3146
3147
        return ! $discount->get_is_recurring();
3148
    }
3149
3150
    public function is_initial_free() {
3151
        $is_initial_free = false;
3152
        
3153
        if ( ! ( (float)wpinv_round_amount( $this->get_total() ) > 0 ) && $this->is_parent() && $this->is_recurring() && ! $this->is_free_trial() && ! $this->is_free() ) {
3154
            $is_initial_free = true;
3155
        }
3156
3157
        return apply_filters( 'wpinv_invoice_is_initial_free', $is_initial_free, $this->cart_details );
3158
    }
3159
    
3160
    public function get_recurring( $object = false ) {
3161
        $item = NULL;
3162
        
3163
        if ( empty( $this->cart_details ) ) {
3164
            return $item;
3165
        }
3166
3167
        foreach( $this->cart_details as $cart_item ) {
3168
            if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
3169
                $item = $cart_item['id'];
3170
                break;
3171
            }
3172
        }
3173
3174
        if ( $object ) {
3175
            $item = $item ? new WPInv_Item( $item ) : NULL;
3176
            
3177
            apply_filters( 'wpinv_invoice_get_recurring_item', $item, $this );
3178
        }
3179
3180
        return apply_filters( 'wpinv_invoice_get_recurring_item_id', $item, $this );
3181
    }
3182
3183
    public function get_subscription_name() {
3184
        $item = $this->get_recurring( true );
3185
3186
        if ( empty( $item ) ) {
3187
            return NULL;
3188
        }
3189
3190
        if ( !($name = $item->get_name()) ) {
3191
            $name = $item->post_name;
0 ignored issues
show
Bug Best Practice introduced by
The property post_name does not exist on WPInv_Item. Since you implemented __get, consider adding a @property annotation.
Loading history...
3192
        }
3193
3194
        return apply_filters( 'wpinv_invoice_get_subscription_name', $name, $this );
3195
    }
3196
3197
    public function get_subscription_id() {
3198
        $subscription_id = $this->get_meta( '_wpinv_subscr_profile_id', true );
3199
3200
        if ( empty( $subscription_id ) && !empty( $this->parent_invoice ) ) {
3201
            $parent_invoice = wpinv_get_invoice( $this->parent_invoice );
3202
3203
            $subscription_id = $parent_invoice->get_meta( '_wpinv_subscr_profile_id', true );
3204
        }
3205
        
3206
        return $subscription_id;
3207
    }
3208
    
3209
    public function is_parent() {
3210
        $is_parent = empty( $this->parent_invoice ) ? true : false;
3211
3212
        return apply_filters( 'wpinv_invoice_is_parent', $is_parent, $this );
3213
    }
3214
    
3215
    public function is_renewal() {
3216
        $is_renewal = $this->parent_invoice && $this->parent_invoice != $this->ID ? true : false;
3217
3218
        return apply_filters( 'wpinv_invoice_is_renewal', $is_renewal, $this );
3219
    }
3220
    
3221
    public function get_parent_payment() {
3222
        $parent_payment = NULL;
3223
        
3224
        if ( $this->is_renewal() ) {
3225
            $parent_payment = wpinv_get_invoice( $this->parent_invoice );
3226
        }
3227
        
3228
        return $parent_payment;
3229
    }
3230
    
3231
    public function is_paid() {
3232
        $is_paid = $this->has_status( array( 'publish', 'wpi-processing', 'wpi-renewal' ) );
3233
3234
        return apply_filters( 'wpinv_invoice_is_paid', $is_paid, $this );
3235
    }
3236
3237
    /**
3238
     * Checks if this is a quote object.
3239
     * 
3240
     * @since 1.0.15
3241
     */
3242
    public function is_quote() {
3243
        return 'wpi_quote' === $this->post_type;
3244
    }
3245
    
3246
    public function is_refunded() {
3247
        $is_refunded = $this->has_status( array( 'wpi-refunded' ) );
3248
3249
        return apply_filters( 'wpinv_invoice_is_refunded', $is_refunded, $this );
3250
    }
3251
    
3252
    public function is_free() {
3253
        $is_free = false;
3254
3255
        if ( !( (float)wpinv_round_amount( $this->get_total() ) > 0 ) ) {
3256
            if ( $this->is_parent() && $this->is_recurring() ) {
3257
                $is_free = (float)wpinv_round_amount( $this->get_recurring_details( 'total' ) ) > 0 ? false : true;
3258
            } else {
3259
                $is_free = true;
3260
            }
3261
        }
3262
3263
        return apply_filters( 'wpinv_invoice_is_free', $is_free, $this );
3264
    }
3265
    
3266
    public function has_vat() {
3267
        global $wpinv_euvat, $wpi_country;
3268
        
3269
        $requires_vat = false;
3270
        
3271
        if ( $this->country ) {
3272
            $wpi_country        = $this->country;
3273
            
3274
            $requires_vat       = $wpinv_euvat->requires_vat( $requires_vat, $this->get_user_id(), $wpinv_euvat->invoice_has_digital_rule( $this ) );
3275
        }
3276
        
3277
        return apply_filters( 'wpinv_invoice_has_vat', $requires_vat, $this );
3278
    }
3279
3280
    public function refresh_item_ids() {
3281
        $item_ids = array();
3282
        
3283
        if ( ! empty( $this->cart_details ) ) {
3284
            foreach ( array_keys( $this->cart_details ) as $item ) {
3285
                if ( ! empty( $item['id'] ) ) {
3286
                    $item_ids[] = $item['id'];
3287
                }
3288
            }
3289
        }
3290
        
3291
        $item_ids = !empty( $item_ids ) ? implode( ',', array_unique( $item_ids ) ) : '';
3292
        
3293
        update_post_meta( $this->ID, '_wpinv_item_ids', $item_ids );
3294
    }
3295
    
3296
    public function get_invoice_quote_type( $post_id ) {
3297
        if ( empty( $post_id ) ) {
3298
            return '';
3299
        }
3300
3301
        $type = get_post_type( $post_id );
3302
3303
        if ( 'wpi_invoice' === $type ) {
3304
            $post_type = __('Invoice', 'invoicing');
3305
        } else{
3306
            $post_type = __('Quote', 'invoicing');
3307
        }
3308
3309
        return apply_filters('get_invoice_type_label', $post_type, $post_id);
3310
    }
3311
}
3312