Passed
Push — master ( c90a51...25dd78 )
by Brian
05:26
created

wpinv_validate_checkout_fields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 18
rs 9.9666
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
function wpinv_get_invoice_cart_id() {
15
    $wpinv_checkout = wpinv_get_checkout_session();
16
    
17
    if ( !empty( $wpinv_checkout['invoice_id'] ) ) {
18
        return $wpinv_checkout['invoice_id'];
19
    }
20
    
21
    return NULL;
22
}
23
24
/**
25
 * Create an invoice
26
 * 
27
 * @param  array $invoice_data   An array of invoice properties.
28
 * @param  bool  $wp_error       Whether to return false or WP_Error on failure.
29
 * @return int|WP_Error|WPInv_Invoice The value 0 or WP_Error on failure. The WPInv_Invoice object on success.
30
 */
31
function wpinv_insert_invoice( $invoice_data = array(), $wp_error = false ) {
32
    if ( empty( $invoice_data ) ) {
33
        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...
34
    }
35
    
36
    if ( !( !empty( $invoice_data['cart_details'] ) && is_array( $invoice_data['cart_details'] ) ) ) {
37
        return $wp_error ? new WP_Error( 'wpinv_invalid_items', __( 'Invoice must have atleast one item.', 'invoicing' ) ) : 0;
38
    }
39
    
40
    // If no user id is provided, default to the current user id
41
    if ( empty( $invoice_data['user_id'] ) ) {
42
        $invoice_data['user_id'] = get_current_user_id();
43
    }
44
    
45
    $invoice_data['invoice_id'] = !empty( $invoice_data['invoice_id'] ) ? (int)$invoice_data['invoice_id'] : 0;
46
    
47
    if ( empty( $invoice_data['status'] ) ) {
48
        $invoice_data['status'] = 'wpi-pending';
49
    }
50
51
    if ( empty( $invoice_data['post_type'] ) ) {
52
        $invoice_data['post_type'] = 'wpi_invoice';
53
    }
54
    
55
    if ( empty( $invoice_data['ip'] ) ) {
56
        $invoice_data['ip'] = wpinv_get_ip();
57
    }
58
59
    // default invoice args, note that status is checked for validity in wpinv_create_invoice()
60
    $default_args = array(
61
        'invoice_id'    => (int)$invoice_data['invoice_id'],
62
        'user_id'       => (int)$invoice_data['user_id'],
63
        'status'        => $invoice_data['status'],
64
        'post_type'     => $invoice_data['post_type'],
65
    );
66
67
    $invoice = wpinv_create_invoice( $default_args, $invoice_data, true );
68
    if ( is_wp_error( $invoice ) ) {
69
        return $wp_error ? $invoice : 0;
70
    }
71
    
72
    if ( empty( $invoice_data['invoice_id'] ) ) {
73
        //$invoice->add_note( wp_sprintf( __( 'Invoice is created with status %s.', 'invoicing' ), wpinv_status_nicename( $invoice->status ) ) );
74
    }
75
    
76
    // User info
77
    $default_user_info = array(
78
        'user_id'               => '',
79
        'first_name'            => '',
80
        'last_name'             => '',
81
        'email'                 => '',
82
        'company'               => '',
83
        'phone'                 => '',
84
        'address'               => '',
85
        'city'                  => '',
86
        'country'               => wpinv_get_default_country(),
87
        'state'                 => wpinv_get_default_state(),
88
        'zip'                   => '',
89
        'vat_number'            => '',
90
        'vat_rate'              => '',
91
        'adddress_confirmed'    => '',
92
        'discount'              => array(),
93
    );
94
95
    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

95
    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...
96
        if ( $user_address = wpinv_get_user_address( $user_id ) ) {
97
            $default_user_info = wp_parse_args( $user_address, $default_user_info );
98
        }
99
    }
100
    
101
    if ( empty( $invoice_data['user_info'] ) ) {
102
        $invoice_data['user_info'] = array();
103
    }
104
    
105
    $user_info = wp_parse_args( $invoice_data['user_info'], $default_user_info );
106
    
107
    if ( empty( $user_info['first_name'] ) ) {
108
        $user_info['first_name'] = $default_user_info['first_name'];
109
        $user_info['last_name'] = $default_user_info['last_name'];
110
    }
111
    
112
    if ( empty( $user_info['country'] ) ) {
113
        $user_info['country'] = $default_user_info['country'];
114
        $user_info['state'] = $default_user_info['state'];
115
        $user_info['city'] = $default_user_info['city'];
116
        $user_info['address'] = $default_user_info['address'];
117
        $user_info['zip'] = $default_user_info['zip'];
118
        $user_info['phone'] = $default_user_info['phone'];
119
    }
120
    
121
    if ( !empty( $user_info['discount'] ) && !is_array( $user_info['discount'] ) ) {
122
        $user_info['discount'] = (array)$user_info['discount'];
123
    }
124
125
    // Payment details
126
    $payment_details = array();
127
    if ( !empty( $invoice_data['payment_details'] ) ) {
128
        $default_payment_details = array(
129
            'gateway'           => 'manual',
130
            'gateway_title'     => '',
131
            'currency'          => wpinv_get_default_country(),
132
            'transaction_id'    => '',
133
        );
134
        
135
        $payment_details = wp_parse_args( $invoice_data['payment_details'], $default_payment_details );
0 ignored issues
show
Security Variable Injection introduced by
$invoice_data['payment_details'] can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_POST, and Data is passed through wp_unslash(), and wp_unslash($_POST) is assigned to $data
    in includes/class-wpinv-ajax.php on line 755
  2. Data is passed through wpinv_clean(), and wpinv_clean($data[$address_field['name']]) is assigned to $address_fields
    in includes/class-wpinv-ajax.php on line 893
  3. wpinv_insert_invoice() is called
    in includes/class-wpinv-ajax.php on line 928
  4. Enters via parameter $invoice_data
    in includes/wpinv-invoice-functions.php on line 31

Used in variable context

  1. wp_parse_args() is called
    in includes/wpinv-invoice-functions.php on line 135
  2. Enters via parameter $args
    in wordpress/wp-includes/functions.php on line 4366
  3. wp_parse_str() is called
    in wordpress/wp-includes/functions.php on line 4372
  4. Enters via parameter $string
    in wordpress/wp-includes/formatting.php on line 4886
  5. parse_str() is called
    in wordpress/wp-includes/formatting.php on line 4887

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
136
        
137
        if ( empty( $payment_details['gateway'] ) ) {
138
            $payment_details['gateway'] = 'manual';
139
        }
140
        
141
        if ( empty( $payment_details['currency'] ) ) {
142
            $payment_details['currency'] = wpinv_get_default_country();
143
        }
144
        
145
        if ( empty( $payment_details['gateway_title'] ) ) {
146
            $payment_details['gateway_title'] = wpinv_get_gateway_checkout_label( $payment_details['gateway'] );
147
        }
148
    }
149
    
150
    $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

150
    $invoice->/** @scrutinizer ignore-call */ 
151
              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...
151
    
152
    if ( !empty( $payment_details ) ) {
153
        $invoice->set( 'currency', $payment_details['currency'] );
154
        $invoice->set( 'gateway', $payment_details['gateway'] );
155
        $invoice->set( 'gateway_title', $payment_details['gateway_title'] );
156
        $invoice->set( 'transaction_id', $payment_details['transaction_id'] );
157
    }
158
    
159
    $invoice->set( 'user_info', $user_info );
160
    $invoice->set( 'first_name', $user_info['first_name'] );
161
    $invoice->set( 'last_name', $user_info['last_name'] );
162
    $invoice->set( 'address', $user_info['address'] );
163
    $invoice->set( 'company', $user_info['company'] );
164
    $invoice->set( 'vat_number', $user_info['vat_number'] );
165
    $invoice->set( 'phone', $user_info['phone'] );
166
    $invoice->set( 'city', $user_info['city'] );
167
    $invoice->set( 'country', $user_info['country'] );
168
    $invoice->set( 'state', $user_info['state'] );
169
    $invoice->set( 'zip', $user_info['zip'] );
170
    $invoice->set( 'discounts', $user_info['discount'] );
171
    $invoice->set( 'ip', ( !empty( $invoice_data['ip'] ) ? $invoice_data['ip'] : wpinv_get_ip() ) );
172
    $invoice->set( 'mode', ( wpinv_is_test_mode() ? 'test' : 'live' ) );
173
    $invoice->set( 'parent_invoice', ( !empty( $invoice_data['parent'] ) ? absint( $invoice_data['parent'] ) : '' ) );
174
    
175
    if ( !empty( $invoice_data['cart_details'] ) && is_array( $invoice_data['cart_details'] ) ) {
176
        foreach ( $invoice_data['cart_details'] as $key => $item ) {
177
            $item_id        = !empty( $item['id'] ) ? $item['id'] : 0;
178
            $quantity       = !empty( $item['quantity'] ) ? $item['quantity'] : 1;
179
            $name           = !empty( $item['name'] ) ? $item['name'] : '';
180
            $item_price     = isset( $item['item_price'] ) ? $item['item_price'] : '';
181
            
182
            $post_item  = new WPInv_Item( $item_id );
183
            if ( !empty( $post_item ) ) {
184
                $name       = !empty( $name ) ? $name : $post_item->get_name();
185
                $item_price = $item_price !== '' ? $item_price : $post_item->get_price();
186
            } else {
187
                continue;
188
            }
189
            
190
            $args = array(
191
                'name'          => $name,
192
                'quantity'      => $quantity,
193
                'item_price'    => $item_price,
194
                'custom_price'  => isset( $item['custom_price'] ) ? $item['custom_price'] : '',
195
                'tax'           => !empty( $item['tax'] ) ? $item['tax'] : 0.00,
196
                'discount'      => isset( $item['discount'] ) ? $item['discount'] : 0,
197
                'meta'          => isset( $item['meta'] ) ? $item['meta'] : array(),
198
                'fees'          => isset( $item['fees'] ) ? $item['fees'] : array(),
199
            );
200
201
            $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

201
            $invoice->/** @scrutinizer ignore-call */ 
202
                      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...
202
        }
203
    }
204
205
    $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

205
    $invoice->/** @scrutinizer ignore-call */ 
206
              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...
206
207
    if ( isset( $invoice_data['post_date'] ) ) {
208
        $invoice->set( 'date', $invoice_data['post_date'] );
209
    }
210
    
211
    // Invoice due date
212
    if ( isset( $invoice_data['due_date'] ) ) {
213
        $invoice->set( 'due_date', $invoice_data['due_date'] );
214
    }
215
    
216
    $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

216
    $invoice->/** @scrutinizer ignore-call */ 
217
              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...
217
    
218
    // Add notes
219
    if ( !empty( $invoice_data['private_note'] ) ) {
220
        $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

220
        $invoice->/** @scrutinizer ignore-call */ 
221
                  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...
221
    }
222
    if ( !empty( $invoice_data['user_note'] ) ) {
223
        $invoice->add_note( $invoice_data['user_note'], true );
224
    }
225
    
226
    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

226
    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...
227
228
        if ( isset( $invoice_data['valid_until'] ) ) {
229
            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...
230
        }
231
        return $invoice;
232
233
    }
234
235
    do_action( 'wpinv_insert_invoice', $invoice->ID, $invoice_data );
236
237
    if ( ! empty( $invoice->ID ) ) {
238
        global $wpi_userID, $wpinv_ip_address_country;
239
        
240
        $checkout_session = wpinv_get_checkout_session();
241
        
242
        $data_session                   = array();
243
        $data_session['invoice_id']     = $invoice->ID;
244
        $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

244
        /** @scrutinizer ignore-call */ 
245
        $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...
245
        
246
        wpinv_set_checkout_session( $data_session );
247
        
248
        $wpi_userID         = (int)$invoice->get_user_id();
249
        
250
        $_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...
251
        $_POST['state']     = $invoice->state;
0 ignored issues
show
Bug introduced by
The property state does not seem to exist on WP_Error.
Loading history...
252
253
        $invoice->set( 'country', sanitize_text_field( $_POST['country'] ) );
254
        $invoice->set( 'state', sanitize_text_field( $_POST['state'] ) );
255
        
256
        $wpinv_ip_address_country = $invoice->country;
257
258
        $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

258
        /** @scrutinizer ignore-call */ 
259
        $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...
259
260
        wpinv_set_checkout_session( $checkout_session );
261
262
        return $invoice;
263
    }
264
    
265
    if ( $wp_error ) {
266
        if ( is_wp_error( $invoice ) ) {
267
            return $invoice;
268
        } else {
269
            return new WP_Error( 'wpinv_insert_invoice_error', __( 'Error in insert invoice.', 'invoicing' ) );
270
        }
271
    } else {
272
        return 0;
273
    }
274
}
275
276
function wpinv_update_invoice( $invoice_data = array(), $wp_error = false ) {
277
    $invoice_ID = !empty( $invoice_data['ID'] ) ? absint( $invoice_data['ID'] ) : NULL;
278
279
    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...
280
        if ( $wp_error ) {
281
            return new WP_Error( 'invalid_invoice_id', __( 'Invalid invoice ID.', 'invoicing' ) );
282
        }
283
        return 0;
284
    }
285
286
    $invoice = wpinv_get_invoice( $invoice_ID );
287
288
    $recurring_item = $invoice->is_recurring() ? $invoice->get_recurring( true ) : NULL;
