Passed
Push — master ( 86b1fc...2bd916 )
by Brian
454:45 queued 345:21
created

wpinv_checkout_validate_discounts()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 32
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 7
eloc 14
nc 8
nop 0
dl 0
loc 32
rs 8.8333
c 1
b 1
f 0
1
<?php
2
/**
3
 * Contains functions related to Invoicing plugin.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
 
9
// MUST have WordPress.
10
if ( !defined( 'WPINC' ) ) {
11
    exit( 'Do NOT access this file directly: ' . basename( __FILE__ ) );
12
}
13
14
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 758
  2. array($data['discount']) is assigned to $address_fields
    in includes/class-wpinv-ajax.php on line 956
  3. wpinv_insert_invoice() is called
    in includes/class-wpinv-ajax.php on line 963
  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 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...
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
        if ( isset( $invoice_data['created_via'] ) ) {
241
            update_post_meta( $invoice->ID, 'wpinv_created_via', $invoice_data['created_via'] );
242
        }
243
244
        $checkout_session = wpinv_get_checkout_session();
245
        
246
        $data_session                   = array();
247
        $data_session['invoice_id']     = $invoice->ID;
248
        $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

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

262
        /** @scrutinizer ignore-call */ 
263
        $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...
263
264
        wpinv_set_checkout_session( $checkout_session );
265
266
        return $invoice;
267
    }
268
    
269
    if ( $wp_error ) {
270
        if ( is_wp_error( $invoice ) ) {
271
            return $invoice;
272
        } else {
273
            return new WP_Error( 'wpinv_insert_invoice_error', __( 'Error in insert invoice.', 'invoicing' ) );
274
        }
275
    } else {
276
        return 0;
277
    }
278
}
279
280
function wpinv_update_invoice( $invoice_data = array(), $wp_error = false ) {
281
    $invoice_ID = !empty( $invoice_data['ID'] ) ? absint( $invoice_data['ID'] ) : NULL;
282
283
    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...
284
        if ( $wp_error ) {
285
            return new WP_Error( 'invalid_invoice_id', __( 'Invalid invoice ID.', 'invoicing' ) );
286
        }
287
        return 0;
288
    }
289
290
    $invoice = wpinv_get_invoice( $invoice_ID );
291
292
    $recurring_item = $invoice->is_recurring() ? $invoice->get_recurring( true ) : NULL;
293
294
    if ( empty( $invoice->ID ) ) {
295
        if ( $wp_error ) {
296
            return new WP_Error( 'invalid_invoice', __( 'Invalid invoice.', 'invoicing' ) );
297
        }
298
        return 0;
299
    }
300
301
    if ( ! $invoice->has_status( array( 'wpi-pending' ) ) && ! $invoice->is_quote()  ) {
302
        if ( $wp_error ) {
303
            return new WP_Error( 'invalid_invoice_status', __( 'Only invoice with pending payment is allowed to update.', 'invoicing' ) );
304
        }
305
        return 0;
306
    }
307
308
    // Invoice status
309
    if ( !empty( $invoice_data['status'] ) ) {
310
        $invoice->set( 'status', $invoice_data['status'] );
311
    }
312
313
    // Invoice date
314
    if ( !empty( $invoice_data['post_date'] ) ) {
315
        $invoice->set( 'date', $invoice_data['post_date'] );
316
    }
317
318
    // Invoice due date
319
    if ( isset( $invoice_data['due_date'] ) ) {
320
        $invoice->set( 'due_date', $invoice_data['due_date'] );
321
    }
322
323
    // Invoice IP address
324
    if ( !empty( $invoice_data['ip'] ) ) {
325
        $invoice->set( 'ip', $invoice_data['ip'] );
326
    }
327
    
328
    // User info
