Passed
Push — master ( 6929e7...c24352 )
by Brian
10:18 queued 05:36
created

WPInv_EUVat::get_user_country()   A

Complexity

Conditions 6
Paths 24

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 8
c 0
b 0
f 0
nc 24
nop 2
dl 0
loc 15
rs 9.2222
1
<?php
2
/**
3
 * Tax calculation and rate finding class.
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
class WPInv_EUVat {
10
11
    /**
12
     * Retrieves an instance of this class.
13
     * 
14
     * @deprecated
15
     * @return WPInv_EUVat
16
     */
17
    public static function get_instance() {
18
        return new self();
19
    }
20
21
    /**
22
     * Inits tax hooks.
23
     */
24
    public function init() {
25
26
        // If this is an admin page...
27
        if ( is_admin() ) {
28
29
            // Register our scripts.
30
            add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
31
            add_action( 'wpinv_settings_sections_taxes', array( $this, 'section_vat_settings' ) );
32
            add_action( 'wpinv_settings_taxes', array( $this, 'vat_settings' ) );
33
            add_filter( 'wpinv_settings_taxes-vat_sanitize', array( $this, 'sanitize_vat_settings' ) );
34
            add_filter( 'wpinv_settings_taxes-vat_rates_sanitize', array( $this, 'sanitize_vat_rates' ) );
35
            add_action( 'wp_ajax_wpinv_add_vat_class', array( $this, 'add_class' ) );
36
            add_action( 'wp_ajax_nopriv_wpinv_add_vat_class', array( $this, 'add_class' ) );
37
            add_action( 'wp_ajax_wpinv_delete_vat_class', array( $this, 'delete_class' ) );
38
            add_action( 'wp_ajax_nopriv_wpinv_delete_vat_class', array( $this, 'delete_class' ) );
39
            add_action( 'wp_ajax_wpinv_update_vat_rates', array( $this, 'update_eu_rates' ) );
40
            add_action( 'wp_ajax_nopriv_wpinv_update_vat_rates', array( $this, 'update_eu_rates' ) );
41
            add_action( 'wp_ajax_wpinv_geoip2', array( $this, 'geoip2_download_database' ) );
42
            add_action( 'wp_ajax_nopriv_wpinv_geoip2', array( $this, 'geoip2_download_database' ) );
43
        }
44
45
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_vat_scripts' ) );
46
        add_filter( 'wpinv_default_billing_country', array( $this, 'get_user_country' ), 10 );
47
        add_filter( 'wpinv_get_user_country', array( $this, 'set_user_country' ), 10 );
48
        add_action( 'wp_ajax_wpinv_vat_validate', array( $this, 'ajax_vat_validate' ) );
49
        add_action( 'wp_ajax_nopriv_wpinv_vat_validate', array( $this, 'ajax_vat_validate' ) );
50
51
        if ( wpinv_use_taxes() && self::allow_vat_rules() ) {
52
            add_filter( 'wpinv_tax_rate', array( $this, 'get_rate' ), 10, 4 );
53
        }
54
    }