289
290
    if ( empty( $invoice->ID ) ) {
291
        if ( $wp_error ) {
292
            return new WP_Error( 'invalid_invoice', __( 'Invalid invoice.', 'invoicing' ) );
293
        }
294
        return 0;
295
    }
296
297
    if ( ! $invoice->has_status( array( 'wpi-pending' ) ) && ! $invoice->is_quote()  ) {
298
        if ( $wp_error ) {
299
            return new WP_Error( 'invalid_invoice_status', __( 'Only invoice with pending payment is allowed to update.', 'invoicing' ) );
300
        }
301
        return 0;
302
    }
303
304
    // Invoice status
305
    if ( !empty( $invoice_data['status'] ) ) {
306
        $invoice->set( 'status', $invoice_data['status'] );
307
    }
308
309
    // Invoice date
310
    if ( !empty( $invoice_data['post_date'] ) ) {
311
        $invoice->set( 'date', $invoice_data['post_date'] );
312
    }
313
314
    // Invoice due date
315
    if ( isset( $invoice_data['due_date'] ) ) {
316
        $invoice->set( 'due_date', $invoice_data['due_date'] );
317
    }
318
319
    // Invoice IP address
320
    if ( !empty( $invoice_data['ip'] ) ) {
321
        $invoice->set( 'ip', $invoice_data['ip'] );
322
    }
323
    
324
    // User info
325
    if ( !empty( $invoice_data['user_info'] ) && is_array( $invoice_data['user_info'] ) ) {
326
        $user_info = wp_parse_args( $invoice_data['user_info'], $invoice->user_info );
0 ignored issues
show
Security Variable Injection introduced by
$invoice_data['user_info'] can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_POST, and Data is passed through wp_unslash(), and wp_unslash($_POST) is assigned to $data
    in includes/class-wpinv-ajax.php on line 755
  2. Data is passed through wpinv_clean(), and wpinv_clean($data[$address_field['name']]) is assigned to $address_fields
    in includes/class-wpinv-ajax.php on line 893
  3. wpinv_update_invoice() is called
    in includes/class-wpinv-ajax.php on line 941
  4. Enters via parameter $invoice_data
    in includes/wpinv-invoice-functions.php on line 276

Used in variable context

  1. wp_parse_args() is called
    in includes/wpinv-invoice-functions.php on line 326
  2. Enters via parameter $args
    in wordpress/wp-includes/functions.php on line 4371
  3. wp_parse_str() is called
    in wordpress/wp-includes/functions.php on line 4377
  4. Enters via parameter $string
    in wordpress/wp-includes/formatting.php on line 4888
  5. parse_str() is called
    in wordpress/wp-includes/formatting.php on line 4889

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
327
328
        if ( $discounts = $invoice->get_discounts() ) {
329
            $set_discount = $discounts;
330
        } else {
331
            $set_discount = '';
332
        }
333
334
        // Manage discount
335
        if ( !empty( $invoice_data['user_info']['discount'] ) ) {
336
            // Remove discount
337
            if ( $invoice_data['user_info']['discount'] == 'none' ) {
338
                $set_discount = '';
339
            } else {
340
                $set_discount = $invoice_data['user_info']['discount'];
341
            }
342
343
            $invoice->set( 'discounts', $set_discount );
344
        }
345
346
        $user_info['discount'] = $set_discount;
347
348
        $invoice->set( 'user_info', $user_info );
349
    }
350
351
    if ( !empty( $invoice_data['cart_details'] ) && is_array( $invoice_data['cart_details'] ) && $cart_details = $invoice_data['cart_details'] ) {
352
        $remove_items = !empty( $cart_details['remove_items'] ) && is_array( $cart_details['remove_items'] ) ? $cart_details['remove_items'] : array();
353
354
        if ( !empty( $remove_items[0]['id'] ) ) {
355
            foreach ( $remove_items as $item ) {
356
                $item_id        = !empty( $item['id'] ) ? $item['id'] : 0;
357
                $quantity       = !empty( $item['quantity'] ) ? $item['quantity'] : 1;
358
                if ( empty( $item_id ) ) {
359
                    continue;
360
                }
361
362
                foreach ( $invoice->cart_details as $cart_index => $cart_item ) {
363
                    if ( $item_id == $cart_item['id'] ) {
364
                        $args = array(
365
                            'id'         => $item_id,
366
                            'quantity'   => $quantity,
367
                            'cart_index' => $cart_index
368
                        );
369
370
                        $invoice->remove_item( $item_id, $args );
371
                        break;
372
                    }
373
                }
374
            }
375
        }
376
377
        $add_items = !empty( $cart_details['add_items'] ) && is_array( $cart_details['add_items'] ) ? $cart_details['add_items'] : array();
378
379
        if ( !empty( $add_items[0]['id'] ) ) {
380
            foreach ( $add_items as $item ) {
381
                $item_id        = !empty( $item['id'] ) ? $item['id'] : 0;
382
                $post_item      = new WPInv_Item( $item_id );
383
                if ( empty( $post_item ) ) {
384
                    continue;
385
                }
386
387
                $valid_item = true;
388
                if ( !empty( $recurring_item ) ) {
389
                    if ( $recurring_item->ID != $item_id ) {
390
                        $valid_item = false;
391
                    }
392
                } else if ( wpinv_is_recurring_item( $item_id ) ) {
393
                    $valid_item = false;
394
                }
395
                
396
                if ( !$valid_item ) {
397
                    if ( $wp_error ) {
398
                        return new WP_Error( 'invalid_invoice_item', __( 'You can not add item because recurring item must be paid individually!', 'invoicing' ) );
399
                    }
400
                    return 0;
401
                }
402
403
                $quantity       = !empty( $item['quantity'] ) ? $item['quantity'] : 1;
404
                $name           = !empty( $item['name'] ) ? $item['name'] : $post_item->get_name();
405
                $item_price     = isset( $item['item_price'] ) ? $item['item_price'] : $post_item->get_price();
406
407
                $args = array(
408
                    'name'          => $name,
409
                    'quantity'      => $quantity,
410
                    'item_price'    => $item_price,
411
                    'custom_price'  => isset( $item['custom_price'] ) ? $item['custom_price'] : '',
412
                    'tax'           => !empty( $item['tax'] ) ? $item['tax'] : 0,
413
                    'discount'      => isset( $item['discount'] ) ? $item['discount'] : 0,
414
                    'meta'          => isset( $item['meta'] ) ? $item['meta'] : array(),
415
                    'fees'          => isset( $item['fees'] ) ? $item['fees'] : array(),
416
                );
417
418
                $invoice->add_item( $item_id, $args );
419
            }
420
        }
421
    }
422
    
423
    // Payment details
424
    if ( !empty( $invoice_data['payment_details'] ) && $payment_details = $invoice_data['payment_details'] ) {
425
        if ( !empty( $payment_details['gateway'] ) ) {
426
            $invoice->set( 'gateway', $payment_details['gateway'] );
427
        }
428
429
        if ( !empty( $payment_details['transaction_id'] ) ) {
430
            $invoice->set( 'transaction_id', $payment_details['transaction_id'] );
431
        }
432
    }
433
434
    do_action( 'wpinv_pre_update_invoice', $invoice->ID, $invoice_data );
435
436
    // Parent invoice
437
    if ( !empty( $invoice_data['parent'] ) ) {
438
        $invoice->set( 'parent_invoice', $invoice_data['parent'] );
439
    }
440
441
    // Save invoice data.
442
    $invoice->save();
443
    
444
    if ( empty( $invoice->ID ) || is_wp_error( $invoice ) ) {
445
        if ( $wp_error ) {
446
            if ( is_wp_error( $invoice ) ) {
447
                return $invoice;
448
            } else {
449
                return new WP_Error( 'wpinv_update_invoice_error', __( 'Error in update invoice.', 'invoicing' ) );
450
            }
451
        } else {
452
            return 0;
453
        }
454
    }
455
456
    // Add private note
457
    if ( !empty( $invoice_data['private_note'] ) ) {
458
        $invoice->add_note( $invoice_data['private_note'] );
459
    }
460
461
    // Add user note
462
    if ( !empty( $invoice_data['user_note'] ) ) {
463
        $invoice->add_note( $invoice_data['user_note'], true );
464
    }
465
466
    if ( $invoice->is_quote() ) {
467
468
        if ( isset( $invoice_data['valid_until'] ) ) {
469
            update_post_meta( $invoice->ID, 'wpinv_quote_valid_until', $invoice_data['valid_until'] );
470
        }
471
        return $invoice;
472
473
    }
474
475
    global $wpi_userID, $wpinv_ip_address_country;
476
477
    $checkout_session = wpinv_get_checkout_session();
478
479
    $data_session                   = array();
480
    $data_session['invoice_id']     = $invoice->ID;
481
    $data_session['cart_discounts'] = $invoice->get_discounts( true );
482
483
    wpinv_set_checkout_session( $data_session );
484
485
    $wpi_userID         = (int)$invoice->get_user_id();
486
487
    $_POST['country']   = !empty( $invoice->country ) ? $invoice->country : wpinv_get_default_country();
488
    $_POST['state']     = $invoice->state;
489
490
    $invoice->set( 'country', sanitize_text_field( $_POST['country'] ) );
491
    $invoice->set( 'state', sanitize_text_field( $_POST['state'] ) );
492
493
    $wpinv_ip_address_country = $invoice->country;
494
495
    $invoice = $invoice->recalculate_totals( true );
496
497
    do_action( 'wpinv_post_update_invoice', $invoice->ID, $invoice_data );
498
499
    wpinv_set_checkout_session( $checkout_session );
500
501
    return $invoice;
502
}
503
504
function wpinv_get_invoice( $invoice_id = 0, $cart = false ) {
505
    if ( $cart && empty( $invoice_id ) ) {
506
        $invoice_id = (int)wpinv_get_invoice_cart_id();
507
    }
508
509
    $invoice = new WPInv_Invoice( $invoice_id );
510
511
    if ( ! empty( $invoice ) && ! empty( $invoice->ID ) ) {
512
        return $invoice;
513
    }
514
515
    return NULL;
516
}
517
518
function wpinv_get_invoice_cart( $invoice_id = 0 ) {
519
    return wpinv_get_invoice( $invoice_id, true );
520
}
521
522
function wpinv_get_invoice_description( $invoice_id = 0 ) {
523
    $invoice = new WPInv_Invoice( $invoice_id );
524
    return $invoice->get_description();
525
}
526
527
function wpinv_get_invoice_currency_code( $invoice_id = 0 ) {
528
    $invoice = new WPInv_Invoice( $invoice_id );
529
    return $invoice->get_currency();
530
}
531
532
function wpinv_get_payment_user_email( $invoice_id ) {
533
    $invoice = new WPInv_Invoice( $invoice_id );
534
    return $invoice->get_email();
535
}
536
537
function wpinv_get_user_id( $invoice_id ) {
538
    $invoice = new WPInv_Invoice( $invoice_id );
539
    return $invoice->get_user_id();
540
}
541
542
function wpinv_get_invoice_status( $invoice_id, $return_label = false ) {
543
    $invoice = new WPInv_Invoice( $invoice_id );
544
    
545
    return $invoice->get_status( $return_label );
546
}
547
548
function wpinv_get_payment_gateway( $invoice_id, $return_label = false ) {
549
    $invoice = new WPInv_Invoice( $invoice_id );
550
    
551
    return $invoice->get_gateway( $return_label );
0 ignored issues
show
Unused Code introduced by
The call to WPInv_Invoice::get_gateway() has too many arguments starting with $return_label. ( Ignorable by Annotation )

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

551
    return $invoice->/** @scrutinizer ignore-call */ get_gateway( $return_label );

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...
552
}
553
554
function wpinv_get_payment_gateway_name( $invoice_id ) {
555
    $invoice = new WPInv_Invoice( $invoice_id );
556
    
557
    return $invoice->get_gateway_title();
558
}
559
560
function wpinv_get_payment_transaction_id( $invoice_id ) {
561
    $invoice = new WPInv_Invoice( $invoice_id );
562
    
563
    return $invoice->get_transaction_id();
564
}
565
566
function wpinv_get_id_by_transaction_id( $key ) {
567
    global $wpdb;
568
569
    $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 ) );
570
571
    if ( $invoice_id != NULL )
572
        return $invoice_id;
573
574
    return 0;