329
    if ( !empty( $invoice_data['user_info'] ) && is_array( $invoice_data['user_info'] ) ) {
330
        $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 758
  2. array($data['discount']) is assigned to $address_fields
    in includes/class-wpinv-ajax.php on line 956
  3. wpinv_update_invoice() is called
    in includes/class-wpinv-ajax.php on line 976
  4. Enters via parameter $invoice_data
    in includes/wpinv-invoice-functions.php on line 280

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

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

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

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

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

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

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

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

1899
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...
1900
    global $wpinv_receipt_args;
1901
1902
    $wpinv_receipt_args = shortcode_atts( array(
1903
        'error'           => __( 'Sorry, trouble retrieving payment receipt.', 'invoicing' ),
1904
        'price'           => true,
1905
        'discount'        => true,
1906
        'items'           => true,
1907
        'date'            => true,
1908
        'notes'           => true,
1909
        'invoice_key'     => false,
1910
        'payment_method'  => true,
1911
        'invoice_id'      => true
1912
    ), $atts, 'wpinv_receipt' );
1913
1914
    // Find the invoice.
1915
    $session = wpinv_get_checkout_session();
1916
1917
    if ( isset( $_GET['invoice_key'] ) ) {
1918
        $invoice_id = wpinv_get_invoice_id_by_key( urldecode( $_GET['invoice_key'] ) );
1919
    } else if ( isset( $_GET['invoice-id'] ) ) {
1920
        $invoice_id = (int) $_GET['invoice-id'];
1921
    } else if ( $session && isset( $session['invoice_key'] ) ) {
1922
        $invoice_id = wpinv_get_invoice_id_by_key( $session['invoice_key'] );
1923
    } else if ( isset( $wpinv_receipt_args['invoice_key'] ) && $wpinv_receipt_args['invoice_key'] ) {
1924
        $invoice_id = wpinv_get_invoice_id_by_key( $wpinv_receipt_args['invoice_key'] );
1925
    }
1926
1927
    // Did we find the invoice?
1928
    if ( empty( $invoice_id ) || ! $invoice = wpinv_get_invoice( $invoice_id ) ) {
1929
        return '<p class="alert alert-error">' . __( 'We could not find your invoice.', 'invoicing' ) . '</p>';
1930
    }
1931
1932
    $invoice_key   = $invoice->get_key();
1933
    $user_can_view = wpinv_can_view_receipt( $invoice_key );
1934
    if ( $user_can_view && isset( $_GET['invoice-id'] ) ) {
1935
        $user_can_view  = $_GET['invoice-id'] == $invoice->ID;
1936
    }
1937
1938
    // Key was provided, but user is logged out. Offer them the ability to login and view the receipt
1939
    if ( ! $user_can_view && ! empty( $invoice_key ) && ! is_user_logged_in() ) {
1940
        // login redirect
1941
        return '<p class="alert alert-error">' . __( 'You must be logged in to view this receipt', 'invoicing' ) . '</p>';
1942
    }
1943
1944
    if ( ! apply_filters( 'wpinv_user_can_view_receipt', $user_can_view, $wpinv_receipt_args ) ) {
1945
        return '<p class="alert alert-error">' . $wpinv_receipt_args['error'] . '</p>';
1946
    }
1947
1948
    ob_start();
1949
1950
    wpinv_get_template_part( 'wpinv-invoice-receipt' );
1951
1952
    $display = ob_get_clean();
1953
1954
    return $display;
1955
}
1956
1957
/**
1958
 * Given an invoice key, this function returns the id.
1959
 */
1960
function wpinv_get_invoice_id_by_key( $key ) {
1961
	global $wpdb;
1962
    $table      = $wpdb->prefix . 'getpaid_invoices';
1963
	return (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $table WHERE`key` = %s LIMIT 1", $key ) );
1964
}
1965
1966
function wpinv_can_view_receipt( $invoice_key = '' ) {
1967
	$return = current_user_can( 'manage_options' );
1968
1969
	if ( empty( $invoice_key ) ) {
1970
		return false;
1971
	}
1972
1973
	global $wpinv_receipt_args;
1974
1975
	$wpinv_receipt_args['id'] = wpinv_get_invoice_id_by_key( $invoice_key );
1976
	if ( isset( $_GET['invoice-id'] ) ) {
1977
		$wpinv_receipt_args['id'] = $invoice_key == wpinv_get_payment_key( (int)$_GET['invoice-id'] ) ? (int)$_GET['invoice-id'] : 0;
1978
	}
1979
1980
	if ( empty( $wpinv_receipt_args['id'] ) ) {
1981
		return $return;
1982
	}
1983
1984
	$invoice = wpinv_get_invoice( $wpinv_receipt_args['id'] );
1985
	if ( !( !empty( $invoice->ID ) && $invoice->get_key() === $invoice_key ) ) {
1986
		return $return;
1987
	}
1988
1989
	if ( is_user_logged_in() ) {
1990
		if ( (int)$invoice->get_user_id() === (int) get_current_user_id() ) {
1991
			$return = true;
1992
		}
1993
	}
1994
1995
	$session = wpinv_get_checkout_session();
1996
	if ( isset( $_GET['invoice_key'] ) || ( $session && isset( $session['invoice_key'] ) ) ) {
1997
		$check_key = isset( $_GET['invoice_key'] ) ? $_GET['invoice_key'] : $session['invoice_key'];
1998
1999
		if ( wpinv_require_login_to_checkout() ) {
2000
			$return = $return && $check_key == $invoice_key;
2001
		} else {
2002
			$return = $check_key == $invoice_key;
2003
		}
2004
	}
2005
2006
	return (bool) apply_filters( 'wpinv_can_view_receipt', $return, $invoice_key );
2007
}
2008
2009
function wpinv_pay_for_invoice() {
2010
    global $wpinv_euvat;
2011
    
2012
    if ( isset( $_GET['invoice_key'] ) ) {
2013
        $checkout_uri   = wpinv_get_checkout_uri();
2014
        $invoice_key    = sanitize_text_field( $_GET['invoice_key'] );
2015
        
2016
        if ( empty( $invoice_key ) ) {
2017
            wpinv_set_error( 'invalid_invoice', __( 'Invoice not found', 'invoicing' ) );
2018
            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

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

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

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

2150
    $formatted_number = $prefix . $formatted_number . /** @scrutinizer ignore-type */ $postfix;
Loading history...
Bug introduced by
Are you sure $prefix of type false|mixed can be used in concatenation? ( Ignorable by Annotation )

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

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

2227
    $number = preg_replace( '/' . /** @scrutinizer ignore-type */ $prefix . '/', '', $number, 1 );
Loading history...
2228
2229
    $length      = strlen( $number );
2230
    $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

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