55
56
    public static function get_eu_states( $sort = true ) {
57
        $eu_states = array( 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE' );
58
        if ( $sort ) {
59
            $sort = sort( $eu_states );
60
        }
61
62
        return apply_filters( 'wpinv_get_eu_states', $eu_states, $sort );
63
    }
64
65
    public static function get_gst_countries( $sort = true ) {
66
        $gst_countries  = array( 'AU', 'NZ', 'CA', 'CN' );
67
68
        if ( $sort ) {
69
            $sort = sort( $gst_countries );
70
        }
71
72
        return apply_filters( 'wpinv_get_gst_countries', $gst_countries, $sort );
73
    }
74
75
    public static function is_eu_state( $country_code ) {
76
        $return = !empty( $country_code ) && in_array( strtoupper( $country_code ), self::get_eu_states() ) ? true : false;
77
78
        return apply_filters( 'wpinv_is_eu_state', $return, $country_code );
79
    }
80
81
    public static function is_gst_country( $country_code ) {
82
        $return = !empty( $country_code ) && in_array( strtoupper( $country_code ), self::get_gst_countries() ) ? true : false;
83
84
        return apply_filters( 'wpinv_is_gst_country', $return, $country_code );
85
    }
86
87
    public function enqueue_vat_scripts() {
88
        if( wpinv_use_taxes() && wpinv_get_option( 'apply_vat_rules' ) ) {
89
            $this->load_vat_scripts();
90
        }
91
    }
92
93
    public function load_vat_scripts(){
94
        $suffix     = '';//defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
95
96
        wp_register_script( 'wpinv-vat-validation-script', WPINV_PLUGIN_URL . 'assets/js/jsvat' . $suffix . '.js', array( 'jquery' ),  WPINV_VERSION );
97
        wp_register_script( 'wpinv-vat-script', WPINV_PLUGIN_URL . 'assets/js/euvat' . $suffix . '.js', array( 'jquery' ),  WPINV_VERSION );
98
99
        $vat_name   = $this->get_vat_name();
100
101
        $vars = array();
102
        $vars['UseTaxes'] = wpinv_use_taxes();
103
        $vars['EUStates'] = self::get_eu_states();
104
        $vars['NoRateSet'] = __( 'You have not set a rate. Do you want to continue?', 'invoicing' );
105
        $vars['EmptyCompany'] = __( 'Please enter your registered company name!', 'invoicing' );
106
        $vars['EmptyVAT'] = wp_sprintf( __( 'Please enter your %s number!', 'invoicing' ), $vat_name );
107
        $vars['TotalsRefreshed'] = wp_sprintf( __( 'The invoice totals will be refreshed to update the %s.', 'invoicing' ), $vat_name );
108
        $vars['ErrValidateVAT'] = wp_sprintf( __( 'Fail to validate the %s number!', 'invoicing' ), $vat_name );
109
        $vars['ErrResetVAT'] = wp_sprintf( __( 'Fail to reset the %s number!', 'invoicing' ), $vat_name );
110
        $vars['ErrInvalidVat'] = wp_sprintf( __( 'The %s number supplied does not have a valid format!', 'invoicing' ), $vat_name );
111
        $vars['ErrInvalidResponse'] = __( 'An invalid response has been received from the server!', 'invoicing' );
112
        $vars['ApplyVATRules'] = $vars['UseTaxes'] ? self::allow_vat_rules() : false;
113
        $vars['HideVatFields'] = $vars['ApplyVATRules'] ? self::hide_vat_fields() : true;
114
        $vars['ErrResponse'] = __( 'The request response is invalid!', 'invoicing' );
115
        $vars['ErrRateResponse'] = __( 'The get rate request response is invalid', 'invoicing' );
116
        $vars['PageRefresh'] = __( 'The page will be refreshed in 10 seconds to show the new options.', 'invoicing' );
117
        $vars['RequestResponseNotValidJSON'] = __( 'The get rate request response is not valid JSON', 'invoicing' );
118
        $vars['GetRateRequestFailed'] = __( 'The get rate request failed: ', 'invoicing' );
119
        $vars['NoRateInformationInResponse'] = __( 'The get rate request response does not contain any rate information', 'invoicing' );
120
        $vars['RatesUpdated'] = __( 'The rates have been updated. Press the save button to record these new rates.', 'invoicing' );
121
        $vars['IPAddressInformation'] = __( 'IP Address Information', 'invoicing' );
122
        $vars['VatValidating'] = wp_sprintf( __( 'Validating %s number...', 'invoicing' ), $vat_name );
123
        $vars['VatReseting'] = __( 'Reseting...', 'invoicing' );
124
        $vars['VatValidated'] = wp_sprintf( __( '%s number validated', 'invoicing' ), $vat_name );
125
        $vars['VatNotValidated'] = wp_sprintf( __( '%s number not validated', 'invoicing' ), $vat_name );
126
        $vars['ConfirmDeleteClass'] = __( 'Are you sure you wish to delete this rates class?', 'invoicing' );
127
        $vars['isFront'] = is_admin() ? false : true;
128
        $vars['baseCountry'] = wpinv_get_default_country();
129
        $vars['disableVATSameCountry'] = ( self::same_country_rule() == 'no' ? true : false );
130
        $vars['disableVATSimpleCheck'] = wpinv_get_option( 'vat_offline_check' ) ? true : false;
131
132
        wp_enqueue_script( 'wpinv-vat-validation-script' );
133
        wp_enqueue_script( 'wpinv-vat-script' );
134
        wp_localize_script( 'wpinv-vat-script', 'WPInv_VAT_Vars', $vars );
135
    }
136
137
    public static function enqueue_admin_scripts() {
138
        if( isset( $_GET['page'] ) && 'wpinv-settings' == $_GET['page'] ) {
139
            self::load_vat_scripts();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_EUVat::load_vat_scripts() is not static, but was called statically. ( Ignorable by Annotation )

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

139
            self::/** @scrutinizer ignore-call */ 
140
                  load_vat_scripts();
Loading history...
140
        }
141
    }
142
143
    public static function section_vat_settings( $sections ) {
144
        if ( !empty( $sections ) ) {
145
            $sections['vat'] = __( 'EU VAT Settings', 'invoicing' );
146
147
            if ( self::allow_vat_classes() ) {
148
                $sections['vat_rates'] = __( 'EU VAT Rates', 'invoicing' );
149
            }
150
        }
151
        return $sections;
152
    }
153
154
    public static function vat_rates_settings() {
155
        $vat_classes = self::get_rate_classes();
156
        $vat_rates = array();
157
        $vat_class = isset( $_REQUEST['wpi_sub'] ) && $_REQUEST['wpi_sub'] !== '' && isset( $vat_classes[$_REQUEST['wpi_sub']] )? sanitize_text_field( $_REQUEST['wpi_sub'] ) : '_new';
158
        $current_url = remove_query_arg( 'wpi_sub' );
159
160
        $vat_rates['vat_rates_header'] = array(
161
            'id' => 'vat_rates_header',
162
            'name' => '<h3>' . __( 'Manage VAT Rates', 'invoicing' ) . '</h3>',
163
            'desc' => '',
164
            'type' => 'header',
165
            'size' => 'regular'
166
        );
167
        $vat_rates['vat_rates_class'] = array(
168
            'id'          => 'vat_rates_class',
169
            'name'        => __( 'Edit VAT Rates', 'invoicing' ),
170
            'desc'        => __( 'The standard rate will apply where no explicit rate is provided.', 'invoicing' ),
171
            'type'        => 'select',
172
            'options'     => array_merge( $vat_classes, array( '_new' => __( 'Add New Rate Class', 'invoicing' ) ) ),
173
            'placeholder' => __( 'Select a VAT Rate', 'invoicing' ),
174
            'selected'    => $vat_class,
175
            'class'       => 'wpi_select2',
176
            'onchange'    => 'document.location.href="' . $current_url . '&wpi_sub=" + this.value;',
177
        );
178
179
        if ( $vat_class != '_standard' && $vat_class != '_new' ) {
180
            $vat_rates['vat_rate_delete'] = array(
181
                'id'   => 'vat_rate_delete',
182
                'type' => 'vat_rate_delete',
183
            );
184
        }
185
186
        if ( $vat_class == '_new' ) {
187
            $vat_rates['vat_rates_settings'] = array(
188
                'id' => 'vat_rates_settings',
189
                'name' => '<h3>' . __( 'Add New Rate Class', 'invoicing' ) . '</h3>',
190
                'type' => 'header',
191
            );
192
            $vat_rates['vat_rate_name'] = array(
193
                'id'   => 'vat_rate_name',
194
                'name' => __( 'Name', 'invoicing' ),
195
                'desc' => __( 'A short name for the new VAT Rate class', 'invoicing' ),
196
                'type' => 'text',
197
                'size' => 'regular',
198
            );
199
            $vat_rates['vat_rate_desc'] = array(
200
                'id'   => 'vat_rate_desc',
201
                'name' => __( 'Description', 'invoicing' ),
202
                'desc' => __( 'Manage VAT Rate class', 'invoicing' ),
203
                'type' => 'text',
204
                'size' => 'regular',
205
            );
206
            $vat_rates['vat_rate_add'] = array(
207
                'id'   => 'vat_rate_add',
208
                'type' => 'vat_rate_add',
209
            );
210
        } else {
211
            $vat_rates['vat_rates'] = array(
212
                'id'   => 'vat_rates',
213
                'name' => '<h3>' . $vat_classes[$vat_class] . '</h3>',
214
                'desc' => self::get_class_desc( $vat_class ),
215
                'type' => 'vat_rates',
216
            );
217
        }
218
219
        return $vat_rates;
220
    }
221
222
    public static function vat_settings( $settings ) {
223
        if ( !empty( $settings ) ) {
224
            $vat_settings = array();
225
            $vat_settings['vat_company_title'] = array(
226
                'id' => 'vat_company_title',
227
                'name' => '<h3>' . __( 'Your Company Details', 'invoicing' ) . '</h3>',
228
                'desc' => '',
229
                'type' => 'header',
230
                'size' => 'regular'
231
            );
232
233
            $vat_settings['vat_company_name'] = array(
234
                'id' => 'vat_company_name',
235
                'name' => __( 'Your Company Name', 'invoicing' ),
236
                'desc' => wp_sprintf(__( 'Your company name as it appears on your VAT return, you can verify it via your VAT ID on the %sEU VIES System.%s', 'invoicing' ), '<a href="http://ec.europa.eu/taxation_customs/vies/" target="_blank">', '</a>' ),
237
                'type' => 'text',
238
                'size' => 'regular',
239
            );
240
241
            $vat_settings['vat_number'] = array(
242
                'id'   => 'vat_number',
243
                'name' => __( 'Your VAT Number', 'invoicing' ),
244
                'type' => 'vat_number',
245
                'size' => 'regular',
246
            );
247
248
            $vat_settings['vat_settings_title'] = array(
249
                'id' => 'vat_settings_title',
250
                'name' => '<h3>' . __( 'Apply VAT Settings', 'invoicing' ) . '</h3>',
251
                'desc' => '',
252
                'type' => 'header',
253
                'size' => 'regular'
254
            );
255
256
            $vat_settings['apply_vat_rules'] = array(
257
                'id' => 'apply_vat_rules',
258
                'name' => __( 'Enable VAT Rules', 'invoicing' ),
259
                'desc' => __( 'Apply VAT to consumer sales from IP addresses within the EU, even if the billing address is outside the EU.', 'invoicing' ) . '<br><font style="color:red">' . __( 'Do not disable unless you know what you are doing.', 'invoicing' ) . '</font>',
260
                'type' => 'checkbox',
261
                'std' => '1'
262
            );
263
264
            /*
265
            $vat_settings['vat_allow_classes'] = array(
266
                'id' => 'vat_allow_classes',
267
                'name' => __( 'Allow the use of VAT rate classes', 'invoicing' ),
268
                'desc' =>  __( 'When enabled this option makes it possible to define alternative rate classes so rates for items that do not use the standard VAT rate in all member states can be defined.<br>A menu option will appear under the "Invoicing -> Settings -> Taxes -> EU VAT Rates" menu heading that will take you to a page on which new classes can be defined and rates entered. A meta-box will appear in the invoice page in which you are able to select one of the alternative classes you create so the rates associated with the class will be applied to invoice.<br>By default the standard rates class will be used just as they are when this option is not enabled.', 'invoicing' ),
269
                'type' => 'checkbox'
270
            );
271
            */
272
273
            $vat_settings['vat_prevent_b2c_purchase'] = array(
274
                'id' => 'vat_prevent_b2c_purchase',
275
                'name' => __( 'Prevent EU B2C Sales', 'invoicing' ),
276
                'desc' => __( 'Enable this option if you are not registered for VAT in the EU.', 'invoicing' ),
277
                'type' => 'checkbox'
278
            );
279
280
281
282
            $vat_settings['vat_same_country_rule'] = array(
283
                'id'          => 'vat_same_country_rule',
284
                'name'        => __( 'Same Country Rule', 'invoicing' ),
285
                'desc'        => __( 'Select how you want to handle VAT charge if sales are in the same country as the base country.', 'invoicing' ),
286
                'type'        => 'select',
287
                'options'     => array(
288
                    ''          => __( 'Normal', 'invoicing' ),
289
                    'no'        => __( 'No VAT', 'invoicing' ),
290
                    'always'    => __( 'Always apply VAT', 'invoicing' ),
291
                ),
292
                'placeholder' => __( 'Select an option', 'invoicing' ),
293
                'std'         => '',
294
                'class'   => 'wpi_select2',
295
            );
296
297
            $vat_settings['vat_checkout_title'] = array(
298
                'id' => 'vat_checkout_title',
299
                'name' => '<h3>' . __( 'Checkout Fields', 'invoicing' ) . '</h3>',
300
                'desc' => '',
301
                'type' => 'header',
302
                'size' => 'regular'
303
            );
304
305
            $vat_settings['vat_disable_fields'] = array(
306
                'id' => 'vat_disable_fields',
307
                'name' => __( 'Disable VAT Fields', 'invoicing' ),
308
                'desc' => __( 'Disable VAT fields if Invoicing is being used for GST.', 'invoicing' ) . '<br><font style="color:red">' . __( 'Do not disable if you have enabled Prevent EU B2C sales, otherwise Prevent EU B2C sales setting will not work.', 'invoicing' ) . '</font>',
309
                'type' => 'checkbox'
310
            );
311
312
            $vat_settings['maxmind_license_key'] = array(
313
                'id'   => 'maxmind_license_key',
314
                'name' => __( 'MaxMind License Key', 'invoicing' ),
315
                'type' => 'text',
316
                'size' => 'regular',
317
                'desc' => '<a href="https://support.maxmind.com/account-faq/license-keys/how-do-i-generate-a-license-key/">' . __( 'The key that will be used when dealing with MaxMind Geolocation services.', 'invoicing' ) . '</a>',
318
            );
319
320
            $vat_settings['vat_ip_lookup'] = array(
321
                'id'   => 'vat_ip_lookup',
322
                'name' => __( 'IP Country Look-up', 'invoicing' ),
323
                'type' => 'vat_ip_lookup',
324
                'size' => 'regular',
325
                'std' => 'default',
326
                'class'   => 'wpi_select2',
327
            );
328
329
            $vat_settings['vat_ip_country_default'] = array(
330
                'id' => 'vat_ip_country_default',
331
                'name' => __( 'Enable IP Country as Default', 'invoicing' ),
332
                'desc' => __( 'Show the country of the users IP as the default country, otherwise the site default country will be used.', 'invoicing' ),
333
                'type' => 'checkbox'
334
            );
335
336
            $vat_settings['vies_validation_title'] = array(
337
                'id' => 'vies_validation_title',
338
                'name' => '<h3>' . __( 'VIES Validation', 'invoicing' ) . '</h3>',
339
                'desc' => '',
340
                'type' => 'header',
341
                'size' => 'regular'
342
            );
343
344
            $vat_settings['vat_vies_check'] = array(
345
                'id' => 'vat_vies_check',
346
                'name' => __( 'Disable VIES VAT ID Check', 'invoicing' ),
347
                'desc' => wp_sprintf( __( 'Prevent VAT numbers from being validated by the %sEU VIES System.%s', 'invoicing' ), '<a href="http://ec.europa.eu/taxation_customs/vies/" target="_blank">', '</a>' ),
348
                'type' => 'checkbox'
349
            );
350
351
            $vat_settings['vat_disable_company_name_check'] = array(
352
                'id' => 'vat_disable_company_name_check',
353
                'name' => __( 'Disable VIES Name Check', 'invoicing' ),
354
                'desc' => wp_sprintf( __( 'Prevent company name from being validated by the %sEU VIES System.%s', 'invoicing' ), '<a href="http://ec.europa.eu/taxation_customs/vies/" target="_blank">', '</a>' ),
355
                'type' => 'checkbox'
356
            );
357
358
            $vat_settings['vat_offline_check'] = array(
359
                'id' => 'vat_offline_check',
360
                'name' => __( 'Disable Basic Checks', 'invoicing' ),
361
                'desc' => __( 'Disable basic JS checks for correct format of VAT number. (Not Recommended)', 'invoicing' ),
362
                'type' => 'checkbox'
363
            );
364
365
366
            $settings['vat'] = $vat_settings;
367
368
            if ( self::allow_vat_classes() ) {
369
                $settings['vat_rates'] = self::vat_rates_settings();
370
            }
371
372
            $eu_fallback_rate = array(
373
                'id'   => 'eu_fallback_rate',
374
                'name' => '<h3>' . __( 'VAT rate for EU member states', 'invoicing' ) . '</h3>',
375
                'type' => 'eu_fallback_rate',
376
                'desc' => __( 'Enter the VAT rate to be charged for EU member states. You can edit the rates for each member state when a country rate has been set up by pressing this button.', 'invoicing' ),
377
                'std'  => '20',
378
                'size' => 'small'
379
            );
380
            $settings['rates']['eu_fallback_rate'] = $eu_fallback_rate;
381
        }
382
383
        return $settings;
384
    }
385
386
    /**
387
     * The folder for our maxmind databases.
388
     */
389
    public static function maxmind_folder() {
390
391
        $upload_dir      = wp_upload_dir();
392
        return $upload_dir['basedir'] . '/invoicing';
393
394
    }
395
396
    /**
397
	 * Fetches the database from the MaxMind service.
398
	 *
399
	 * @param string $license_key The license key to be used when downloading the database.
400
	 */
401
    public static function geoip2_download_database() {
402
403
        // Allow us to easily interact with the filesystem.
404
        require_once ABSPATH . 'wp-admin/includes/file.php';
405
		WP_Filesystem();
406
        global $wp_filesystem;
407
408
        $license_key = wpinv_get_option( 'maxmind_license_key' );
409
410
        if ( empty( $license_key ) ) {
411
            echo __( 'Please enter your MaxMind license key then save the settings first before downloading the databases.', 'invoicing' );
412
            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...
413
        }
414
415
        // The database files that we will download.
416
        $database_files     = array( 'GeoLite2-Country', 'GeoLite2-City' );
417
418
        // The destination dir of all databases.
419
        $destination_dir = self::maxmind_folder();
420
421
        if ( ! $wp_filesystem->is_dir( $destination_dir ) ) {
422
            $wp_filesystem->mkdir( $destination_dir );
423
        }
424
425
        foreach( $database_files as $database ) {
426
427
            $database_path = self::geoip2_download_file( $license_key, $database );
428
            $target_path   = trailingslashit( $destination_dir ) .  $database . '.mmdb';
429
430
            if ( is_wp_error( $database_path ) ) {
431
                echo $database_path->get_error_message();
432
                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...
433
            }
434
435
            // Move the new database into position.
436
		    $wp_filesystem->move( $database_path, $target_path, true );
437
            $wp_filesystem->delete( dirname( $database_path ) );
0 ignored issues
show
Bug introduced by
$database_path of type WP_Error is incompatible with the type string expected by parameter $path of dirname(). ( Ignorable by Annotation )

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

437
            $wp_filesystem->delete( dirname( /** @scrutinizer ignore-type */ $database_path ) );
Loading history...
438
439
            wpinv_update_option( 'wpinv_geoip2_date_updated', current_time( 'timestamp' ) );
440
            echo sprintf( __( 'GeoIP2 %s database updated successfully.', 'invoicing' ), $database ) . ' ';
441
        }
442
443
        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...
444
    }
445
446
    /**
447
     * Actually downloads and unzips a database.
448
     *
449
     * @return string|WP_Error
450
     */
451
    public static function geoip2_download_file( $license_key, $database ) {
452
453
        // The download URI of the database.
454
        $source_url = add_query_arg(
455
			array(
456
                'license_key' => urlencode( sanitize_text_field( $license_key ) ),
457
                'edition_id'  => $database,
458
				'suffix'      => 'tar.gz',
459
			),
460
			'https://download.maxmind.com/app/geoip_download'
461
        );
462
463
        // Needed for the download_url call right below.
464
		require_once ABSPATH . 'wp-admin/includes/file.php';
465
466
        // Download the file.
467
        $tmp_archive_path = download_url( esc_url_raw( $source_url ) );
468
469
        // Did we encounter an error?
470
        if ( is_wp_error( $tmp_archive_path ) ) {
471
472
            // Transform the error into something more informative.
473
			$error_data = $tmp_archive_path->get_error_data();
474
			if ( isset( $error_data['code'] ) ) {
475
				switch ( $error_data['code'] ) {
476
					case 401:
477
						return new WP_Error(
478
							'invoicing_maxmind_geolocation_database_license_key',
479
							__( 'The MaxMind license key is invalid. If you have recently created this key, you may need to wait for it to become active.', 'invoicing' )
480
						);
481
				}
482
			}
483
484
            return new WP_Error( 'invoicing_maxmind_geolocation_database_download', __( 'Failed to download the MaxMind database.', 'invoicing' ) );
485
486
        }
487
488
        // Extract the database from the archive.
489
        try {
490
			$file      = new PharData( $tmp_archive_path );
0 ignored issues
show
Bug introduced by
$tmp_archive_path of type WP_Error is incompatible with the type string expected by parameter $fname of PharData::__construct(). ( Ignorable by Annotation )

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

490
			$file      = new PharData( /** @scrutinizer ignore-type */ $tmp_archive_path );
Loading history...
491
            $file_path = trailingslashit( dirname( $tmp_archive_path ) ) . trailingslashit( $file->current()->getFilename() ) . $database . '.mmdb';
0 ignored issues
show
Bug introduced by
$tmp_archive_path of type WP_Error is incompatible with the type string expected by parameter $path of dirname(). ( Ignorable by Annotation )

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

491
            $file_path = trailingslashit( dirname( /** @scrutinizer ignore-type */ $tmp_archive_path ) ) . trailingslashit( $file->current()->getFilename() ) . $database . '.mmdb';
Loading history...
492
493
			$file->extractTo(
494
				dirname( $tmp_archive_path ),
495
				trailingslashit( $file->current()->getFilename() ) . $database . '.mmdb',
496
				true
497
            );
498
499
		} catch ( Exception $exception ) {
500
			return new WP_Error( 'invoicing_maxmind_geolocation_database_archive', $exception->getMessage() );
501
		} finally {
502
			// Remove the archive since we only care about a single file in it.
503
            unlink( $tmp_archive_path );
0 ignored issues
show
Bug introduced by
$tmp_archive_path of type WP_Error is incompatible with the type string expected by parameter $filename of unlink(). ( Ignorable by Annotation )

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

503
            unlink( /** @scrutinizer ignore-type */ $tmp_archive_path );
Loading history...
504
        }
505
506
        return $file_path;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $file_path does not seem to be defined for all execution paths leading up to this point.
Loading history...
507
    }
508
509
    public static function load_geoip2() {
510
        if ( defined( 'WPINV_GEOIP2_LODDED' ) ) {
511
            return;
512
        }
513
514
        if ( !class_exists( '\MaxMind\Db\Reader' ) ) {
515
            $maxmind_db_files = array(
516
                'Reader/Decoder.php',
517
                'Reader/InvalidDatabaseException.php',
518
                'Reader/Metadata.php',
519
                'Reader/Util.php',
520
                'Reader.php',
521
            );
522
523
            foreach ( $maxmind_db_files as $key => $file ) {
524
                require_once( WPINV_PLUGIN_DIR . 'includes/libraries/MaxMind/Db/' . $file );
525
            }
526
        }
527
528
        if ( !class_exists( '\GeoIp2\Database\Reader' ) ) {
529
            $geoip2_files = array(
530
                'ProviderInterface.php',
531
                'Compat/JsonSerializable.php',
532
                'Database/Reader.php',
533
                'Exception/GeoIp2Exception.php',
534
                'Exception/AddressNotFoundException.php',
535
                'Exception/AuthenticationException.php',
536
                'Exception/HttpException.php',
537
                'Exception/InvalidRequestException.php',
538
                'Exception/OutOfQueriesException.php',
539
                'Model/AbstractModel.php',
540
                'Model/AnonymousIp.php',
541
                'Model/Country.php',
542
                'Model/City.php',
543
                'Model/ConnectionType.php',
544
                'Model/Domain.php',
545
                'Model/Enterprise.php',
546
                'Model/Insights.php',
547
                'Model/Isp.php',
548
                'Record/AbstractRecord.php',
549
                'Record/AbstractPlaceRecord.php',
550
                'Record/Country.php',
551
                'Record/City.php',
552
                'Record/Continent.php',
553
                'Record/Location.php',
554
                'Record/MaxMind.php',
555
                'Record/Postal.php',
556
                'Record/RepresentedCountry.php',
557
                'Record/Subdivision.php',
558
                'Record/Traits.php',
559
                'WebService/Client.php',
560
            );
561
562
            foreach ( $geoip2_files as $key => $file ) {
563
                require_once( WPINV_PLUGIN_DIR . 'includes/libraries/GeoIp2/' . $file );
564
            }
565
        }
566
567
        define( 'WPINV_GEOIP2_LODDED', true );
568
    }
569
570
    public static function geoip2_country_dbfile() {
571
        $upload_dir = wp_upload_dir();
572
573
        if ( !isset( $upload_dir['basedir'] ) ) {
574
            return false;
575
        }
576
577
        $filename = $upload_dir['basedir'] . '/invoicing/GeoLite2-Country.mmdb';
578
        if ( !file_exists( $filename ) ) {
579
            return false;
580
        }
581
582
        return $filename;
583
    }
584
585
    public static function geoip2_city_dbfile() {
586
        $upload_dir = wp_upload_dir();
587
588
        if ( !isset( $upload_dir['basedir'] ) ) {
589
            return false;
590
        }
591
592
        $filename = $upload_dir['basedir'] . '/invoicing/GeoLite2-City.mmdb';
593
        if ( !file_exists( $filename ) ) {
594
            return false;
595
        }
596
597
        return $filename;
598
    }
599
600
    public static function geoip2_country_reader() {
601
        try {
602
            self::load_geoip2();
603
604
            if ( $filename = self::geoip2_country_dbfile() ) {
605
                return new \GeoIp2\Database\Reader( $filename );
606
            }
607
        } catch( Exception $e ) {
608
            return false;
609
        }
610
611
        return false;
612
    }
613
614
    public static function geoip2_city_reader() {
615
        try {
616
            self::load_geoip2();
617
618
            if ( $filename = self::geoip2_city_dbfile() ) {
619
                return new \GeoIp2\Database\Reader( $filename );
620
            }
621
        } catch( Exception $e ) {
622
            return false;
623
        }
624
625
        return false;
626
    }
627
628
    public static function geoip2_country_record( $ip_address ) {
629
        try {
630
            $reader = self::geoip2_country_reader();
631
632
            if ( $reader ) {
633
                $record = $reader->country( $ip_address );
634
635
                if ( !empty( $record->country->isoCode ) ) {
636
                    return $record;
637
                }
638
            }
639
        } catch(\InvalidArgumentException $e) {
640
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
641
642
            return false;
643
        } catch(\GeoIp2\Exception\AddressNotFoundException $e) {
644
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
645
646
            return false;
647
        } catch( Exception $e ) {
648
            return false;
649
        }
650
651
        return false;
652
    }
653
654
    public static function geoip2_city_record( $ip_address ) {
655
        try {
656
            $reader = self::geoip2_city_reader();
657
658
            if ( $reader ) {
659
                $record = $reader->city( $ip_address );
660
661
                if ( !empty( $record->country->isoCode ) ) {
662
                    return $record;
663
                }
664
            }
665
        } catch(\InvalidArgumentException $e) {
666
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
667
668
            return false;
669
        } catch(\GeoIp2\Exception\AddressNotFoundException $e) {
670
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
671
672
            return false;
673
        } catch( Exception $e ) {
674
            return false;
675
        }
676
677
        return false;
678
    }
679
680
    public static function geoip2_country_code( $ip_address ) {
681
        $record = self::geoip2_country_record( $ip_address );
682
        return !empty( $record->country->isoCode ) ? $record->country->isoCode : wpinv_get_default_country();
683
    }
684
685
    // Find country by IP address.
686
    public static function get_country_by_ip( $ip = '' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $ip 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

686
    public static function get_country_by_ip( /** @scrutinizer ignore-unused */ $ip = '' ) {

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...
687
        global $wpinv_ip_address_country;
688
        return '';
689
        if ( !empty( $wpinv_ip_address_country ) ) {
0 ignored issues
show
Unused Code introduced by
IfNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
690
            return $wpinv_ip_address_country;
691
        }
692
693
        if ( empty( $ip ) ) {
694
            $ip = wpinv_get_ip();
695
        }
696
697
        $ip_country_service = wpinv_get_option( 'vat_ip_lookup' );
698
        $is_default         = empty( $ip_country_service ) || $ip_country_service === 'default' ? true : false;
699
700
        if ( !empty( $ip ) && $ip !== '127.0.0.1' ) { // For 127.0.0.1(localhost) use default country.
701
            if ( function_exists( 'geoip_country_code_by_name') && ( $ip_country_service === 'geoip' || $is_default ) ) {
702
                try {
703
                    $wpinv_ip_address_country = geoip_country_code_by_name( $ip );
704
                } catch( Exception $e ) {
705
                    wpinv_error_log( $e->getMessage(), 'GeoIP Lookup( ' . $ip . ' )' );
706
                }
707
            } else if ( self::geoip2_country_dbfile() && ( $ip_country_service === 'geoip2' || $is_default ) ) {
708
                $wpinv_ip_address_country = self::geoip2_country_code( $ip );
709
            } else if ( function_exists( 'simplexml_load_file' ) && ini_get('allow_url_fopen') && ( $ip_country_service === 'geoplugin' || $is_default ) ) {
710
                $load_xml = simplexml_load_file( 'http://www.geoplugin.net/xml.gp?ip=' . $ip );
711
712
                if ( !empty( $load_xml ) && !empty( $load_xml->geoplugin_countryCode ) ) {
713
                    $wpinv_ip_address_country = (string)$load_xml->geoplugin_countryCode;
714
                }
715
            }elseif(!empty( $ip )){
716
                $url = 'http://ip-api.com/json/' . $ip;
717
                $response = wp_remote_get($url);
718
719
                if ( is_array( $response ) && wp_remote_retrieve_response_code( $response ) == '200' ) {
720
                    $data = json_decode(wp_remote_retrieve_body( $response ),true);
721
                    if(!empty($data['countryCode'])){
722
                        $wpinv_ip_address_country = (string)$data['countryCode'];
723
                    }
724
                }
725
            }
726
        }
727
728
        if ( empty( $wpinv_ip_address_country ) ) {
729
            $wpinv_ip_address_country = wpinv_get_default_country();
730
        }
731
732
        return $wpinv_ip_address_country;
733
    }
734
735
    public static function sanitize_vat_settings( $input ) {
736
        global $wpinv_options;
737
738
        $valid      = false;
739
        $message    = '';
740
741
        if ( !empty( $wpinv_options['vat_vies_check'] ) ) {
742
            if ( empty( $wpinv_options['vat_offline_check'] ) ) {
743
                $valid = self::offline_check( $input['vat_number'] );
744
            } else {
745
                $valid = true;
746
            }
747
748
            $message = $valid ? '' : __( 'VAT number not validated', 'invoicing' );
749
        } else {
750
            $result = self::check_vat( $input['vat_number'] );
751
752
            if ( empty( $result['valid'] ) ) {
753
                $valid      = false;
754
                $message    = $result['message'];
755
            } else {
756
                $valid      = ( isset( $result['company'] ) && ( $result['company'] == '---' || ( strcasecmp( trim( $result['company'] ), trim( $input['vat_company_name'] ) ) == 0 ) ) ) || !empty( $wpinv_options['vat_disable_company_name_check'] );
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $valid = (IssetNode && $..._company_name_check'])), Probably Intended Meaning: $valid = IssetNode && ($..._company_name_check']))
Loading history...
757
                $message    = $valid ? '' : __( 'The company name associated with the VAT number provided is not the same as the company name provided.', 'invoicing' );
758
            }
759
        }
760
761
        if ( $message && self::is_vat_validated() != $valid ) {
762
            add_settings_error( 'wpinv-notices', '', $message, ( $valid ? 'updated' : 'error' ) );
763
        }
764
765
        $input['vat_valid'] = $valid;
766
        return $input;
767
    }