575
}
576
577
function wpinv_get_invoice_meta( $invoice_id = 0, $meta_key = '_wpinv_payment_meta', $single = true ) {
578
    $invoice = new WPInv_Invoice( $invoice_id );
579
580
    return $invoice->get_meta( $meta_key, $single );
581
}
582
583
function wpinv_update_invoice_meta( $invoice_id = 0, $meta_key = '', $meta_value = '', $prev_value = '' ) {
584
    $invoice = new WPInv_Invoice( $invoice_id );
585
    
586
    return $invoice->update_meta( $meta_key, $meta_value, $prev_value );
587
}
588
589
function wpinv_get_items( $invoice_id = 0 ) {
590
    $invoice            = wpinv_get_invoice( $invoice_id );
591
    
592
    $items              = $invoice->get_items();
593
    $invoice_currency   = $invoice->get_currency();
594
595
    if ( !empty( $items ) && is_array( $items ) ) {
596
        foreach ( $items as $key => $item ) {
597
            $items[$key]['currency'] = $invoice_currency;
598
599
            if ( !isset( $item['subtotal'] ) ) {
600
                $items[$key]['subtotal'] = $items[$key]['amount'] * 1;
601
            }
602
        }
603
    }
604
605
    return apply_filters( 'wpinv_get_items', $items, $invoice_id );
606
}
607
608
function wpinv_get_fees( $invoice_id = 0 ) {
609
    $invoice           = wpinv_get_invoice( $invoice_id );
610
    $fees              = $invoice->get_fees();
611
612
    return apply_filters( 'wpinv_get_fees', $fees, $invoice_id );
613
}
614
615
function wpinv_get_invoice_ip( $invoice_id ) {
616
    $invoice = new WPInv_Invoice( $invoice_id );
617
    return $invoice->get_ip();
618
}
619
620
function wpinv_get_invoice_user_info( $invoice_id ) {
621
    $invoice = new WPInv_Invoice( $invoice_id );
622
    return $invoice->get_user_info();
623
}
624
625
function wpinv_subtotal( $invoice_id = 0, $currency = false ) {
626
    $invoice = new WPInv_Invoice( $invoice_id );
627
628
    return $invoice->get_subtotal( $currency );
629
}
630
631
function wpinv_tax( $invoice_id = 0, $currency = false ) {
632
    $invoice = new WPInv_Invoice( $invoice_id );
633
634
    return $invoice->get_tax( $currency );
635
}
636
637
function wpinv_discount( $invoice_id = 0, $currency = false, $dash = false ) {
638
    $invoice = wpinv_get_invoice( $invoice_id );
639
640
    return $invoice->get_discount( $currency, $dash );
641
}
642
643
function wpinv_discount_code( $invoice_id = 0 ) {
644
    $invoice = new WPInv_Invoice( $invoice_id );
645
646
    return $invoice->get_discount_code();
647
}
648
649
function wpinv_payment_total( $invoice_id = 0, $currency = false ) {
650
    $invoice = new WPInv_Invoice( $invoice_id );
651
652
    return $invoice->get_total( $currency );
653
}
654
655
function wpinv_get_date_created( $invoice_id = 0, $format = '' ) {
656
    $invoice = new WPInv_Invoice( $invoice_id );
657
658
    $format         = !empty( $format ) ? $format : get_option( 'date_format' );
659
    $date_created   = $invoice->get_created_date();
660
    $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

660
    $date_created   = $date_created != '' && $date_created != '0000-00-00 00:00:00' ? date_i18n( /** @scrutinizer ignore-type */ $format, strtotime( $date_created ) ) : '';
Loading history...
661
662
    return $date_created;
663
}
664
665
function wpinv_get_invoice_date( $invoice_id = 0, $format = '', $default = true ) {
666
    $invoice = new WPInv_Invoice( $invoice_id );
667
    
668
    $format         = !empty( $format ) ? $format : get_option( 'date_format' );
669
    $date_completed = $invoice->get_completed_date();
670
    $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

670
    $invoice_date   = $date_completed != '' && $date_completed != '0000-00-00 00:00:00' ? date_i18n( /** @scrutinizer ignore-type */ $format, strtotime( $date_completed ) ) : '';
Loading history...
671
    if ( $invoice_date == '' && $default ) {
672
        $invoice_date   = wpinv_get_date_created( $invoice_id, $format );
673
    }
674
675
    return $invoice_date;
676
}
677
678
function wpinv_get_invoice_vat_number( $invoice_id = 0 ) {
679
    $invoice = new WPInv_Invoice( $invoice_id );
680
    
681
    return $invoice->vat_number;
682
}
683
684
function wpinv_insert_payment_note( $invoice_id = 0, $note = '', $user_type = false, $added_by_user = false, $system = false ) {
685
    $invoice = new WPInv_Invoice( $invoice_id );
686
687
    return $invoice->add_note( $note, $user_type, $added_by_user, $system );
688
}
689
690
function wpinv_get_invoice_notes( $invoice_id = 0, $type = '' ) {
691
    global $invoicing;
692
    
693
    if ( empty( $invoice_id ) ) {
694
        return NULL;
695
    }
696
    
697
    $notes = $invoicing->notes->get_invoice_notes( $invoice_id, $type );
698
    
699
    return apply_filters( 'wpinv_invoice_notes', $notes, $invoice_id, $type );
700
}
701
702
function wpinv_get_payment_key( $invoice_id = 0 ) {
703
	$invoice = new WPInv_Invoice( $invoice_id );
704
    return $invoice->get_key();
705
}
706
707
function wpinv_get_invoice_number( $invoice_id = 0 ) {
708
    $invoice = new WPInv_Invoice( $invoice_id );
709
    return $invoice->get_number();
710
}
711
712
function wpinv_get_cart_discountable_subtotal( $code_id ) {
713
    $cart_items = wpinv_get_cart_content_details();
714
    $items      = array();
715
716
    $excluded_items = wpinv_get_discount_excluded_items( $code_id );
717
718
    if( $cart_items ) {
719
720
        foreach( $cart_items as $item ) {
721
722
            if( ! in_array( $item['id'], $excluded_items ) ) {
723
                $items[] =  $item;
724
            }
725
        }
726
    }
727
728
    $subtotal = wpinv_get_cart_items_subtotal( $items );
729
730
    return apply_filters( 'wpinv_get_cart_discountable_subtotal', $subtotal );
731
}
732
733
function wpinv_get_cart_items_subtotal( $items ) {
734
    $subtotal = 0.00;
735
736
    if ( is_array( $items ) && ! empty( $items ) ) {
737
        $prices = wp_list_pluck( $items, 'subtotal' );
738
739
        if( is_array( $prices ) ) {
0 ignored issues
show
introduced by
The condition is_array($prices) is always true.
Loading history...
740
            $subtotal = array_sum( $prices );
741
        } else {
742
            $subtotal = 0.00;
743
        }
744
745
        if( $subtotal < 0 ) {
746
            $subtotal = 0.00;
747
        }
748
    }
749
750
    return apply_filters( 'wpinv_get_cart_items_subtotal', $subtotal );
751
}
752
753
function wpinv_get_cart_subtotal( $items = array() ) {
754
    $items    = !empty( $items ) ? $items : wpinv_get_cart_content_details();
755
    $subtotal = wpinv_get_cart_items_subtotal( $items );
756
757
    return apply_filters( 'wpinv_get_cart_subtotal', $subtotal );
758
}
759
760
function wpinv_cart_subtotal( $items = array(), $currency = '' ) {
761
762
    if( empty( $currency ) ) {
763
        $currency = wpinv_get_currency();
764
    }
765
766
    $price = wpinv_price( wpinv_format_amount( wpinv_get_cart_subtotal( $items ) ), $currency );
767
768
    return $price;
769
}
770
771
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

771
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...
772
    $subtotal  = (float)wpinv_get_cart_subtotal( $items );
773
    $discounts = (float)wpinv_get_cart_discounted_amount( $items );
774
    $cart_tax  = (float)wpinv_get_cart_tax( $items, $invoice );
775
    $fees      = (float)wpinv_get_cart_fee_total();
776
    if ( !empty( $invoice ) && $invoice->is_free_trial() ) {
777
        $total = 0;
778
    } else {
779
        $total     = $subtotal - $discounts + $cart_tax + $fees;
780
    }
781
782
    if ( $total < 0 ) {
783
        $total = 0.00;
784
    }
785
    
786
    $total = (float)apply_filters( 'wpinv_get_cart_total', $total, $items );
787
788
    return wpinv_sanitize_amount( $total );
789
}
790
791
function wpinv_cart_total( $cart_items = array(), $echo = true, $invoice = array() ) {
792
    global $cart_total;
793
    $total = wpinv_price( wpinv_format_amount( wpinv_get_cart_total( $cart_items, NULL, $invoice ) ), $invoice->get_currency() );
794
    $total = apply_filters( 'wpinv_cart_total', $total, $cart_items, $invoice );
795
    
796
    $cart_total = $total;
797
798
    if ( !$echo ) {
799
        return $total;
800
    }
801
802
    echo $total;
803
}
804
805
function wpinv_get_cart_tax( $items = array(), $invoice = 0 ) {
806
807
    if ( ! empty( $invoice ) && ! $invoice->is_taxable() ) {
808
        return 0;
809
    }
810
811
    $cart_tax = 0;
812
    $items    = !empty( $items ) ? $items : wpinv_get_cart_content_details();
813
814
    if ( $items ) {
815
        $taxes = wp_list_pluck( $items, 'tax' );
816
817
        if( is_array( $taxes ) ) {
0 ignored issues
show
introduced by
The condition is_array($taxes) is always true.
Loading history...
818
            $cart_tax = array_sum( $taxes );
819
        }
820
    }
821
822
    $cart_tax += wpinv_get_cart_fee_tax();
823
824
    return apply_filters( 'wpinv_get_cart_tax', wpinv_sanitize_amount( $cart_tax ) );
825
}
826
827
function wpinv_cart_tax( $items = array(), $echo = false, $currency = '', $invoice = 0 ) {
828
829
    if ( ! empty( $invoice && ! $invoice->is_taxable() ) ) {
830
        echo wpinv_price( wpinv_format_amount( 0 ), $currency );
831
        return;
832
    }
833
834
    $cart_tax = wpinv_get_cart_tax( $items, $invoice );
835
    $cart_tax = wpinv_price( wpinv_format_amount( $cart_tax ), $currency );
836
837
    $tax = apply_filters( 'wpinv_cart_tax', $cart_tax, $items );
838
839
    if ( !$echo ) {
840
        return $tax;
841
    }
842
843
    echo $tax;
844
}
845
846
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

846
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...
847
    $invoice = wpinv_get_invoice_cart();
848
    $cart_discount_code = !empty( $invoice ) ? $invoice->get_discount_code() : '';
849
    
850
    return apply_filters( 'wpinv_get_cart_discount_code', $cart_discount_code );
