Passed
Push — master ( ffff9d...114960 )
by Brian
402:17 queued 294:32
created

wpinv_record_status_change()   B

Complexity

Conditions 10
Paths 5

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 12
nc 5
nop 3
dl 0
loc 25
rs 7.6666
c 1
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    if ( wpinv_use_taxes() && $new_status == 'publish' ) {
1090
        
1091
        if ( WPInv_EUVat::same_country_rule() == 'no' && wpinv_is_base_country( $invoice->country ) ) {
1092
            $invoice->add_note( __( 'VAT was reverse charged', 'invoicing' ), false, false, true );
1093
        }
1094
    }
1095
1096
    $old_status = wpinv_status_nicename( $old_status );
1097
    $new_status = wpinv_status_nicename( $new_status );
1098
1099
    $status_change = sprintf( __( 'Invoice status changed from %s to %s', 'invoicing' ), $old_status, $new_status );
1100
1101
    // Add note
1102
    return $invoice->add_note( $status_change, false, false, true );
1103
}
1104
add_action( 'wpinv_update_status', 'wpinv_record_status_change', 100, 3 );
1105
1106
function wpinv_complete_payment( $invoice_id, $new_status, $old_status ) {
1107
    global $wpi_has_free_trial;
1108
    
1109
    $wpi_has_free_trial = false;
1110
    
1111
    if ( $old_status == 'publish' ) {
1112
        return; // Make sure that payments are only paid once
1113
    }
1114
1115
    // Make sure the payment completion is only processed when new status is paid
1116
    if ( $new_status != 'publish' ) {
1117
        return;
1118
    }
1119
1120
    $invoice = new WPInv_Invoice( $invoice_id );
1121
    if ( empty( $invoice ) ) {
1122
        return;
1123
    }
1124
1125
    $wpi_has_free_trial = $invoice->is_free_trial();
1126
    $completed_date = $invoice->completed_date;
1127
    $cart_details   = $invoice->cart_details;
1128
1129
    do_action( 'wpinv_pre_complete_payment', $invoice_id );
1130
1131
    if ( is_array( $cart_details ) ) {
0 ignored issues
show
introduced by
The condition is_array($cart_details) is always true.
Loading history...
1132
        // Increase purchase count and earnings
1133
        foreach ( $cart_details as $cart_index => $item ) {
1134
            // Ensure these actions only run once, ever
1135
            if ( empty( $completed_date ) ) {
1136
                do_action( 'wpinv_complete_item_payment', $item['id'], $invoice_id, $item, $cart_index );
1137
            }
1138
        }
1139
    }
1140
    
1141
    // Check for discount codes and increment their use counts
1142
    if ( $discounts = $invoice->get_discounts( true ) ) {
1143
        if( ! empty( $discounts ) ) {
1144
            foreach( $discounts as $code ) {
1145
                wpinv_increase_discount_usage( $code );
1146
            }
1147
        }
1148
    }
1149
    
1150
    // Ensure this action only runs once ever
1151
    if( empty( $completed_date ) ) {
1152
        // Save the completed date
1153
        $invoice->set( 'completed_date', current_time( 'mysql', 0 ) );
1154
        $invoice->save();
1155
1156
        do_action( 'wpinv_complete_payment', $invoice_id );
1157
    }
1158
1159
    // Empty the shopping cart
1160
    wpinv_empty_cart();
1161
}
1162
add_action( 'wpinv_update_status', 'wpinv_complete_payment', 100, 3 );
1163
1164
function wpinv_update_payment_status( $invoice_id, $new_status = 'publish' ) {    
1165
    $invoice = !empty( $invoice_id ) && is_object( $invoice_id ) ? $invoice_id : wpinv_get_invoice( (int)$invoice_id );
1166
1167
    if ( empty( $invoice ) ) {
1168
        return false;
1169
    }
1170
1171
    return $invoice->update_status( $new_status );
1172
}
1173
1174
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

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

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

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

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

2157
    $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

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

2234
    $number = preg_replace( '/' . /** @scrutinizer ignore-type */ $prefix . '/', '', $number, 1 );
Loading history...
2235
2236
    $length      = strlen( $number );
2237
    $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

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