768
769
    public static function sanitize_vat_rates( $input ) {
770
        if( !wpinv_current_user_can_manage_invoicing() ) {
771
            add_settings_error( 'wpinv-notices', '', __( 'Your account does not have permission to add rate classes.', 'invoicing' ), 'error' );
772
            return $input;
773
        }
774
775
        $vat_classes = self::get_rate_classes();
776
        $vat_class = !empty( $_REQUEST['wpi_vat_class'] ) && isset( $vat_classes[$_REQUEST['wpi_vat_class']] )? sanitize_text_field( $_REQUEST['wpi_vat_class'] ) : '';
777
778
        if ( empty( $vat_class ) ) {
779
            add_settings_error( 'wpinv-notices', '', __( 'No valid VAT rates class contained in the request to save rates.', 'invoicing' ), 'error' );
780
781
            return $input;
782
        }
783
784
        $new_rates = ! empty( $_POST['vat_rates'] ) ? array_values( $_POST['vat_rates'] ) : array();
785
786
        if ( $vat_class === '_standard' ) {
787
            // Save the active rates in the invoice settings
788
            update_option( 'wpinv_tax_rates', $new_rates );
789
        } else {
790
            // Get the existing set of rates
791
            $rates = self::get_non_standard_rates();
792
            $rates[$vat_class] = $new_rates;
793
794
            update_option( 'wpinv_vat_rates', $rates );
795
        }
796
797
        return $input;
798
    }