851
}
852
853
function wpinv_cart_discount_code( $items = array(), $echo = false ) {
854
    $cart_discount_code = wpinv_get_cart_discount_code( $items );
855
856
    if ( $cart_discount_code != '' ) {
857
        $cart_discount_code = ' (' . $cart_discount_code . ')';
858
    }
859
    
860
    $discount_code = apply_filters( 'wpinv_cart_discount_code', $cart_discount_code, $items );
861
862
    if ( !$echo ) {
863
        return $discount_code;
864
    }
865
866
    echo $discount_code;
867
}
868
869
function wpinv_get_cart_discount( $items = array() ) {
870
    $invoice = wpinv_get_invoice_cart();
871
    $cart_discount = !empty( $invoice ) ? $invoice->get_discount() : 0;
872
    
873
    return apply_filters( 'wpinv_get_cart_discount', wpinv_sanitize_amount( $cart_discount ), $items );
874
}
875
876
function wpinv_cart_discount( $items = array(), $echo = false ) {
877
    $cart_discount = wpinv_get_cart_discount( $items );
878
    $cart_discount = wpinv_price( wpinv_format_amount( $cart_discount ) );
879
880
    $discount = apply_filters( 'wpinv_cart_discount', $cart_discount, $items );
881
882
    if ( !$echo ) {
883
        return $discount;
884
    }
885
886
    echo $discount;
887
}
888
889
function wpinv_get_cart_fees( $type = 'all', $item_id = 0 ) {
890
    $item = new WPInv_Item( $item_id );
891
    
892
    return $item->get_fees( $type, $item_id );
893
}
894
895
function wpinv_get_cart_fee_total() {
896
    $total  = 0;
897
    $fees = wpinv_get_cart_fees();
898
    
899
    if ( $fees ) {
900
        foreach ( $fees as $fee_id => $fee ) {
901
            $total += $fee['amount'];
902
        }
903
    }
904
905
    return apply_filters( 'wpinv_get_cart_fee_total', $total );
906
}
907
908
function wpinv_get_cart_fee_tax() {
909
    $tax  = 0;
910
    $fees = wpinv_get_cart_fees();
911
912
    if ( $fees ) {
913
        foreach ( $fees as $fee_id => $fee ) {
914
            if( ! empty( $fee['no_tax'] ) ) {
915
                continue;
916
            }
917
918
            $tax += wpinv_calculate_tax( $fee['amount'] );
919
        }
920
    }
921
922
    return apply_filters( 'wpinv_get_cart_fee_tax', $tax );
923
}
924
925
function wpinv_cart_has_recurring_item() {
926
    $cart_items = wpinv_get_cart_contents();
927
    
928
    if ( empty( $cart_items ) ) {
929
        return false;
930
    }
931
    
932
    $has_subscription = false;
933
    foreach( $cart_items as $cart_item ) {
934
        if ( !empty( $cart_item['id'] ) && wpinv_is_recurring_item( $cart_item['id'] )  ) {
935
            $has_subscription = true;
936
            break;
937
        }
938
    }
939
    
940
    return apply_filters( 'wpinv_cart_has_recurring_item', $has_subscription, $cart_items );
941
}
942
943
function wpinv_cart_has_free_trial() {
944
    $invoice = wpinv_get_invoice_cart();
945
    
946
    $free_trial = false;
947
    
948
    if ( !empty( $invoice ) && $invoice->is_free_trial() ) {
949
        $free_trial = true;
950
    }
951
    
952
    return apply_filters( 'wpinv_cart_has_free_trial', $free_trial, $invoice );
953
}
954
955
function wpinv_get_cart_contents() {
956
    $cart_details = wpinv_get_cart_details();
957
    
958
    return apply_filters( 'wpinv_get_cart_contents', $cart_details );
959
}
960
961
function wpinv_get_cart_content_details() {
962
    global $wpinv_euvat, $wpi_current_id, $wpi_item_id, $wpinv_is_last_cart_item, $wpinv_flat_discount_total;
963
    $cart_items = wpinv_get_cart_contents();
964
    
965
    if ( empty( $cart_items ) ) {
966
        return false;
967
    }
968
    $invoice = wpinv_get_invoice_cart();
969
	if ( empty( $invoice ) ) {
970
        return false;
971
    }
972
973
    $details = array();
974
    $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...
975
    
976
    if ( empty( $_POST['country'] ) ) {
977
        $_POST['country'] = $invoice->country;
978
    }
979
    if ( !isset( $_POST['state'] ) ) {
980
        $_POST['state'] = $invoice->state;
981
    }
982
983
    foreach( $cart_items as $key => $item ) {
984
        $item_id            = isset( $item['id'] ) ? sanitize_text_field( $item['id'] ) : '';
985
        if ( empty( $item_id ) ) {
986
            continue;
987
        }
988
        
989
        $wpi_current_id         = $invoice->ID;
990
        $wpi_item_id            = $item_id;
991
        
992
        if ( isset( $item['custom_price'] ) && $item['custom_price'] !== '' ) {
993
            $item_price = $item['custom_price'];
994
        } else {
995
            if ( isset( $item['item_price'] ) && $item['item_price'] !== '' && $item['item_price'] !== false ) {
996
                $item_price = $item['item_price'];
997
            } else {
998
                $item_price = wpinv_get_item_price( $item_id );
999
            }
1000
        }
1001
        $discount           = wpinv_get_cart_item_discount_amount( $item );
1002
        $discount           = apply_filters( 'wpinv_get_cart_content_details_item_discount_amount', $discount, $item );
1003
        $quantity           = wpinv_get_cart_item_quantity( $item );
1004
        $fees               = wpinv_get_cart_fees( 'fee', $item_id );
1005
        
1006
        $subtotal           = $item_price * $quantity;
1007
        $tax_rate           = wpinv_get_tax_rate( $_POST['country'], $_POST['state'], $wpi_item_id );
1008
        $tax_class          = $wpinv_euvat->get_item_class( $item_id );
1009
        $tax                = wpinv_get_cart_item_tax( $item_id, $subtotal - $discount );
1010
        
1011
        if ( ! $invoice->is_taxable() ) {
1012
            $tax = 0;
1013
        }
1014
        if ( wpinv_prices_include_tax() ) {
1015
            $subtotal -= wpinv_round_amount( $tax );
1016
        }
1017
        
1018
        $total              = $subtotal - $discount + $tax;
1019
        
1020
        // Do not allow totals to go negatve
1021
        if( $total < 0 ) {
1022
            $total = 0;
1023
        }
1024
        
1025
        $details[ $key ]  = array(
1026
            'id'                => $item_id,
1027
            '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

1027
            'name'              => !empty($item['name']) ? $item['name'] : get_the_title( /** @scrutinizer ignore-type */ $item_id ),
Loading history...
1028
            'item_price'        => wpinv_round_amount( $item_price ),
1029
            'custom_price'      => isset( $item['custom_price'] ) ? $item['custom_price'] : '',
1030
            'quantity'          => $quantity,
1031
            'discount'          => wpinv_round_amount( $discount ),
1032
            'subtotal'          => wpinv_round_amount( $subtotal ),
1033
            'tax'               => wpinv_round_amount( $tax ),
1034
            'price'             => wpinv_round_amount( $total ),
1035
            'vat_rates_class'   => $tax_class,
1036
            'vat_rate'          => $tax_rate,
1037
            'meta'              => isset( $item['meta'] ) ? $item['meta'] : array(),
1038
            'fees'              => $fees,
1039
        );
1040
        
1041
        if ( $wpinv_is_last_cart_item ) {
1042
            $wpinv_is_last_cart_item   = false;
1043
            $wpinv_flat_discount_total = 0.00;
1044
        }
1045
    }
1046
    
1047
    return $details;
1048
}
1049
1050
function wpinv_get_cart_details( $invoice_id = 0 ) {
1051
    global $ajax_cart_details;
1052
1053
    $invoice      = wpinv_get_invoice_cart( $invoice_id );
1054
    $cart_details = $ajax_cart_details;
1055
    if ( empty( $cart_details ) && ! empty( $invoice->cart_details ) ) {
1056
        $cart_details = $invoice->cart_details;
1057
    }
1058
1059
    if ( ! empty( $cart_details ) && is_array( $cart_details ) ) {
1060
        $invoice_currency = ! empty( $invoice->currency ) ? $invoice->currency : wpinv_get_default_country();
1061
1062
        foreach ( $cart_details as $key => $cart_item ) {
1063
            $cart_details[ $key ]['currency'] = $invoice_currency;
1064
1065
            if ( ! isset( $cart_item['subtotal'] ) ) {
1066
                $cart_details[ $key ]['subtotal'] = $cart_item['price'];
1067
            }
1068
        }
1069
    }
1070
1071
    return apply_filters( 'wpinv_get_cart_details', $cart_details, $invoice_id );
1072
}
1073
1074
function wpinv_record_status_change( $invoice_id, $new_status, $old_status ) {
1075
    if ( 'wpi_invoice' != get_post_type( $invoice_id ) ) {
1076
        return;
1077
    }
1078
1079
    if ( ( $old_status == 'wpi-pending' && $new_status == 'draft' ) || ( $old_status == 'draft' && $new_status == 'wpi-pending' ) ) {
1080
        return;
1081
    }
1082
1083
    $invoice    = wpinv_get_invoice( $invoice_id );
1084
    
1085
    $old_status = wpinv_status_nicename( $old_status );
1086
    $new_status = wpinv_status_nicename( $new_status );
1087
1088
    $status_change = sprintf( __( 'Invoice status changed from %s to %s', 'invoicing' ), $old_status, $new_status );
1089
    
1090
    // Add note
1091
    return $invoice->add_note( $status_change, false, false, true );
1092
}
1093
add_action( 'wpinv_update_status', 'wpinv_record_status_change', 100, 3 );
1094
1095
function wpinv_complete_payment( $invoice_id, $new_status, $old_status ) {
1096
    global $wpi_has_free_trial;
1097
    
1098
    $wpi_has_free_trial = false;
1099
    
1100
    if ( $old_status == 'publish' ) {
1101
        return; // Make sure that payments are only paid once
1102
    }
1103
1104
    // Make sure the payment completion is only processed when new status is paid
1105
    if ( $new_status != 'publish' ) {
1106
        return;
1107
    }
1108
1109
    $invoice = new WPInv_Invoice( $invoice_id );
1110
    if ( empty( $invoice ) ) {
1111
        return;
1112
    }
1113
1114
    $wpi_has_free_trial = $invoice->is_free_trial();
1115
    $completed_date = $invoice->completed_date;
1116
    $cart_details   = $invoice->cart_details;
1117
1118
    do_action( 'wpinv_pre_complete_payment', $invoice_id );
1119
1120
    if ( is_array( $cart_details ) ) {
0 ignored issues
show
introduced by
The condition is_array($cart_details) is always true.
Loading history...
1121
        // Increase purchase count and earnings
1122
        foreach ( $cart_details as $cart_index => $item ) {
1123
            // Ensure these actions only run once, ever
1124
            if ( empty( $completed_date ) ) {
1125
                do_action( 'wpinv_complete_item_payment', $item['id'], $invoice_id, $item, $cart_index );
1126
            }
1127
        }
1128
    }
1129
    
1130
    // Check for discount codes and increment their use counts
1131
    if ( $discounts = $invoice->get_discounts( true ) ) {
1132
        if( ! empty( $discounts ) ) {
1133
            foreach( $discounts as $code ) {
1134
                wpinv_increase_discount_usage( $code );
1135
            }
1136
        }
1137
    }
1138
    
1139
    // Ensure this action only runs once ever
1140
    if( empty( $completed_date ) ) {
1141
        // Save the completed date
1142
        $invoice->set( 'completed_date', current_time( 'mysql', 0 ) );
1143
        $invoice->save();
1144
1145
        do_action( 'wpinv_complete_payment', $invoice_id );
1146
    }
1147
1148
    // Empty the shopping cart
1149
    wpinv_empty_cart();
1150
}
1151
add_action( 'wpinv_update_status', 'wpinv_complete_payment', 100, 3 );
1152
1153
function wpinv_update_payment_status( $invoice_id, $new_status = 'publish' ) {    
1154
    $invoice = !empty( $invoice_id ) && is_object( $invoice_id ) ? $invoice_id : wpinv_get_invoice( (int)$invoice_id );
1155
1156
    if ( empty( $invoice ) ) {
1157
        return false;
1158
    }
1159
1160
    return $invoice->update_status( $new_status );
1161
}
1162
1163
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

1163
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...
1164
    return false;
