Passed
Pull Request — master (#377)
by Brian
05:01
created

getpaid_maybe_process_refund()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 19
rs 9.2222
cc 6
nc 4
nop 3
1
<?php
2
/**
3
 * Contains functions related to Invoicing plugin.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
 
9
// MUST have WordPress.
10
if ( !defined( 'WPINC' ) ) {
11
    exit( 'Do NOT access this file directly: ' . basename( __FILE__ ) );
12
}
13
14
/**
15
 * Returns an array of all invoice post types.
16
 */
17
function getpaid_get_invoice_post_types() {
18
    $post_types = array(
19
        'wpi_quote'   => __( 'Quote', 'invoicing' ),
20
        'wpi_invoice' => __( 'Invoice', 'invoicing' ),
21
    );
22
    
23
    return apply_filters( 'getpaid_invoice_post_types', $post_types );
24
}
25
26
/**
27
 * Checks if this is an invocing post type.
28
 */
29
function getpaid_is_invoice_post_type( $post_type ) {
30
    return array_key_exists( $post_type, getpaid_get_invoice_post_types() );
31
}
32
33
function wpinv_get_invoice_cart_id() {
34
    $wpinv_checkout = wpinv_get_checkout_session();
35
    
36
    if ( !empty( $wpinv_checkout['invoice_id'] ) ) {
37
        return $wpinv_checkout['invoice_id'];
38
    }
39
    
40
    return NULL;
41
}
42
43
/**
44
 * Create an invoice
45
 * 
46
 * @param  array $invoice_data   An array of invoice properties.
47
 * @param  bool  $wp_error       Whether to return false or WP_Error on failure.
48
 * @return int|WP_Error|WPInv_Invoice The value 0 or WP_Error on failure. The WPInv_Invoice object on success.
49
 */
50
function wpinv_insert_invoice( $invoice_data = array(), $wp_error = false ) {
51
    if ( empty( $invoice_data ) ) {
52
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type WPInv_Invoice|WP_Error|integer.
Loading history...
53
    }
54
    
55
    if ( !( !empty( $invoice_data['cart_details'] ) && is_array( $invoice_data['cart_details'] ) ) ) {
56
        return $wp_error ? new WP_Error( 'wpinv_invalid_items', __( 'Invoice must have atleast one item.', 'invoicing' ) ) : 0;
57
    }
58
    
59
    // If no user id is provided, default to the current user id
60
    if ( empty( $invoice_data['user_id'] ) ) {
61
        $invoice_data['user_id'] = get_current_user_id();
62
    }
63
    
64
    $invoice_data['invoice_id'] = !empty( $invoice_data['invoice_id'] ) ? (int)$invoice_data['invoice_id'] : 0;
65
    
66
    if ( empty( $invoice_data['status'] ) ) {
67
        $invoice_data['status'] = 'wpi-pending';
68
    }
69
70
    if ( empty( $invoice_data['post_type'] ) ) {
71
        $invoice_data['post_type'] = 'wpi_invoice';
72
    }
73
    
74
    if ( empty( $invoice_data['ip'] ) ) {
75
        $invoice_data['ip'] = wpinv_get_ip();
76
    }
77
78
    // default invoice args, note that status is checked for validity in wpinv_create_invoice()
79
    $default_args = array(
80
        'invoice_id'    => (int)$invoice_data['invoice_id'],
81
        'user_id'       => (int)$invoice_data['user_id'],
82
        'status'        => $invoice_data['status'],
83
        'post_type'     => $invoice_data['post_type'],
84
    );
85
86
    $invoice = wpinv_create_invoice( $default_args, $invoice_data, true );
87
    if ( is_wp_error( $invoice ) ) {
88
        return $wp_error ? $invoice : 0;
89
    }
90
    
91
    if ( empty( $invoice_data['invoice_id'] ) ) {
92
        //$invoice->add_note( wp_sprintf( __( 'Invoice is created with status %s.', 'invoicing' ), wpinv_status_nicename( $invoice->status ) ) );
93
    }
94
    
95
    // User info
96
    $default_user_info = array(
97
        'user_id'               => '',
98
        'first_name'            => '',
99
        'last_name'             => '',
100
        'email'                 => '',
101
        'company'               => '',
102
        'phone'                 => '',
103
        'address'               => '',
104
        'city'                  => '',
105
        'country'               => wpinv_get_default_country(),
106
        'state'                 => wpinv_get_default_state(),
107
        'zip'                   => '',
108
        'vat_number'            => '',
109
        'vat_rate'              => '',
110
        'adddress_confirmed'    => '',
111
        'discount'              => array(),
112
    );
113
114
    if ( $user_id = (int)$invoice->get_user_id() ) {
0 ignored issues
show
Bug introduced by
The method get_user_id() does not exist on WP_Error. ( Ignorable by Annotation )

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

114
    if ( $user_id = (int)$invoice->/** @scrutinizer ignore-call */ get_user_id() ) {

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...
115
        if ( $user_address = wpinv_get_user_address( $user_id ) ) {
116
            $default_user_info = wp_parse_args( $user_address, $default_user_info );
117
        }
118
    }
119
    
120
    if ( empty( $invoice_data['user_info'] ) ) {
121
        $invoice_data['user_info'] = array();
122
    }
123
    
124
    $user_info = wp_parse_args( $invoice_data['user_info'], $default_user_info );
125
    
126
    if ( empty( $user_info['first_name'] ) ) {
127
        $user_info['first_name'] = $default_user_info['first_name'];
128
        $user_info['last_name'] = $default_user_info['last_name'];
129
    }
130
    
131
    if ( empty( $user_info['country'] ) ) {
132
        $user_info['country'] = $default_user_info['country'];
133
        $user_info['state'] = $default_user_info['state'];
134
        $user_info['city'] = $default_user_info['city'];
135
        $user_info['address'] = $default_user_info['address'];
136
        $user_info['zip'] = $default_user_info['zip'];
137
        $user_info['phone'] = $default_user_info['phone'];
138
    }
139
    
140
    if ( !empty( $user_info['discount'] ) && !is_array( $user_info['discount'] ) ) {
141
        $user_info['discount'] = (array)$user_info['discount'];
142
    }
143
144
    // Payment details
145
    $payment_details = array();
146
    if ( !empty( $invoice_data['payment_details'] ) ) {
147
        $default_payment_details = array(
148
            'gateway'           => 'manual',
149
            'gateway_title'     => '',
150
            'currency'          => wpinv_get_default_country(),
151
            'transaction_id'    => '',
152
        );
153
        
154
        $payment_details = wp_parse_args( $invoice_data['payment_details'], $default_payment_details );
155
        
156
        if ( empty( $payment_details['gateway'] ) ) {
157
            $payment_details['gateway'] = 'manual';
158
        }
159
        
160
        if ( empty( $payment_details['currency'] ) ) {
161
            $payment_details['currency'] = wpinv_get_default_country();
162
        }
163
        
164
        if ( empty( $payment_details['gateway_title'] ) ) {
165
            $payment_details['gateway_title'] = wpinv_get_gateway_checkout_label( $payment_details['gateway'] );
166
        }
167
    }
168
    
169
    $invoice->set( 'status', ( !empty( $invoice_data['status'] ) ? $invoice_data['status'] : 'wpi-pending' ) );
0 ignored issues
show
Bug introduced by
The method set() does not exist on WP_Error. ( Ignorable by Annotation )

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

169
    $invoice->/** @scrutinizer ignore-call */ 
170
              set( 'status', ( !empty( $invoice_data['status'] ) ? $invoice_data['status'] : 'wpi-pending' ) );

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...
170
    
171
    if ( !empty( $payment_details ) ) {
172
        $invoice->set( 'currency', $payment_details['currency'] );
173
        $invoice->set( 'gateway', $payment_details['gateway'] );
174
        $invoice->set( 'gateway_title', $payment_details['gateway_title'] );
175
        $invoice->set( 'transaction_id', $payment_details['transaction_id'] );
176
    }
177
    
178
    $invoice->set( 'user_info', $user_info );
179
    $invoice->set( 'first_name', $user_info['first_name'] );
180
    $invoice->set( 'last_name', $user_info['last_name'] );
181
    $invoice->set( 'address', $user_info['address'] );
182
    $invoice->set( 'company', $user_info['company'] );
183
    $invoice->set( 'vat_number', $user_info['vat_number'] );
184
    $invoice->set( 'phone', $user_info['phone'] );
185
    $invoice->set( 'city', $user_info['city'] );
186
    $invoice->set( 'country', $user_info['country'] );
187
    $invoice->set( 'state', $user_info['state'] );
188
    $invoice->set( 'zip', $user_info['zip'] );
189
    $invoice->set( 'discounts', $user_info['discount'] );
190
    $invoice->set( 'ip', ( !empty( $invoice_data['ip'] ) ? $invoice_data['ip'] : wpinv_get_ip() ) );
191
    $invoice->set( 'mode', ( wpinv_is_test_mode() ? 'test' : 'live' ) );
192
    $invoice->set( 'parent_invoice', ( !empty( $invoice_data['parent'] ) ? absint( $invoice_data['parent'] ) : '' ) );
193
    
194
    if ( !empty( $invoice_data['cart_details'] ) && is_array( $invoice_data['cart_details'] ) ) {
195
        foreach ( $invoice_data['cart_details'] as $key => $item ) {
196
            $item_id        = !empty( $item['id'] ) ? $item['id'] : 0;
197
            $quantity       = !empty( $item['quantity'] ) ? $item['quantity'] : 1;
198
            $name           = !empty( $item['name'] ) ? $item['name'] : '';
199
            $item_price     = isset( $item['item_price'] ) ? $item['item_price'] : '';
200
            
201
            $post_item  = new WPInv_Item( $item_id );
202
            if ( !empty( $post_item ) ) {
203
                $name       = !empty( $name ) ? $name : $post_item->get_name();
204
                $item_price = $item_price !== '' ? $item_price : $post_item->get_price();
205
            } else {
206
                continue;
207
            }
208
            
209
            $args = array(
210
                'name'          => $name,
211
                'quantity'      => $quantity,
212
                'item_price'    => $item_price,
213
                'custom_price'  => isset( $item['custom_price'] ) ? $item['custom_price'] : '',
214
                'tax'           => !empty( $item['tax'] ) ? $item['tax'] : 0.00,
215
                'discount'      => isset( $item['discount'] ) ? $item['discount'] : 0,
216
                'meta'          => isset( $item['meta'] ) ? $item['meta'] : array(),
217
                'fees'          => isset( $item['fees'] ) ? $item['fees'] : array(),
218
            );
219
220
            $invoice->add_item( $item_id, $args );
0 ignored issues
show
Bug introduced by
The method add_item() does not exist on WP_Error. ( Ignorable by Annotation )

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

220
            $invoice->/** @scrutinizer ignore-call */ 
221
                      add_item( $item_id, $args );

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...
Bug introduced by
It seems like $item_id can also be of type integer; however, parameter $item of WPInv_Invoice::add_item() does only seem to accept GetPaid_Form_Item, 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

220
            $invoice->add_item( /** @scrutinizer ignore-type */ $item_id, $args );
Loading history...
Unused Code introduced by
The call to WPInv_Invoice::add_item() has too many arguments starting with $args. ( Ignorable by Annotation )

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

220
            $invoice->/** @scrutinizer ignore-call */ 
221
                      add_item( $item_id, $args );

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...
221
        }
222
    }
223
224
    $invoice->increase_tax( wpinv_get_cart_fee_tax() );
0 ignored issues
show
Bug introduced by
The method increase_tax() does not exist on WP_Error. ( Ignorable by Annotation )

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

224
    $invoice->/** @scrutinizer ignore-call */ 
225
              increase_tax( wpinv_get_cart_fee_tax() );

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...
Bug introduced by
The method increase_tax() 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

224
    $invoice->/** @scrutinizer ignore-call */ 
225
              increase_tax( wpinv_get_cart_fee_tax() );

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...
225
226
    if ( isset( $invoice_data['post_date'] ) ) {
227
        $invoice->set( 'date', $invoice_data['post_date'] );
228
    }
229
    
230
    // Invoice due date
231
    if ( isset( $invoice_data['due_date'] ) ) {
232
        $invoice->set( 'due_date', $invoice_data['due_date'] );
233
    }
234
    
235
    $invoice->save();
0 ignored issues
show
Bug introduced by
The method save() does not exist on WP_Error. ( Ignorable by Annotation )

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

235
    $invoice->/** @scrutinizer ignore-call */ 
236
              save();

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...
236
    
237
    // Add notes
238
    if ( !empty( $invoice_data['private_note'] ) ) {
239
        $invoice->add_note( $invoice_data['private_note'] );
0 ignored issues
show
Bug introduced by
The method add_note() does not exist on WP_Error. ( Ignorable by Annotation )

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

239
        $invoice->/** @scrutinizer ignore-call */ 
240
                  add_note( $invoice_data['private_note'] );

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...
240
    }
241
    if ( !empty( $invoice_data['user_note'] ) ) {
242
        $invoice->add_note( $invoice_data['user_note'], true );
243
    }
244
    
245
    if ( $invoice->is_quote() ) {
0 ignored issues
show
Bug introduced by
The method is_quote() does not exist on WP_Error. ( Ignorable by Annotation )

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

245
    if ( $invoice->/** @scrutinizer ignore-call */ is_quote() ) {

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...
246
247
        if ( isset( $invoice_data['valid_until'] ) ) {
248
            update_post_meta( $invoice->ID, 'wpinv_quote_valid_until', $invoice_data['valid_until'] );
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on WP_Error.
Loading history...
249
        }
250
        return $invoice;
251
252
    }
253
254
    do_action( 'wpinv_insert_invoice', $invoice->ID, $invoice_data );
255
256
    if ( ! empty( $invoice->ID ) ) {
257
        global $wpi_userID, $wpinv_ip_address_country;
258
        
259
        if ( isset( $invoice_data['created_via'] ) ) {
260
            update_post_meta( $invoice->ID, 'wpinv_created_via', $invoice_data['created_via'] );
261
        }
262
263
        $checkout_session = wpinv_get_checkout_session();
264
        
265
        $data_session                   = array();
266
        $data_session['invoice_id']     = $invoice->ID;
267
        $data_session['cart_discounts'] = $invoice->get_discounts( true );
0 ignored issues
show
Bug introduced by
The method get_discounts() does not exist on WP_Error. ( Ignorable by Annotation )

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

267
        /** @scrutinizer ignore-call */ 
268
        $data_session['cart_discounts'] = $invoice->get_discounts( true );

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...
Bug introduced by
true of type true is incompatible with the type string expected by parameter $context of WPInv_Invoice::get_discounts(). ( Ignorable by Annotation )

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

267
        $data_session['cart_discounts'] = $invoice->get_discounts( /** @scrutinizer ignore-type */ true );
Loading history...
268
        
269
        wpinv_set_checkout_session( $data_session );
270
        
271
        $wpi_userID         = (int)$invoice->get_user_id();
272
        
273
        $_POST['country']   = !empty( $invoice->country ) ? $invoice->country : wpinv_get_default_country();
0 ignored issues
show
Bug introduced by
The property country does not seem to exist on WP_Error.
Loading history...
Bug Best Practice introduced by
The property country does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
274
        $_POST['state']     = $invoice->state;
0 ignored issues
show
Bug introduced by
The property state does not seem to exist on WP_Error.
Loading history...
Bug Best Practice introduced by
The property state does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
275
276
        $invoice->set( 'country', sanitize_text_field( $_POST['country'] ) );
277
        $invoice->set( 'state', sanitize_text_field( $_POST['state'] ) );
278
        
279
        $wpinv_ip_address_country = $invoice->country;
280
281
        $invoice = $invoice->recalculate_totals( true );
0 ignored issues
show
Bug introduced by
The method recalculate_totals() does not exist on WP_Error. ( Ignorable by Annotation )

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

281
        /** @scrutinizer ignore-call */ 
282
        $invoice = $invoice->recalculate_totals( true );

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...
Deprecated Code introduced by
The function WPInv_Invoice::recalculate_totals() has been deprecated. ( Ignorable by Annotation )

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

281
        $invoice = /** @scrutinizer ignore-deprecated */ $invoice->recalculate_totals( true );
Loading history...
282
283
        wpinv_set_checkout_session( $checkout_session );
284
285
        return $invoice;
286
    }
287
    
288
    if ( $wp_error ) {
289
        if ( is_wp_error( $invoice ) ) {
290
            return $invoice;
291
        } else {
292
            return new WP_Error( 'wpinv_insert_invoice_error', __( 'Error in insert invoice.', 'invoicing' ) );
293
        }
294
    } else {
295
        return 0;
296
    }
297
}
298
299
function wpinv_update_invoice( $invoice_data = array(), $wp_error = false ) {
300
    $invoice_ID = !empty( $invoice_data['ID'] ) ? absint( $invoice_data['ID'] ) : NULL;
301
302
    if ( !$invoice_ID ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $invoice_ID of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
303
        if ( $wp_error ) {
304
            return new WP_Error( 'invalid_invoice_id', __( 'Invalid invoice ID.', 'invoicing' ) );
305
        }
306
        return 0;
307
    }
308
309
    $invoice = wpinv_get_invoice( $invoice_ID );
310
311
    $recurring_item = $invoice->is_recurring() ? $invoice->get_recurring( true ) : NULL;
312
313
    if ( empty( $invoice->ID ) ) {
314
        if ( $wp_error ) {
315
            return new WP_Error( 'invalid_invoice', __( 'Invalid invoice.', 'invoicing' ) );
316
        }
317
        return 0;
318
    }
319
320
    if ( ! $invoice->has_status( array( 'wpi-pending' ) ) && ! $invoice->is_quote()  ) {
321
        if ( $wp_error ) {
322
            return new WP_Error( 'invalid_invoice_status', __( 'Only invoice with pending payment is allowed to update.', 'invoicing' ) );
323
        }
324
        return 0;
325
    }
326
327
    // Invoice status
328
    if ( !empty( $invoice_data['status'] ) ) {
329
        $invoice->set( 'status', $invoice_data['status'] );
330
    }
331
332
    // Invoice date
333
    if ( !empty( $invoice_data['post_date'] ) ) {
334
        $invoice->set( 'date', $invoice_data['post_date'] );
335
    }
336
337
    // Invoice due date
338
    if ( isset( $invoice_data['due_date'] ) ) {
339
        $invoice->set( 'due_date', $invoice_data['due_date'] );
340
    }
341
342
    // Invoice IP address
343
    if ( !empty( $invoice_data['ip'] ) ) {
344
        $invoice->set( 'ip', $invoice_data['ip'] );
345
    }
346
    
347
    // User info
348
    if ( !empty( $invoice_data['user_info'] ) && is_array( $invoice_data['user_info'] ) ) {
349
        $user_info = wp_parse_args( $invoice_data['user_info'], $invoice->user_info );
0 ignored issues
show
Bug Best Practice introduced by
The property user_info does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
350
351
        if ( $discounts = $invoice->get_discounts() ) {
352
            $set_discount = $discounts;
353
        } else {
354
            $set_discount = '';
355
        }
356
357
        // Manage discount
358
        if ( !empty( $invoice_data['user_info']['discount'] ) ) {
359
            // Remove discount
360
            if ( $invoice_data['user_info']['discount'] == 'none' ) {
361
                $set_discount = '';
362
            } else {
363
                $set_discount = $invoice_data['user_info']['discount'];
364
            }
365
366
            $invoice->set( 'discounts', $set_discount );
367
        }
368
369
        $user_info['discount'] = $set_discount;
370
371
        $invoice->set( 'user_info', $user_info );
372
    }
373
374
    if ( !empty( $invoice_data['cart_details'] ) && is_array( $invoice_data['cart_details'] ) && $cart_details = $invoice_data['cart_details'] ) {
375
        $remove_items = !empty( $cart_details['remove_items'] ) && is_array( $cart_details['remove_items'] ) ? $cart_details['remove_items'] : array();
376
377
        if ( !empty( $remove_items[0]['id'] ) ) {
378
            foreach ( $remove_items as $item ) {
379
                $item_id        = !empty( $item['id'] ) ? $item['id'] : 0;
380
                $quantity       = !empty( $item['quantity'] ) ? $item['quantity'] : 1;
381
                if ( empty( $item_id ) ) {
382
                    continue;
383
                }
384
385
                foreach ( $invoice->cart_details as $cart_index => $cart_item ) {
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
386
                    if ( $item_id == $cart_item['id'] ) {
387
                        $args = array(
388
                            'id'         => $item_id,
389
                            'quantity'   => $quantity,
390
                            'cart_index' => $cart_index
391
                        );
392
393
                        $invoice->remove_item( $item_id, $args );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::remove_item() has too many arguments starting with $args. ( Ignorable by Annotation )

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

393
                        $invoice->/** @scrutinizer ignore-call */ 
394
                                  remove_item( $item_id, $args );

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...
394
                        break;
395
                    }
396
                }
397
            }
398
        }
399
400
        $add_items = !empty( $cart_details['add_items'] ) && is_array( $cart_details['add_items'] ) ? $cart_details['add_items'] : array();
401
402
        if ( !empty( $add_items[0]['id'] ) ) {
403
            foreach ( $add_items as $item ) {
404
                $item_id        = !empty( $item['id'] ) ? $item['id'] : 0;
405
                $post_item      = new WPInv_Item( $item_id );
406
                if ( empty( $post_item ) ) {
407
                    continue;
408
                }
409
410
                $valid_item = true;
411
                if ( !empty( $recurring_item ) ) {
412
                    if ( $recurring_item->ID != $item_id ) {
413
                        $valid_item = false;
414
                    }
415
                } else if ( wpinv_is_recurring_item( $item_id ) ) {
416
                    $valid_item = false;
417
                }
418
                
419
                if ( !$valid_item ) {
420
                    if ( $wp_error ) {
421
                        return new WP_Error( 'invalid_invoice_item', __( 'You can not add item because recurring item must be paid individually!', 'invoicing' ) );
422
                    }
423
                    return 0;
424
                }
425
426
                $quantity       = !empty( $item['quantity'] ) ? $item['quantity'] : 1;
427
                $name           = !empty( $item['name'] ) ? $item['name'] : $post_item->get_name();
428
                $item_price     = isset( $item['item_price'] ) ? $item['item_price'] : $post_item->get_price();
429
430
                $args = array(
431
                    'name'          => $name,
432
                    'quantity'      => $quantity,
433
                    'item_price'    => $item_price,
434
                    'custom_price'  => isset( $item['custom_price'] ) ? $item['custom_price'] : '',
435
                    'tax'           => !empty( $item['tax'] ) ? $item['tax'] : 0,
436
                    'discount'      => isset( $item['discount'] ) ? $item['discount'] : 0,
437
                    'meta'          => isset( $item['meta'] ) ? $item['meta'] : array(),
438
                    'fees'          => isset( $item['fees'] ) ? $item['fees'] : array(),
439
                );
440
441
                $invoice->add_item( $item_id, $args );
0 ignored issues
show
Bug introduced by
It seems like $item_id can also be of type integer; however, parameter $item of WPInv_Invoice::add_item() does only seem to accept GetPaid_Form_Item, 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

441
                $invoice->add_item( /** @scrutinizer ignore-type */ $item_id, $args );
Loading history...
Unused Code introduced by
The call to WPInv_Invoice::add_item() has too many arguments starting with $args. ( Ignorable by Annotation )

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

441
                $invoice->/** @scrutinizer ignore-call */ 
442
                          add_item( $item_id, $args );

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...
442
            }
443
        }
444
    }
445
    
446
    // Payment details
447
    if ( !empty( $invoice_data['payment_details'] ) && $payment_details = $invoice_data['payment_details'] ) {
448
        if ( !empty( $payment_details['gateway'] ) ) {
449
            $invoice->set( 'gateway', $payment_details['gateway'] );
450
        }
451
452
        if ( !empty( $payment_details['transaction_id'] ) ) {
453
            $invoice->set( 'transaction_id', $payment_details['transaction_id'] );
454
        }
455
    }
456
457
    do_action( 'wpinv_pre_update_invoice', $invoice->ID, $invoice_data );
458
459
    // Parent invoice
460
    if ( !empty( $invoice_data['parent'] ) ) {
461
        $invoice->set( 'parent_invoice', $invoice_data['parent'] );
462
    }
463
464
    // Save invoice data.
465
    $invoice->save();
466
    
467
    if ( empty( $invoice->ID ) || is_wp_error( $invoice ) ) {
468
        if ( $wp_error ) {
469
            if ( is_wp_error( $invoice ) ) {
470
                return $invoice;
471
            } else {
472
                return new WP_Error( 'wpinv_update_invoice_error', __( 'Error in update invoice.', 'invoicing' ) );
473
            }
474
        } else {
475
            return 0;
476
        }
477
    }
478
479
    // Add private note
480
    if ( !empty( $invoice_data['private_note'] ) ) {
481
        $invoice->add_note( $invoice_data['private_note'] );
482
    }
483
484
    // Add user note
485
    if ( !empty( $invoice_data['user_note'] ) ) {
486
        $invoice->add_note( $invoice_data['user_note'], true );
487
    }
488
489
    if ( $invoice->is_quote() ) {
490
491
        if ( isset( $invoice_data['valid_until'] ) ) {
492
            update_post_meta( $invoice->ID, 'wpinv_quote_valid_until', $invoice_data['valid_until'] );
493
        }
494
        return $invoice;
495
496
    }
497
498
    global $wpi_userID, $wpinv_ip_address_country;
499
500
    $checkout_session = wpinv_get_checkout_session();
501
502
    $data_session                   = array();
503
    $data_session['invoice_id']     = $invoice->ID;
504
    $data_session['cart_discounts'] = $invoice->get_discounts( true );
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $context of WPInv_Invoice::get_discounts(). ( Ignorable by Annotation )

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

504
    $data_session['cart_discounts'] = $invoice->get_discounts( /** @scrutinizer ignore-type */ true );
Loading history...
505
506
    wpinv_set_checkout_session( $data_session );
507
508
    $wpi_userID         = (int)$invoice->get_user_id();
509
510
    $_POST['country']   = !empty( $invoice->country ) ? $invoice->country : wpinv_get_default_country();
0 ignored issues
show
Bug Best Practice introduced by
The property country does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
511
    $_POST['state']     = $invoice->state;
0 ignored issues
show
Bug Best Practice introduced by
The property state does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
512
513
    $invoice->set( 'country', sanitize_text_field( $_POST['country'] ) );
514
    $invoice->set( 'state', sanitize_text_field( $_POST['state'] ) );
515
516
    $wpinv_ip_address_country = $invoice->country;
517
518
    $invoice = $invoice->recalculate_totals( true );
0 ignored issues
show
Deprecated Code introduced by
The function WPInv_Invoice::recalculate_totals() has been deprecated. ( Ignorable by Annotation )

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

518
    $invoice = /** @scrutinizer ignore-deprecated */ $invoice->recalculate_totals( true );
Loading history...
519
520
    do_action( 'wpinv_post_update_invoice', $invoice->ID, $invoice_data );
521
522
    wpinv_set_checkout_session( $checkout_session );
523
524
    return $invoice;
525
}
526
527
function wpinv_get_invoice( $invoice_id = 0, $cart = false ) {
528
    if ( $cart && empty( $invoice_id ) ) {
529
        $invoice_id = (int)wpinv_get_invoice_cart_id();
530
    }
531
532
    $invoice = new WPInv_Invoice( $invoice_id );
533
534
    if ( ! empty( $invoice ) && ! empty( $invoice->ID ) ) {
535
        return $invoice;
536
    }
537
538
    return NULL;
539
}
540
541
function wpinv_get_invoice_cart( $invoice_id = 0 ) {
542
    return wpinv_get_invoice( $invoice_id, true );
543
}
544
545
function wpinv_get_invoice_description( $invoice_id = 0 ) {
546
    $invoice = new WPInv_Invoice( $invoice_id );
547
    return $invoice->get_description();
548
}
549
550
function wpinv_get_invoice_currency_code( $invoice_id = 0 ) {
551
    $invoice = new WPInv_Invoice( $invoice_id );
552
    return $invoice->get_currency();
553
}
554
555
function wpinv_get_payment_user_email( $invoice_id ) {
556
    $invoice = new WPInv_Invoice( $invoice_id );
557
    return $invoice->get_email();
558
}
559
560
function wpinv_get_user_id( $invoice_id ) {
561
    $invoice = new WPInv_Invoice( $invoice_id );
562
    return $invoice->get_user_id();
563
}
564
565
function wpinv_get_invoice_status( $invoice_id, $return_label = false ) {
566
    $invoice = new WPInv_Invoice( $invoice_id );
567
    
568
    return $invoice->get_status( $return_label );
569
}
570
571
function wpinv_get_payment_gateway( $invoice_id, $return_label = false ) {
572
    $invoice = new WPInv_Invoice( $invoice_id );
573
    
574
    return $invoice->get_gateway( $return_label );
575
}
576
577
function wpinv_get_payment_gateway_name( $invoice_id ) {
578
    $invoice = new WPInv_Invoice( $invoice_id );
579
    
580
    return $invoice->get_gateway_title();
581
}
582
583
function wpinv_get_payment_transaction_id( $invoice_id ) {
584
    $invoice = new WPInv_Invoice( $invoice_id );
585
    
586
    return $invoice->get_transaction_id();
587
}
588
589
function wpinv_get_id_by_transaction_id( $key ) {
590
    global $wpdb;
591
592
    $invoice_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wpinv_transaction_id' AND meta_value = %s LIMIT 1", $key ) );
593
594
    if ( $invoice_id != NULL )
595
        return $invoice_id;
596
597
    return 0;
598
}
599
600
function wpinv_get_invoice_meta( $invoice_id = 0, $meta_key = '_wpinv_payment_meta', $single = true ) {
601
    $invoice = new WPInv_Invoice( $invoice_id );
602
603
    return $invoice->get_meta( $meta_key, $single );
604
}
605
606
function wpinv_update_invoice_meta( $invoice_id = 0, $meta_key = '', $meta_value = '', $prev_value = '' ) {
607
    $invoice = new WPInv_Invoice( $invoice_id );
608
    
609
    return $invoice->update_meta( $meta_key, $meta_value, $prev_value );
0 ignored issues
show
Bug introduced by
The method update_meta() does not exist on WPInv_Invoice. Did you maybe mean update_meta_data()? ( Ignorable by Annotation )

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

609
    return $invoice->/** @scrutinizer ignore-call */ update_meta( $meta_key, $meta_value, $prev_value );

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...
610
}
611
612
function wpinv_get_items( $invoice_id = 0 ) {
613
    $invoice            = wpinv_get_invoice( $invoice_id );
614
    
615
    $items              = $invoice->get_items();
616
    $invoice_currency   = $invoice->get_currency();
617
618
    if ( !empty( $items ) && is_array( $items ) ) {
619
        foreach ( $items as $key => $item ) {
620
            $items[$key]['currency'] = $invoice_currency;
621
622
            if ( !isset( $item['subtotal'] ) ) {
623
                $items[$key]['subtotal'] = $items[$key]['amount'] * 1;
624
            }
625
        }
626
    }
627
628
    return apply_filters( 'wpinv_get_items', $items, $invoice_id );
629
}
630
631
function wpinv_get_fees( $invoice_id = 0 ) {
632
    $invoice           = wpinv_get_invoice( $invoice_id );
633
    $fees              = $invoice->get_fees();
634
635
    return apply_filters( 'wpinv_get_fees', $fees, $invoice_id );
636
}
637
638
function wpinv_get_invoice_ip( $invoice_id ) {
639
    $invoice = new WPInv_Invoice( $invoice_id );
640
    return $invoice->get_ip();
641
}
642
643
function wpinv_get_invoice_user_info( $invoice_id ) {
644
    $invoice = new WPInv_Invoice( $invoice_id );
645
    return $invoice->get_user_info();
646
}
647
648
function wpinv_subtotal( $invoice_id = 0, $currency = false ) {
649
    $invoice = new WPInv_Invoice( $invoice_id );
650
651
    return $invoice->get_subtotal( $currency );
652
}
653
654
function wpinv_tax( $invoice_id = 0, $currency = false ) {
655
    $invoice = new WPInv_Invoice( $invoice_id );
656
657
    return $invoice->get_tax( $currency );
658
}
659
660
function wpinv_discount( $invoice_id = 0, $currency = false, $dash = false ) {
661
    $invoice = wpinv_get_invoice( $invoice_id );
662
663
    return $invoice->get_discount( $currency, $dash );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_discount() has too many arguments starting with $dash. ( Ignorable by Annotation )

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

663
    return $invoice->/** @scrutinizer ignore-call */ get_discount( $currency, $dash );

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...
664
}
665
666
function wpinv_discount_code( $invoice_id = 0 ) {
667
    $invoice = new WPInv_Invoice( $invoice_id );
668
669
    return $invoice->get_discount_code();
670
}
671
672
function wpinv_payment_total( $invoice_id = 0, $currency = false ) {
673
    $invoice = new WPInv_Invoice( $invoice_id );
674
675
    return $invoice->get_total( $currency );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_total() has too many arguments starting with $currency. ( Ignorable by Annotation )

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

675
    return $invoice->/** @scrutinizer ignore-call */ get_total( $currency );

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...
676
}
677
678
function wpinv_get_date_created( $invoice_id = 0, $format = '' ) {
679
    $invoice = new WPInv_Invoice( $invoice_id );
680
681
    $format         = !empty( $format ) ? $format : get_option( 'date_format' );
682
    $date_created   = $invoice->get_created_date();
0 ignored issues
show
Bug introduced by
The method get_created_date() does not exist on WPInv_Invoice. Did you maybe mean get_date_created()? ( Ignorable by Annotation )

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

682
    /** @scrutinizer ignore-call */ 
683
    $date_created   = $invoice->get_created_date();

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...
683
    $date_created   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? date_i18n( $format, strtotime( $date_created ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like $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

683
    $date_created   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? date_i18n( /** @scrutinizer ignore-type */ $format, strtotime( $date_created ) ) : '';
Loading history...
684
685
    return $date_created;
686
}
687
688
function wpinv_get_invoice_date( $invoice_id = 0, $format = '', $default = true ) {
689
    $invoice = new WPInv_Invoice( $invoice_id );
690
    
691
    $format         = !empty( $format ) ? $format : get_option( 'date_format' );
692
    $date_completed = $invoice->get_completed_date();
693
    $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? date_i18n( $format, strtotime( $date_completed ) ) : '';
0 ignored issues
show
Bug introduced by
It seems like $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

693
    $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? date_i18n( /** @scrutinizer ignore-type */ $format, strtotime( $date_completed ) ) : '';
Loading history...
694
    if ( $invoice_date == '' && $default ) {
695
        $invoice_date   = wpinv_get_date_created( $invoice_id, $format );
696
    }
697
698
    return $invoice_date;
699
}
700
701
function wpinv_get_invoice_vat_number( $invoice_id = 0 ) {
702
    $invoice = new WPInv_Invoice( $invoice_id );
703
    
704
    return $invoice->vat_number;
0 ignored issues
show
Bug Best Practice introduced by
The property vat_number does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
705
}
706
707
function wpinv_insert_payment_note( $invoice_id = 0, $note = '', $user_type = false, $added_by_user = false, $system = false ) {
708
    $invoice = new WPInv_Invoice( $invoice_id );
709
710
    return $invoice->add_note( $note, $user_type, $added_by_user, $system );
711
}
712
713
function wpinv_get_invoice_notes( $invoice_id = 0, $type = '' ) {
714
    global $invoicing;
715
    
716
    if ( empty( $invoice_id ) ) {
717
        return NULL;
718
    }
719
    
720
    $notes = $invoicing->notes->get_invoice_notes( $invoice_id, $type );
721
    
722
    return apply_filters( 'wpinv_invoice_notes', $notes, $invoice_id, $type );
723
}
724
725
function wpinv_get_payment_key( $invoice_id = 0 ) {
726
	$invoice = new WPInv_Invoice( $invoice_id );
727
    return $invoice->get_key();
728
}
729
730
function wpinv_get_invoice_number( $invoice_id = 0 ) {
731
    $invoice = new WPInv_Invoice( $invoice_id );
732
    return $invoice->get_number();
733
}
734
735
function wpinv_get_cart_discountable_subtotal( $code_id ) {
736
    $cart_items = wpinv_get_cart_content_details();
737
    $items      = array();
738
739
    $excluded_items = wpinv_get_discount_excluded_items( $code_id );
740
741
    if( $cart_items ) {
742
743
        foreach( $cart_items as $item ) {
744
745
            if( ! in_array( $item['id'], $excluded_items ) ) {
746
                $items[] =  $item;
747
            }
748
        }
749
    }
750
751
    $subtotal = wpinv_get_cart_items_subtotal( $items );
752
753
    return apply_filters( 'wpinv_get_cart_discountable_subtotal', $subtotal );
754
}
755
756
function wpinv_get_cart_items_subtotal( $items ) {
757
    $subtotal = 0.00;
758
759
    if ( is_array( $items ) && ! empty( $items ) ) {
760
        $prices = wp_list_pluck( $items, 'subtotal' );
761
762
        if( is_array( $prices ) ) {
0 ignored issues
show
introduced by
The condition is_array($prices) is always true.
Loading history...
763
            $subtotal = array_sum( $prices );
764
        } else {
765
            $subtotal = 0.00;
766
        }
767
768
        if( $subtotal < 0 ) {
769
            $subtotal = 0.00;
770
        }
771
    }
772
773
    return apply_filters( 'wpinv_get_cart_items_subtotal', $subtotal );
774
}
775
776
function wpinv_get_cart_subtotal( $items = array() ) {
777
    $items    = !empty( $items ) ? $items : wpinv_get_cart_content_details();
778
    $subtotal = wpinv_get_cart_items_subtotal( $items );
779
780
    return apply_filters( 'wpinv_get_cart_subtotal', $subtotal );
781
}
782
783
function wpinv_cart_subtotal( $items = array(), $currency = '' ) {
784
785
    if( empty( $currency ) ) {
786
        $currency = wpinv_get_currency();
787
    }
788
789
    $price = wpinv_price( wpinv_format_amount( wpinv_get_cart_subtotal( $items ) ), $currency );
790
791
    return $price;
792
}
793
794
function wpinv_get_cart_total( $items = array(), $discounts = false, $invoice = array() ) {
0 ignored issues
show
Unused Code introduced by
The parameter $discounts is not used and could be removed. ( Ignorable by Annotation )

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

794
function wpinv_get_cart_total( $items = array(), /** @scrutinizer ignore-unused */ $discounts = false, $invoice = array() ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
795
    $subtotal  = (float)wpinv_get_cart_subtotal( $items );
796
    $discounts = (float)wpinv_get_cart_discounted_amount( $items );
797
    $cart_tax  = (float)wpinv_get_cart_tax( $items, $invoice );
798
    $fees      = (float)wpinv_get_cart_fee_total();
799
    if ( !empty( $invoice ) && $invoice->is_free_trial() ) {
800
        $total = 0;
801
    } else {
802
        $total     = $subtotal - $discounts + $cart_tax + $fees;
803
    }
804
805
    if ( $total < 0 ) {
806
        $total = 0.00;
807
    }
808
    
809
    $total = (float)apply_filters( 'wpinv_get_cart_total', $total, $items );
810
811
    return wpinv_sanitize_amount( $total );
812
}
813
814
function wpinv_cart_total( $cart_items = array(), $echo = true, $invoice = array() ) {
815
    global $cart_total;
816
    $total = wpinv_price( wpinv_format_amount( wpinv_get_cart_total( $cart_items, NULL, $invoice ) ), $invoice->get_currency() );
817
    $total = apply_filters( 'wpinv_cart_total', $total, $cart_items, $invoice );
818
    
819
    $cart_total = $total;
820
821
    if ( !$echo ) {
822
        return $total;
823
    }
824
825
    echo $total;
826
}
827
828
function wpinv_get_cart_tax( $items = array(), $invoice = 0 ) {
829
830
    if ( ! empty( $invoice ) && ! $invoice->is_taxable() ) {
831
        return 0;
832
    }
833
834
    $cart_tax = 0;
835
    $items    = !empty( $items ) ? $items : wpinv_get_cart_content_details();
836
837
    if ( $items ) {
838
        $taxes = wp_list_pluck( $items, 'tax' );
839
840
        if( is_array( $taxes ) ) {
0 ignored issues
show
introduced by
The condition is_array($taxes) is always true.
Loading history...
841
            $cart_tax = array_sum( $taxes );
842
        }
843
    }
844
845
    $cart_tax += wpinv_get_cart_fee_tax();
846
847
    return apply_filters( 'wpinv_get_cart_tax', wpinv_sanitize_amount( $cart_tax ) );
848
}
849
850
function wpinv_cart_tax( $items = array(), $echo = false, $currency = '', $invoice = 0 ) {
851
852
    if ( ! empty( $invoice && ! $invoice->is_taxable() ) ) {
853
        echo wpinv_price( wpinv_format_amount( 0 ), $currency );
854
        return;
855
    }
856
857
    $cart_tax = wpinv_get_cart_tax( $items, $invoice );
858
    $cart_tax = wpinv_price( wpinv_format_amount( $cart_tax ), $currency );
859
860
    $tax = apply_filters( 'wpinv_cart_tax', $cart_tax, $items );
861
862
    if ( !$echo ) {
863
        return $tax;
864
    }
865
866
    echo $tax;
867
}
868
869
function wpinv_get_cart_discount_code( $items = array() ) {
0 ignored issues
show
Unused Code introduced by
The parameter $items is not used and could be removed. ( Ignorable by Annotation )

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

869
function wpinv_get_cart_discount_code( /** @scrutinizer ignore-unused */ $items = array() ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
870
    $invoice = wpinv_get_invoice_cart();
871
    $cart_discount_code = !empty( $invoice ) ? $invoice->get_discount_code() : '';
872
    
873
    return apply_filters( 'wpinv_get_cart_discount_code', $cart_discount_code );
874
}
875
876
function wpinv_cart_discount_code( $items = array(), $echo = false ) {
877
    $cart_discount_code = wpinv_get_cart_discount_code( $items );
878
879
    if ( $cart_discount_code != '' ) {
880
        $cart_discount_code = ' (' . $cart_discount_code . ')';
881
    }
882
    
883
    $discount_code = apply_filters( 'wpinv_cart_discount_code', $cart_discount_code, $items );
884
885
    if ( !$echo ) {
886
        return $discount_code;
887
    }
888
889
    echo $discount_code;
890
}
891
892
function wpinv_get_cart_discount( $items = array() ) {
893
    $invoice = wpinv_get_invoice_cart();
894
    $cart_discount = !empty( $invoice ) ? $invoice->get_discount() : 0;
0 ignored issues
show
Bug introduced by
The call to WPInv_Invoice::get_discount() has too few arguments starting with discount. ( Ignorable by Annotation )

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

894
    $cart_discount = !empty( $invoice ) ? $invoice->/** @scrutinizer ignore-call */ get_discount() : 0;

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
895
    
896
    return apply_filters( 'wpinv_get_cart_discount', wpinv_sanitize_amount( $cart_discount ), $items );
897
}
898
899
function wpinv_cart_discount( $items = array(), $echo = false ) {
900
    $cart_discount = wpinv_get_cart_discount( $items );
901
    $cart_discount = wpinv_price( wpinv_format_amount( $cart_discount ) );
902
903
    $discount = apply_filters( 'wpinv_cart_discount', $cart_discount, $items );
904
905
    if ( !$echo ) {
906
        return $discount;
907
    }
908
909
    echo $discount;
910
}
911
912
function wpinv_get_cart_fees( $type = 'all', $item_id = 0 ) {
913
    $item = new WPInv_Item( $item_id );
914
    
915
    return $item->get_fees( $type, $item_id );
916
}
917
918
function wpinv_get_cart_fee_total() {
919
    $total  = 0;
920
    $fees = wpinv_get_cart_fees();
921
    
922
    if ( $fees ) {
923
        foreach ( $fees as $fee_id => $fee ) {
924
            $total += $fee['amount'];
925
        }
926
    }
927
928
    return apply_filters( 'wpinv_get_cart_fee_total', $total );
929
}
930
931
function wpinv_get_cart_fee_tax() {
932
    $tax  = 0;
933
    $fees = wpinv_get_cart_fees();
934
935
    if ( $fees ) {
936
        foreach ( $fees as $fee_id => $fee ) {
937
            if( ! empty( $fee['no_tax'] ) ) {
938
                continue;
939
            }
940
941
            $tax += wpinv_calculate_tax( $fee['amount'] );
942
        }
943
    }
944
945
    return apply_filters( 'wpinv_get_cart_fee_tax', $tax );
946
}
947
948
function wpinv_cart_has_recurring_item() {
949
    $cart_items = wpinv_get_cart_contents();
950
    
951
    if ( empty( $cart_items ) ) {
952
        return false;
953
    }
954
    
955
    $has_subscription = false;
956
    foreach( $cart_items as $cart_item ) {
957
        if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
958
            $has_subscription = true;
959
            break;
960
        }
961
    }
962
    
963
    return apply_filters( 'wpinv_cart_has_recurring_item', $has_subscription, $cart_items );
964
}
965
966
function wpinv_cart_has_free_trial() {
967
    $invoice = wpinv_get_invoice_cart();
968
    
969
    $free_trial = false;
970
    
971
    if ( !empty( $invoice ) && $invoice->is_free_trial() ) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $invoice->is_free_trial() targeting WPInv_Invoice::is_free_trial() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
Deprecated Code introduced by
The function WPInv_Invoice::is_free_trial() has been deprecated. ( Ignorable by Annotation )

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

971
    if ( !empty( $invoice ) && /** @scrutinizer ignore-deprecated */ $invoice->is_free_trial() ) {
Loading history...
972
        $free_trial = true;
973
    }
974
    
975
    return apply_filters( 'wpinv_cart_has_free_trial', $free_trial, $invoice );
976
}
977
978
function wpinv_get_cart_contents() {
979
    $cart_details = wpinv_get_cart_details();
980
    
981
    return apply_filters( 'wpinv_get_cart_contents', $cart_details );
982
}
983
984
function wpinv_get_cart_content_details() {
985
    global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpinv_is_last_cart_item, $wpinv_flat_discount_total;
986
    $cart_items = wpinv_get_cart_contents();
987
    
988
    if ( empty( $cart_items ) ) {
989
        return false;
990
    }
991
    $invoice = wpinv_get_invoice_cart();
992
	if ( empty( $invoice ) ) {
993
        return false;
994
    }
995
996
    $details = array();
997
    $length  = count( $cart_items ) - 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $length is dead and can be removed.
Loading history...
998
    
999
    if ( empty( $_POST['country'] ) ) {
1000
        $_POST['country'] = $invoice->country;
0 ignored issues
show
Bug Best Practice introduced by
The property country does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1001
    }
1002
    if ( !isset( $_POST['state'] ) ) {
1003
        $_POST['state'] = $invoice->state;
0 ignored issues
show
Bug Best Practice introduced by
The property state does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1004
    }
1005
1006
    foreach( $cart_items as $key => $item ) {
1007
        $item_id            = isset( $item['id'] ) ? sanitize_text_field( $item['id'] ) : '';
1008
        if ( empty( $item_id ) ) {
1009
            continue;
1010
        }
1011
        
1012
        $wpi_current_id         = $invoice->ID;
1013
        $wpi_item_id            = $item_id;
1014
        
1015
        if ( isset( $item['custom_price'] ) && $item['custom_price'] !== '' ) {
1016
            $item_price = $item['custom_price'];
1017
        } else {
1018
            if ( isset( $item['item_price'] ) && $item['item_price'] !== '' && $item['item_price'] !== false ) {
1019
                $item_price = $item['item_price'];
1020
            } else {
1021
                $item_price = wpinv_get_item_price( $item_id );
1022
            }
1023
        }
1024
        $discount           = wpinv_get_cart_item_discount_amount( $item );
1025
        $discount           = apply_filters( 'wpinv_get_cart_content_details_item_discount_amount', $discount, $item );
1026
        $quantity           = wpinv_get_cart_item_quantity( $item );
1027
        $fees               = wpinv_get_cart_fees( 'fee', $item_id );
1028
        
1029
        $subtotal           = $item_price * $quantity;
1030
        $tax_rate           = wpinv_get_tax_rate( $_POST['country'], $_POST['state'], $wpi_item_id );
1031
        $tax_class          = $wpinv_euvat->get_item_class( $item_id );
1032
        $tax                = wpinv_get_cart_item_tax( $item_id, $subtotal - $discount );
1033
        
1034
        if ( ! $invoice->is_taxable() ) {
1035
            $tax = 0;
1036
        }
1037
        if ( wpinv_prices_include_tax() ) {
1038
            $subtotal -= wpinv_round_amount( $tax );
1039
        }
1040
        
1041
        $total              = $subtotal - $discount + $tax;
1042
        
1043
        // Do not allow totals to go negatve
1044
        if( $total < 0 ) {
1045
            $total = 0;
1046
        }
1047
        
1048
        $details[ $key ]  = array(
1049
            'id'                => $item_id,
1050
            'name'              => !empty($item['name']) ? $item['name'] : get_the_title( $item_id ),
0 ignored issues
show
Bug introduced by
$item_id of type string is incompatible with the type WP_Post|integer expected by parameter $post of get_the_title(). ( Ignorable by Annotation )

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

1050
            'name'              => !empty($item['name']) ? $item['name'] : get_the_title( /** @scrutinizer ignore-type */ $item_id ),
Loading history...
1051
            'item_price'        => wpinv_round_amount( $item_price ),
1052
            'custom_price'      => isset( $item['custom_price'] ) ? $item['custom_price'] : '',
1053
            'quantity'          => $quantity,
1054
            'discount'          => wpinv_round_amount( $discount ),
1055
            'subtotal'          => wpinv_round_amount( $subtotal ),
1056
            'tax'               => wpinv_round_amount( $tax ),
1057
            'price'             => wpinv_round_amount( $total ),
1058
            'vat_rates_class'   => $tax_class,
1059
            'vat_rate'          => $tax_rate,
1060
            'meta'              => isset( $item['meta'] ) ? $item['meta'] : array(),
1061
            'fees'              => $fees,
1062
        );
1063
        
1064
        if ( $wpinv_is_last_cart_item ) {
1065
            $wpinv_is_last_cart_item   = false;
1066
            $wpinv_flat_discount_total = 0.00;
1067
        }
1068
    }
1069
    
1070
    return $details;
1071
}
1072
1073
function wpinv_get_cart_details( $invoice_id = 0 ) {
1074
    global $ajax_cart_details;
1075
1076
    $invoice      = wpinv_get_invoice_cart( $invoice_id );
1077
    $cart_details = $ajax_cart_details;
1078
    if ( empty( $cart_details ) && ! empty( $invoice->cart_details ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1079
        $cart_details = $invoice->cart_details;
1080
    }
1081
1082
    if ( ! empty( $cart_details ) && is_array( $cart_details ) ) {
1083
        $invoice_currency = ! empty( $invoice->currency ) ? $invoice->currency : wpinv_get_default_country();
0 ignored issues
show
Bug Best Practice introduced by
The property currency does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1084
1085
        foreach ( $cart_details as $key => $cart_item ) {
1086
            $cart_details[ $key ]['currency'] = $invoice_currency;
1087
1088
            if ( ! isset( $cart_item['subtotal'] ) ) {
1089
                $cart_details[ $key ]['subtotal'] = $cart_item['price'];
1090
            }
1091
        }
1092
    }
1093
1094
    return apply_filters( 'wpinv_get_cart_details', $cart_details, $invoice_id );
1095
}
1096
1097
function wpinv_record_status_change( $invoice_id, $new_status, $old_status ) {
1098
    if ( 'wpi_invoice' != get_post_type( $invoice_id ) ) {
1099
        return;
1100
    }
1101
1102
    if ( ( $old_status == 'wpi-pending' && $new_status == 'draft' ) || ( $old_status == 'draft' && $new_status == 'wpi-pending' ) ) {
1103
        return;
1104
    }
1105
1106
    $invoice    = wpinv_get_invoice( $invoice_id );
1107
1108
    if ( wpinv_use_taxes() && $new_status == 'publish' ) {
1109
        
1110
        if ( WPInv_EUVat::same_country_rule() == 'no' && wpinv_is_base_country( $invoice->country ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The property country does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1111
            $invoice->add_note( __( 'VAT was reverse charged', 'invoicing' ), false, false, true );
1112
        }
1113
    }
1114
1115
    $old_status = wpinv_status_nicename( $old_status );
1116
    $new_status = wpinv_status_nicename( $new_status );
1117
1118
    $status_change = sprintf( __( 'Invoice status changed from %s to %s', 'invoicing' ), $old_status, $new_status );
1119
1120
    // Add note
1121
    return $invoice->add_note( $status_change, false, false, true );
1122
}
1123
add_action( 'wpinv_update_status', 'wpinv_record_status_change', 100, 3 );
1124
1125
function wpinv_complete_payment( $invoice_id, $new_status, $old_status ) {
1126
    global $wpi_has_free_trial;
1127
    
1128
    $wpi_has_free_trial = false;
1129
    
1130
    if ( $old_status == 'publish' ) {
1131
        return; // Make sure that payments are only paid once
1132
    }
1133
1134
    // Make sure the payment completion is only processed when new status is paid
1135
    if ( $new_status != 'publish' ) {
1136
        return;
1137
    }
1138
1139
    $invoice = new WPInv_Invoice( $invoice_id );
1140
    if ( empty( $invoice ) ) {
1141
        return;
1142
    }
1143
1144
    $wpi_has_free_trial = $invoice->is_free_trial();
0 ignored issues
show
Deprecated Code introduced by
The function WPInv_Invoice::is_free_trial() has been deprecated. ( Ignorable by Annotation )

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

1144
    $wpi_has_free_trial = /** @scrutinizer ignore-deprecated */ $invoice->is_free_trial();
Loading history...
Bug introduced by
Are you sure the assignment to $wpi_has_free_trial is correct as $invoice->is_free_trial() targeting WPInv_Invoice::is_free_trial() 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...
1145
    $completed_date = $invoice->completed_date;
0 ignored issues
show
Bug Best Practice introduced by
The property completed_date does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1146
    $cart_details   = $invoice->cart_details;
0 ignored issues
show
Bug Best Practice introduced by
The property cart_details does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
1147
1148
    do_action( 'wpinv_pre_complete_payment', $invoice_id );
1149
1150
    if ( is_array( $cart_details ) ) {
1151
        // Increase purchase count and earnings
1152
        foreach ( $cart_details as $cart_index => $item ) {
1153
            // Ensure these actions only run once, ever
1154
            if ( empty( $completed_date ) ) {
1155
                do_action( 'wpinv_complete_item_payment', $item['id'], $invoice_id, $item, $cart_index );
1156
            }
1157
        }
1158
    }
1159
    
1160
    // Check for discount codes and increment their use counts
1161
    if ( $discounts = $invoice->get_discounts( true ) ) {
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $context of WPInv_Invoice::get_discounts(). ( Ignorable by Annotation )

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

1161
    if ( $discounts = $invoice->get_discounts( /** @scrutinizer ignore-type */ true ) ) {
Loading history...
1162
        if( ! empty( $discounts ) ) {
1163
            foreach( $discounts as $code ) {
1164
                wpinv_increase_discount_usage( $code );
1165
            }
1166
        }
1167
    }
1168
    
1169
    // Ensure this action only runs once ever
1170
    if( empty( $completed_date ) ) {
1171
        // Save the completed date
1172
        $invoice->set( 'completed_date', current_time( 'mysql', 0 ) );
1173
        $invoice->save();
1174
1175
        do_action( 'wpinv_complete_payment', $invoice_id );
1176
    }
1177
1178
    // Empty the shopping cart
1179
    wpinv_empty_cart();
1180
}
1181
add_action( 'wpinv_update_status', 'wpinv_complete_payment', 100, 3 );
1182
1183
function wpinv_update_payment_status( $invoice_id, $new_status = 'publish' ) {    
1184
    $invoice = !empty( $invoice_id ) && is_object( $invoice_id ) ? $invoice_id : wpinv_get_invoice( (int)$invoice_id );
1185
1186
    if ( empty( $invoice ) ) {
1187
        return false;
1188
    }
1189
1190
    return $invoice->update_status( $new_status );
1191
}
1192
1193
function wpinv_cart_has_fees( $type = 'all' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

1193
function wpinv_cart_has_fees( /** @scrutinizer ignore-unused */ $type = 'all' ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1194
    return false;
1195
}
1196
1197
function wpinv_validate_checkout_fields() {
1198
    // Check if there is $_POST
1199
    if ( empty( $_POST ) ) {
1200
        return false;
1201
    }
1202
1203
    // Start an array to collect valid data
1204
    $valid_data = array(
1205
        'gateway'          => wpinv_checkout_validate_gateway(), // Gateway fallback
1206
        'discount'         => wpinv_checkout_validate_discounts(), // Set default discount
1207
        'cc_info'          => wpinv_checkout_validate_cc() // Credit card info
1208
    );
1209
1210
    $valid_data['invoice_user'] = wpinv_checkout_validate_invoice_user();
1211
    $valid_data['current_user'] = wpinv_checkout_validate_current_user();
1212
1213
    // Return collected data
1214
    return $valid_data;
1215
}
1216
1217
function wpinv_checkout_validate_gateway() {
1218
    $gateway = wpinv_get_default_gateway();
1219
    
1220
    $invoice = wpinv_get_invoice_cart();
1221
    $has_subscription = $invoice->is_recurring();
1222
    if ( empty( $invoice ) ) {
1223
        wpinv_set_error( 'invalid_invoice', __( 'Your cart is empty.', 'invoicing' ) );
1224
        return $gateway;
1225
    }
1226
1227
    // Check if a gateway value is present
1228
    if ( !empty( $_REQUEST['wpi-gateway'] ) ) {
1229
        $gateway = sanitize_text_field( $_REQUEST['wpi-gateway'] );
1230
1231
        if ( $invoice->is_free() ) {
1232
            $gateway = 'manual';
1233
        } elseif ( !wpinv_is_gateway_active( $gateway ) ) {
1234
            wpinv_set_error( 'invalid_gateway', __( 'The selected payment gateway is not enabled', 'invoicing' ) );
1235
        } elseif ( $has_subscription && !wpinv_gateway_support_subscription( $gateway ) ) {
1236
            if ( apply_filters( 'wpinv_reject_non_recurring_gateway', true ) ) {
1237
                wpinv_set_error( 'invalid_gateway', __( 'The selected payment gateway does not support subscription payment', 'invoicing' ) );
1238
            }
1239
        }
1240
    }
1241
1242
    if ( $has_subscription && count( wpinv_get_cart_contents() ) > 1 ) {
1243
        wpinv_set_error( 'subscription_invalid', __( 'Only one subscription may be purchased through payment per checkout.', 'invoicing' ) );
1244
    }
1245
1246
    return $gateway;
1247
}
1248
1249
function wpinv_checkout_validate_discounts() {
1250
    global $wpi_cart;
1251
    
1252
    // Retrieve the discount stored in cookies
1253
    $discounts = wpinv_get_cart_discounts();
1254
1255
    if ( ! is_array( $discounts ) ) {
1256
        return NULL;
1257
    }
1258
1259
    $discounts = array_filter( $discounts );
1260
    $error    = false;
1261
1262
    if ( empty( $discounts ) ) {
1263
        return NULL;
1264
    }
1265
1266
    // If we have discounts, loop through them
1267
    foreach ( $discounts as $discount ) {
1268
        // Check if valid
1269
        if (  ! wpinv_is_discount_valid( $discount, (int) $wpi_cart->get_user_id() ) ) {
1270
            // Discount is not valid
1271
            $error = true;
1272
        }
1273
1274
    }
1275
1276
    if ( $error && ! wpinv_get_errors() ) {
1277
        wpinv_set_error( 'invalid_discount', __( 'Discount code you entered is invalid', 'invoicing' ) );
1278
    }
1279
1280
    return implode( ',', $discounts );
1281
}
1282
1283
function wpinv_checkout_validate_cc() {
1284
    $card_data = wpinv_checkout_get_cc_info();
1285
1286
    // Validate the card zip
1287
    if ( !empty( $card_data['wpinv_zip'] ) ) {
1288
        if ( !wpinv_checkout_validate_cc_zip( $card_data['wpinv_zip'], $card_data['wpinv_country'] ) ) {
1289
            wpinv_set_error( 'invalid_cc_zip', __( 'The zip / postcode you entered for your billing address is invalid', 'invoicing' ) );
1290
        }
1291
    }
1292
1293
    // This should validate card numbers at some point too
1294
    return $card_data;
1295
}
1296
1297
function wpinv_checkout_get_cc_info() {
1298
	$cc_info = array();
1299
	$cc_info['card_name']      = isset( $_POST['card_name'] )       ? sanitize_text_field( $_POST['card_name'] )       : '';
1300
	$cc_info['card_number']    = isset( $_POST['card_number'] )     ? sanitize_text_field( $_POST['card_number'] )     : '';
1301
	$cc_info['card_cvc']       = isset( $_POST['card_cvc'] )        ? sanitize_text_field( $_POST['card_cvc'] )        : '';
1302
	$cc_info['card_exp_month'] = isset( $_POST['card_exp_month'] )  ? sanitize_text_field( $_POST['card_exp_month'] )  : '';
1303
	$cc_info['card_exp_year']  = isset( $_POST['card_exp_year'] )   ? sanitize_text_field( $_POST['card_exp_year'] )   : '';
1304
	$cc_info['card_address']   = isset( $_POST['wpinv_address'] )  ? sanitize_text_field( $_POST['wpinv_address'] ) : '';
1305
	$cc_info['card_city']      = isset( $_POST['wpinv_city'] )     ? sanitize_text_field( $_POST['wpinv_city'] )    : '';
1306
	$cc_info['card_state']     = isset( $_POST['wpinv_state'] )    ? sanitize_text_field( $_POST['wpinv_state'] )   : '';
1307
	$cc_info['card_country']   = isset( $_POST['wpinv_country'] )  ? sanitize_text_field( $_POST['wpinv_country'] ) : '';
1308
	$cc_info['card_zip']       = isset( $_POST['wpinv_zip'] )      ? sanitize_text_field( $_POST['wpinv_zip'] )     : '';
1309
1310
	// Return cc info
1311
	return $cc_info;
1312
}
1313
1314
function wpinv_checkout_validate_cc_zip( $zip = 0, $country_code = '' ) {
1315
    $ret = false;
1316
1317
    if ( empty( $zip ) || empty( $country_code ) )
1318
        return $ret;
1319
1320
    $country_code = strtoupper( $country_code );
1321
1322
    $zip_regex = array(
1323
        "AD" => "AD\d{3}",
1324
        "AM" => "(37)?\d{4}",
1325
        "AR" => "^([A-Z]{1}\d{4}[A-Z]{3}|[A-Z]{1}\d{4}|\d{4})$",
1326
        "AS" => "96799",
1327
        "AT" => "\d{4}",
1328
        "AU" => "^(0[289][0-9]{2})|([1345689][0-9]{3})|(2[0-8][0-9]{2})|(290[0-9])|(291[0-4])|(7[0-4][0-9]{2})|(7[8-9][0-9]{2})$",
1329
        "AX" => "22\d{3}",
1330
        "AZ" => "\d{4}",
1331
        "BA" => "\d{5}",
1332
        "BB" => "(BB\d{5})?",
1333
        "BD" => "\d{4}",
1334
        "BE" => "^[1-9]{1}[0-9]{3}$",
1335
        "BG" => "\d{4}",
1336
        "BH" => "((1[0-2]|[2-9])\d{2})?",
1337
        "BM" => "[A-Z]{2}[ ]?[A-Z0-9]{2}",
1338
        "BN" => "[A-Z]{2}[ ]?\d{4}",
1339
        "BR" => "\d{5}[\-]?\d{3}",
1340
        "BY" => "\d{6}",
1341
        "CA" => "^[ABCEGHJKLMNPRSTVXY]{1}\d{1}[A-Z]{1} *\d{1}[A-Z]{1}\d{1}$",
1342
        "CC" => "6799",
1343
        "CH" => "^[1-9][0-9][0-9][0-9]$",
1344
        "CK" => "\d{4}",
1345
        "CL" => "\d{7}",
1346
        "CN" => "\d{6}",
1347
        "CR" => "\d{4,5}|\d{3}-\d{4}",
1348
        "CS" => "\d{5}",
1349
        "CV" => "\d{4}",
1350
        "CX" => "6798",
1351
        "CY" => "\d{4}",
1352
        "CZ" => "\d{3}[ ]?\d{2}",
1353
        "DE" => "\b((?:0[1-46-9]\d{3})|(?:[1-357-9]\d{4})|(?:[4][0-24-9]\d{3})|(?:[6][013-9]\d{3}))\b",
1354
        "DK" => "^([D-d][K-k])?( |-)?[1-9]{1}[0-9]{3}$",
1355
        "DO" => "\d{5}",
1356
        "DZ" => "\d{5}",
1357
        "EC" => "([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?",
1358
        "EE" => "\d{5}",
1359
        "EG" => "\d{5}",
1360
        "ES" => "^([1-9]{2}|[0-9][1-9]|[1-9][0-9])[0-9]{3}$",
1361
        "ET" => "\d{4}",
1362
        "FI" => "\d{5}",
1363
        "FK" => "FIQQ 1ZZ",
1364
        "FM" => "(9694[1-4])([ \-]\d{4})?",
1365
        "FO" => "\d{3}",
1366
        "FR" => "^(F-)?((2[A|B])|[0-9]{2})[0-9]{3}$",
1367
        "GE" => "\d{4}",
1368
        "GF" => "9[78]3\d{2}",
1369
        "GL" => "39\d{2}",
1370
        "GN" => "\d{3}",
1371
        "GP" => "9[78][01]\d{2}",
1372
        "GR" => "\d{3}[ ]?\d{2}",
1373
        "GS" => "SIQQ 1ZZ",
1374
        "GT" => "\d{5}",
1375
        "GU" => "969[123]\d([ \-]\d{4})?",
1376
        "GW" => "\d{4}",
1377
        "HM" => "\d{4}",
1378
        "HN" => "(?:\d{5})?",
1379
        "HR" => "\d{5}",
1380
        "HT" => "\d{4}",
1381
        "HU" => "\d{4}",
1382
        "ID" => "\d{5}",
1383
        "IE" => "((D|DUBLIN)?([1-9]|6[wW]|1[0-8]|2[024]))?",
1384
        "IL" => "\d{5}",
1385
        "IN"=> "^[1-9][0-9][0-9][0-9][0-9][0-9]$", //india
1386
        "IO" => "BBND 1ZZ",
1387
        "IQ" => "\d{5}",
1388
        "IS" => "\d{3}",
1389
        "IT" => "^(V-|I-)?[0-9]{5}$",
1390
        "JO" => "\d{5}",
1391
        "JP" => "\d{3}-\d{4}",
1392
        "KE" => "\d{5}",
1393
        "KG" => "\d{6}",
1394
        "KH" => "\d{5}",
1395
        "KR" => "\d{3}[\-]\d{3}",
1396
        "KW" => "\d{5}",
1397
        "KZ" => "\d{6}",
1398
        "LA" => "\d{5}",
1399
        "LB" => "(\d{4}([ ]?\d{4})?)?",
1400
        "LI" => "(948[5-9])|(949[0-7])",
1401
        "LK" => "\d{5}",
1402
        "LR" => "\d{4}",
1403
        "LS" => "\d{3}",
1404
        "LT" => "\d{5}",
1405
        "LU" => "\d{4}",
1406
        "LV" => "\d{4}",
1407
        "MA" => "\d{5}",
1408
        "MC" => "980\d{2}",
1409
        "MD" => "\d{4}",
1410
        "ME" => "8\d{4}",
1411
        "MG" => "\d{3}",
1412
        "MH" => "969[67]\d([ \-]\d{4})?",
1413
        "MK" => "\d{4}",
1414
        "MN" => "\d{6}",
1415
        "MP" => "9695[012]([ \-]\d{4})?",
1416
        "MQ" => "9[78]2\d{2}",
1417
        "MT" => "[A-Z]{3}[ ]?\d{2,4}",
1418
        "MU" => "(\d{3}[A-Z]{2}\d{3})?",
1419
        "MV" => "\d{5}",
1420
        "MX" => "\d{5}",
1421
        "MY" => "\d{5}",
1422
        "NC" => "988\d{2}",
1423
        "NE" => "\d{4}",
1424
        "NF" => "2899",
1425
        "NG" => "(\d{6})?",
1426
        "NI" => "((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?",
1427
        "NL" => "^[1-9][0-9]{3}\s?([a-zA-Z]{2})?$",
1428
        "NO" => "\d{4}",
1429
        "NP" => "\d{5}",
1430
        "NZ" => "\d{4}",
1431
        "OM" => "(PC )?\d{3}",
1432
        "PF" => "987\d{2}",
1433
        "PG" => "\d{3}",
1434
        "PH" => "\d{4}",
1435
        "PK" => "\d{5}",
1436
        "PL" => "\d{2}-\d{3}",
1437
        "PM" => "9[78]5\d{2}",
1438
        "PN" => "PCRN 1ZZ",
1439
        "PR" => "00[679]\d{2}([ \-]\d{4})?",
1440
        "PT" => "\d{4}([\-]\d{3})?",
1441
        "PW" => "96940",
1442
        "PY" => "\d{4}",
1443
        "RE" => "9[78]4\d{2}",
1444
        "RO" => "\d{6}",
1445
        "RS" => "\d{5}",
1446
        "RU" => "\d{6}",
1447
        "SA" => "\d{5}",
1448
        "SE" => "^(s-|S-){0,1}[0-9]{3}\s?[0-9]{2}$",
1449
        "SG" => "\d{6}",
1450
        "SH" => "(ASCN|STHL) 1ZZ",
1451
        "SI" => "\d{4}",
1452
        "SJ" => "\d{4}",
1453
        "SK" => "\d{3}[ ]?\d{2}",
1454
        "SM" => "4789\d",
1455
        "SN" => "\d{5}",
1456
        "SO" => "\d{5}",
1457
        "SZ" => "[HLMS]\d{3}",
1458
        "TC" => "TKCA 1ZZ",
1459
        "TH" => "\d{5}",
1460
        "TJ" => "\d{6}",
1461
        "TM" => "\d{6}",
1462
        "TN" => "\d{4}",
1463
        "TR" => "\d{5}",
1464
        "TW" => "\d{3}(\d{2})?",
1465
        "UA" => "\d{5}",
1466
        "UK" => "^(GIR|[A-Z]\d[A-Z\d]??|[A-Z]{2}\d[A-Z\d]??)[ ]??(\d[A-Z]{2})$",
1467
        "US" => "^\d{5}([\-]?\d{4})?$",
1468
        "UY" => "\d{5}",
1469
        "UZ" => "\d{6}",
1470
        "VA" => "00120",
1471
        "VE" => "\d{4}",
1472
        "VI" => "008(([0-4]\d)|(5[01]))([ \-]\d{4})?",
1473
        "WF" => "986\d{2}",
1474
        "YT" => "976\d{2}",
1475
        "YU" => "\d{5}",
1476
        "ZA" => "\d{4}",
1477
        "ZM" => "\d{5}"
1478
    );
1479
1480
    if ( ! isset ( $zip_regex[ $country_code ] ) || preg_match( "/" . $zip_regex[ $country_code ] . "/i", $zip ) )
1481
        $ret = true;
1482
1483
    return apply_filters( 'wpinv_is_zip_valid', $ret, $zip, $country_code );
1484
}
1485
1486
function wpinv_checkout_validate_agree_to_terms() {
1487
    // Validate agree to terms
1488
    if ( ! isset( $_POST['wpi_agree_to_terms'] ) || $_POST['wpi_agree_to_terms'] != 1 ) {
1489
        // User did not agree
1490
        wpinv_set_error( 'agree_to_terms', apply_filters( 'wpinv_agree_to_terms_text', __( 'You must agree to the terms of use', 'invoicing' ) ) );
1491
    }
1492
}
1493
1494
function wpinv_checkout_validate_invoice_user() {
1495
    global $wpi_cart, $user_ID;
1496
1497
    if(empty($wpi_cart)){
1498
        $wpi_cart = wpinv_get_invoice_cart();
1499
    }
1500
1501
    $invoice_user = (int)$wpi_cart->get_user_id();
1502
    $valid_user_data = array(
1503
        'user_id' => $invoice_user
1504
    );
1505
1506
    // If guest checkout allowed
1507
    if ( !wpinv_require_login_to_checkout() ) {
1508
        return $valid_user_data;
1509
    }
1510
    
1511
    // Verify there is a user_ID
1512
    if ( $user_ID == $invoice_user ) {
1513
        // Get the logged in user data
1514
        $user_data = get_userdata( $user_ID );
1515
        $required_fields  = wpinv_checkout_required_fields();
1516
1517
        // Loop through required fields and show error messages
1518
         if ( !empty( $required_fields ) ) {
1519
            foreach ( $required_fields as $field_name => $value ) {
1520
                if ( in_array( $value, $required_fields ) && empty( $_POST[ 'wpinv_' . $field_name ] ) ) {
1521
                    wpinv_set_error( $value['error_id'], $value['error_message'] );
1522
                }
1523
            }
1524
        }
1525
1526
        // Verify data
1527
        if ( $user_data ) {
1528
            // Collected logged in user data
1529
            $valid_user_data = array(
1530
                'user_id'     => $user_ID,
1531
                'email'       => isset( $_POST['wpinv_email'] ) ? sanitize_email( $_POST['wpinv_email'] ) : $user_data->user_email,
1532
                'first_name'  => isset( $_POST['wpinv_first_name'] ) && ! empty( $_POST['wpinv_first_name'] ) ? sanitize_text_field( $_POST['wpinv_first_name'] ) : $user_data->first_name,
1533
                'last_name'   => isset( $_POST['wpinv_last_name'] ) && ! empty( $_POST['wpinv_last_name']  ) ? sanitize_text_field( $_POST['wpinv_last_name']  ) : $user_data->last_name,
1534
            );
1535
1536
            if ( !empty( $_POST[ 'wpinv_email' ] ) && !is_email( $_POST[ 'wpinv_email' ] ) ) {
1537
                wpinv_set_error( 'invalid_email', __( 'Please enter a valid email address', 'invoicing' ) );
1538
            }
1539
        } else {
1540
            // Set invalid user error
1541
            wpinv_set_error( 'invalid_user', __( 'The user billing information is invalid', 'invoicing' ) );
1542
        }
1543
    } else {
1544
        // Set invalid user error
1545
        wpinv_set_error( 'invalid_user_id', __( 'The invalid invoice user id', 'invoicing' ) );
1546
    }
1547
1548
    // Return user data
1549
    return $valid_user_data;
1550
}
1551
1552
function wpinv_checkout_validate_current_user() {
1553
    global $wpi_cart;
1554
1555
    $data = array();
1556
    
1557
    if ( is_user_logged_in() ) {
1558
        if ( !wpinv_require_login_to_checkout() || ( wpinv_require_login_to_checkout() && (int)$wpi_cart->get_user_id() === (int)get_current_user_id() ) ) {
1559
            $data['user_id'] = (int)get_current_user_id();
1560
        } else {
1561
            wpinv_set_error( 'logged_in_only', __( 'You are not allowed to pay for this invoice', 'invoicing' ) );
1562
        }
1563
    } else {
1564
        // If guest checkout allowed
1565
        if ( !wpinv_require_login_to_checkout() ) {
1566
            $data['user_id'] = 0;
1567
        } else {
1568
            wpinv_set_error( 'logged_in_only', __( 'You must be logged in to pay for this invoice', 'invoicing' ) );
1569
        }
1570
    }
1571
1572
    return $data;
1573
}
1574
1575
function wpinv_checkout_form_get_user( $valid_data = array() ) {
1576
1577
    if ( !empty( $valid_data['current_user']['user_id'] ) ) {
1578
        $user = $valid_data['current_user'];
1579
    } else {
1580
        // Set the valid invoice user
1581
        $user = $valid_data['invoice_user'];
1582
    }
1583
1584
    // Verify invoice have an user
1585
    if ( false === $user || empty( $user ) ) {
1586
        return false;
1587
    }
1588
1589
    $address_fields = array(
1590
        'first_name',
1591
        'last_name',
1592
        'company',
1593
        'vat_number',
1594
        'phone',
1595
        'address',
1596
        'city',
1597
        'state',
1598
        'country',
1599
        'zip',
1600
    );
1601
    
1602
    foreach ( $address_fields as $field ) {
1603
        $user[$field]  = !empty( $_POST['wpinv_' . $field] ) ? sanitize_text_field( $_POST['wpinv_' . $field] ) : false;
1604
        
1605
        if ( !empty( $user['user_id'] ) && !empty( $valid_data['current_user']['user_id'] ) && $valid_data['current_user']['user_id'] == $valid_data['invoice_user']['user_id'] ) {
1606
            update_user_meta( $user['user_id'], '_wpinv_' . $field, $user[$field] );
1607
        }
1608
    }
1609
1610
    // Return valid user
1611
    return $user;
1612
}
1613
1614
function wpinv_set_checkout_session( $invoice_data = array() ) {
1615
    global $wpi_session;
1616
    return $wpi_session->set( 'wpinv_checkout', $invoice_data );
1617
}
1618
1619
function wpinv_get_checkout_session() {
1620
	global $wpi_session;
1621
    return $wpi_session->get( 'wpinv_checkout' );
1622
}
1623
1624
function wpinv_empty_cart() {
1625
    global $wpi_session;
1626
1627
    // Remove cart contents
1628
    $wpi_session->set( 'wpinv_checkout', NULL );
1629
1630
    // Remove all cart fees
1631
    $wpi_session->set( 'wpi_cart_fees', NULL );
1632
1633
    do_action( 'wpinv_empty_cart' );
1634
}
1635
1636
function wpinv_process_checkout() {
1637
    global $wpinv_euvat, $wpi_checkout_id, $wpi_cart;
1638
1639
    wpinv_clear_errors();
1640
1641
    $invoice = wpinv_get_invoice_cart();
1642
    if ( empty( $invoice ) ) {
1643
        return false;
1644
    }
1645
1646
    $wpi_cart = $invoice;
1647
1648
    $wpi_checkout_id = $invoice->ID;
1649
1650
    do_action( 'wpinv_pre_process_checkout' );
1651
    
1652
    if ( !wpinv_get_cart_contents() ) { // Make sure the cart isn't empty
1653
        $valid_data = false;
1654
        wpinv_set_error( 'empty_cart', __( 'Your cart is empty', 'invoicing' ) );
1655
    } else {
1656
        // Validate the form $_POST data
1657
        $valid_data = wpinv_validate_checkout_fields();
1658
        
1659
        // Allow themes and plugins to hook to errors
1660
        do_action( 'wpinv_checkout_error_checks', $valid_data, $_POST );
1661
    }
1662
    
1663
    $is_ajax    = defined( 'DOING_AJAX' ) && DOING_AJAX;
1664
    
1665
    // Validate the user
1666
    $user = wpinv_checkout_form_get_user( $valid_data );
1667
1668
    // Let extensions validate fields after user is logged in if user has used login/registration form
1669
    do_action( 'wpinv_checkout_user_error_checks', $user, $valid_data, $_POST );
1670
    
1671
    if ( false === $valid_data || wpinv_get_errors() || ! $user ) {
1672
        if ( $is_ajax && 'wpinv_payment_form' != $_REQUEST['action'] ) {
1673
            do_action( 'wpinv_ajax_checkout_errors' );
1674
            die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1675
        } else {
1676
            return false;
1677
        }
1678
    }
1679
1680
    if ( $is_ajax && 'wpinv_payment_form' != $_REQUEST['action'] ) {
1681
        // Save address fields.
1682
        $address_fields = array( 'first_name', 'last_name', 'phone', 'address', 'city', 'country', 'state', 'zip', 'company' );
1683
        foreach ( $address_fields as $field ) {
1684
            if ( isset( $user[$field] ) ) {
1685
                $invoice->set( $field, $user[$field] );
1686
            }
1687
1688
            $invoice->save();
1689
        }
1690
1691
        $response['success']            = true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$response was never initialized. Although not strictly required by PHP, it is generally a good practice to add $response = array(); before regardless.
Loading history...
1692
        $response['data']['subtotal']   = $invoice->get_subtotal();
1693
        $response['data']['subtotalf']  = $invoice->get_subtotal( true );
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $context of WPInv_Invoice::get_subtotal(). ( Ignorable by Annotation )

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

1693
        $response['data']['subtotalf']  = $invoice->get_subtotal( /** @scrutinizer ignore-type */ true );
Loading history...
1694
        $response['data']['discount']   = $invoice->get_discount();
0 ignored issues
show
Bug introduced by
The call to WPInv_Invoice::get_discount() has too few arguments starting with discount. ( Ignorable by Annotation )

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

1694
        /** @scrutinizer ignore-call */ 
1695
        $response['data']['discount']   = $invoice->get_discount();

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
1695
        $response['data']['discountf']  = $invoice->get_discount( true );
1696
        $response['data']['tax']        = $invoice->get_tax();
0 ignored issues
show
Bug introduced by
The call to WPInv_Invoice::get_tax() has too few arguments starting with tax. ( Ignorable by Annotation )

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

1696
        /** @scrutinizer ignore-call */ 
1697
        $response['data']['tax']        = $invoice->get_tax();

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
1697
        $response['data']['taxf']       = $invoice->get_tax( true );
1698
        $response['data']['total']      = $invoice->get_total();
1699
        $response['data']['totalf']     = $invoice->get_total( true );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_total() has too many arguments starting with true. ( Ignorable by Annotation )

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

1699
        /** @scrutinizer ignore-call */ 
1700
        $response['data']['totalf']     = $invoice->get_total( true );

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...
1700
	    $response['data']['free']       = $invoice->is_free() && ( ! ( (float) $response['data']['total'] > 0 ) || $invoice->is_free_trial() ) ? true : false;
0 ignored issues
show
Deprecated Code introduced by
The function WPInv_Invoice::is_free_trial() has been deprecated. ( Ignorable by Annotation )

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

1700
	    $response['data']['free']       = $invoice->is_free() && ( ! ( (float) $response['data']['total'] > 0 ) || /** @scrutinizer ignore-deprecated */ $invoice->is_free_trial() ) ? true : false;
Loading history...
Bug introduced by
Are you sure the usage of $invoice->is_free_trial() targeting WPInv_Invoice::is_free_trial() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
1701
1702
        wp_send_json( $response );
1703
    }
1704
    
1705
    $user_info = array(
1706
        'user_id'        => $user['user_id'],
1707
        'first_name'     => $user['first_name'],
1708
        'last_name'      => $user['last_name'],
1709
        'email'          => $invoice->get_email(),
1710
        'company'        => $user['company'],
1711
        'phone'          => $user['phone'],
1712
        'address'        => $user['address'],
1713
        'city'           => $user['city'],
1714
        'country'        => $user['country'],
1715
        'state'          => $user['state'],
1716
        'zip'            => $user['zip'],
1717
    );
1718
    
1719
    $cart_items = wpinv_get_cart_contents();
1720
    $discounts  = wpinv_get_cart_discounts();
1721
    
1722
    // Setup invoice information
1723
    $invoice_data = array(
1724
        'invoice_id'        => !empty( $invoice ) ? $invoice->ID : 0,
1725
        'items'             => $cart_items,
1726
        'cart_discounts'    => $discounts,
1727
        'fees'              => wpinv_get_cart_fees(),        // Any arbitrary fees that have been added to the cart
1728
        'subtotal'          => wpinv_get_cart_subtotal( $cart_items ),    // Amount before taxes and discounts
1729
        'discount'          => wpinv_get_cart_items_discount_amount( $cart_items, $discounts ), // Discounted amount
1730
        'tax'               => wpinv_get_cart_tax( $cart_items, $invoice ),               // Taxed amount
1731
        'price'             => wpinv_get_cart_total( $cart_items, $discounts ),    // Amount after taxes
1732
        'invoice_key'       => $invoice->get_key() ? $invoice->get_key() : $invoice->generate_key(),
1733
        'user_email'        => $invoice->get_email(),
1734
        'date'              => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ),
1735
        'user_info'         => stripslashes_deep( $user_info ),
1736
        'post_data'         => $_POST,
1737
        'cart_details'      => $cart_items,
1738
        'gateway'           => $valid_data['gateway'],
1739
        'card_info'         => $valid_data['cc_info']
1740
    );
1741
    
1742
    $vat_info   = $wpinv_euvat->current_vat_data();
1743
    if ( is_array( $vat_info ) ) {
1744
        $invoice_data['user_info']['vat_number']        = $vat_info['number'];
1745
        $invoice_data['user_info']['vat_rate']          = wpinv_get_tax_rate($invoice_data['user_info']['country'], $invoice_data['user_info']['state']);
1746
        $invoice_data['user_info']['adddress_confirmed']    = isset($vat_info['adddress_confirmed']) ? $vat_info['adddress_confirmed'] : false;
1747
1748
        // Add the VAT rate to each item in the cart
1749
        foreach( $invoice_data['cart_details'] as $key => $item_data) {
1750
            $rate = wpinv_get_tax_rate($invoice_data['user_info']['country'], $invoice_data['user_info']['state'], $item_data['id']);
1751
            $invoice_data['cart_details'][$key]['vat_rate'] = wpinv_round_amount( $rate, 4 );
1752
        }
1753
    }
1754
    
1755
    // Save vat fields.
1756
    $address_fields = array( 'vat_number', 'vat_rate', 'adddress_confirmed' );
1757
    foreach ( $address_fields as $field ) {
1758
        if ( isset( $invoice_data['user_info'][$field] ) ) {
1759
            $invoice->set( $field, $invoice_data['user_info'][$field] );
1760
        }
1761
    }
1762
    $invoice->save();
1763
1764
    // Add the user data for hooks
1765
    $valid_data['user'] = $user;
1766
    
1767
    // Allow themes and plugins to hook before the gateway
1768
    do_action( 'wpinv_checkout_before_gateway', $_POST, $user_info, $valid_data );
1769
1770
     // If it is free, abort.
1771
     if ( $invoice->is_free() && ( ! $invoice->is_recurring() || 0 ==  $invoice->get_recurring_details( 'total' ) ) ) {
1772
        $invoice_data['gateway'] = 'manual';
1773
        $_POST['wpi-gateway'] = 'manual';
1774
    }
1775
1776
    // Allow the invoice data to be modified before it is sent to the gateway
1777
    $invoice_data = apply_filters( 'wpinv_data_before_gateway', $invoice_data, $valid_data );
1778
    
1779
    if ( $invoice_data['price'] && $invoice_data['gateway'] == 'manual' ) {
1780
        $mode = 'test';
1781
    } else {
1782
        $mode = wpinv_is_test_mode( $invoice_data['gateway'] ) ? 'test' : 'live';
1783
    }
1784
1785
    // Setup the data we're storing in the purchase session
1786
    $session_data = $invoice_data;
1787
    // Make sure credit card numbers are never stored in sessions
1788
    if ( !empty( $session_data['card_info']['card_number'] ) ) {
1789
        unset( $session_data['card_info']['card_number'] );
1790
    }
1791
    
1792
    // Used for showing item links to non logged-in users after purchase, and for other plugins needing purchase data.
1793
    wpinv_set_checkout_session( $invoice_data );
1794
    
1795
    // Set gateway
1796
    $invoice->update_meta( '_wpinv_gateway', $invoice_data['gateway'] );
1797
    $invoice->update_meta( '_wpinv_mode', $mode );
1798
    $invoice->update_meta( '_wpinv_checkout', date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) );
1799
    
1800
    do_action( 'wpinv_checkout_before_send_to_gateway', $invoice, $invoice_data );
1801
1802
    // Send info to the gateway for payment processing
1803
    wpinv_send_to_gateway( $invoice_data['gateway'], $invoice_data );
1804
    die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1805
}
1806
add_action( 'wpinv_payment', 'wpinv_process_checkout' );
1807
1808
function wpinv_get_invoices( $args ) {
1809
    $args = wp_parse_args( $args, array(
1810
        'status'   => array_keys( wpinv_get_invoice_statuses() ),
1811
        'type'     => 'wpi_invoice',
1812
        'parent'   => null,
1813
        'user'     => null,
1814
        'email'    => '',
1815
        'limit'    => get_option( 'posts_per_page' ),
1816
        'offset'   => null,
1817
        'page'     => 1,
1818
        'exclude'  => array(),
1819
        'orderby'  => 'date',
1820
        'order'    => 'DESC',
1821
        'return'   => 'objects',
1822
        'paginate' => false,
1823
    ) );
1824
    
1825
    // Handle some BW compatibility arg names where wp_query args differ in naming.
1826
    $map_legacy = array(
1827
        'numberposts'    => 'limit',
1828
        'post_type'      => 'type',
1829
        'post_status'    => 'status',
1830
        'post_parent'    => 'parent',
1831
        'author'         => 'user',
1832
        'posts_per_page' => 'limit',
1833
        'paged'          => 'page',
1834
    );
1835
1836
    foreach ( $map_legacy as $from => $to ) {
1837
        if ( isset( $args[ $from ] ) ) {
1838
            $args[ $to ] = $args[ $from ];
1839
        }
1840
    }
1841
1842
    if ( get_query_var( 'paged' ) )
1843
        $args['page'] = get_query_var('paged');
1844
    else if ( get_query_var( 'page' ) )
1845
        $args['page'] = get_query_var( 'page' );
1846
    else if ( !empty( $args[ 'page' ] ) )
1847
        $args['page'] = $args[ 'page' ];
1848
    else
1849
        $args['page'] = 1;
1850
1851
    /**
1852
     * Generate WP_Query args. This logic will change if orders are moved to
1853
     * custom tables in the future.
1854
     */
1855
    $wp_query_args = array(
1856
        'post_type'      => 'wpi_invoice',
1857
        'post_status'    => $args['status'],
1858
        'posts_per_page' => $args['limit'],
1859
        'meta_query'     => array(),
1860
        'date_query'     => !empty( $args['date_query'] ) ? $args['date_query'] : array(),
1861
        'fields'         => 'ids',
1862
        'orderby'        => $args['orderby'],
1863
        'order'          => $args['order'],
1864
    );
1865
    
1866
    if ( !empty( $args['user'] ) ) {
1867
        $wp_query_args['author'] = absint( $args['user'] );
1868
    }
1869
1870
    if ( ! is_null( $args['parent'] ) ) {
1871
        $wp_query_args['post_parent'] = absint( $args['parent'] );
1872
    }
1873
1874
    if ( ! is_null( $args['offset'] ) ) {
1875
        $wp_query_args['offset'] = absint( $args['offset'] );
1876
    } else {
1877
        $wp_query_args['paged'] = absint( $args['page'] );
1878
    }
1879
1880
    if ( ! empty( $args['exclude'] ) ) {
1881
        $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] );
1882
    }
1883
1884
    if ( ! $args['paginate' ] ) {
1885
        $wp_query_args['no_found_rows'] = true;
1886
    }
1887
1888
    $wp_query_args = apply_filters('wpinv_get_invoices_args', $wp_query_args, $args);
1889
1890
    // Get results.
1891
    $invoices = new WP_Query( $wp_query_args );
1892
1893
    if ( 'objects' === $args['return'] ) {
1894
        $return = array_map( 'wpinv_get_invoice', $invoices->posts );
1895
    } elseif ( 'self' === $args['return'] ) {
1896
        return $invoices;
1897
    } else {
1898
        $return = $invoices->posts;
1899
    }
1900
1901
    if ( $args['paginate' ] ) {
1902
        return (object) array(
1903
            'invoices'      => $return,
1904
            'total'         => $invoices->found_posts,
1905
            'max_num_pages' => $invoices->max_num_pages,
1906
        );
1907
    } else {
1908
        return $return;
1909
    }
1910
}
1911
1912
function wpinv_get_user_invoices_columns() {
1913
    $columns = array(
1914
            'invoice-number'  => array( 'title' => __( 'ID', 'invoicing' ), 'class' => 'text-left' ),
1915
            'created-date'    => array( 'title' => __( 'Created Date', 'invoicing' ), 'class' => 'text-left' ),
1916
            'payment-date'    => array( 'title' => __( 'Payment Date', 'invoicing' ), 'class' => 'text-left' ),
1917
            'invoice-status'  => array( 'title' => __( 'Status', 'invoicing' ), 'class' => 'text-center' ),
1918
            'invoice-total'   => array( 'title' => __( 'Total', 'invoicing' ), 'class' => 'text-right' ),
1919
            'invoice-actions' => array( 'title' => '&nbsp;', 'class' => 'text-center' ),
1920
        );
1921
1922
    return apply_filters( 'wpinv_user_invoices_columns', $columns );
1923
}
1924
1925
function wpinv_payment_receipt( $atts, $content = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $content is not used and could be removed. ( Ignorable by Annotation )

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

1925
function wpinv_payment_receipt( $atts, /** @scrutinizer ignore-unused */ $content = null ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1926
    global $wpinv_receipt_args;
1927
1928
    $wpinv_receipt_args = shortcode_atts( array(
1929
        'error'           => __( 'Sorry, trouble retrieving payment receipt.', 'invoicing' ),
1930
        'price'           => true,
1931
        'discount'        => true,
1932
        'items'           => true,
1933
        'date'            => true,
1934
        'notes'           => true,
1935
        'invoice_key'     => false,
1936
        'payment_method'  => true,
1937
        'invoice_id'      => true
1938
    ), $atts, 'wpinv_receipt' );
1939
1940
    // Find the invoice.
1941
    $session = wpinv_get_checkout_session();
1942
1943
    if ( isset( $_GET['invoice_key'] ) ) {
1944
        $invoice_id = wpinv_get_invoice_id_by_key( urldecode( $_GET['invoice_key'] ) );
1945
    } else if ( isset( $_GET['invoice-id'] ) ) {
1946
        $invoice_id = (int) $_GET['invoice-id'];
1947
    } else if ( $session && isset( $session['invoice_key'] ) ) {
1948
        $invoice_id = wpinv_get_invoice_id_by_key( $session['invoice_key'] );
1949
    } else if ( isset( $wpinv_receipt_args['invoice_key'] ) && $wpinv_receipt_args['invoice_key'] ) {
1950
        $invoice_id = wpinv_get_invoice_id_by_key( $wpinv_receipt_args['invoice_key'] );
1951
    }
1952
1953
    // Did we find the invoice?
1954
    if ( empty( $invoice_id ) || ! $invoice = wpinv_get_invoice( $invoice_id ) ) {
1955
        return '<p class="alert alert-error">' . __( 'We could not find your invoice.', 'invoicing' ) . '</p>';
1956
    }
1957
1958
    $invoice_key   = $invoice->get_key();
1959
    $user_can_view = wpinv_can_view_receipt( $invoice_key );
1960
    if ( $user_can_view && isset( $_GET['invoice-id'] ) ) {
1961
        $user_can_view  = $_GET['invoice-id'] == $invoice->ID;
1962
    }
1963
1964
    // Key was provided, but user is logged out. Offer them the ability to login and view the receipt
1965
    if ( ! $user_can_view && ! empty( $invoice_key ) && ! is_user_logged_in() ) {
1966
        // login redirect
1967
        return '<p class="alert alert-error">' . __( 'You must be logged in to view this receipt', 'invoicing' ) . '</p>';
1968
    }
1969
1970
    if ( ! apply_filters( 'wpinv_user_can_view_receipt', $user_can_view, $wpinv_receipt_args ) ) {
1971
        return '<p class="alert alert-error">' . $wpinv_receipt_args['error'] . '</p>';
1972
    }
1973
1974
    ob_start();
1975
1976
    wpinv_get_template_part( 'wpinv-invoice-receipt' );
1977
1978
    $display = ob_get_clean();
1979
1980
    return $display;
1981
}
1982
1983
/**
1984
 * Given an invoice key, this function returns the id.
1985
 */
1986
function wpinv_get_invoice_id_by_key( $key ) {
1987
	global $wpdb;
1988
    $table      = $wpdb->prefix . 'getpaid_invoices';
1989
	return (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $table WHERE`key` = %s LIMIT 1", $key ) );
1990
}
1991
1992
function wpinv_can_view_receipt( $invoice_key = '' ) {
1993
	$return = current_user_can( 'manage_options' );
1994
1995
	if ( empty( $invoice_key ) ) {
1996
		return false;
1997
	}
1998
1999
	global $wpinv_receipt_args;
2000
2001
	$wpinv_receipt_args['id'] = wpinv_get_invoice_id_by_key( $invoice_key );
2002
	if ( isset( $_GET['invoice-id'] ) ) {
2003
		$wpinv_receipt_args['id'] = $invoice_key == wpinv_get_payment_key( (int)$_GET['invoice-id'] ) ? (int)$_GET['invoice-id'] : 0;
2004
	}
2005
2006
	if ( empty( $wpinv_receipt_args['id'] ) ) {
2007
		return $return;
2008
	}
2009
2010
	$invoice = wpinv_get_invoice( $wpinv_receipt_args['id'] );
2011
	if ( !( !empty( $invoice->ID ) && $invoice->get_key() === $invoice_key ) ) {
2012
		return $return;
2013
	}
2014
2015
	if ( is_user_logged_in() ) {
2016
		if ( (int)$invoice->get_user_id() === (int) get_current_user_id() ) {
2017
			$return = true;
2018
		}
2019
	}
2020
2021
	$session = wpinv_get_checkout_session();
2022
	if ( isset( $_GET['invoice_key'] ) || ( $session && isset( $session['invoice_key'] ) ) ) {
2023
		$check_key = isset( $_GET['invoice_key'] ) ? $_GET['invoice_key'] : $session['invoice_key'];
2024
2025
		if ( wpinv_require_login_to_checkout() ) {
2026
			$return = $return && $check_key == $invoice_key;
2027
		} else {
2028
			$return = $check_key == $invoice_key;
2029
		}
2030
	}
2031
2032
	return (bool) apply_filters( 'wpinv_can_view_receipt', $return, $invoice_key );
2033
}
2034
2035
function wpinv_pay_for_invoice() {
2036
    global $wpinv_euvat;
2037
    
2038
    if ( isset( $_GET['invoice_key'] ) ) {
2039
        $checkout_uri   = wpinv_get_checkout_uri();
2040
        $invoice_key    = sanitize_text_field( $_GET['invoice_key'] );
2041
        
2042
        if ( empty( $invoice_key ) ) {
2043
            wpinv_set_error( 'invalid_invoice', __( 'Invoice not found', 'invoicing' ) );
2044
            wp_redirect( $checkout_uri );
0 ignored issues
show
Bug introduced by
It seems like $checkout_uri can also be of type false; however, parameter $location of wp_redirect() 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

2044
            wp_redirect( /** @scrutinizer ignore-type */ $checkout_uri );
Loading history...
2045
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2046
        }
2047
        
2048
        do_action( 'wpinv_check_pay_for_invoice', $invoice_key );
2049
2050
        $invoice_id    = wpinv_get_invoice_id_by_key( $invoice_key );
2051
        $user_can_view = wpinv_can_view_receipt( $invoice_key );
2052
        if ( $user_can_view && isset( $_GET['invoice-id'] ) ) {
2053
            $invoice_id     = (int)$_GET['invoice-id'];
2054
            $user_can_view  = $invoice_key == wpinv_get_payment_key( (int)$_GET['invoice-id'] ) ? true : false;
2055
        }
2056
        
2057
        if ( $invoice_id && $user_can_view && ( $invoice = wpinv_get_invoice( $invoice_id ) ) ) {
2058
            if ( $invoice->needs_payment() ) {
2059
                $data                   = array();
2060
                $data['invoice_id']     = $invoice_id;
2061
                $data['cart_discounts'] = $invoice->get_discounts( true );
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $context of WPInv_Invoice::get_discounts(). ( Ignorable by Annotation )

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

2061
                $data['cart_discounts'] = $invoice->get_discounts( /** @scrutinizer ignore-type */ true );
Loading history...
2062
                
2063
                wpinv_set_checkout_session( $data );
2064
                
2065
                if ( wpinv_get_option( 'vat_ip_country_default' ) ) {
2066
                    $_POST['country']   = $wpinv_euvat->get_country_by_ip();
2067
                    $_POST['state']     = $_POST['country'] == $invoice->country ? $invoice->state : '';
0 ignored issues
show
Bug Best Practice introduced by
The property country does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property state does not exist on WPInv_Invoice. Since you implemented __get, consider adding a @property annotation.
Loading history...
2068
                    
2069
                    wpinv_recalculate_tax( true );
2070
                }
2071
                
2072
            } else {
2073
                $checkout_uri = $invoice->get_view_url();
2074
            }
2075
        } else {
2076
            wpinv_set_error( 'invalid_invoice', __( 'You are not allowed to view this invoice', 'invoicing' ) );
2077
            
2078
            $checkout_uri = is_user_logged_in() ? wpinv_get_history_page_uri() : wp_login_url( get_permalink() );
0 ignored issues
show
Bug introduced by
It seems like get_permalink() can also be of type false; however, parameter $redirect of wp_login_url() 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

2078
            $checkout_uri = is_user_logged_in() ? wpinv_get_history_page_uri() : wp_login_url( /** @scrutinizer ignore-type */ get_permalink() );
Loading history...
2079
        }
2080
        
2081
        if(wp_redirect( $checkout_uri )){
2082
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2083
        };
2084
        wpinv_die();
2085
    }
2086
}
2087
add_action( 'wpinv_pay_for_invoice', 'wpinv_pay_for_invoice' );
2088
2089
function wpinv_handle_pay_via_invoice_link( $invoice_key ) {
2090
    if ( !empty( $invoice_key ) && !empty( $_REQUEST['_wpipay'] ) && !is_user_logged_in() && $invoice_id = wpinv_get_invoice_id_by_key( $invoice_key ) ) {
2091
        if ( $invoice = wpinv_get_invoice( $invoice_id ) ) {
2092
            $user_id = $invoice->get_user_id();
2093
            $secret = sanitize_text_field( $_GET['_wpipay'] );
2094
            
2095
            if ( $secret === md5( $user_id . '::' . $invoice->get_email() . '::' . $invoice_key ) ) { // valid invoice link
2096
                $redirect_to = remove_query_arg( '_wpipay', get_permalink() );
2097
                
2098
                wpinv_guest_redirect( $redirect_to, $user_id );
2099
                exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2100
            }
2101
        }
2102
    }
2103
}
2104
add_action( 'wpinv_check_pay_for_invoice', 'wpinv_handle_pay_via_invoice_link' );
2105
2106
function wpinv_set_payment_transaction_id( $invoice_id = 0, $transaction_id = '' ) {
2107
    $invoice_id = is_object( $invoice_id ) && !empty( $invoice_id->ID ) ? $invoice_id : $invoice_id;
2108
    
2109
    if ( empty( $invoice_id ) && $invoice_id > 0 ) {
2110
        return false;
2111
    }
2112
    
2113
    if ( empty( $transaction_id ) ) {
2114
        $transaction_id = $invoice_id;
2115
    }
2116
2117
    $transaction_id = apply_filters( 'wpinv_set_payment_transaction_id', $transaction_id, $invoice_id );
2118
    
2119
    return wpinv_update_invoice_meta( $invoice_id, '_wpinv_transaction_id', $transaction_id );
2120
}
2121
2122
function wpinv_invoice_status_label( $status, $status_display = '' ) {
2123
    if ( empty( $status_display ) ) {
2124
        $status_display = wpinv_status_nicename( $status );
2125
    }
2126
    
2127
    switch ( $status ) {
2128
        case 'publish' :
2129
        case 'wpi-renewal' :
2130
            $class = 'label-success';
2131
        break;
2132
        case 'wpi-pending' :
2133
            $class = 'label-primary';
2134
        break;
2135
        case 'wpi-processing' :
2136
            $class = 'label-warning';
2137
        break;
2138
        case 'wpi-onhold' :
2139
            $class = 'label-info';
2140
        break;
2141
        case 'wpi-cancelled' :
2142
        case 'wpi-failed' :
2143
            $class = 'label-danger';
2144
        break;
2145
        default:
2146
            $class = 'label-default';
2147
        break;
2148
    }
2149
    
2150
    $label = '<span class="label label-inv-' . $status . ' ' . $class . '">' . $status_display . '</span>';
2151
    
2152
    return apply_filters( 'wpinv_invoice_status_label', $label, $status, $status_display );
2153
}
2154
2155
function wpinv_format_invoice_number( $number, $type = '' ) {
2156
    $check = apply_filters( 'wpinv_pre_format_invoice_number', null, $number, $type );
2157
    if ( null !== $check ) {
2158
        return $check;
2159
    }
2160
2161
    if ( !empty( $number ) && !is_numeric( $number ) ) {
2162
        return $number;
2163
    }
2164
2165
    $padd  = wpinv_get_option( 'invoice_number_padd' );
2166
    $prefix  = wpinv_get_option( 'invoice_number_prefix' );
2167
    $postfix = wpinv_get_option( 'invoice_number_postfix' );
2168
    
2169
    $padd = absint( $padd );
2170
    $formatted_number = absint( $number );
2171
    
2172
    if ( $padd > 0 ) {
2173
        $formatted_number = zeroise( $formatted_number, $padd );
2174
    }    
2175
2176
    $formatted_number = $prefix . $formatted_number . $postfix;
0 ignored issues
show
Bug introduced by
Are you sure $postfix of type false|mixed 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

2176
    $formatted_number = $prefix . $formatted_number . /** @scrutinizer ignore-type */ $postfix;
Loading history...
Bug introduced by
Are you sure $prefix of type false|mixed 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

2176
    $formatted_number = /** @scrutinizer ignore-type */ $prefix . $formatted_number . $postfix;
Loading history...
2177
2178
    return apply_filters( 'wpinv_format_invoice_number', $formatted_number, $number, $prefix, $postfix, $padd );
2179
}
2180
2181
function wpinv_get_next_invoice_number( $type = '' ) {
2182
    $check = apply_filters( 'wpinv_get_pre_next_invoice_number', null, $type );
2183
    if ( null !== $check ) {
2184
        return $check;
2185
    }
2186
    
2187
    if ( !wpinv_sequential_number_active() ) {
2188
        return false;
2189
    }
2190
2191
    $number = $last_number = get_option( 'wpinv_last_invoice_number', 0 );
2192
    $start  = wpinv_get_option( 'invoice_sequence_start', 1 );
2193
    if ( !absint( $start ) > 0 ) {
2194
        $start = 1;
2195
    }
2196
    $increment_number = true;
2197
    $save_number = false;
2198
2199
    if ( !empty( $number ) && !is_numeric( $number ) && $number == wpinv_format_invoice_number( $number ) ) {
2200
        $number = wpinv_clean_invoice_number( $number );
2201
    }
2202
2203
    if ( empty( $number ) ) {
2204
        if ( !( $last_number === 0 || $last_number === '0' ) ) {
2205
            $last_invoice = wpinv_get_invoices( array( 'limit' => 1, 'order' => 'DESC', 'orderby' => 'ID', 'return' => 'posts', 'fields' => 'ids', 'status' => array_keys( wpinv_get_invoice_statuses( true, true ) ) ) );
2206
2207
            if ( !empty( $last_invoice[0] ) && $invoice_number = wpinv_get_invoice_number( $last_invoice[0] ) ) {
2208
                if ( is_numeric( $invoice_number ) ) {
2209
                    $number = $invoice_number;
2210
                } else {
2211
                    $number = wpinv_clean_invoice_number( $invoice_number );
2212
                }
2213
            }
2214
2215
            if ( empty( $number ) ) {
2216
                $increment_number = false;
2217
                $number = $start;
2218
                $save_number = ( $number - 1 );
2219
            } else {
2220
                $save_number = $number;
2221
            }
2222
        }
2223
    }
2224
2225
    if ( $start > $number ) {
2226
        $increment_number = false;
2227
        $number = $start;
2228
        $save_number = ( $number - 1 );
2229
    }
2230
2231
    if ( $save_number !== false ) {
2232
        update_option( 'wpinv_last_invoice_number', $save_number );
2233
    }
2234
    
2235
    $increment_number = apply_filters( 'wpinv_increment_payment_number', $increment_number, $number );
2236
2237
    if ( $increment_number ) {
2238
        $number++;
2239
    }
2240
2241
    return apply_filters( 'wpinv_get_next_invoice_number', $number );
2242
}
2243
2244
function wpinv_clean_invoice_number( $number, $type = '' ) {
2245
    $check = apply_filters( 'wpinv_pre_clean_invoice_number', null, $number, $type );
2246
    if ( null !== $check ) {
2247
        return $check;
2248
    }
2249
    
2250
    $prefix  = wpinv_get_option( 'invoice_number_prefix' );
2251
    $postfix = wpinv_get_option( 'invoice_number_postfix' );
2252
2253
    $number = preg_replace( '/' . $prefix . '/', '', $number, 1 );
0 ignored issues
show
Bug introduced by
Are you sure $prefix of type false|mixed 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

2253
    $number = preg_replace( '/' . /** @scrutinizer ignore-type */ $prefix . '/', '', $number, 1 );
Loading history...
2254
2255
    $length      = strlen( $number );
2256
    $postfix_pos = strrpos( $number, $postfix );
0 ignored issues
show
Bug introduced by
It seems like $postfix can also be of type false; however, parameter $needle of strrpos() 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

2256
    $postfix_pos = strrpos( $number, /** @scrutinizer ignore-type */ $postfix );
Loading history...
2257
    
2258
    if ( false !== $postfix_pos ) {
2259
        $number      = substr_replace( $number, '', $postfix_pos, $length );
2260
    }
2261
2262
    $number = intval( $number );
2263
2264
    return apply_filters( 'wpinv_clean_invoice_number', $number, $prefix, $postfix );
2265
}
2266
2267
function wpinv_save_number_post_saved( $post_ID, $post, $update ) {
2268
    global $wpdb;
2269
2270
    if ( !$update && !get_post_meta( $post_ID, '_wpinv_number', true ) ) {
2271
        wpinv_update_invoice_number( $post_ID, $post->post_status != 'auto-draft', $post->post_type );
2272
    }
2273
2274
    if ( !$update ) {
2275
        $wpdb->update( $wpdb->posts, array( 'post_name' => wpinv_generate_post_name( $post_ID ) ), array( 'ID' => $post_ID ) );
2276
        clean_post_cache( $post_ID );
2277
    }
2278
}
2279
add_action( 'save_post_wpi_invoice', 'wpinv_save_number_post_saved', 1, 3 );
2280
2281
function wpinv_save_number_post_updated( $post_ID, $post_after, $post_before ) {
2282
    if ( !empty( $post_after->post_type ) && $post_after->post_type == 'wpi_invoice' && $post_before->post_status == 'auto-draft' && $post_after->post_status != $post_before->post_status ) {
2283
        wpinv_update_invoice_number( $post_ID, true, $post_after->post_type );
2284
    }
2285
}
2286
add_action( 'post_updated', 'wpinv_save_number_post_updated', 1, 3 );
2287
2288
function wpinv_update_invoice_number( $post_ID, $save_sequential = false, $type = '' ) {
2289
    global $wpdb;
2290
    
2291
    $check = apply_filters( 'wpinv_pre_update_invoice_number', null, $post_ID, $save_sequential, $type );
2292
    if ( null !== $check ) {
2293
        return $check;
2294
    }
2295
2296
    if ( wpinv_sequential_number_active() ) {
2297
        $number = wpinv_get_next_invoice_number();
2298
2299
        if ( $save_sequential ) {
2300
            update_option( 'wpinv_last_invoice_number', $number );
2301
        }
2302
    } else {
2303
        $number = $post_ID;
2304
    }
2305
2306
    $number = wpinv_format_invoice_number( $number );
2307
2308
    update_post_meta( $post_ID, '_wpinv_number', $number );
2309
2310
    $wpdb->update( $wpdb->posts, array( 'post_title' => $number ), array( 'ID' => $post_ID ) );
2311
2312
    clean_post_cache( $post_ID );
2313
2314
    return $number;
2315
}
2316
2317
function wpinv_post_name_prefix( $post_type = 'wpi_invoice' ) {
2318
    return apply_filters( 'wpinv_post_name_prefix', 'inv-', $post_type );
2319
}
2320
2321
function wpinv_generate_post_name( $post_ID ) {
2322
    $prefix = wpinv_post_name_prefix( get_post_type( $post_ID ) );
2323
    $post_name = sanitize_title( $prefix . $post_ID );
2324
2325
    return apply_filters( 'wpinv_generate_post_name', $post_name, $post_ID, $prefix );
2326
}
2327
2328
function wpinv_is_invoice_viewed( $invoice_id ) {
2329
    if ( empty( $invoice_id ) ) {
2330
        return false;
2331
    }
2332
2333
    $viewed_meta = get_post_meta( $invoice_id, '_wpinv_is_viewed', true );
2334
2335
    return apply_filters( 'wpinv_is_invoice_viewed', 1 === (int)$viewed_meta, $invoice_id );
2336
}
2337
2338
function wpinv_mark_invoice_viewed() {
2339
2340
    if ( isset( $_GET['invoice_key'] ) || is_singular( 'wpi_invoice' ) || is_singular( 'wpi_quote' ) ) {
2341
        $invoice_key = isset( $_GET['invoice_key'] ) ? urldecode($_GET['invoice_key']) : '';
2342
	    global $post;
2343
2344
        if(!empty($invoice_key)){
2345
	        $invoice_id = wpinv_get_invoice_id_by_key($invoice_key);
2346
        } else if(!empty( $post ) && ($post->post_type == 'wpi_invoice' || $post->post_type == 'wpi_quote')) {
2347
			$invoice_id = $post->ID;
2348
        } else {
2349
        	return;
2350
        }
2351
2352
        $invoice = new WPInv_Invoice($invoice_id);
2353
2354
        if(!$invoice_id){
2355
            return;
2356
        }
2357
2358
	    if ( is_user_logged_in() ) {
2359
		    if ( (int)$invoice->get_user_id() === get_current_user_id() ) {
2360
			    update_post_meta($invoice_id,'_wpinv_is_viewed', 1);
2361
		    } else if ( !wpinv_require_login_to_checkout() && isset( $_GET['invoice_key'] ) && $_GET['invoice_key'] === $invoice->get_key() ) {
2362
			    update_post_meta($invoice_id,'_wpinv_is_viewed', 1);
2363
		    }
2364
	    } else {
2365
		    if ( !wpinv_require_login_to_checkout() && isset( $_GET['invoice_key'] ) && $_GET['invoice_key'] === $invoice->get_key() ) {
2366
			    update_post_meta($invoice_id,'_wpinv_is_viewed', 1);
2367
		    }
2368
	    }
2369
    }
2370
2371
}
2372
add_action( 'template_redirect', 'wpinv_mark_invoice_viewed' );
2373
2374
/**
2375
 * @return WPInv_Subscription
2376
 */
2377
function wpinv_get_subscription( $invoice, $by_parent = false ) {
2378
    if ( empty( $invoice ) ) {
2379
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type WPInv_Subscription.
Loading history...
2380
    }
2381
    
2382
    if ( ! is_object( $invoice ) && is_scalar( $invoice ) ) {
2383
        $invoice = wpinv_get_invoice( $invoice );
2384
    }
2385
    
2386
    if ( !( is_object( $invoice ) && ! empty( $invoice->ID ) && $invoice->is_recurring() ) ) {
2387
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type WPInv_Subscription.
Loading history...
2388
    }
2389
    
2390
    $invoice_id = ! $by_parent && ! empty( $invoice->parent_invoice ) ? $invoice->parent_invoice : $invoice->ID;
2391
    
2392
    $subs_db    = new WPInv_Subscriptions_DB;
2393
    $subs       = $subs_db->get_subscriptions( array( 'parent_payment_id' => $invoice_id, 'number' => 1 ) );
2394
    
2395
    if ( ! empty( $subs ) ) {
2396
        return reset( $subs );
2397
    }
2398
    
2399
    return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type WPInv_Subscription.
Loading history...
2400
}
2401
2402
function wpinv_filter_posts_clauses( $clauses, $wp_query ) {
2403
    global $wpdb;
2404
2405
    if ( ! empty( $wp_query->query_vars['orderby'] ) && $wp_query->query_vars['orderby'] == 'invoice_date' ) {
2406
        if ( !empty( $clauses['join'] ) ) {
2407
            $clauses['join'] .= " ";
2408
        }
2409
2410
        if ( !empty( $clauses['fields'] ) ) {
2411
            $clauses['fields'] .= ", ";
2412
        }
2413
2414
        $clauses['join'] .= "LEFT JOIN {$wpdb->postmeta} ON ( {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_wpinv_completed_date' )";
2415
        $clauses['fields'] .= "IF( {$wpdb->postmeta}.meta_value, {$wpdb->postmeta}.meta_value, {$wpdb->posts}.post_date ) AS invoice_date";
2416
        $clauses['orderby'] = "invoice_date DESC, {$wpdb->posts}.post_date DESC, {$wpdb->posts}.ID DESC";
2417
    }
2418
2419
    return $clauses;
2420
}
2421
add_filter( 'posts_clauses', 'wpinv_filter_posts_clauses', 10, 2 );
2422
2423
/**
2424
 * Processes an invoice refund.
2425
 * 
2426
 * @param int $invoice_id
2427
 * @param WPInv_Invoice $invoice
2428
 * @param array $status_transition
2429
 * @todo: descrease customer/store earnings
2430
 */
2431
function getpaid_maybe_process_refund( $invoice_id, $invoice, $status_transition ) {
2432
2433
    if ( empty( $status_transition['from'] ) || ! $invoice->has_status( 'wpi-refunded' ) || in_array( $status_transition['from'], array( 'publish', 'wpi-processing', 'wpi-renewal' ) ) ) {
2434
        return;
2435
    }
2436
2437
    $discount_code = $invoice->get_discount_code();
2438
    if ( ! empty( $discount_code ) ) {
2439
        $discount = wpinv_get_discount_obj( $discount_code );
2440
2441
        if ( $discount->exists() ) {
2442
            $discount->increase_usage( -1 );
2443
        }
2444
    
2445
    }
2446
2447
    do_action( 'wpinv_pre_refund_invoice', $invoice, $invoice_id );
2448
    do_action( 'wpinv_refund_invoice', $invoice, $invoice_id );
2449
    do_action( 'wpinv_post_refund_invoice', $invoice, $invoice_id );
2450
}
2451
add_action( 'getpaid_invoice_status_wpi-refunded', 'getpaid_maybe_process_refund', 10, 3 );
2452
2453
/**
2454
 * Fires when a payment fails.
2455
 * 
2456
 * @param int $invoice_id
2457
 * @param WPInv_Invoice $invoice
2458
 */
2459
function getpaid_maybe_process_failure( $invoice_id, $invoice ) {
2460
2461
    if ( ! $invoice->has_status( 'wpi-failed' ) ) {
2462
        return;
2463
    }
2464
2465
    $discount_code = $invoice->get_discount_code();
2466
    if ( ! empty( $discount_code ) ) {
2467
        $discount = wpinv_get_discount_obj( $discount_code );
2468
2469
        if ( $discount->exists() ) {
2470
            $discount->increase_usage( -1 );
2471
        }
2472
2473
    }
2474
2475
    do_action( 'wpinv_invoice_payment_failed', $invoice, $invoice_id );
2476
}
2477
add_action( 'getpaid_invoice_status_wpi-failed', 'getpaid_maybe_process_failure', 10, 2 );