799
800
    public static function add_class() {
801
        $response = array();
802
        $response['success'] = false;
803
804
        if ( !wpinv_current_user_can_manage_invoicing() ) {
805
            $response['error'] = __( 'Invalid access!', 'invoicing' );
806
            wp_send_json( $response );
807
        }
808
809
        $vat_class_name = !empty( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : false;
810
        $vat_class_desc = !empty( $_POST['desc'] ) ? sanitize_text_field( $_POST['desc'] ) : false;
811
812
        if ( empty( $vat_class_name ) ) {
813
            $response['error'] = __( 'Select the VAT rate name', 'invoicing' );
814
            wp_send_json( $response );
815
        }
816
817
        $vat_classes = (array)self::get_rate_classes();
818
819
        if ( !empty( $vat_classes ) && in_array( strtolower( $vat_class_name ), array_map( 'strtolower', array_values( $vat_classes ) ) ) ) {
820
            $response['error'] = wp_sprintf( __( 'A VAT Rate name "%s" already exists', 'invoicing' ), $vat_class_name );
821
            wp_send_json( $response );
822
        }
823
824
        $rate_class_key = normalize_whitespace( 'wpi-' . $vat_class_name );
825
        $rate_class_key = sanitize_key( str_replace( " ", "-", $rate_class_key ) );
826
827
        $vat_classes = (array)self::get_rate_classes( true );
828
        $vat_classes[$rate_class_key] = array( 'name' => $vat_class_name, 'desc' => $vat_class_desc );
829
830
        update_option( '_wpinv_vat_rate_classes', $vat_classes );
831
832
        $response['success'] = true;
833
        $response['redirect'] = admin_url( 'admin.php?page=wpinv-settings&tab=taxes&section=vat_rates&wpi_sub=' . $rate_class_key );
834
835
        wp_send_json( $response );
836
    }
837
838
    public static function delete_class() {
839
        $response = array();
840
        $response['success'] = false;
841
842
        if ( !wpinv_current_user_can_manage_invoicing() || !isset( $_POST['class'] ) ) {
843
            $response['error'] = __( 'Invalid access!', 'invoicing' );
844
            wp_send_json( $response );
845
        }
846
847
        $vat_class = isset( $_POST['class'] ) && $_POST['class'] !== '' ? sanitize_text_field( $_POST['class'] ) : false;
848
        $vat_classes = (array)self::get_rate_classes();
849
850
        if ( !isset( $vat_classes[$vat_class] ) ) {
851
            $response['error'] = __( 'Requested class does not exists', 'invoicing' );
852
            wp_send_json( $response );
853
        }
854
855
        if ( $vat_class == '_new' || $vat_class == '_standard' ) {
856
            $response['error'] = __( 'You can not delete standard rates class', 'invoicing' );
857
            wp_send_json( $response );
858
        }
859
860
        $vat_classes = (array)self::get_rate_classes( true );
861
        unset( $vat_classes[$vat_class] );
862
863
        update_option( '_wpinv_vat_rate_classes', $vat_classes );
864
865
        $response['success'] = true;
866
        $response['redirect'] = admin_url( 'admin.php?page=wpinv-settings&tab=taxes&section=vat_rates&wpi_sub=_new' );
867
868
        wp_send_json( $response );
869
    }
870
871
    public static function update_eu_rates() {
872
        $response               = array();
873
        $response['success']    = false;
874
        $response['error']      = null;
875
        $response['data']       = null;
876
877
        if ( !wpinv_current_user_can_manage_invoicing() ) {
878
            $response['error'] = __( 'Invalid access!', 'invoicing' );
879
            wp_send_json( $response );
880
        }
881
882
        $group      = !empty( $_POST['group'] ) ? sanitize_text_field( $_POST['group'] ) : '';
883
        $euvatrates = self::request_euvatrates( $group );
884
885
        if ( !empty( $euvatrates ) ) {
886
            if ( !empty( $euvatrates['success'] ) && !empty( $euvatrates['rates'] ) ) {
887
                $response['success']        = true;
888
                $response['data']['rates']  = $euvatrates['rates'];
889
            } else if ( !empty( $euvatrates['error'] ) ) {
890
                $response['error']          = $euvatrates['error'];
891
            }
892
        }
893
894
        wp_send_json( $response );
895
    }
896
897
    public static function hide_vat_fields() {
898
        $hide = wpinv_get_option( 'vat_disable_fields' );
899
900
        return apply_filters( 'wpinv_hide_vat_fields', $hide );
901
    }
902
903
    public static function same_country_rule() {
904
        $same_country_rule = wpinv_get_option( 'vat_same_country_rule' );
905
906
        return apply_filters( 'wpinv_vat_same_country_rule', $same_country_rule );
907
    }
908
909
    /**
910
     * Retrieves the vat name.
911
     */
912
    public function get_vat_name() {
913
        $vat_name = wpinv_get_option( 'vat_name' );
914
        return empty( $vat_name ) ? __( 'VAT', 'invoicing' ) : sanitize_text_field( $vat_name );
915
    }
916
917
    public static function get_company_name() {
918
        $company_name = wpinv_get_option( 'vat_company_name' );
919
920
        return apply_filters( 'wpinv_get_owner_company_name', $company_name );
921
    }
922
923
    public static function get_vat_number() {
924
        $vat_number = wpinv_get_option( 'vat_number' );
925
926
        return apply_filters( 'wpinv_get_owner_vat_number', $vat_number );
927
    }
928
929
    public static function is_vat_validated() {
930
        $validated = self::get_vat_number() && wpinv_get_option( 'vat_valid' );
931
932
        return apply_filters( 'wpinv_is_owner_vat_validated', $validated );
933
    }
934
935
    public static function sanitize_vat( $vat_number, $country_code = '' ) {
936
        $vat_number = str_replace( array(' ', '.', '-', '_', ',' ), '', strtoupper( trim( $vat_number ) ) );
937
938
        if ( empty( $country_code ) ) {
939
            $country_code = substr( $vat_number, 0, 2 );
940
        }
941
942
        if ( strpos( $vat_number , $country_code ) === 0 ) {
943
            $vat = str_replace( $country_code, '', $vat_number );
944
        } else {
945
            $vat = $country_code . $vat_number;
946
        }
947
948
        $return                 = array();
949
        $return['vat']          = $vat;
950
        $return['iso']          = $country_code;
951
        $return['vat_number']   = $country_code . $vat;
952
953
        return $return;
954
    }
955
956
    public static function offline_check( $vat_number, $country_code = '', $formatted = false ) {
957
        $vat            = self::sanitize_vat( $vat_number, $country_code );
958
        $vat_number     = $vat['vat_number'];
959
        $country_code   = $vat['iso'];
960
        $regex          = array();
961
962
        switch ( $country_code ) {
963
            case 'AT':
964
                $regex[] = '/^(AT)U(\d{8})$/';                           // Austria
965
                break;
966
            case 'BE':
967
                $regex[] = '/^(BE)(0?\d{9})$/';                          // Belgium
968
                break;
969
            case 'BG':
970
                $regex[] = '/^(BG)(\d{9,10})$/';                         // Bulgaria
971
                break;
972
            case 'CH':
973
            case 'CHE':
974
                $regex[] = '/^(CHE)(\d{9})MWST$/';                       // Switzerland (Not EU)
975
                break;
976
            case 'CY':
977
                $regex[] = '/^(CY)([0-5|9]\d{7}[A-Z])$/';                // Cyprus
978
                break;
979
            case 'CZ':
980
                $regex[] = '/^(CZ)(\d{8,13})$/';                         // Czech Republic
981
                break;
982
            case 'DE':
983
                $regex[] = '/^(DE)([1-9]\d{8})$/';                       // Germany
984
                break;
985
            case 'DK':
986
                $regex[] = '/^(DK)(\d{8})$/';                            // Denmark
987
                break;
988
            case 'EE':
989
                $regex[] = '/^(EE)(10\d{7})$/';                          // Estonia
990
                break;
991
            case 'EL':
992
                $regex[] = '/^(EL)(\d{9})$/';                            // Greece
993
                break;
994
            case 'ES':
995
                $regex[] = '/^(ES)([A-Z]\d{8})$/';                       // Spain (National juridical entities)
996
                $regex[] = '/^(ES)([A-H|N-S|W]\d{7}[A-J])$/';            // Spain (Other juridical entities)
997
                $regex[] = '/^(ES)([0-9|Y|Z]\d{7}[A-Z])$/';              // Spain (Personal entities type 1)
998
                $regex[] = '/^(ES)([K|L|M|X]\d{7}[A-Z])$/';              // Spain (Personal entities type 2)
999
                break;
1000
            case 'EU':
1001
                $regex[] = '/^(EU)(\d{9})$/';                            // EU-type
1002
                break;
1003
            case 'FI':
1004
                $regex[] = '/^(FI)(\d{8})$/';                            // Finland
1005
                break;
1006
            case 'FR':
1007
                $regex[] = '/^(FR)(\d{11})$/';                           // France (1)
1008
                $regex[] = '/^(FR)[(A-H)|(J-N)|(P-Z)](\d{10})$/';        // France (2)
1009
                $regex[] = '/^(FR)\d[(A-H)|(J-N)|(P-Z)](\d{9})$/';       // France (3)
1010
                $regex[] = '/^(FR)[(A-H)|(J-N)|(P-Z)]{2}(\d{9})$/';      // France (4)
1011
                break;
1012
            case 'GB':
1013
                $regex[] = '/^(GB)?(\d{9})$/';                           // UK (Standard)
1014
                $regex[] = '/^(GB)?(\d{12})$/';                          // UK (Branches)
1015
                $regex[] = '/^(GB)?(GD\d{3})$/';                         // UK (Government)
1016
                $regex[] = '/^(GB)?(HA\d{3})$/';                         // UK (Health authority)
1017
                break;
1018
            case 'GR':
1019
                $regex[] = '/^(GR)(\d{8,9})$/';                          // Greece
1020
                break;
1021
            case 'HR':
1022
                $regex[] = '/^(HR)(\d{11})$/';                           // Croatia
1023
                break;
1024
            case 'HU':
1025
                $regex[] = '/^(HU)(\d{8})$/';                            // Hungary
1026
                break;
1027
            case 'IE':
1028
                $regex[] = '/^(IE)(\d{7}[A-W])$/';                       // Ireland (1)
1029
                $regex[] = '/^(IE)([7-9][A-Z\*\+)]\d{5}[A-W])$/';        // Ireland (2)
1030
                $regex[] = '/^(IE)(\d{7}[A-Z][AH])$/';                   // Ireland (3) (new format from 1 Jan 2013)
1031
                break;
1032
            case 'IT':
1033
                $regex[] = '/^(IT)(\d{11})$/';                           // Italy
1034
                break;
1035
            case 'LV':
1036
                $regex[] = '/^(LV)(\d{11})$/';                           // Latvia
1037
                break;
1038
            case 'LT':
1039
                $regex[] = '/^(LT)(\d{9}|\d{12})$/';                     // Lithuania
1040
                break;
1041
            case 'LU':
1042
                $regex[] = '/^(LU)(\d{8})$/';                            // Luxembourg
1043
                break;
1044
            case 'MT':
1045
                $regex[] = '/^(MT)([1-9]\d{7})$/';                       // Malta
1046
                break;
1047
            case 'NL':
1048
                $regex[] = '/^(NL)(\d{9})B\d{2}$/';                      // Netherlands
1049
                break;
1050
            case 'NO':
1051
                $regex[] = '/^(NO)(\d{9})$/';                            // Norway (Not EU)
1052
                break;
1053
            case 'PL':
1054
                $regex[] = '/^(PL)(\d{10})$/';                           // Poland
1055
                break;
1056
            case 'PT':
1057
                $regex[] = '/^(PT)(\d{9})$/';                            // Portugal
1058
                break;
1059
            case 'RO':
1060
                $regex[] = '/^(RO)([1-9]\d{1,9})$/';                     // Romania
1061
                break;
1062
            case 'RS':
1063
                $regex[] = '/^(RS)(\d{9})$/';                            // Serbia (Not EU)
1064
                break;
1065
            case 'SI':
1066
                $regex[] = '/^(SI)([1-9]\d{7})$/';                       // Slovenia
1067
                break;
1068
            case 'SK':
1069
                $regex[] = '/^(SK)([1-9]\d[(2-4)|(6-9)]\d{7})$/';        // Slovakia Republic
1070
                break;
1071
            case 'SE':
1072
                $regex[] = '/^(SE)(\d{10}01)$/';                         // Sweden
1073
                break;
1074
            default:
1075
                $regex = array();
1076
            break;
1077
        }
1078
1079
        if ( empty( $regex ) ) {
1080
            return false;
1081
        }
1082
1083
        foreach ( $regex as $pattern ) {
1084
            $matches = null;
1085
            preg_match_all( $pattern, $vat_number, $matches );
1086
1087
            if ( !empty( $matches[1][0] ) && !empty( $matches[2][0] ) ) {
1088
                if ( $formatted ) {
1089
                    return array( 'code' => $matches[1][0], 'number' => $matches[2][0] );
1090
                } else {
1091
                    return true;
1092
                }
1093
            }
1094
        }
1095
1096
        return false;
1097
    }
1098
1099
    public static function vies_check( $vat_number, $country_code = '', $result = false ) {
1100
        $vat            = self::sanitize_vat( $vat_number, $country_code );
1101
        $vat_number     = $vat['vat'];
1102
        $iso            = $vat['iso'];
1103
1104
        $url = 'http://ec.europa.eu/taxation_customs/vies/viesquer.do?ms=' . urlencode( $iso ) . '&iso=' . urlencode( $iso ) . '&vat=' . urlencode( $vat_number );
1105
1106
        if ( ini_get( 'allow_url_fopen' ) ) {
1107
            $response = file_get_contents( $url );
1108
        } else if ( function_exists( 'curl_init' ) ) {
1109
            $ch = curl_init();
1110
1111
            curl_setopt( $ch, CURLOPT_URL, $url );
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, 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

1111
            curl_setopt( /** @scrutinizer ignore-type */ $ch, CURLOPT_URL, $url );
Loading history...
1112
            curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 30 );
1113
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1114
            curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
1115
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
1116
1117
            $response = curl_exec( $ch );
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, 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

1117
            $response = curl_exec( /** @scrutinizer ignore-type */ $ch );
Loading history...
1118
1119
            if ( curl_errno( $ch ) ) {
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_errno() does only seem to accept resource, 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

1119
            if ( curl_errno( /** @scrutinizer ignore-type */ $ch ) ) {
Loading history...
1120
                wpinv_error_log( curl_error( $ch ), 'VIES CHECK ERROR' );
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_error() does only seem to accept resource, 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

1120
                wpinv_error_log( curl_error( /** @scrutinizer ignore-type */ $ch ), 'VIES CHECK ERROR' );
Loading history...
1121
                $response = '';
1122
            }
1123
1124
            curl_close( $ch );
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, 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

1124
            curl_close( /** @scrutinizer ignore-type */ $ch );
Loading history...
1125
        } else {
1126
            wpinv_error_log( 'To use VIES CHECK you must have allow_url_fopen is ON or cURL installed & active on your server.', 'VIES CHECK ERROR' );
1127
        }
1128
1129
        if ( empty( $response ) ) {
1130
            return $result;
1131
        }
1132
1133
        if ( preg_match( '/invalid VAT number/i', $response ) ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.
Loading history...
1134
            return false;
1135
        } else if ( preg_match( '/valid VAT number/i', $response, $matches ) ) {
1136
            $content = explode( "valid VAT number", htmlentities( $response ) );
1137
1138
            if ( !empty( $content[1] ) ) {
1139
                preg_match_all( '/<tr>(.*?)<td.*?>(.*?)<\/td>(.*?)<\/tr>/si', html_entity_decode( $content[1] ), $matches );
1140
1141
                if ( !empty( $matches[2] ) && $matches[3] ) {
1142
                    $return = array();
1143
1144
                    foreach ( $matches[2] as $key => $label ) {
1145
                        $label = trim( $label );
1146
1147
                        switch ( strtolower( $label ) ) {
1148
                            case 'member state':
1149
                                $return['state'] = trim( strip_tags( $matches[3][$key] ) );
1150
                            break;
1151
                            case 'vat number':
1152
                                $return['number'] = trim( strip_tags( $matches[3][$key] ) );
1153
                            break;
1154
                            case 'name':
1155
                                $return['company'] = trim( strip_tags( $matches[3][$key] ) );
1156
                            break;
1157
                            case 'address':
1158
                                $address           = str_replace( array( "<br><br>", "<br /><br />", "<br/><br/>" ), "<br>", html_entity_decode( trim( $matches[3][$key] ) ) );
1159
                                $return['address'] = trim( strip_tags( $address, '<br>' ) );
1160
                            break;
1161
                            case 'consultation number':
1162
                                $return['consultation'] = trim( strip_tags( $matches[3][$key] ) );
1163
                            break;
1164
                        }
1165
                    }
1166
1167
                    if ( !empty( $return ) ) {
1168
                        return $return;
1169
                    }
1170
                }
1171
            }
1172
1173
            return true;
1174
        } else {
1175
            return $result;
1176
        }
1177
    }
1178
1179
    public static function check_vat( $vat_number, $country_code = '' ) {
1180
        $vat_name           = getpaid_vat_name();
1181
1182
        $return             = array();
1183
        $return['valid']    = false;
1184
        $return['message']  = wp_sprintf( __( '%s number not validated', 'invoicing' ), $vat_name );
1185
1186
        if ( !wpinv_get_option( 'vat_offline_check' ) && !self::offline_check( $vat_number, $country_code ) ) {
1187
            return $return;
1188
        }
1189
1190
        $response = self::vies_check( $vat_number, $country_code );
1191
1192
        if ( $response ) {
1193
            $return['valid']    = true;
1194
1195
            if ( is_array( $response ) ) {
1196
                $return['company'] = isset( $response['company'] ) ? $response['company'] : '';
1197
                $return['address'] = isset( $response['address'] ) ? $response['address'] : '';
1198
                $return['message'] = $return['company'] . '<br/>' . $return['address'];
1199
            }
1200
        } else {
1201
            $return['valid']    = false;
1202
            $return['message']  = wp_sprintf( __( 'Fail to validate the %s number: EU Commission VAT server (VIES) check fails.', 'invoicing' ), $vat_name );
1203
        }
1204
1205
        return $return;
1206
    }
1207
1208
    public static function request_euvatrates( $group ) {
1209
        $response               = array();
1210
        $response['success']    = false;
1211
        $response['error']      = null;
1212
        $response['eurates']    = null;
1213
1214
        $euvatrates_url = 'https://euvatrates.com/rates.json';
1215
        $euvatrates_url = apply_filters( 'wpinv_euvatrates_url', $euvatrates_url );
1216
        $api_response   = wp_remote_get( $euvatrates_url );
1217
1218
        try {
1219
            if ( is_wp_error( $api_response ) ) {
1220
                $response['error']      = __( $api_response->get_error_message(), 'invoicing' );
1221
            } else {
1222
                $body = json_decode( $api_response['body'] );
1223
                if ( isset( $body->rates ) ) {
1224
                    $rates = array();
1225
1226
                    foreach ( $body->rates as $country_code => $rate ) {
1227
                        $vat_rate = array();
1228
                        $vat_rate['country']        = $rate->country;
1229
                        $vat_rate['standard']       = (float)$rate->standard_rate;
1230
                        $vat_rate['reduced']        = (float)$rate->reduced_rate;
1231
                        $vat_rate['superreduced']   = (float)$rate->super_reduced_rate;
1232
                        $vat_rate['parking']        = (float)$rate->parking_rate;
1233
1234
                        if ( $group !== '' && in_array( $group, array( 'standard', 'reduced', 'superreduced', 'parking' ) ) ) {
1235
                            $vat_rate_group = array();
1236
                            $vat_rate_group['country'] = $rate->country;
1237
                            $vat_rate_group[$group]    = $vat_rate[$group];
1238
1239
                            $vat_rate = $vat_rate_group;
1240
                        }
1241
1242
                        $rates[$country_code] = $vat_rate;
1243
                    }
1244
1245
                    $response['success']    = true;
1246
                    $response['rates']      = apply_filters( 'wpinv_process_euvatrates', $rates, $api_response, $group );
1247
                } else {
1248
                    $response['error']      = __( 'No EU rates found!', 'invoicing' );
1249
                }
1250
            }
1251
        } catch ( Exception $e ) {
1252
            $response['error'] = __( $e->getMessage(), 'invoicing' );
1253
        }
1254
1255
        return apply_filters( 'wpinv_response_euvatrates', $response, $group );
1256
    }
1257
1258
    public static function requires_vat( $requires_vat = false, $user_id = 0, $is_digital = null ) {
1259
        global $wpi_item_id, $wpi_country;
1260
1261
        if ( !empty( $_POST['wpinv_country'] ) ) {
1262
            $country_code = trim( $_POST['wpinv_country'] );
1263
        } else if ( !empty( $_POST['country'] ) ) {
1264
            $country_code = trim( $_POST['country'] );
1265
        } else if ( !empty( $wpi_country ) ) {
1266
            $country_code = $wpi_country;
1267
        } else {
1268
            $country_code = self::get_user_country( '', $user_id );
1269
        }
1270
1271
        if ( $is_digital === null && $wpi_item_id ) {
1272
            $is_digital = $wpi_item_id ? self::item_has_digital_rule( $wpi_item_id ) : self::allow_vat_rules();
1273
        }
1274
1275
        if ( !empty( $country_code ) ) {
1276
            $requires_vat = ( self::is_eu_state( $country_code ) && ( self::is_eu_state( wpinv_get_default_country() ) || $is_digital ) ) || ( self::is_gst_country( $country_code ) && self::is_gst_country( wpinv_get_default_country() ) );
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $requires_vat = (self::i...get_default_country())), Probably Intended Meaning: $requires_vat = self::is...get_default_country()))
Loading history...
1277
        }