1165
}
1166
1167
function wpinv_validate_checkout_fields() {    
1168
    // Check if there is $_POST
1169
    if ( empty( $_POST ) ) {
1170
        return false;
1171
    }
1172
1173
    // Start an array to collect valid data
1174
    $valid_data = array(
1175
        'gateway'          => wpinv_checkout_validate_gateway(), // Gateway fallback
1176
        'discount'         => wpinv_checkout_validate_discounts(), // Set default discount
1177
        'cc_info'          => wpinv_checkout_validate_cc() // Credit card info
1178
    );
1179
1180
    $valid_data['invoice_user'] = wpinv_checkout_validate_invoice_user();
1181
    $valid_data['current_user'] = wpinv_checkout_validate_current_user();
1182
1183
    // Return collected data
1184
    return $valid_data;
1185
}
1186
1187
function wpinv_checkout_validate_gateway() {
1188
    $gateway = wpinv_get_default_gateway();
1189
    
1190
    $invoice = wpinv_get_invoice_cart();
1191
    $has_subscription = $invoice->is_recurring();
1192
    if ( empty( $invoice ) ) {
1193
        wpinv_set_error( 'invalid_invoice', __( 'Your cart is empty.', 'invoicing' ) );
1194
        return $gateway;
1195
    }
1196
1197
    // Check if a gateway value is present
1198
    if ( !empty( $_REQUEST['wpi-gateway'] ) ) {
1199
        $gateway = sanitize_text_field( $_REQUEST['wpi-gateway'] );
1200
1201
        if ( $invoice->is_free() ) {
1202
            $gateway = 'manual';
1203
        } elseif ( !wpinv_is_gateway_active( $gateway ) ) {
1204
            wpinv_set_error( 'invalid_gateway', __( 'The selected payment gateway is not enabled', 'invoicing' ) );
1205
        } elseif ( $has_subscription && !wpinv_gateway_support_subscription( $gateway ) ) {
1206
            if ( apply_filters( 'wpinv_reject_non_recurring_gateway', true ) ) {
1207
                wpinv_set_error( 'invalid_gateway', __( 'The selected payment gateway does not support subscription payment', 'invoicing' ) );
1208
            }
1209
        }
1210
    }
1211
1212
    if ( $has_subscription && count( wpinv_get_cart_contents() ) > 1 ) {
1213
        wpinv_set_error( 'subscription_invalid', __( 'Only one subscription may be purchased through payment per checkout.', 'invoicing' ) );
1214
    }
1215
1216
    return $gateway;
1217
}
1218
1219
function wpinv_checkout_validate_discounts() {
1220
    global $wpi_cart;
1221
    
1222
    // Retrieve the discount stored in cookies
1223
    $discounts = wpinv_get_cart_discounts();
1224
    
1225
    $error = false;
1226
    // If we have discounts, loop through them
1227
    if ( ! empty( $discounts ) ) {
1228
        foreach ( $discounts as $discount ) {
1229
            // Check if valid
1230
            if (  !wpinv_is_discount_valid( $discount, (int)$wpi_cart->get_user_id() ) ) {
1231
                // Discount is not valid
1232
                $error = true;
1233
            }
1234
        }
1235
    } else {
1236
        // No discounts
1237
        return NULL;
1238
    }
1239
1240
    if ( $error && !wpinv_get_errors() ) {
1241
        wpinv_set_error( 'invalid_discount', __( 'Discount code you entered is invalid', 'invoicing' ) );
1242
    }
1243
1244
    return implode( ',', $discounts );
1245
}
1246
1247
function wpinv_checkout_validate_cc() {
1248
    $card_data = wpinv_checkout_get_cc_info();
1249
1250
    // Validate the card zip
1251
    if ( !empty( $card_data['wpinv_zip'] ) ) {
1252
        if ( !wpinv_checkout_validate_cc_zip( $card_data['wpinv_zip'], $card_data['wpinv_country'] ) ) {
1253
            wpinv_set_error( 'invalid_cc_zip', __( 'The zip / postcode you entered for your billing address is invalid', 'invoicing' ) );
1254
        }
1255
    }
1256
1257
    // This should validate card numbers at some point too
1258
    return $card_data;
1259
}
1260
1261
function wpinv_checkout_get_cc_info() {
1262
	$cc_info = array();
1263
	$cc_info['card_name']      = isset( $_POST['card_name'] )       ? sanitize_text_field( $_POST['card_name'] )       : '';
1264
	$cc_info['card_number']    = isset( $_POST['card_number'] )     ? sanitize_text_field( $_POST['card_number'] )     : '';
1265
	$cc_info['card_cvc']       = isset( $_POST['card_cvc'] )        ? sanitize_text_field( $_POST['card_cvc'] )        : '';
1266
	$cc_info['card_exp_month'] = isset( $_POST['card_exp_month'] )  ? sanitize_text_field( $_POST['card_exp_month'] )  : '';
1267
	$cc_info['card_exp_year']  = isset( $_POST['card_exp_year'] )   ? sanitize_text_field( $_POST['card_exp_year'] )   : '';
1268
	$cc_info['card_address']   = isset( $_POST['wpinv_address'] )  ? sanitize_text_field( $_POST['wpinv_address'] ) : '';
1269
	$cc_info['card_city']      = isset( $_POST['wpinv_city'] )     ? sanitize_text_field( $_POST['wpinv_city'] )    : '';
1270
	$cc_info['card_state']     = isset( $_POST['wpinv_state'] )    ? sanitize_text_field( $_POST['wpinv_state'] )   : '';
1271
	$cc_info['card_country']   = isset( $_POST['wpinv_country'] )  ? sanitize_text_field( $_POST['wpinv_country'] ) : '';
1272
	$cc_info['card_zip']       = isset( $_POST['wpinv_zip'] )      ? sanitize_text_field( $_POST['wpinv_zip'] )     : '';
1273
1274
	// Return cc info
1275
	return $cc_info;
1276
}
1277
1278
function wpinv_checkout_validate_cc_zip( $zip = 0, $country_code = '' ) {
1279
    $ret = false;
1280
1281
    if ( empty( $zip ) || empty( $country_code ) )
1282
        return $ret;
1283
1284
    $country_code = strtoupper( $country_code );
1285
1286
    $zip_regex = array(
1287
        "AD" => "AD\d{3}",
1288
        "AM" => "(37)?\d{4}",
1289
        "AR" => "^([A-Z]{1}\d{4}[A-Z]{3}|[A-Z]{1}\d{4}|\d{4})$",
1290
        "AS" => "96799",
1291
        "AT" => "\d{4}",
1292
        "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})$",
1293
        "AX" => "22\d{3}",
1294
        "AZ" => "\d{4}",
1295
        "BA" => "\d{5}",
1296
        "BB" => "(BB\d{5})?",
1297
        "BD" => "\d{4}",
1298
        "BE" => "^[1-9]{1}[0-9]{3}$",
1299
        "BG" => "\d{4}",
1300
        "BH" => "((1[0-2]|[2-9])\d{2})?",
1301
        "BM" => "[A-Z]{2}[ ]?[A-Z0-9]{2}",
1302
        "BN" => "[A-Z]{2}[ ]?\d{4}",
1303
        "BR" => "\d{5}[\-]?\d{3}",
1304
        "BY" => "\d{6}",
1305
        "CA" => "^[ABCEGHJKLMNPRSTVXY]{1}\d{1}[A-Z]{1} *\d{1}[A-Z]{1}\d{1}$",
1306
        "CC" => "6799",
1307
        "CH" => "^[1-9][0-9][0-9][0-9]$",
1308
        "CK" => "\d{4}",
1309
        "CL" => "\d{7}",
1310
        "CN" => "\d{6}",
1311
        "CR" => "\d{4,5}|\d{3}-\d{4}",
1312
        "CS" => "\d{5}",
1313
        "CV" => "\d{4}",
1314
        "CX" => "6798",
1315
        "CY" => "\d{4}",
1316
        "CZ" => "\d{3}[ ]?\d{2}",
1317
        "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",
1318
        "DK" => "^([D-d][K-k])?( |-)?[1-9]{1}[0-9]{3}$",
1319
        "DO" => "\d{5}",
1320
        "DZ" => "\d{5}",
1321
        "EC" => "([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?",
1322
        "EE" => "\d{5}",
1323
        "EG" => "\d{5}",
1324
        "ES" => "^([1-9]{2}|[0-9][1-9]|[1-9][0-9])[0-9]{3}$",
1325
        "ET" => "\d{4}",
1326
        "FI" => "\d{5}",
1327
        "FK" => "FIQQ 1ZZ",
1328
        "FM" => "(9694[1-4])([ \-]\d{4})?",
1329
        "FO" => "\d{3}",
1330
        "FR" => "^(F-)?((2[A|B])|[0-9]{2})[0-9]{3}$",
1331
        "GE" => "\d{4}",
1332
        "GF" => "9[78]3\d{2}",
1333
        "GL" => "39\d{2}",
1334
        "GN" => "\d{3}",
1335
        "GP" => "9[78][01]\d{2}",
1336
        "GR" => "\d{3}[ ]?\d{2}",
1337
        "GS" => "SIQQ 1ZZ",
1338
        "GT" => "\d{5}",
1339
        "GU" => "969[123]\d([ \-]\d{4})?",
1340
        "GW" => "\d{4}",
1341
        "HM" => "\d{4}",
1342
        "HN" => "(?:\d{5})?",
1343
        "HR" => "\d{5}",
1344
        "HT" => "\d{4}",
1345
        "HU" => "\d{4}",
1346
        "ID" => "\d{5}",
1347
        "IE" => "((D|DUBLIN)?([1-9]|6[wW]|1[0-8]|2[024]))?",
1348
        "IL" => "\d{5}",
1349
        "IN"=> "^[1-9][0-9][0-9][0-9][0-9][0-9]$", //india
1350
        "IO" => "BBND 1ZZ",
1351
        "IQ" => "\d{5}",
1352
        "IS" => "\d{3}",
1353
        "IT" => "^(V-|I-)?[0-9]{5}$",
1354
        "JO" => "\d{5}",
1355
        "JP" => "\d{3}-\d{4}",
1356
        "KE" => "\d{5}",
1357
        "KG" => "\d{6}",
1358
        "KH" => "\d{5}",
1359
        "KR" => "\d{3}[\-]\d{3}",
1360
        "KW" => "\d{5}",
1361
        "KZ" => "\d{6}",
1362
        "LA" => "\d{5}",
1363
        "LB" => "(\d{4}([ ]?\d{4})?)?",
1364
        "LI" => "(948[5-9])|(949[0-7])",
1365
        "LK" => "\d{5}",
1366
        "LR" => "\d{4}",
1367
        "LS" => "\d{3}",
1368
        "LT" => "\d{5}",
1369
        "LU" => "\d{4}",
1370
        "LV" => "\d{4}",
1371
        "MA" => "\d{5}",
1372
        "MC" => "980\d{2}",
1373
        "MD" => "\d{4}",
1374
        "ME" => "8\d{4}",
1375
        "MG" => "\d{3}",
1376
        "MH" => "969[67]\d([ \-]\d{4})?",
1377
        "MK" => "\d{4}",
1378
        "MN" => "\d{6}",
1379
        "MP" => "9695[012]([ \-]\d{4})?",
1380
        "MQ" => "9[78]2\d{2}",
1381
        "MT" => "[A-Z]{3}[ ]?\d{2,4}",
1382
        "MU" => "(\d{3}[A-Z]{2}\d{3})?",
1383
        "MV" => "\d{5}",
1384
        "MX" => "\d{5}",
1385
        "MY" => "\d{5}",
1386
        "NC" => "988\d{2}",
1387
        "NE" => "\d{4}",
1388
        "NF" => "2899",
1389
        "NG" => "(\d{6})?",
1390
        "NI" => "((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?",
1391
        "NL" => "^[1-9][0-9]{3}\s?([a-zA-Z]{2})?$",
1392
        "NO" => "\d{4}",
1393
        "NP" => "\d{5}",
1394
        "NZ" => "\d{4}",
1395
        "OM" => "(PC )?\d{3}",
1396
        "PF" => "987\d{2}",
1397
        "PG" => "\d{3}",
1398
        "PH" => "\d{4}",
1399
        "PK" => "\d{5}",
1400
        "PL" => "\d{2}-\d{3}",
1401
        "PM" => "9[78]5\d{2}",
1402
        "PN" => "PCRN 1ZZ",
1403
        "PR" => "00[679]\d{2}([ \-]\d{4})?",
1404
        "PT" => "\d{4}([\-]\d{3})?",
1405
        "PW" => "96940",
1406
        "PY" => "\d{4}",
1407
        "RE" => "9[78]4\d{2}",
1408
        "RO" => "\d{6}",
1409
        "RS" => "\d{5}",
1410
        "RU" => "\d{6}",
1411
        "SA" => "\d{5}",
1412
        "SE" => "^(s-|S-){0,1}[0-9]{3}\s?[0-9]{2}$",
1413
        "SG" => "\d{6}",
1414
        "SH" => "(ASCN|STHL) 1ZZ",
1415
        "SI" => "\d{4}",
1416
        "SJ" => "\d{4}",
1417
        "SK" => "\d{3}[ ]?\d{2}",
1418
        "SM" => "4789\d",
1419
        "SN" => "\d{5}",
1420
        "SO" => "\d{5}",
1421
        "SZ" => "[HLMS]\d{3}",
1422
        "TC" => "TKCA 1ZZ",
1423
        "TH" => "\d{5}",
1424
        "TJ" => "\d{6}",
1425
        "TM" => "\d{6}",
1426
        "TN" => "\d{4}",
1427
        "TR" => "\d{5}",
1428
        "TW" => "\d{3}(\d{2})?",
1429
        "UA" => "\d{5}",
1430
        "UK" => "^(GIR|[A-Z]\d[A-Z\d]??|[A-Z]{2}\d[A-Z\d]??)[ ]??(\d[A-Z]{2})$",
1431
        "US" => "^\d{5}([\-]?\d{4})?$",
1432
        "UY" => "\d{5}",
1433
        "UZ" => "\d{6}",
1434
        "VA" => "00120",
1435
        "VE" => "\d{4}",
1436
        "VI" => "008(([0-4]\d)|(5[01]))([ \-]\d{4})?",
1437
        "WF" => "986\d{2}",
1438
        "YT" => "976\d{2}",
1439
        "YU" => "\d{5}",
1440
        "ZA" => "\d{4}",
1441
        "ZM" => "\d{5}"
1442
    );
1443
1444
    if ( ! isset ( $zip_regex[ $country_code ] ) || preg_match( "/" . $zip_regex[ $country_code ] . "/i", $zip ) )
1445
        $ret = true;
1446
1447
    return apply_filters( 'wpinv_is_zip_valid', $ret, $zip, $country_code );
1448
}
1449
1450
function wpinv_checkout_validate_agree_to_terms() {
1451
    // Validate agree to terms
1452
    if ( ! isset( $_POST['wpi_agree_to_terms'] ) || $_POST['wpi_agree_to_terms'] != 1 ) {
1453
        // User did not agree
1454
        wpinv_set_error( 'agree_to_terms', apply_filters( 'wpinv_agree_to_terms_text', __( 'You must agree to the terms of use', 'invoicing' ) ) );
1455
    }
1456
}
1457
1458
function wpinv_checkout_validate_invoice_user() {
1459
    global $wpi_cart, $user_ID;
1460
1461
    if(empty($wpi_cart)){
1462
        $wpi_cart = wpinv_get_invoice_cart();
1463
    }
1464
1465
    $invoice_user = (int)$wpi_cart->get_user_id();
1466
    $valid_user_data = array(
1467
        'user_id' => $invoice_user
1468
    );
1469
1470
    // If guest checkout allowed
1471
    if ( !wpinv_require_login_to_checkout() ) {
1472
        return $valid_user_data;
1473
    }
1474
    
1475
    // Verify there is a user_ID
1476
    if ( $user_ID == $invoice_user ) {
1477
        // Get the logged in user data
1478
        $user_data = get_userdata( $user_ID );
1479
        $required_fields  = wpinv_checkout_required_fields();
1480
1481
        // Loop through required fields and show error messages
1482
         if ( !empty( $required_fields ) ) {
1483
            foreach ( $required_fields as $field_name => $value ) {
1484
                if ( in_array( $value, $required_fields ) && empty( $_POST[ 'wpinv_' . $field_name ] ) ) {
1485
                    wpinv_set_error( $value['error_id'], $value['error_message'] );
1486
                }
1487
            }
1488
        }
1489
1490
        // Verify data
1491
        if ( $user_data ) {
1492
            // Collected logged in user data
1493
            $valid_user_data = array(
1494
                'user_id'     => $user_ID,
1495
                'email'       => isset( $_POST['wpinv_email'] ) ? sanitize_email( $_POST['wpinv_email'] ) : $user_data->user_email,
1496
                'first_name'  => isset( $_POST['wpinv_first_name'] ) && ! empty( $_POST['wpinv_first_name'] ) ? sanitize_text_field( $_POST['wpinv_first_name'] ) : $user_data->first_name,
1497
                'last_name'   => isset( $_POST['wpinv_last_name'] ) && ! empty( $_POST['wpinv_last_name']  ) ? sanitize_text_field( $_POST['wpinv_last_name']  ) : $user_data->last_name,
1498
            );
1499
1500
            if ( !empty( $_POST[ 'wpinv_email' ] ) && !is_email( $_POST[ 'wpinv_email' ] ) ) {
1501
                wpinv_set_error( 'invalid_email', __( 'Please enter a valid email address', 'invoicing' ) );
1502
            }
1503
        } else {
1504
            // Set invalid user error
1505
            wpinv_set_error( 'invalid_user', __( 'The user billing information is invalid', 'invoicing' ) );
1506
        }
1507
    } else {
1508
        // Set invalid user error
1509
        wpinv_set_error( 'invalid_user_id', __( 'The invalid invoice user id', 'invoicing' ) );
1510
    }
1511
1512
    // Return user data
1513
    return $valid_user_data;
1514
}
1515
1516
function wpinv_checkout_validate_current_user() {
1517
    global $wpi_cart;
1518
1519
    $data = array();
1520
    
1521
    if ( is_user_logged_in() ) {
1522
        if ( !wpinv_require_login_to_checkout() || ( wpinv_require_login_to_checkout() && (int)$wpi_cart->get_user_id() === (int)get_current_user_id() ) ) {
1523
            $data['user_id'] = (int)get_current_user_id();
1524
        } else {
1525
            wpinv_set_error( 'logged_in_only', __( 'You are not allowed to pay for this invoice', 'invoicing' ) );
1526
        }
1527
    } else {
1528
        // If guest checkout allowed
1529
        if ( !wpinv_require_login_to_checkout() ) {
1530
            $data['user_id'] = 0;
1531
        } else {
1532
            wpinv_set_error( 'logged_in_only', __( 'You must be logged in to pay for this invoice', 'invoicing' ) );
1533
        }
1534
    }
1535
1536
    return $data;
1537
}
1538
1539
function wpinv_checkout_form_get_user( $valid_data = array() ) {
1540
1541
    if ( !empty( $valid_data['current_user']['user_id'] ) ) {
1542
        $user = $valid_data['current_user'];
1543
    } else {
1544
        // Set the valid invoice user
1545
        $user = $valid_data['invoice_user'];
1546
    }
1547
1548
    // Verify invoice have an user
1549
    if ( false === $user || empty( $user ) ) {
1550
        return false;
1551
    }
1552
1553
    $address_fields = array(
1554
        'first_name',
1555
        'last_name',
1556
        'company',
1557
        'vat_number',
1558
        'phone',
1559
        'address',
1560
        'city',
1561
        'state',
1562
        'country',
1563
        'zip',
1564
    );
1565
    
1566
    foreach ( $address_fields as $field ) {
1567
        $user[$field]  = !empty( $_POST['wpinv_' . $field] ) ? sanitize_text_field( $_POST['wpinv_' . $field] ) : false;
1568
        
1569
        if ( !empty( $user['user_id'] ) && !empty( $valid_data['current_user']['user_id'] ) && $valid_data['current_user']['user_id'] == $valid_data['invoice_user']['user_id'] ) {
1570
            update_user_meta( $user['user_id'], '_wpinv_' . $field, $user[$field] );
1571
        }
1572
    }
1573
1574
    // Return valid user
1575
    return $user;
1576
}
1577
1578
function wpinv_set_checkout_session( $invoice_data = array() ) {
1579
    global $wpi_session;
1580
    
1581
    return $wpi_session->set( 'wpinv_checkout', $invoice_data );
1582
}
1583
1584
function wpinv_get_checkout_session() {
1585
	global $wpi_session;
1586
1587
    return $wpi_session->get( 'wpinv_checkout' );
1588
}
1589
1590
function wpinv_empty_cart() {
1591
    global $wpi_session;
1592
1593
    // Remove cart contents
1594
    $wpi_session->set( 'wpinv_checkout', NULL );
1595
1596
    // Remove all cart fees
1597
    $wpi_session->set( 'wpi_cart_fees', NULL );
1598
1599
    do_action( 'wpinv_empty_cart' );
1600
}
1601
1602
function wpinv_process_checkout() {
1603
    global $wpinv_euvat, $wpi_checkout_id, $wpi_cart;
1604
1605
    wpinv_clear_errors();
1606
1607
    $invoice = wpinv_get_invoice_cart();
1608
    if ( empty( $invoice ) ) {
1609
        return false;
1610
    }
1611
1612
    $wpi_cart = $invoice;
1613
1614
    $wpi_checkout_id = $invoice->ID;
1615
1616
    do_action( 'wpinv_pre_process_checkout' );
1617
    
1618
    if ( !wpinv_get_cart_contents() ) { // Make sure the cart isn't empty
1619
        $valid_data = false;
1620
        wpinv_set_error( 'empty_cart', __( 'Your cart is empty', 'invoicing' ) );
1621
    } else {
1622
        // Validate the form $_POST data
1623
        $valid_data = wpinv_validate_checkout_fields();
1624
        
1625
        // Allow themes and plugins to hook to errors
1626
        do_action( 'wpinv_checkout_error_checks', $valid_data, $_POST );
1627
    }
1628
    
1629
    $is_ajax    = defined( 'DOING_AJAX' ) && DOING_AJAX;
1630
    
1631
    // Validate the user
1632
    $user = wpinv_checkout_form_get_user( $valid_data );
1633
1634
    // Let extensions validate fields after user is logged in if user has used login/registration form
1635
    do_action( 'wpinv_checkout_user_error_checks', $user, $valid_data, $_POST );
1636
    
1637
    if ( false === $valid_data || wpinv_get_errors() || ! $user ) {
1638
        if ( $is_ajax && 'wpinv_payment_form' != $_REQUEST['action'] ) {
1639
            do_action( 'wpinv_ajax_checkout_errors' );
1640
            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...
1641
        } else {
1642
            return false;
1643
        }
1644
    }
1645
1646
    if ( $is_ajax && 'wpinv_payment_form' != $_REQUEST['action'] ) {
1647
        // Save address fields.
1648
        $address_fields = array( 'first_name', 'last_name', 'phone', 'address', 'city', 'country', 'state', 'zip', 'company' );
1649
        foreach ( $address_fields as $field ) {
1650
            if ( isset( $user[$field] ) ) {
1651
                $invoice->set( $field, $user[$field] );
1652
            }
1653
1654
            $invoice->save();
1655
        }
1656
1657
        $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...
1658
        $response['data']['subtotal']   = $invoice->get_subtotal();
1659
        $response['data']['subtotalf']  = $invoice->get_subtotal( true );
1660
        $response['data']['discount']   = $invoice->get_discount();
1661
        $response['data']['discountf']  = $invoice->get_discount( true );
1662
        $response['data']['tax']        = $invoice->get_tax();
1663
        $response['data']['taxf']       = $invoice->get_tax( true );
1664
        $response['data']['total']      = $invoice->get_total();
1665
        $response['data']['totalf']     = $invoice->get_total( true );
1666
	    $response['data']['free']       = $invoice->is_free() && ( ! ( (float) $response['data']['total'] > 0 ) || $invoice->is_free_trial() ) ? true : false;
1667
1668
        wp_send_json( $response );
1669
    }
1670
    
1671
    $user_info = array(
1672
        'user_id'        => $user['user_id'],
1673
        'first_name'     => $user['first_name'],
1674
        'last_name'      => $user['last_name'],
1675
        'email'          => $invoice->get_email(),
1676
        'company'        => $user['company'],
1677
        'phone'          => $user['phone'],
1678
        'address'        => $user['address'],
1679
        'city'           => $user['city'],
1680
        'country'        => $user['country'],
1681
        'state'          => $user['state'],
1682
        'zip'            => $user['zip'],
1683
    );
1684
    
1685
    $cart_items = wpinv_get_cart_contents();
1686
    $discounts  = wpinv_get_cart_discounts();
1687
    
1688
    // Setup invoice information
1689
    $invoice_data = array(
1690
        'invoice_id'        => !empty( $invoice ) ? $invoice->ID : 0,
1691
        'items'             => $cart_items,
1692
        'cart_discounts'    => $discounts,
1693
        'fees'              => wpinv_get_cart_fees(),        // Any arbitrary fees that have been added to the cart
1694
        'subtotal'          => wpinv_get_cart_subtotal( $cart_items ),    // Amount before taxes and discounts
1695
        'discount'          => wpinv_get_cart_items_discount_amount( $cart_items, $discounts ), // Discounted amount
1696
        'tax'               => wpinv_get_cart_tax( $cart_items, $invoice ),               // Taxed amount
1697
        'price'             => wpinv_get_cart_total( $cart_items, $discounts ),    // Amount after taxes
1698
        'invoice_key'       => $invoice->get_key() ? $invoice->get_key() : $invoice->generate_key(),
1699
        'user_email'        => $invoice->get_email(),
1700
        'date'              => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ),
1701
        'user_info'         => stripslashes_deep( $user_info ),
1702
        'post_data'         => $_POST,
1703
        'cart_details'      => $cart_items,
1704
        'gateway'           => $valid_data['gateway'],
1705
        'card_info'         => $valid_data['cc_info']
1706
    );