1278
1279
        return apply_filters( 'wpinv_requires_vat', $requires_vat, $user_id );
1280
    }
1281
1282
    public static function tax_label( $label = '' ) {
1283
        global $wpi_requires_vat;
1284
1285
        if ( !( $wpi_requires_vat !== 0 && $wpi_requires_vat ) ) {
1286
            $wpi_requires_vat = self::requires_vat( 0, false );
1287
        }
1288
1289
        return $wpi_requires_vat ? __( self::get_vat_name(), 'invoicing' ) : ( $label ? $label : __( 'Tax', 'invoicing' ) );
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_EUVat::get_vat_name() is not static, but was called statically. ( Ignorable by Annotation )

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

1289
        return $wpi_requires_vat ? __( self::/** @scrutinizer ignore-call */ get_vat_name(), 'invoicing' ) : ( $label ? $label : __( 'Tax', 'invoicing' ) );
Loading history...
1290
    }
1291
1292
    public static function standard_rates_label() {
1293
        return __( 'Standard Rates', 'invoicing' );
1294
    }
1295
1296
    public static function get_rate_classes( $with_desc = false ) {
1297
        $rate_classes_option = get_option( '_wpinv_vat_rate_classes', true );
1298
        $classes = maybe_unserialize( $rate_classes_option );
1299
1300
        if ( empty( $classes ) || !is_array( $classes ) ) {
1301
            $classes = array();
1302
        }
1303
1304
        $rate_classes = array();
1305
        if ( !array_key_exists( '_standard', $classes ) ) {
1306
            if ( $with_desc ) {
1307
                $rate_classes['_standard'] = array( 'name' => self::standard_rates_label(), 'desc' => __( 'EU member states standard VAT rates', 'invoicing' ) );
1308
            } else {
1309
                $rate_classes['_standard'] = self::standard_rates_label();
1310
            }
1311
        }
1312
1313
        foreach ( $classes as $key => $class ) {
1314
            $name = !empty( $class['name'] ) ? __( $class['name'], 'invoicing' ) : $key;
1315
            $desc = !empty( $class['desc'] ) ? __( $class['desc'], 'invoicing' ) : '';
1316
1317
            if ( $with_desc ) {
1318
                $rate_classes[$key] = array( 'name' => $name, 'desc' => $desc );
1319
            } else {
1320
                $rate_classes[$key] = $name;
1321
            }
1322
        }
1323
1324
        return $rate_classes;
1325
    }
1326
1327
    public static function get_all_classes() {
1328
        $classes            = self::get_rate_classes();
1329
        $classes['_exempt'] = __( 'Exempt (0%)', 'invoicing' );
1330
1331
        return apply_filters( 'wpinv_vat_get_all_classes', $classes );
1332
    }
1333
1334
    public static function get_class_desc( $rate_class ) {
1335
        $rate_classes = self::get_rate_classes( true );
1336
1337
        if ( !empty( $rate_classes ) && isset( $rate_classes[$rate_class] ) && isset( $rate_classes[$rate_class]['desc'] ) ) {
1338
            return $rate_classes[$rate_class]['desc'];
1339
        }
1340
1341
        return '';
1342
    }
1343
1344
    public static function get_vat_groups() {
1345
        $vat_groups = array(
1346
            'standard'      => 'Standard',
1347
            'reduced'       => 'Reduced',
1348
            'superreduced'  => 'Super Reduced',
1349
            'parking'       => 'Parking',
1350
            'increased'     => 'Increased'
1351
        );
1352
1353
        return apply_filters( 'wpinv_get_vat_groups', $vat_groups );
1354
    }
1355
1356
    public static function get_rules() {
1357
        $vat_rules = array(
1358
            'digital' => __( 'Digital Product', 'invoicing' ),
1359
            'physical' => __( 'Physical Product', 'invoicing' ),
1360
            '_exempt' => __( 'Tax-Free Product', 'invoicing' ),
1361
        );
1362
        return apply_filters( 'wpinv_get_vat_rules', $vat_rules );
1363
    }
1364
1365
    public static function get_vat_rates( $class ) {
1366
        if ( $class === '_standard' ) {
1367
            return wpinv_get_tax_rates();
1368
        }
1369
1370
        $rates = self::get_non_standard_rates();
1371
1372
        return array_key_exists( $class, $rates ) ? $rates[$class] : array();
1373
    }
1374
1375
    public static function get_non_standard_rates() {
1376
        $option = get_option( 'wpinv_vat_rates', array());
1377
        return is_array( $option ) ? $option : array();
1378
    }
1379
1380
    public static function allow_vat_rules() {
1381
        return ( wpinv_use_taxes() && wpinv_get_option( 'apply_vat_rules' ) ? true : false );
1382
    }