1707
    
1708
    $vat_info   = $wpinv_euvat->current_vat_data();
1709
    if ( is_array( $vat_info ) ) {
1710
        $invoice_data['user_info']['vat_number']        = $vat_info['number'];
1711
        $invoice_data['user_info']['vat_rate']          = wpinv_get_tax_rate($invoice_data['user_info']['country'], $invoice_data['user_info']['state']);
1712
        $invoice_data['user_info']['adddress_confirmed']    = isset($vat_info['adddress_confirmed']) ? $vat_info['adddress_confirmed'] : false;
1713
1714
        // Add the VAT rate to each item in the cart
1715
        foreach( $invoice_data['cart_details'] as $key => $item_data) {
1716
            $rate = wpinv_get_tax_rate($invoice_data['user_info']['country'], $invoice_data['user_info']['state'], $item_data['id']);
1717
            $invoice_data['cart_details'][$key]['vat_rate'] = wpinv_round_amount( $rate, 4 );
1718
        }
1719
    }
1720
    
1721
    // Save vat fields.
1722
    $address_fields = array( 'vat_number', 'vat_rate', 'adddress_confirmed' );
1723
    foreach ( $address_fields as $field ) {
1724
        if ( isset( $invoice_data['user_info'][$field] ) ) {
1725
            $invoice->set( $field, $invoice_data['user_info'][$field] );
1726
        }
1727
1728
        $invoice->save();
1729
    }
1730
1731
    // Add the user data for hooks
1732
    $valid_data['user'] = $user;
1733
    
1734
    // Allow themes and plugins to hook before the gateway
1735
    do_action( 'wpinv_checkout_before_gateway', $_POST, $user_info, $valid_data );
1736
    
1737
    // If the total amount in the cart is 0, send to the manual gateway. This emulates a free invoice
1738
    if ( !$invoice_data['price'] ) {
1739
        // Revert to manual
1740
        $invoice_data['gateway'] = 'manual';
1741
        $_POST['wpi-gateway'] = 'manual';
1742
    }
1743
    
1744
    // Allow the invoice data to be modified before it is sent to the gateway
1745
    $invoice_data = apply_filters( 'wpinv_data_before_gateway', $invoice_data, $valid_data );
1746
    
1747
    if ( $invoice_data['price'] && $invoice_data['gateway'] == 'manual' ) {
1748
        $mode = 'test';
1749
    } else {
1750
        $mode = wpinv_is_test_mode( $invoice_data['gateway'] ) ? 'test' : 'live';
1751
    }
1752
    
1753
    // Setup the data we're storing in the purchase session
1754
    $session_data = $invoice_data;
1755
    // Make sure credit card numbers are never stored in sessions
1756
    if ( !empty( $session_data['card_info']['card_number'] ) ) {
1757
        unset( $session_data['card_info']['card_number'] );
1758
    }
1759
    
1760
    // Used for showing item links to non logged-in users after purchase, and for other plugins needing purchase data.
1761
    wpinv_set_checkout_session( $invoice_data );
1762
    
1763
    // Set gateway
1764
    $invoice->update_meta( '_wpinv_gateway', $invoice_data['gateway'] );
1765
    $invoice->update_meta( '_wpinv_mode', $mode );
1766
    $invoice->update_meta( '_wpinv_checkout', date_i18n( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) );
1767
    
1768
    do_action( 'wpinv_checkout_before_send_to_gateway', $invoice, $invoice_data );
1769
1770
    // Send info to the gateway for payment processing
1771
    wpinv_send_to_gateway( $invoice_data['gateway'], $invoice_data );
1772
    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...