1383
1384
    public static function allow_vat_classes() {
1385
        return false; // TODO
1386
        return ( wpinv_get_option( 'vat_allow_classes' ) ? true : false );
0 ignored issues
show
Unused Code introduced by
return wpinv_get_option(...lasses') ? true : false is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1387
    }
1388
1389
    public static function get_item_class( $postID ) {
1390
        $class = get_post_meta( $postID, '_wpinv_vat_class', true );
1391
1392
        if ( empty( $class ) ) {
1393
            $class = '_standard';
1394
        }
1395
1396
        return apply_filters( 'wpinv_get_item_vat_class', $class, $postID );
1397
    }
1398
1399
    public static function item_class_label( $postID ) {
1400
        $vat_classes = self::get_all_classes();
1401
1402
        $class = self::get_item_class( $postID );
1403
        $class = isset( $vat_classes[$class] ) ? $vat_classes[$class] : __( $class, 'invoicing' );
1404
1405
        return apply_filters( 'wpinv_item_class_label', $class, $postID );
1406
    }
1407
1408
    public static function get_item_rule( $postID ) {
1409
        $rule_type = get_post_meta( $postID, '_wpinv_vat_rule', true );
1410
1411
        if ( empty( $rule_type ) ) {
1412
            $rule_type = self::allow_vat_rules() ? 'digital' : 'physical';
1413
        }
1414
1415
        return apply_filters( 'wpinv_item_get_vat_rule', $rule_type, $postID );
1416
    }
1417
1418
    public static function item_rule_label( $postID ) {
1419
        $vat_rules  = self::get_rules();
1420
        $vat_rule   = self::get_item_rule( $postID );
1421
        $vat_rule   = isset( $vat_rules[$vat_rule] ) ? $vat_rules[$vat_rule] : $vat_rule;
1422
1423
        return apply_filters( 'wpinv_item_rule_label', $vat_rule, $postID );
1424
    }
1425
1426
    public static function item_has_digital_rule( $item_id = 0 ) {
1427
        return self::get_item_rule( $item_id ) == 'digital' ? true : false;
1428
    }
1429
1430
    public static function invoice_has_digital_rule( $invoice = 0 ) {
1431
        if ( !self::allow_vat_rules() ) {
1432
            return false;
1433
        }
1434
1435
        if ( empty( $invoice ) ) {
1436
            return true;
1437
        }
1438
1439
        if ( is_int( $invoice ) ) {
1440
            $invoice = new WPInv_Invoice( $invoice );
1441
        }
1442
1443
        if ( !( is_object( $invoice ) && is_a( $invoice, 'WPInv_Invoice' ) ) ) {
1444
            return true;
1445
        }
1446
1447
        $cart_items  = $invoice->get_cart_details();
1448
1449
        if ( !empty( $cart_items ) ) {
1450
            $has_digital_rule = false;
1451
1452
            foreach ( $cart_items as $key => $item ) {
1453
                if ( self::item_has_digital_rule( $item['id'] ) ) {
1454
                    $has_digital_rule = true;
1455
                    break;
1456
                }
1457
            }
1458
        } else {
1459
            $has_digital_rule = true;
1460
        }
1461
1462
        return $has_digital_rule;
1463
    }
1464
1465
    public static function item_is_taxable( $item_id = 0, $country = false, $state = false ) {
1466
        if ( !wpinv_use_taxes() ) {
1467
            return false;
1468
        }
1469
1470
        $is_taxable = true;
1471
1472
        if ( !empty( $item_id ) && self::get_item_class( $item_id ) == '_exempt' ) {
1473
            $is_taxable = false;
1474
        }
1475
1476
        if ( !empty( $item_id ) && self::get_item_rule( $item_id ) == '_exempt' ) {
1477
            $is_taxable = false;
1478
        }
1479
1480
        return apply_filters( 'wpinv_item_is_taxable', $is_taxable, $item_id, $country , $state );
1481
    }
1482
1483
    public static function find_rate( $country, $state, $rate, $class ) {
1484
        global $wpi_zero_tax;
1485
1486
        if ( $class === '_exempt' || $wpi_zero_tax ) {
1487
            return 0;
1488
        }
1489
1490
        $tax_rates   = wpinv_get_tax_rates();
1491
1492
        if ( $class !== '_standard' ) {
1493
            $class_rates = self::get_vat_rates( $class );
1494
1495
            if ( is_array( $class_rates ) ) {
1496
                $indexed_class_rates = array();
1497
1498
                foreach ( $class_rates as $key => $cr ) {
1499
                    $indexed_class_rates[$cr['country']] = $cr;
1500
                }
1501
1502
                $tax_rates = array_map( function( $tr ) use( $indexed_class_rates ) {
1503
                    $tr_country = $tr['country'];
1504
                    if ( !isset( $indexed_class_rates[$tr_country] ) ) {
1505
                        return $tr;
1506
                    }
1507
                    $icr = $indexed_class_rates[$tr_country];
1508
                    return ( empty( $icr['rate'] ) && $icr['rate'] !== '0' ) ? $tr : $icr;
1509
1510
                }, $tax_rates, $class_rates );
0 ignored issues
show
Bug introduced by
It seems like $tax_rates can also be of type false; however, parameter $arr1 of array_map() does only seem to accept array, 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

1510
                }, /** @scrutinizer ignore-type */ $tax_rates, $class_rates );
Loading history...
1511
            }
1512
        }
1513
1514
        if ( !empty( $tax_rates ) ) {
1515
            foreach ( $tax_rates as $key => $tax_rate ) {
1516
                if ( $country != $tax_rate['country'] )
1517
                    continue;
1518
1519
                if ( !empty( $tax_rate['global'] ) ) {
1520
                    if ( 0 !== $tax_rate['rate'] || !empty( $tax_rate['rate'] ) ) {
1521
                        $rate = number_format( $tax_rate['rate'], 4 );
1522
                    }
1523
                } else {
1524
                    if ( empty( $tax_rate['state'] ) || strtolower( $state ) != strtolower( $tax_rate['state'] ) )
1525
                        continue;
1526
1527
                    $state_rate = $tax_rate['rate'];
1528
                    if ( 0 !== $state_rate || !empty( $state_rate ) ) {
1529
                        $rate = number_format( $state_rate, 4 );
1530
                    }
1531
                }
1532
            }
1533
        }
1534
1535
        return $rate;
1536
    }
1537
1538
    public static function get_rate( $rate = 1, $country = '', $state = '', $item_id = 0 ) {
1539
        global $wpinv_options, $wpi_session, $wpi_item_id, $wpi_zero_tax;
1540
1541
        $item_id = $item_id > 0 ? $item_id : $wpi_item_id;
1542
        $allow_vat_classes = self::allow_vat_classes();
1543
        $class = $item_id ? self::get_item_class( $item_id ) : '_standard';
1544
1545
        if ( $class === '_exempt' || $wpi_zero_tax ) {
1546
            return 0;
1547
        } else if ( !$allow_vat_classes ) {
0 ignored issues
show
introduced by
The condition $allow_vat_classes is always false.
Loading history...
1548
            $class = '_standard';
1549
        }
1550
1551
        if( !empty( $_POST['wpinv_country'] ) ) {
1552
            $post_country = $_POST['wpinv_country'];
1553
        } elseif( !empty( $_POST['wpinv_country'] ) ) {
1554
            $post_country = $_POST['wpinv_country'];
1555
        } elseif( !empty( $_POST['country'] ) ) {
1556
            $post_country = $_POST['country'];
1557
        } else {
1558
            $post_country = '';
1559
        }
1560
1561
        $country        = !empty( $post_country ) ? $post_country : wpinv_default_billing_country( $country );
1562
        $base_country   = wpinv_is_base_country( $country );
1563
1564
        $requires_vat   = self::requires_vat( 0, false );
1565
        $is_digital     = self::get_item_rule( $item_id ) == 'digital' ;
1566
        $rate           = $requires_vat && isset( $wpinv_options['eu_fallback_rate'] ) ? $wpinv_options['eu_fallback_rate'] : $rate;
1567
1568
        if ( self::same_country_rule() == 'no' && $base_country ) { // Disable VAT to same country
1569
            $rate = 0;
1570
        } else if ( $requires_vat ) {
1571
            $vat_number = self::get_user_vat_number( '', 0, true );
1572
            $vat_info   = self::current_vat_data();
1573
1574
            if ( is_array( $vat_info ) ) {
1575
                $vat_number = isset( $vat_info['number'] ) && !empty( $vat_info['valid'] ) ? $vat_info['number'] : "";
1576
            }
1577
1578
            if ( $country == 'UK' ) {
1579
                $country = 'GB';
1580
            }
1581
1582
            if ( !empty( $vat_number ) ) {
1583
                $rate = 0;
1584
            } else {
1585
                $rate = self::find_rate( $country, $state, $rate, $class ); // Fix if there are no tax rated and you try to pay an invoice it does not add the fallback tax rate
1586
            }
1587
1588
            if ( empty( $vat_number ) && !$is_digital ) {
1589
                if ( $base_country ) {
1590
                    $rate = self::find_rate( $country, null, $rate, $class );
1591
                } else {
1592
                    if ( empty( $country ) && isset( $wpinv_options['eu_fallback_rate'] ) ) {
1593
                        $rate = $wpinv_options['eu_fallback_rate'];
1594
                    } else if( !empty( $country ) ) {
1595
                        $rate = self::find_rate( $country, $state, $rate, $class );
1596
                    }
1597
                }
1598
            } else if ( empty( $vat_number ) || ( self::same_country_rule() == 'always' && $base_country ) ) {
1599
                if ( empty( $country ) && isset( $wpinv_options['eu_fallback_rate'] ) ) {
1600
                    $rate = $wpinv_options['eu_fallback_rate'];
1601
                } else if( !empty( $country ) ) {
1602
                    $rate = self::find_rate( $country, $state, $rate, $class );
1603
                }
1604
            }
1605
        } else {
1606
            if ( $is_digital ) {
1607
                $ip_country_code = self::get_country_by_ip();
1608
1609
                if ( $ip_country_code && self::is_eu_state( $ip_country_code ) ) {
1610
                    $rate = self::find_rate( $ip_country_code, '', 0, $class );
1611
                } else {
1612
                    $rate = self::find_rate( $country, $state, $rate, $class );
1613
                }
1614
            } else {
1615
                $rate = self::find_rate( $country, $state, $rate, $class );
1616
            }
1617
        }
1618
1619
        return $rate;
1620
    }
1621
1622
    public static function current_vat_data() {
1623
        global $wpi_session;
1624
1625
        return $wpi_session->get( 'user_vat_data' );
1626
    }
1627
1628
    public static function get_user_country( $country = '', $user_id = 0 ) {
1629
        $user_address = wpinv_get_user_address( $user_id, false );
1630
1631
        if ( wpinv_get_option( 'vat_ip_country_default' ) ) {
1632
            $country = '';
1633
        }
1634
1635
        $country    = empty( $user_address ) || !isset( $user_address['country'] ) || empty( $user_address['country'] ) ? $country : $user_address['country'];
1636
        $result     = apply_filters( 'wpinv_get_user_country', $country, $user_id );
1637
1638
        if ( empty( $result ) ) {
1639
            $result = self::get_country_by_ip();
1640
        }
1641
1642
        return $result;
1643
    }
1644
1645
    public static function set_user_country( $country = '', $user_id = 0 ) {
0 ignored issues
show
Unused Code introduced by
The parameter $user_id 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

1645
    public static function set_user_country( $country = '', /** @scrutinizer ignore-unused */ $user_id = 0 ) {

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...
1646
        global $wpi_userID;
1647
1648
        if ( empty($country) && !empty($wpi_userID) && get_current_user_id() != $wpi_userID ) {
1649
            $country = wpinv_get_default_country();
1650
        }
1651
1652
        return $country;
1653
    }
1654
1655
    public static function get_user_vat_number( $vat_number = '', $user_id = 0, $is_valid = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $vat_number 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

1655
    public static function get_user_vat_number( /** @scrutinizer ignore-unused */ $vat_number = '', $user_id = 0, $is_valid = false ) {

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...
1656
        global $wpi_current_id, $wpi_userID;
1657
1658
        if ( !empty( $_POST['new_user'] ) ) {
1659
            return '';
1660
        }
1661
1662
        if ( empty( $user_id ) ) {
1663
            $user_id = !empty( $wpi_userID ) ? $wpi_userID : ( $wpi_current_id ? wpinv_get_user_id( $wpi_current_id ) : get_current_user_id() );
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_get_user_id() has been deprecated. ( Ignorable by Annotation )

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

1663
            $user_id = !empty( $wpi_userID ) ? $wpi_userID : ( $wpi_current_id ? /** @scrutinizer ignore-deprecated */ wpinv_get_user_id( $wpi_current_id ) : get_current_user_id() );
Loading history...
1664
        }
1665
1666
        $vat_number = empty( $user_id ) ? '' : get_user_meta( $user_id, '_wpinv_vat_number', true );
1667
1668
        /* TODO
1669
        if ( $is_valid && $vat_number ) {
1670
            $adddress_confirmed = empty( $user_id ) ? false : get_user_meta( $user_id, '_wpinv_adddress_confirmed', true );
1671
            if ( !$adddress_confirmed ) {
1672
                $vat_number = '';
1673
            }
1674
        }
1675
        */
1676
1677
        return apply_filters('wpinv_get_user_vat_number', $vat_number, $user_id, $is_valid );
1678
    }
1679
1680
    public static function get_user_company( $company = '', $user_id = 0 ) {
0 ignored issues
show
Unused Code introduced by
The parameter $company 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

1680
    public static function get_user_company( /** @scrutinizer ignore-unused */ $company = '', $user_id = 0 ) {

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...
1681
        global $wpi_current_id, $wpi_userID;
1682
1683
        if ( empty( $user_id ) ) {
1684
            $user_id = !empty( $wpi_userID ) ? $wpi_userID : ( $wpi_current_id ? wpinv_get_user_id( $wpi_current_id ) : get_current_user_id() );
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_get_user_id() has been deprecated. ( Ignorable by Annotation )

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

1684
            $user_id = !empty( $wpi_userID ) ? $wpi_userID : ( $wpi_current_id ? /** @scrutinizer ignore-deprecated */ wpinv_get_user_id( $wpi_current_id ) : get_current_user_id() );
Loading history...
1685
        }
1686
1687
        $company = empty( $user_id ) ? "" : get_user_meta( $user_id, '_wpinv_company', true );
1688
1689
        return apply_filters( 'wpinv_user_company', $company, $user_id );
1690
    }
1691
1692
    public static function save_user_vat_details( $company = '', $vat_number = '' ) {
1693
        $save = apply_filters( 'wpinv_allow_save_user_vat_details', true );
1694
1695
        if ( is_user_logged_in() && $save ) {
1696
            $user_id = get_current_user_id();
1697
1698
            if ( !empty( $vat_number ) ) {
1699
                update_user_meta( $user_id, '_wpinv_vat_number', $vat_number );
1700
            } else {
1701
                delete_user_meta( $user_id, '_wpinv_vat_number');
1702
            }
1703
1704
            if ( !empty( $company ) ) {
1705
                update_user_meta( $user_id, '_wpinv_company', $company );
1706
            } else {
1707
                delete_user_meta( $user_id, '_wpinv_company');
1708
                delete_user_meta( $user_id, '_wpinv_vat_number');
1709
            }
1710
        }
1711
1712
        do_action('wpinv_save_user_vat_details', $company, $vat_number);
1713
    }
1714
1715
    public static function ajax_vat_validate() {
1716
        global $wpinv_options, $wpi_session;
1717
1718
        $is_checkout            = ( !empty( $_POST['source'] ) && $_POST['source'] == 'checkout' ) ? true : false;
1719
        $response               = array();
1720
        $response['success']    = false;
1721
1722
        if ( empty( $_REQUEST['_wpi_nonce'] ) || ( !empty( $_REQUEST['_wpi_nonce'] ) && !wp_verify_nonce( $_REQUEST['_wpi_nonce'], 'vat_validation' ) ) ) {
1723
            $response['error'] = __( 'Invalid security nonce', 'invoicing' );
1724
            wp_send_json( $response );
1725
        }
1726
1727
        $vat_name   = self::get_vat_name();
0 ignored issues
show
Bug Best Practice introduced by
The method WPInv_EUVat::get_vat_name() is not static, but was called statically. ( Ignorable by Annotation )

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

1727
        /** @scrutinizer ignore-call */ 
1728
        $vat_name   = self::get_vat_name();
Loading history...
1728
1729
        if ( $is_checkout ) {
1730
            $invoice = wpinv_get_invoice_cart();
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_get_invoice_cart() has been deprecated. ( Ignorable by Annotation )

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

1730
            $invoice = /** @scrutinizer ignore-deprecated */ wpinv_get_invoice_cart();
Loading history...
1731
1732
            if ( !self::requires_vat( false, 0, self::invoice_has_digital_rule( $invoice ) ) ) {
1733
                $vat_info = array();
1734
                $wpi_session->set( 'user_vat_data', $vat_info );
1735
1736
                self::save_user_vat_details();
1737
1738
                $response['success'] = true;
1739
                $response['message'] = wp_sprintf( __( 'Ignore %s', 'invoicing' ), $vat_name );
1740
                wp_send_json( $response );
1741
            }
1742
        }
1743
1744
        $company    = !empty( $_POST['company'] ) ? sanitize_text_field( $_POST['company'] ) : '';
1745
        $vat_number = !empty( $_POST['number'] ) ? sanitize_text_field( $_POST['number'] ) : '';
1746
1747
        $vat_info = $wpi_session->get( 'user_vat_data' );
1748
        if ( !is_array( $vat_info ) || empty( $vat_info ) ) {
1749
            $vat_info = array( 'company'=> $company, 'number' => '', 'valid' => true );
1750
        }
1751
1752
        if ( empty( $vat_number ) ) {
1753
            if ( $is_checkout ) {
1754
                $response['success'] = true;
1755
                $response['message'] = wp_sprintf( __( 'No %s number has been applied. %s will be added to invoice totals', 'invoicing' ), $vat_name, $vat_name );
1756
1757
                $vat_info = $wpi_session->get( 'user_vat_data' );
1758
                $vat_info['number'] = "";
1759
                $vat_info['valid'] = true;
1760
1761
                self::save_user_vat_details( $company );
1762
            } else {
1763
                $response['error'] = wp_sprintf( __( 'Please enter your %s number!', 'invoicing' ), $vat_name );
1764
1765
                $vat_info['valid'] = false;
1766
            }
1767
1768
            $wpi_session->set( 'user_vat_data', $vat_info );
1769
            wp_send_json( $response );
1770
        }
1771
1772
        if ( empty( $company ) ) {
1773
            $vat_info['valid'] = false;
1774
            $wpi_session->set( 'user_vat_data', $vat_info );
1775
1776
            $response['error'] = __( 'Please enter your registered company name!', 'invoicing' );
1777
            wp_send_json( $response );
1778
        }
1779
1780
        if ( !empty( $wpinv_options['vat_vies_check'] ) ) {
1781
            if ( empty( $wpinv_options['vat_offline_check'] ) && !self::offline_check( $vat_number ) ) {
1782
                $vat_info['valid'] = false;
1783
                $wpi_session->set( 'user_vat_data', $vat_info );
1784
1785
                $response['error'] = wp_sprintf( __( '%s number not validated', 'invoicing' ), $vat_name );
1786
                wp_send_json( $response );
1787
            }
1788
1789
            $response['success'] = true;
1790
            $response['message'] = wp_sprintf( __( '%s number validated', 'invoicing' ), $vat_name );
1791
        } else {
1792
            $result = self::check_vat( $vat_number );
1793
1794
            if ( empty( $result['valid'] ) ) {
1795
                $response['error'] = $result['message'];
1796
                wp_send_json( $response );
1797
            }
1798
1799
            $vies_company = !empty( $result['company'] ) ? $result['company'] : '';
1800
            $vies_company = apply_filters( 'wpinv_vies_company_name', $vies_company );
1801
1802
            $valid_company = $vies_company && $company && ( $vies_company == '---' || strcasecmp( trim( $vies_company ), trim( $company ) ) == 0 ) ? true : false;
1803
1804
            if ( !empty( $wpinv_options['vat_disable_company_name_check'] ) || $valid_company ) {
1805
                $response['success'] = true;
1806
                $response['message'] = wp_sprintf( __( '%s number validated', 'invoicing' ), $vat_name );
1807
            } else {
1808
                $vat_info['valid'] = false;
1809
                $wpi_session->set( 'user_vat_data', $vat_info );
1810
1811
                $response['success'] = false;
1812
                $response['message'] = wp_sprintf( __( 'The company name associated with the %s number provided is not the same as the company name provided.', 'invoicing' ), $vat_name );
1813
                wp_send_json( $response );
1814
            }
1815
        }
1816
1817
        if ( $is_checkout ) {
1818
            self::save_user_vat_details( $company, $vat_number );
1819
1820
            $vat_info = array('company' => $company, 'number' => $vat_number, 'valid' => true );
1821
            $wpi_session->set( 'user_vat_data', $vat_info );
1822
        }
1823
1824
        wp_send_json( $response );
1825
    }
1826
1827
    /**
1828
     * Validates a vat number.
1829
     * 
1830
     * @return string
1831
     */
1832
    public static function validate_vat_number( $vat_number, $company, $country ) {
1833
        global $wpinv_options;
1834
1835
        // If we are not checking the vat number via view...
1836
        if ( ! empty( $wpinv_options['vat_vies_check'] ) ) {
1837
1838
            // Try validating via regex.
1839
            if ( empty( $wpinv_options['vat_offline_check'] ) && ! self::offline_check( $vat_number, $country ) ) {
1840
                return wp_sprintf(
1841
                    __( 'Your %s number is invalid', 'invoicing' ),
1842
                    getpaid_vat_name()
1843
                );
1844
            }
1845
1846
            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 string.
Loading history...
1847
        }
1848
1849
        // Validate the vat number.
1850
        $result = self::check_vat( $vat_number, $country );
1851
1852
        if ( empty( $result['valid'] ) ) {
1853
            return wp_sprintf(
1854
                __( 'Failed to validate the %s number via EU Commission VAT server (VIES).', 'invoicing' ),
1855
                getpaid_vat_name()
1856
            );
1857
        }
1858
1859
        // Validate the company.
1860
        $vies_company  = ! empty( $result['company'] ) ? $result['company'] : '';
1861
        $vies_company  = apply_filters( 'wpinv_vies_company_name', $vies_company );
1862
        $valid_company = $vies_company && $company && ( $vies_company == '---' || strcasecmp( trim( $vies_company ), trim( $company ) ) == 0 ) ? true : false;
1863
1864
        if ( ! $valid_company && ! empty( $wpinv_options['vat_disable_company_name_check'] ) ) {
1865
           return wp_sprintf(
1866
                __( 'The company name associated with the %s number provided is not the same as the company name provided.', 'invoicing' ),
1867
                getpaid_vat_name()
1868
            );
1869
        }
1870
1871
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type string.
Loading history...
1872
    }
1873
1874
}
1875
1876