1773
}
1774
add_action( 'wpinv_payment', 'wpinv_process_checkout' );
1775
1776
function wpinv_get_invoices( $args ) {
1777
    $args = wp_parse_args( $args, array(
1778
        'status'   => array_keys( wpinv_get_invoice_statuses() ),
1779
        'type'     => 'wpi_invoice',
1780
        'parent'   => null,
1781
        'user'     => null,
1782
        'email'    => '',
1783
        'limit'    => get_option( 'posts_per_page' ),
1784
        'offset'   => null,
1785
        'page'     => 1,
1786
        'exclude'  => array(),
1787
        'orderby'  => 'date',
1788
        'order'    => 'DESC',
1789
        'return'   => 'objects',
1790
        'paginate' => false,
1791
    ) );
1792
    
1793
    // Handle some BW compatibility arg names where wp_query args differ in naming.
1794
    $map_legacy = array(
1795
        'numberposts'    => 'limit',
1796
        'post_type'      => 'type',
1797
        'post_status'    => 'status',
1798
        'post_parent'    => 'parent',
1799
        'author'         => 'user',
1800
        'posts_per_page' => 'limit',
1801
        'paged'          => 'page',
1802
    );
1803
1804
    foreach ( $map_legacy as $from => $to ) {
1805
        if ( isset( $args[ $from ] ) ) {
1806
            $args[ $to ] = $args[ $from ];
1807
        }
1808
    }
1809
1810
    if ( get_query_var( 'paged' ) )
1811
        $args['page'] = get_query_var('paged');
1812
    else if ( get_query_var( 'page' ) )
1813
        $args['page'] = get_query_var( 'page' );
1814
    else if ( !empty( $args[ 'page' ] ) )
1815
        $args['page'] = $args[ 'page' ];
1816
    else
1817
        $args['page'] = 1;
1818
1819
    /**
1820
     * Generate WP_Query args. This logic will change if orders are moved to
1821
     * custom tables in the future.
1822
     */
1823
    $wp_query_args = array(
1824
        'post_type'      => 'wpi_invoice',
1825
        'post_status'    => $args['status'],
1826
        'posts_per_page' => $args['limit'],
1827
        'meta_query'     => array(),
1828
        'date_query'     => !empty( $args['date_query'] ) ? $args['date_query'] : array(),
1829
        'fields'         => 'ids',
1830
        'orderby'        => $args['orderby'],
1831
        'order'          => $args['order'],
1832
    );
1833
    
1834
    if ( !empty( $args['user'] ) ) {
1835
        $wp_query_args['author'] = absint( $args['user'] );
1836
    }
1837
1838
    if ( ! is_null( $args['parent'] ) ) {
1839
        $wp_query_args['post_parent'] = absint( $args['parent'] );
1840
    }
1841
1842
    if ( ! is_null( $args['offset'] ) ) {
1843
        $wp_query_args['offset'] = absint( $args['offset'] );
1844
    } else {
1845
        $wp_query_args['paged'] = absint( $args['page'] );
1846
    }
1847
1848
    if ( ! empty( $args['exclude'] ) ) {
1849
        $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] );
1850
    }
1851
1852
    if ( ! $args['paginate' ] ) {
1853
        $wp_query_args['no_found_rows'] = true;
1854
    }
1855
1856
    $wp_query_args = apply_filters('wpinv_get_invoices_args', $wp_query_args, $args);
1857
1858
    // Get results.
1859
    $invoices = new WP_Query( $wp_query_args );
1860
1861
    if ( 'objects' === $args['return'] ) {
1862
        $return = array_map( 'wpinv_get_invoice', $invoices->posts );
1863
    } elseif ( 'self' === $args['return'] ) {
1864
        return $invoices;
1865
    } else {
1866
        $return = $invoices->posts;
1867
    }
1868
1869
    if ( $args['paginate' ] ) {
1870
        return (object) array(
1871
            'invoices'      => $return,
1872
            'total'         => $invoices->found_posts,
1873
            'max_num_pages' => $invoices->max_num_pages,
1874
        );
1875
    } else {
1876
        return $return;
1877
    }
1878
}
1879
1880
function wpinv_get_user_invoices_columns() {
1881
    $columns = array(
1882
            'invoice-number'  => array( 'title' => __( 'ID', 'invoicing' ), 'class' => 'text-left' ),
1883
            'created-date'    => array( 'title' => __( 'Created Date', 'invoicing' ), 'class' => 'text-left' ),
1884
            'payment-date'    => array( 'title' => __( 'Payment Date', 'invoicing' ), 'class' => 'text-left' ),
1885
            'invoice-status'  => array( 'title' => __( 'Status', 'invoicing' ), 'class' => 'text-center' ),
1886
            'invoice-total'   => array( 'title' => __( 'Total', 'invoicing' ), 'class' => 'text-right' ),
1887
            'invoice-actions' => array( 'title' => '&nbsp;', 'class' => 'text-center' ),
1888
        );
1889
1890
    return apply_filters( 'wpinv_user_invoices_columns', $columns );
1891
}
1892
1893
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

1893
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...
1894
    global $wpinv_receipt_args;
1895
1896
    $wpinv_receipt_args = shortcode_atts( array(
1897
        'error'           => __( 'Sorry, trouble retrieving payment receipt.', 'invoicing' ),
1898
        'price'           => true,
1899
        'discount'        => true,
1900
        'items'           => true,
1901
        'date'            => true,
1902
        'notes'           => true,
1903
        'invoice_key'     => false,
1904
        'payment_method'  => true,
1905
        'invoice_id'      => true
1906
    ), $atts, 'wpinv_receipt' );
1907
1908
    $session = wpinv_get_checkout_session();
1909
    if ( isset( $_GET['invoice_key'] ) ) {
1910
        $invoice_key = urldecode( $_GET['invoice_key'] );
1911
    } else if ( $session && isset( $session['invoice_key'] ) ) {
1912
        $invoice_key = $session['invoice_key'];
1913
    } elseif ( isset( $wpinv_receipt_args['invoice_key'] ) && $wpinv_receipt_args['invoice_key'] ) {
1914
        $invoice_key = $wpinv_receipt_args['invoice_key'];
1915
    } else if ( isset( $_GET['invoice-id'] ) ) {
1916
        $invoice_key = wpinv_get_payment_key( (int)$_GET['invoice-id'] );
1917
    }
1918
1919
    // No key found
1920
    if ( ! isset( $invoice_key ) ) {
1921
        return '<p class="alert alert-error">' . $wpinv_receipt_args['error'] . '</p>';
1922
    }
1923
1924
    $invoice_id    = wpinv_get_invoice_id_by_key( $invoice_key );
0 ignored issues
show
Unused Code introduced by
The assignment to $invoice_id is dead and can be removed.
Loading history...
1925
    $user_can_view = wpinv_can_view_receipt( $invoice_key );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $invoice_key does not seem to be defined for all execution paths leading up to this point.
Loading history...
1926
    if ( $user_can_view && isset( $_GET['invoice-id'] ) ) {
1927
        $invoice_id     = (int)$_GET['invoice-id'];
1928
        $user_can_view  = $invoice_key == wpinv_get_payment_key( (int)$_GET['invoice-id'] ) ? true : false;
1929
    }
1930
1931
    // Key was provided, but user is logged out. Offer them the ability to login and view the receipt
1932
    if ( ! $user_can_view && ! empty( $invoice_key ) && ! is_user_logged_in() ) {
1933
        // login redirect
1934
        return '<p class="alert alert-error">' . __( 'You are not allowed to access this section', 'invoicing' ) . '</p>';
1935
    }
1936
1937
    if ( ! apply_filters( 'wpinv_user_can_view_receipt', $user_can_view, $wpinv_receipt_args ) ) {
1938
        return '<p class="alert alert-error">' . $wpinv_receipt_args['error'] . '</p>';
1939
    }
1940
1941
    ob_start();
1942
1943
    wpinv_get_template_part( 'wpinv-invoice-receipt' );
1944
1945
    $display = ob_get_clean();
1946
1947
    return $display;
1948
}
1949
1950
function wpinv_get_invoice_id_by_key( $key ) {
1951
	global $wpdb;
1952
1953
	$invoice_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wpinv_key' AND meta_value = %s LIMIT 1", $key ) );
1954
1955
	if ( $invoice_id != NULL )
1956
		return $invoice_id;
1957
1958
	return 0;
1959
}
1960
1961
function wpinv_can_view_receipt( $invoice_key = '' ) {
1962
	$return = false;
1963
1964
	if ( empty( $invoice_key ) ) {
1965
		return $return;
1966
	}
1967
1968
	global $wpinv_receipt_args;
1969
1970
	$wpinv_receipt_args['id'] = wpinv_get_invoice_id_by_key( $invoice_key );
1971
	if ( isset( $_GET['invoice-id'] ) ) {
1972
		$wpinv_receipt_args['id'] = $invoice_key == wpinv_get_payment_key( (int)$_GET['invoice-id'] ) ? (int)$_GET['invoice-id'] : 0;
1973
	}
1974
1975
	if ( empty( $wpinv_receipt_args['id'] ) ) {
1976
		return $return;
1977
	}
1978
1979
	$invoice = wpinv_get_invoice( $wpinv_receipt_args['id'] );
1980
	if ( !( !empty( $invoice->ID ) && $invoice->get_key() === $invoice_key ) ) {
1981
		return $return;
1982
	}
1983
1984
	if ( is_user_logged_in() ) {
1985
		if ( (int)$invoice->get_user_id() === (int) get_current_user_id() ) {
1986
			$return = true;
1987
		}
1988
	}
1989
1990
	$session = wpinv_get_checkout_session();
1991
	if ( isset( $_GET['invoice_key'] ) || ( $session && isset( $session['invoice_key'] ) ) ) {
1992
		$check_key = isset( $_GET['invoice_key'] ) ? $_GET['invoice_key'] : $session['invoice_key'];
1993
1994
		if ( wpinv_require_login_to_checkout() ) {
1995
			$return = $return && $check_key === $invoice_key;
1996
		} else {
1997
			$return = $check_key === $invoice_key;
1998
		}
1999
	}
2000
2001
	return (bool) apply_filters( 'wpinv_can_view_receipt', $return, $invoice_key );
2002
}
2003
2004
function wpinv_pay_for_invoice() {
2005
    global $wpinv_euvat;
2006
    
2007
    if ( isset( $_GET['invoice_key'] ) ) {
2008
        $checkout_uri   = wpinv_get_checkout_uri();
2009
        $invoice_key    = sanitize_text_field( $_GET['invoice_key'] );
2010
        
2011
        if ( empty( $invoice_key ) ) {
2012
            wpinv_set_error( 'invalid_invoice', __( 'Invoice not found', 'invoicing' ) );
2013
            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

2013
            wp_redirect( /** @scrutinizer ignore-type */ $checkout_uri );
Loading history...
2014
            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...
2015
        }
2016
        
2017
        do_action( 'wpinv_check_pay_for_invoice', $invoice_key );
2018
2019
        $invoice_id    = wpinv_get_invoice_id_by_key( $invoice_key );
2020
        $user_can_view = wpinv_can_view_receipt( $invoice_key );
2021
        if ( $user_can_view && isset( $_GET['invoice-id'] ) ) {
2022
            $invoice_id     = (int)$_GET['invoice-id'];
2023
            $user_can_view  = $invoice_key == wpinv_get_payment_key( (int)$_GET['invoice-id'] ) ? true : false;
2024
        }
2025
        
2026
        if ( $invoice_id && $user_can_view && ( $invoice = wpinv_get_invoice( $invoice_id ) ) ) {
2027
            if ( $invoice->needs_payment() ) {
2028
                $data                   = array();
2029
                $data['invoice_id']     = $invoice_id;
2030
                $data['cart_discounts'] = $invoice->get_discounts( true );
2031
                
2032
                wpinv_set_checkout_session( $data );
2033
                
2034
                if ( wpinv_get_option( 'vat_ip_country_default' ) ) {
2035
                    $_POST['country']   = $wpinv_euvat->get_country_by_ip();
2036
                    $_POST['state']     = $_POST['country'] == $invoice->country ? $invoice->state : '';
2037
                    
2038
                    wpinv_recalculate_tax( true );
2039
                }
2040
                
2041
            } else {
2042
                $checkout_uri = $invoice->get_view_url();
2043
            }
2044
        } else {
2045
            wpinv_set_error( 'invalid_invoice', __( 'You are not allowed to view this invoice', 'invoicing' ) );
2046
            
2047
            $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

2047
            $checkout_uri = is_user_logged_in() ? wpinv_get_history_page_uri() : wp_login_url( /** @scrutinizer ignore-type */ get_permalink() );
Loading history...
2048
        }
2049
        
2050
        if(wp_redirect( $checkout_uri )){
2051
            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...
2052
        };
2053
        wpinv_die();
2054
    }
2055
}
2056
add_action( 'wpinv_pay_for_invoice', 'wpinv_pay_for_invoice' );
2057
2058
function wpinv_handle_pay_via_invoice_link( $invoice_key ) {
2059
    if ( !empty( $invoice_key ) && !empty( $_REQUEST['_wpipay'] ) && !is_user_logged_in() && $invoice_id = wpinv_get_invoice_id_by_key( $invoice_key ) ) {
2060
        if ( $invoice = wpinv_get_invoice( $invoice_id ) ) {
2061
            $user_id = $invoice->get_user_id();
2062
            $secret = sanitize_text_field( $_GET['_wpipay'] );
2063
            
2064
            if ( $secret === md5( $user_id . '::' . $invoice->get_email() . '::' . $invoice_key ) ) { // valid invoice link
2065
                $redirect_to = remove_query_arg( '_wpipay', get_permalink() );
2066
                
2067
                wpinv_guest_redirect( $redirect_to, $user_id );
2068
                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...
2069
            }
2070
        }
2071
    }
2072
}
2073
add_action( 'wpinv_check_pay_for_invoice', 'wpinv_handle_pay_via_invoice_link' );
2074
2075
function wpinv_set_payment_transaction_id( $invoice_id = 0, $transaction_id = '' ) {
2076
    $invoice_id = is_object( $invoice_id ) && !empty( $invoice_id->ID ) ? $invoice_id : $invoice_id;
2077
    
2078
    if ( empty( $invoice_id ) && $invoice_id > 0 ) {
2079
        return false;
2080
    }
2081
    
2082
    if ( empty( $transaction_id ) ) {
2083
        $transaction_id = $invoice_id;
2084
    }
2085
2086
    $transaction_id = apply_filters( 'wpinv_set_payment_transaction_id', $transaction_id, $invoice_id );
2087
    
2088
    return wpinv_update_invoice_meta( $invoice_id, '_wpinv_transaction_id', $transaction_id );
2089
}
2090
2091
function wpinv_invoice_status_label( $status, $status_display = '' ) {
2092
    if ( empty( $status_display ) ) {
2093
        $status_display = wpinv_status_nicename( $status );
2094
    }
2095
    
2096
    switch ( $status ) {
2097
        case 'publish' :
2098
        case 'wpi-renewal' :
2099
            $class = 'label-success';
2100
        break;
2101
        case 'wpi-pending' :
2102
            $class = 'label-primary';
2103
        break;
2104
        case 'wpi-processing' :
2105
            $class = 'label-warning';
2106
        break;
2107
        case 'wpi-onhold' :
2108
            $class = 'label-info';
2109
        break;
2110
        case 'wpi-cancelled' :
2111
        case 'wpi-failed' :
2112
            $class = 'label-danger';
2113
        break;
2114
        default:
2115
            $class = 'label-default';
2116
        break;
2117
    }
2118
    
2119
    $label = '<span class="label label-inv-' . $status . ' ' . $class . '">' . $status_display . '</span>';
2120
    
2121
    return apply_filters( 'wpinv_invoice_status_label', $label, $status, $status_display );
2122
}
2123
2124
function wpinv_format_invoice_number( $number, $type = '' ) {
2125
    $check = apply_filters( 'wpinv_pre_format_invoice_number', null, $number, $type );
2126
    if ( null !== $check ) {
2127
        return $check;
2128
    }
2129
2130
    if ( !empty( $number ) && !is_numeric( $number ) ) {
2131
        return $number;
2132
    }
2133
2134
    $padd  = wpinv_get_option( 'invoice_number_padd' );
2135
    $prefix  = wpinv_get_option( 'invoice_number_prefix' );
2136
    $postfix = wpinv_get_option( 'invoice_number_postfix' );
2137
    
2138
    $padd = absint( $padd );
2139
    $formatted_number = absint( $number );
2140
    
2141
    if ( $padd > 0 ) {
2142
        $formatted_number = zeroise( $formatted_number, $padd );
2143
    }    
2144
2145
    $formatted_number = $prefix . $formatted_number . $postfix;
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

2145
    $formatted_number = /** @scrutinizer ignore-type */ $prefix . $formatted_number . $postfix;
Loading history...
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

2145
    $formatted_number = $prefix . $formatted_number . /** @scrutinizer ignore-type */ $postfix;
Loading history...
2146
2147
    return apply_filters( 'wpinv_format_invoice_number', $formatted_number, $number, $prefix, $postfix, $padd );
2148
}
2149
2150
function wpinv_get_next_invoice_number( $type = '' ) {
2151
    $check = apply_filters( 'wpinv_get_pre_next_invoice_number', null, $type );
2152
    if ( null !== $check ) {
2153
        return $check;
2154
    }
2155
    
2156
    if ( !wpinv_sequential_number_active() ) {
2157
        return false;
2158
    }
2159
2160
    $number = $last_number = get_option( 'wpinv_last_invoice_number', 0 );
2161
    $start  = wpinv_get_option( 'invoice_sequence_start', 1 );
2162
    if ( !absint( $start ) > 0 ) {
2163
        $start = 1;
2164
    }
2165
    $increment_number = true;
2166
    $save_number = false;
2167
2168
    if ( !empty( $number ) && !is_numeric( $number ) && $number == wpinv_format_invoice_number( $number ) ) {
2169
        $number = wpinv_clean_invoice_number( $number );
2170
    }
2171
2172
    if ( empty( $number ) ) {
2173
        if ( !( $last_number === 0 || $last_number === '0' ) ) {
2174
            $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 ) ) ) );
2175
2176
            if ( !empty( $last_invoice[0] ) && $invoice_number = wpinv_get_invoice_number( $last_invoice[0] ) ) {
2177
                if ( is_numeric( $invoice_number ) ) {
2178
                    $number = $invoice_number;
2179
                } else {
2180
                    $number = wpinv_clean_invoice_number( $invoice_number );
2181
                }
2182
            }
2183
2184
            if ( empty( $number ) ) {
2185
                $increment_number = false;
2186
                $number = $start;
2187
                $save_number = ( $number - 1 );
2188
            } else {
2189
                $save_number = $number;
2190
            }
2191
        }
2192
    }
2193
2194
    if ( $start > $number ) {
2195
        $increment_number = false;
2196
        $number = $start;
2197
        $save_number = ( $number - 1 );
2198
    }
2199
2200
    if ( $save_number !== false ) {
2201
        update_option( 'wpinv_last_invoice_number', $save_number );
2202
    }
2203
    
2204
    $increment_number = apply_filters( 'wpinv_increment_payment_number', $increment_number, $number );
2205
2206
    if ( $increment_number ) {
2207
        $number++;
2208
    }
2209
2210
    return apply_filters( 'wpinv_get_next_invoice_number', $number );
2211
}
2212
2213
function wpinv_clean_invoice_number( $number, $type = '' ) {
2214
    $check = apply_filters( 'wpinv_pre_clean_invoice_number', null, $number, $type );
2215
    if ( null !== $check ) {
2216
        return $check;
2217
    }
2218
    
2219
    $prefix  = wpinv_get_option( 'invoice_number_prefix' );
2220
    $postfix = wpinv_get_option( 'invoice_number_postfix' );
2221
2222
    $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

2222
    $number = preg_replace( '/' . /** @scrutinizer ignore-type */ $prefix . '/', '', $number, 1 );
Loading history...
2223
2224
    $length      = strlen( $number );
2225
    $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

2225
    $postfix_pos = strrpos( $number, /** @scrutinizer ignore-type */ $postfix );
Loading history...
2226
    
2227
    if ( false !== $postfix_pos ) {
2228
        $number      = substr_replace( $number, '', $postfix_pos, $length );
2229
    }
2230
2231
    $number = intval( $number );
2232
2233
    return apply_filters( 'wpinv_clean_invoice_number', $number, $prefix, $postfix );
2234
}
2235
2236
function wpinv_save_number_post_saved( $post_ID, $post, $update ) {
2237
    global $wpdb;
2238
2239
    if ( !$update && !get_post_meta( $post_ID, '_wpinv_number', true ) ) {
2240
        wpinv_update_invoice_number( $post_ID, $post->post_status != 'auto-draft', $post->post_type );
2241
    }
2242
2243
    if ( !$update ) {
2244
        $wpdb->update( $wpdb->posts, array( 'post_name' => wpinv_generate_post_name( $post_ID ) ), array( 'ID' => $post_ID ) );
2245
        clean_post_cache( $post_ID );
2246
    }
2247
}
2248
add_action( 'save_post_wpi_invoice', 'wpinv_save_number_post_saved', 1, 3 );
2249
2250
function wpinv_save_number_post_updated( $post_ID, $post_after, $post_before ) {
2251
    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 ) {
2252
        wpinv_update_invoice_number( $post_ID, true, $post_after->post_type );
2253
    }
2254
}
2255
add_action( 'post_updated', 'wpinv_save_number_post_updated', 1, 3 );
2256
2257
function wpinv_update_invoice_number( $post_ID, $save_sequential = false, $type = '' ) {
2258
    global $wpdb;
2259
    
2260
    $check = apply_filters( 'wpinv_pre_update_invoice_number', null, $post_ID, $save_sequential, $type );
2261
    if ( null !== $check ) {
2262
        return $check;
2263
    }
2264
2265
    if ( wpinv_sequential_number_active() ) {
2266
        $number = wpinv_get_next_invoice_number();
2267
2268
        if ( $save_sequential ) {
2269
            update_option( 'wpinv_last_invoice_number', $number );
2270
        }
2271
    } else {
2272
        $number = $post_ID;
2273
    }
2274
2275
    $number = wpinv_format_invoice_number( $number );
2276
2277
    update_post_meta( $post_ID, '_wpinv_number', $number );
2278
2279
    $wpdb->update( $wpdb->posts, array( 'post_title' => $number ), array( 'ID' => $post_ID ) );
2280
2281
    clean_post_cache( $post_ID );
2282
2283
    return $number;
2284
}
2285
2286
function wpinv_post_name_prefix( $post_type = 'wpi_invoice' ) {
2287
    return apply_filters( 'wpinv_post_name_prefix', 'inv-', $post_type );
2288
}
2289
2290
function wpinv_generate_post_name( $post_ID ) {
2291
    $prefix = wpinv_post_name_prefix( get_post_type( $post_ID ) );
2292
    $post_name = sanitize_title( $prefix . $post_ID );
2293
2294
    return apply_filters( 'wpinv_generate_post_name', $post_name, $post_ID, $prefix );
2295
}
2296
2297
function wpinv_is_invoice_viewed( $invoice_id ) {
2298
    if ( empty( $invoice_id ) ) {
2299
        return false;
2300
    }
2301
2302
    $viewed_meta = get_post_meta( $invoice_id, '_wpinv_is_viewed', true );
2303
2304
    return apply_filters( 'wpinv_is_invoice_viewed', 1 === (int)$viewed_meta, $invoice_id );
2305
}
2306
2307
function wpinv_mark_invoice_viewed() {
2308
2309
    if ( isset( $_GET['invoice_key'] ) || is_singular( 'wpi_invoice' ) || is_singular( 'wpi_quote' ) ) {
2310
        $invoice_key = isset( $_GET['invoice_key'] ) ? urldecode($_GET['invoice_key']) : '';
2311
	    global $post;
2312
2313
        if(!empty($invoice_key)){
2314
	        $invoice_id = wpinv_get_invoice_id_by_key($invoice_key);
2315
        } else if(!empty( $post ) && ($post->post_type == 'wpi_invoice' || $post->post_type == 'wpi_quote')) {
2316
			$invoice_id = $post->ID;
2317
        } else {
2318
        	return;
2319
        }
2320
2321
        $invoice = new WPInv_Invoice($invoice_id);
2322
2323
        if(!$invoice_id){
2324
            return;
2325
        }
2326
2327
	    if ( is_user_logged_in() ) {
2328
		    if ( (int)$invoice->get_user_id() === get_current_user_id() ) {
2329
			    update_post_meta($invoice_id,'_wpinv_is_viewed', 1);
2330
		    } else if ( !wpinv_require_login_to_checkout() && isset( $_GET['invoice_key'] ) && $_GET['invoice_key'] === $invoice->get_key() ) {
2331
			    update_post_meta($invoice_id,'_wpinv_is_viewed', 1);
2332
		    }
2333
	    } else {
2334
		    if ( !wpinv_require_login_to_checkout() && isset( $_GET['invoice_key'] ) && $_GET['invoice_key'] === $invoice->get_key() ) {
2335
			    update_post_meta($invoice_id,'_wpinv_is_viewed', 1);
2336
		    }
2337
	    }
2338
    }
2339
2340
}
2341
add_action( 'template_redirect', 'wpinv_mark_invoice_viewed' );
2342
2343
function wpinv_get_subscription( $invoice, $by_parent = false ) {
2344
    if ( empty( $invoice ) ) {
2345
        return false;
2346
    }
2347
    
2348
    if ( ! is_object( $invoice ) && is_scalar( $invoice ) ) {
2349
        $invoice = wpinv_get_invoice( $invoice );
2350
    }
2351
    
2352
    if ( !( is_object( $invoice ) && ! empty( $invoice->ID ) && $invoice->is_recurring() ) ) {
2353
        return false;
2354
    }
2355
    
2356
    $invoice_id = ! $by_parent && ! empty( $invoice->parent_invoice ) ? $invoice->parent_invoice : $invoice->ID;
2357
    
2358
    $subs_db    = new WPInv_Subscriptions_DB;
2359
    $subs       = $subs_db->get_subscriptions( array( 'parent_payment_id' => $invoice_id, 'number' => 1 ) );
2360
    
2361
    if ( ! empty( $subs ) ) {
2362
        return reset( $subs );
2363
    }
2364
    
2365
    return false;
2366
}
2367
2368
function wpinv_filter_posts_clauses( $clauses, $wp_query ) {
2369
    global $wpdb;
2370
2371
    if ( ! empty( $wp_query->query_vars['orderby'] ) && $wp_query->query_vars['orderby'] == 'invoice_date' ) {
2372
        if ( !empty( $clauses['join'] ) ) {
2373
            $clauses['join'] .= " ";
2374
        }
2375
2376
        if ( !empty( $clauses['fields'] ) ) {
2377
            $clauses['fields'] .= ", ";
2378
        }
2379
2380
        $clauses['join'] .= "LEFT JOIN {$wpdb->postmeta} ON ( {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = '_wpinv_completed_date' )";
2381
        $clauses['fields'] .= "IF( {$wpdb->postmeta}.meta_value, {$wpdb->postmeta}.meta_value, {$wpdb->posts}.post_date ) AS invoice_date";
2382
        $clauses['orderby'] = "invoice_date DESC, {$wpdb->posts}.post_date DESC, {$wpdb->posts}.ID DESC";
2383
    }
2384
2385
    return $clauses;
2386
}
2387
add_filter( 'posts_clauses', 'wpinv_filter_posts_clauses', 10, 2 );