Passed
Push — master ( f7b81c...169dfd )
by Stiofan
04:09
created

WPInv_EUVat::get_user_company()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 11
Code Lines 6

Duplication

Lines 3
Ratio 27.27 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 10
nop 2
dl 3
loc 11
rs 8.8571
c 0
b 0
f 0
1
<?php
2
// Exit if accessed directly.
3
if (!defined( 'ABSPATH' ) ) exit;
4
5
class WPInv_EUVat {
6
    private static $is_ajax = false;
7
    private static $default_country;
8
    private static $instance = false;
9
    
10
    public static function get_instance() {
11
        if ( !self::$instance ) {
12
            self::$instance = new self();
0 ignored issues
show
Documentation Bug introduced by
It seems like new self() of type object<WPInv_EUVat> is incompatible with the declared type boolean of property $instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
13
            self::$instance->actions();
14
        }
15
16
        return self::$instance;
17
    }
18
    
19
    public function __construct() {
20
        self::$is_ajax          = defined( 'DOING_AJAX' ) && DOING_AJAX;
21
        self::$default_country  = wpinv_get_default_country();
22
    }
23
    
24
    public static function actions() {
25
        if ( is_admin() ) {
26
            add_action( 'admin_enqueue_scripts', array( self::$instance, 'enqueue_admin_scripts' ) );
27
            add_action( 'wpinv_settings_sections_taxes', array( self::$instance, 'section_vat_settings' ) ); 
28
            add_action( 'wpinv_settings_taxes', array( self::$instance, 'vat_settings' ) );
29
            add_filter( 'wpinv_settings_taxes-vat_sanitize', array( self::$instance, 'sanitize_vat_settings' ) );
30
            add_filter( 'wpinv_settings_taxes-vat_rates_sanitize', array( self::$instance, 'sanitize_vat_rates' ) );
31
            add_action( 'wp_ajax_wpinv_add_vat_class', array( self::$instance, 'add_class' ) );
32
            add_action( 'wp_ajax_nopriv_wpinv_add_vat_class', array( self::$instance, 'add_class' ) );
33
            add_action( 'wp_ajax_wpinv_delete_vat_class', array( self::$instance, 'delete_class' ) );
34
            add_action( 'wp_ajax_nopriv_wpinv_delete_vat_class', array( self::$instance, 'delete_class' ) );
35
            add_action( 'wp_ajax_wpinv_update_vat_rates', array( self::$instance, 'update_eu_rates' ) );
36
            add_action( 'wp_ajax_nopriv_wpinv_update_vat_rates', array( self::$instance, 'update_eu_rates' ) );
37
            add_action( 'wp_ajax_wpinv_geoip2', array( self::$instance, 'geoip2_download_database' ) );
38
            add_action( 'wp_ajax_nopriv_wpinv_geoip2', array( self::$instance, 'geoip2_download_database' ) );
39
        }
40
        
41
        add_action( 'wp_enqueue_scripts', array( self::$instance, 'enqueue_vat_scripts' ) );
42
        add_filter( 'wpinv_default_billing_country', array( self::$instance, 'get_user_country' ), 10 );
43
        add_filter( 'wpinv_get_user_country', array( self::$instance, 'set_user_country' ), 10 );
44
        add_action( 'wp_ajax_wpinv_vat_validate', array( self::$instance, 'ajax_vat_validate' ) );
45
        add_action( 'wp_ajax_nopriv_wpinv_vat_validate', array( self::$instance, 'ajax_vat_validate' ) );
46
        add_action( 'wp_ajax_wpinv_vat_reset', array( self::$instance, 'ajax_vat_reset' ) );
47
        add_action( 'wp_ajax_nopriv_wpinv_vat_reset', array( self::$instance, 'ajax_vat_reset' ) );
48
        add_action( 'wpinv_invoice_print_after_line_items', array( self::$instance, 'show_vat_notice' ), 999, 1 );
49
        if ( wpinv_use_taxes() ) {
50
            add_action( 'wpinv_after_billing_fields', array( self::$instance, 'checkout_vat_fields' ) );
51
            if ( self::allow_vat_rules() ) {
52
                add_action( 'wpinv_checkout_error_checks', array( self::$instance, 'checkout_vat_validate' ), 10, 2 );
53
                add_filter( 'wpinv_tax_rate', array( self::$instance, 'get_rate' ), 10, 4 );
54
            }
55
        }
56
    }        
57
    
58
    public static function get_eu_states( $sort = true ) {
59
        $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' );
60
        if ( $sort ) {
61
            $sort = sort( $eu_states );
62
        }
63
        
64
        return apply_filters( 'wpinv_get_eu_states', $eu_states, $sort );
65
    }
66
    
67
    public static function get_gst_countries( $sort = true ) {
68
        $gst_countries  = array( 'AU', 'NZ', 'CA', 'CN' );
69
        
70
        if ( $sort ) {
71
            $sort = sort( $gst_countries );
72
        }
73
        
74
        return apply_filters( 'wpinv_get_gst_countries', $gst_countries, $sort );
75
    }
76
    
77
    public static function is_eu_state( $country_code ) {
78
        $return = !empty( $country_code ) && in_array( strtoupper( $country_code ), self::get_eu_states() ) ? true : false;
79
                
80
        return apply_filters( 'wpinv_is_eu_state', $return, $country_code );
81
    }
82
    
83
    public static function is_gst_country( $country_code ) {
84
        $return = !empty( $country_code ) && in_array( strtoupper( $country_code ), self::get_gst_countries() ) ? true : false;
85
                
86
        return apply_filters( 'wpinv_is_gst_country', $return, $country_code );
87
    }
88
    
89
    public static function enqueue_vat_scripts() {
90
        $suffix     = '';//defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
91
        
92
        wp_register_script( 'wpinv-vat-validation-script', WPINV_PLUGIN_URL . 'assets/js/jsvat' . $suffix . '.js', array( 'jquery' ),  WPINV_VERSION );
93
        wp_register_script( 'wpinv-vat-script', WPINV_PLUGIN_URL . 'assets/js/euvat' . $suffix . '.js', array( 'jquery' ),  WPINV_VERSION );
94
        
95
        $vat_name   = self::get_vat_name();
96
        
97
        $vars = array();
98
        $vars['UseTaxes'] = wpinv_use_taxes();
99
        $vars['EUStates'] = self::get_eu_states();
100
        $vars['NoRateSet'] = __( 'You have not set a rate. Do you want to continue?', 'invoicing' );
101
        $vars['EmptyCompany'] = __( 'Please enter your registered company name!', 'invoicing' );
102
        $vars['EmptyVAT'] = wp_sprintf( __( 'Please enter your %s number!', 'invoicing' ), $vat_name );
103
        $vars['TotalsRefreshed'] = wp_sprintf( __( 'The invoice totals will be refreshed to update the %s.', 'invoicing' ), $vat_name );
104
        $vars['ErrValidateVAT'] = wp_sprintf( __( 'Fail to validate the %s number!', 'invoicing' ), $vat_name );
105
        $vars['ErrResetVAT'] = wp_sprintf( __( 'Fail to reset the %s number!', 'invoicing' ), $vat_name );
106
        $vars['ErrInvalidVat'] = wp_sprintf( __( 'The %s number supplied does not have a valid format!', 'invoicing' ), $vat_name );
107
        $vars['ErrInvalidResponse'] = __( 'An invalid response has been received from the server!', 'invoicing' );
108
        $vars['ApplyVATRules'] = $vars['UseTaxes'] ? self::allow_vat_rules() : false;
109
        $vars['HideVatFields'] = $vars['ApplyVATRules'] ? self::hide_vat_fields() : true;
110
        $vars['ErrResponse'] = __( 'The request response is invalid!', 'invoicing' );
111
        $vars['ErrRateResponse'] = __( 'The get rate request response is invalid', 'invoicing' );
112
        $vars['PageRefresh'] = __( 'The page will be refreshed in 10 seconds to show the new options.', 'invoicing' );
113
        $vars['RequestResponseNotValidJSON'] = __( 'The get rate request response is not valid JSON', 'invoicing' );
114
        $vars['GetRateRequestFailed'] = __( 'The get rate request failed: ', 'invoicing' );
115
        $vars['NoRateInformationInResponse'] = __( 'The get rate request response does not contain any rate information', 'invoicing' );
116
        $vars['RatesUpdated'] = __( 'The rates have been updated. Press the save button to record these new rates.', 'invoicing' );
117
        $vars['IPAddressInformation'] = __( 'IP Address Information', 'invoicing' );
118
        $vars['VatValidating'] = wp_sprintf( __( 'Validating %s number...', 'invoicing' ), $vat_name );
119
        $vars['VatReseting'] = __( 'Reseting...', 'invoicing' );
120
        $vars['VatValidated'] = wp_sprintf( __( '%s number validated', 'invoicing' ), $vat_name );
121
        $vars['VatNotValidated'] = wp_sprintf( __( '%s number not validated', 'invoicing' ), $vat_name );
122
        $vars['ConfirmDeleteClass'] = __( 'Are you sure you wish to delete this rates class?', 'invoicing' );
123
        $vars['isFront'] = is_admin() ? false : true;
124
        $vars['checkoutNonce'] = wp_create_nonce( 'wpinv_checkout_nonce' );
125
        $vars['baseCountry'] = wpinv_get_default_country();
126
        $vars['disableVATSameCountry'] = ( self::same_country_rule() == 'no' ? true : false );
127
        $vars['disableVATSimpleCheck'] = wpinv_get_option( 'vat_offline_check' ) ? true : false;
128
        
129
        wp_enqueue_script( 'wpinv-vat-validation-script' );
130
        wp_enqueue_script( 'wpinv-vat-script' );
131
        wp_localize_script( 'wpinv-vat-script', 'WPInv_VAT_Vars', $vars );
132
    }
133
134
    public static function enqueue_admin_scripts() {
135
        if( isset( $_GET['page'] ) && 'wpinv-settings' == $_GET['page'] ) {
136
            self::enqueue_vat_scripts();
137
        }
138
    }
139
    
140
    public static function section_vat_settings( $sections ) {
141
        if ( !empty( $sections ) ) {
142
            $sections['vat'] = __( 'EU VAT Settings', 'invoicing' );
143
            
144
            if ( self::allow_vat_classes() ) {
145
                $sections['vat_rates'] = __( 'EU VAT Rates', 'invoicing' );
146
            }
147
        }
148
        return $sections;
149
    }
150
    
151
    public static function vat_rates_settings() {
152
        $vat_classes = self::get_rate_classes();
153
        $vat_rates = array();
154
        $vat_class = isset( $_REQUEST['wpi_sub'] ) && $_REQUEST['wpi_sub'] !== '' && isset( $vat_classes[$_REQUEST['wpi_sub']] )? sanitize_text_field( $_REQUEST['wpi_sub'] ) : '_new';
155
        $current_url = remove_query_arg( 'wpi_sub' );
156
        
157
        $vat_rates['vat_rates_header'] = array(
158
            'id' => 'vat_rates_header',
159
            'name' => '<h3>' . __( 'Manage VAT Rates', 'invoicing' ) . '</h3>',
160
            'desc' => '',
161
            'type' => 'header',
162
            'size' => 'regular'
163
        );
164
        $vat_rates['vat_rates_class'] = array(
165
            'id'          => 'vat_rates_class',
166
            'name'        => __( 'Edit VAT Rates', 'invoicing' ),
167
            'desc'        => __( 'The standard rate will apply where no explicit rate is provided.', 'invoicing' ),
168
            'type'        => 'select',
169
            'options'     => array_merge( $vat_classes, array( '_new' => __( 'Add New Rate Class', 'invoicing' ) ) ),
170
            'chosen'      => true,
171
            'placeholder' => __( 'Select a VAT Rate', 'invoicing' ),
172
            'selected'    => $vat_class,
173
            'onchange'    => 'document.location.href="' . $current_url . '&wpi_sub=" + this.value;',
174
        );
175
        
176
        if ( $vat_class != '_standard' && $vat_class != '_new' ) {
177
            $vat_rates['vat_rate_delete'] = array(
178
                'id'   => 'vat_rate_delete',
179
                'type' => 'vat_rate_delete',
180
            );
181
        }
182
                    
183
        if ( $vat_class == '_new' ) {
184
            $vat_rates['vat_rates_settings'] = array(
185
                'id' => 'vat_rates_settings',
186
                'name' => '<h3>' . __( 'Add New Rate Class', 'invoicing' ) . '</h3>',
187
                'type' => 'header',
188
            );
189
            $vat_rates['vat_rate_name'] = array(
190
                'id'   => 'vat_rate_name',
191
                'name' => __( 'Name', 'invoicing' ),
192
                'desc' => __( 'A short name for the new VAT Rate class', 'invoicing' ),
193
                'type' => 'text',
194
                'size' => 'regular',
195
            );
196
            $vat_rates['vat_rate_desc'] = array(
197
                'id'   => 'vat_rate_desc',
198
                'name' => __( 'Description', 'invoicing' ),
199
                'desc' => __( 'Manage VAT Rate class', 'invoicing' ),
200
                'type' => 'text',
201
                'size' => 'regular',
202
            );
203
            $vat_rates['vat_rate_add'] = array(
204
                'id'   => 'vat_rate_add',
205
                'type' => 'vat_rate_add',
206
            );
207
        } else {
208
            $vat_rates['vat_rates'] = array(
209
                'id'   => 'vat_rates',
210
                'name' => '<h3>' . $vat_classes[$vat_class] . '</h3>',
211
                'desc' => self::get_class_desc( $vat_class ),
212
                'type' => 'vat_rates',
213
            );
214
        }
215
        
216
        return $vat_rates;
217
    }
218
    
219
    public static function vat_settings( $settings ) {
220
        if ( !empty( $settings ) ) {    
221
            $vat_settings = array();
222
            $vat_settings['vat_company_title'] = array(
223
                'id' => 'vat_company_title',
224
                'name' => '<h3>' . __( 'Your Company Details', 'invoicing' ) . '</h3>',
225
                'desc' => '',
226
                'type' => 'header',
227
                'size' => 'regular'
228
            );
229
            
230
            $vat_settings['vat_company_name'] = array(
231
                'id' => 'vat_company_name',
232
                'name' => __( 'Your Company Name', 'invoicing' ),
233
                '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>' ),
234
                'type' => 'text',
235
                'size' => 'regular',
236
            );
237
            
238
            $vat_settings['vat_number'] = array(
239
                'id'   => 'vat_number',
240
                'name' => __( 'Your VAT Number', 'invoicing' ),
241
                'type' => 'vat_number',
242
                'size' => 'regular',
243
            );
244
245
            $vat_settings['vat_settings_title'] = array(
246
                'id' => 'vat_settings_title',
247
                'name' => '<h3>' . __( 'Apply VAT Settings', 'invoicing' ) . '</h3>',
248
                'desc' => '',
249
                'type' => 'header',
250
                'size' => 'regular'
251
            );
252
253
            $vat_settings['apply_vat_rules'] = array(
254
                'id' => 'apply_vat_rules',
255
                'name' => __( 'Enable VAT Rules', 'invoicing' ),
256
                '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>',
257
                'type' => 'checkbox',
258
                'std' => '1'
259
            );
260
261
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
262
            $vat_settings['vat_allow_classes'] = array(
263
                'id' => 'vat_allow_classes',
264
                'name' => __( 'Allow the use of VAT rate classes', 'invoicing' ),
265
                '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' ),
266
                'type' => 'checkbox'
267
            );
268
            */
269
270
            $vat_settings['vat_prevent_b2c_purchase'] = array(
271
                'id' => 'vat_prevent_b2c_purchase',
272
                'name' => __( 'Prevent EU B2C Sales', 'invoicing' ),
273
                'desc' => __( 'Enable this option if you are not registered for VAT in the EU.', 'invoicing' ),
274
                'type' => 'checkbox'
275
            );
276
277
278
279
            $vat_settings['vat_same_country_rule'] = array(
280
                'id'          => 'vat_same_country_rule',
281
                'name'        => __( 'Same Country Rule', 'invoicing' ),
282
                'desc'        => __( 'Select how you want to handle VAT charge if sales are in the same country as the base country.', 'invoicing' ),
283
                'type'        => 'select',
284
                'options'     => array(
285
                    ''          => __( 'Normal', 'invoicing' ),
286
                    'no'        => __( 'No VAT', 'invoicing' ),
287
                    'always'    => __( 'Always apply VAT', 'invoicing' ),
288
                ),
289
                'placeholder' => __( 'Select an option', 'invoicing' ),
290
                'std'         => ''
291
            );
292
293
            $vat_settings['vat_checkout_title'] = array(
294
                'id' => 'vat_checkout_title',
295
                'name' => '<h3>' . __( 'Checkout Fields', 'invoicing' ) . '</h3>',
296
                'desc' => '',
297
                'type' => 'header',
298
                'size' => 'regular'
299
            );
300
301
            $vat_settings['vat_disable_fields'] = array(
302
                'id' => 'vat_disable_fields',
303
                'name' => __( 'Disable VAT Fields', 'invoicing' ),
304
                '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>',
305
                'type' => 'checkbox'
306
            );
307
308
            $vat_settings['vat_ip_lookup'] = array(
309
                'id'   => 'vat_ip_lookup',
310
                'name' => __( 'IP Country Look-up', 'invoicing' ),
311
                'type' => 'vat_ip_lookup',
312
                'size' => 'regular',
313
                'std' => 'default'
314
            );
315
316
            $vat_settings['hide_ip_address'] = array(
317
                'id' => 'hide_ip_address',
318
                'name' => __( 'Hide IP Info at Checkout', 'invoicing' ),
319
                'desc' => __( 'Hide the user IP info at checkout.', 'invoicing' ),
320
                'type' => 'checkbox'
321
            );
322
323
            $vat_settings['vat_ip_country_default'] = array(
324
                'id' => 'vat_ip_country_default',
325
                'name' => __( 'Enable IP Country as Default', 'invoicing' ),
326
                'desc' => __( 'Show the country of the users IP as the default country, otherwise the site default country will be used.', 'invoicing' ),
327
                'type' => 'checkbox'
328
            );
329
330
            $vat_settings['vies_validation_title'] = array(
331
                'id' => 'vies_validation_title',
332
                'name' => '<h3>' . __( 'VIES Validation', 'invoicing' ) . '</h3>',
333
                'desc' => '',
334
                'type' => 'header',
335
                'size' => 'regular'
336
            );
337
338
            $vat_settings['vat_vies_check'] = array(
339
                'id' => 'vat_vies_check',
340
                'name' => __( 'Disable VIES VAT ID Check', 'invoicing' ),
341
                '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>' ),
342
                'type' => 'checkbox'
343
            );
344
345
            $vat_settings['vat_disable_company_name_check'] = array(
346
                'id' => 'vat_disable_company_name_check',
347
                'name' => __( 'Disable VIES Name Check', 'invoicing' ),
348
                '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>' ),
349
                'type' => 'checkbox'
350
            );
351
352
            $vat_settings['vat_offline_check'] = array(
353
                'id' => 'vat_offline_check',
354
                'name' => __( 'Disable Basic Checks', 'invoicing' ),
355
                'desc' => __( 'This will disable basic JS correct format validation attempts, it is very rare this should need to be disabled.', 'invoicing' ),
356
                'type' => 'checkbox'
357
            );
358
            
359
360
            $settings['vat'] = $vat_settings;
361
            
362
            if ( self::allow_vat_classes() ) {
363
                $settings['vat_rates'] = self::vat_rates_settings();
364
            }
365
            
366
            $eu_fallback_rate = array(
367
                'id'   => 'eu_fallback_rate',
368
                'name' => '<h3>' . __( 'VAT rate for EU member states', 'invoicing' ) . '</h3>',
369
                'type' => 'eu_fallback_rate',
370
                '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' ),
371
                'std'  => '20',
372
                'size' => 'small'
373
            );
374
            $settings['rates']['eu_fallback_rate'] = $eu_fallback_rate;
375
        }
376
377
        return $settings;
378
    }
379
    // IP Geolocation
380
    public static function geoip2_download_database() {
381
        $upload_dir         = wp_upload_dir();
382
        
383
        $database_url       = 'http' . (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] === 'on' ? 's' : '') . '://geolite.maxmind.com/download/geoip/database/';
384
        $destination_dir    = $upload_dir['basedir'] . '/invoicing';
385
        
386
        if ( !is_dir( $destination_dir ) ) { 
387
            mkdir( $destination_dir );
388
        }
389
        
390
        $database_files     = array(
391
            'country'   => array(
392
                'source'        => $database_url . 'GeoLite2-Country.mmdb.gz',
393
                'destination'   => $destination_dir . '/GeoLite2-Country.mmdb',
394
            ),
395
            'city'      => array(
396
                'source'        => $database_url . 'GeoLite2-City.mmdb.gz',
397
                'destination'   => $destination_dir . '/GeoLite2-City.mmdb',
398
            )
399
        );
400
401
        foreach( $database_files as $database => $files ) {
402
            $result = self::geoip2_download_file( $files['source'], $files['destination'] );
403
            
404
            if ( empty( $result['success'] ) ) {
405
                echo $result['message'];
406
                exit;
407
            }
408
            
409
            wpinv_update_option( 'wpinv_geoip2_date_updated', current_time( 'timestamp' ) );
410
            echo sprintf(__( 'GeoIp2 %s database updated successfully.', 'invoicing' ), $database ) . ' ';
411
        }
412
        
413
        exit;
414
    }
415
    
416
    public static function geoip2_download_file( $source_url, $destination_file ) {
417
        $success    = false;
418
        $message    = '';
419
        
420
        if ( !function_exists( 'download_url' ) ) {
421
            require_once( ABSPATH . 'wp-admin/includes/file.php' );
422
        }
423
424
        $temp_file  = download_url( $source_url );
425
        
426
        if ( is_wp_error( $temp_file ) ) {
427
            $message = sprintf( __( 'Error while downloading GeoIp2 database( %s ): %s', 'invoicing' ), $source_url, $temp_file->get_error_message() );
428
        } else {
429
            $handle = gzopen( $temp_file, 'rb' );
430
            
431
            if ( $handle ) {
432
                $fopen  = fopen( $destination_file, 'wb' );
433
                if ( $fopen ) {
434
                    while ( ( $data = gzread( $handle, 4096 ) ) != false ) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $data = gzread($handle, 4096) of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
435
                        fwrite( $fopen, $data );
436
                    }
437
438
                    gzclose( $handle );
439
                    fclose( $fopen );
440
                        
441
                    $success = true;
442
                } else {
443
                    gzclose( $handle );
444
                    $message = sprintf( __( 'Error could not open destination GeoIp2 database file for writing: %s', 'invoicing' ), $destination_file );
445
                }
446
            } else {
447
                $message = sprintf( __( 'Error could not open GeoIp2 database file for reading: %s', 'invoicing' ), $temp_file );
448
            }
449
            
450
            if ( file_exists( $temp_file ) ) {
451
                unlink( $temp_file );
452
            }
453
        }
454
        
455
        $return             = array();
456
        $return['success']  = $success;
457
        $return['message']  = $message;
458
459
        return $return;
460
    }
461
    
462
    public static function load_geoip2() {
463
        if ( defined( 'WPINV_GEOIP2_LODDED' ) ) {
464
            return;
465
        }
466
        
467
        if ( !class_exists( '\MaxMind\Db\Reader' ) ) {
468
            $maxmind_db_files = array(
469
                'Reader/Decoder.php',
470
                'Reader/InvalidDatabaseException.php',
471
                'Reader/Metadata.php',
472
                'Reader/Util.php',
473
                'Reader.php',
474
            );
475
            
476
            foreach ( $maxmind_db_files as $key => $file ) {
477
                require_once( WPINV_PLUGIN_DIR . 'includes/libraries/MaxMind/Db/' . $file );
478
            }
479
        }
480
        
481
        if ( !class_exists( '\GeoIp2\Database\Reader' ) ) {        
482
            $geoip2_files = array(
483
                'ProviderInterface.php',
484
                'Compat/JsonSerializable.php',
485
                'Database/Reader.php',
486
                'Exception/GeoIp2Exception.php',
487
                'Exception/AddressNotFoundException.php',
488
                'Exception/AuthenticationException.php',
489
                'Exception/HttpException.php',
490
                'Exception/InvalidRequestException.php',
491
                'Exception/OutOfQueriesException.php',
492
                'Model/AbstractModel.php',
493
                'Model/AnonymousIp.php',
494
                'Model/Country.php',
495
                'Model/City.php',
496
                'Model/ConnectionType.php',
497
                'Model/Domain.php',
498
                'Model/Enterprise.php',
499
                'Model/Insights.php',
500
                'Model/Isp.php',
501
                'Record/AbstractRecord.php',
502
                'Record/AbstractPlaceRecord.php',
503
                'Record/Country.php',
504
                'Record/City.php',
505
                'Record/Continent.php',
506
                'Record/Location.php',
507
                'Record/MaxMind.php',
508
                'Record/Postal.php',
509
                'Record/RepresentedCountry.php',
510
                'Record/Subdivision.php',
511
                'Record/Traits.php',
512
                'WebService/Client.php',
513
            );
514
            
515
            foreach ( $geoip2_files as $key => $file ) {
516
                require_once( WPINV_PLUGIN_DIR . 'includes/libraries/GeoIp2/' . $file );
517
            }
518
        }
519
520
        define( 'WPINV_GEOIP2_LODDED', true );
521
    }
522
523 View Code Duplication
    public static function geoip2_country_dbfile() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
524
        $upload_dir = wp_upload_dir();
525
526
        if ( !isset( $upload_dir['basedir'] ) ) {
527
            return false;
528
        }
529
530
        $filename = $upload_dir['basedir'] . '/invoicing/GeoLite2-Country.mmdb';
531
        if ( !file_exists( $filename ) ) {
532
            return false;
533
        }
534
        
535
        return $filename;
536
    }
537
538 View Code Duplication
    public static function geoip2_city_dbfile() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
539
        $upload_dir = wp_upload_dir();
540
541
        if ( !isset( $upload_dir['basedir'] ) ) {
542
            return false;
543
        }
544
545
        $filename = $upload_dir['basedir'] . '/invoicing/GeoLite2-City.mmdb';
546
        if ( !file_exists( $filename ) ) {
547
            return false;
548
        }
549
        
550
        return $filename;
551
    }
552
553 View Code Duplication
    public static function geoip2_country_reader() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
554
        try {
555
            self::load_geoip2();
556
557
            if ( $filename = self::geoip2_country_dbfile() ) {
558
                return new \GeoIp2\Database\Reader( $filename );
559
            }
560
        } catch( Exception $e ) {
561
            return false;
562
        }
563
        
564
        return false;
565
    }
566
567 View Code Duplication
    public static function geoip2_city_reader() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
568
        try {
569
            self::load_geoip2();
570
571
            if ( $filename = self::geoip2_city_dbfile() ) {
572
                return new \GeoIp2\Database\Reader( $filename );
573
            }
574
        } catch( Exception $e ) {
575
            return false;
576
        }
577
        
578
        return false;
579
    }
580
581 View Code Duplication
    public static function geoip2_country_record( $ip_address ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
582
        try {
583
            $reader = self::geoip2_country_reader();
584
585
            if ( $reader ) {
586
                $record = $reader->country( $ip_address );
587
                
588
                if ( !empty( $record->country->isoCode ) ) {
589
                    return $record;
590
                }
591
            }
592
        } catch(\InvalidArgumentException $e) {
593
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
594
            
595
            return false;
596
        } catch(\GeoIp2\Exception\AddressNotFoundException $e) {
597
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
598
            
599
            return false;
600
        } catch( Exception $e ) {
601
            return false;
602
        }
603
        
604
        return false;
605
    }
606
607 View Code Duplication
    public static function geoip2_city_record( $ip_address ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
608
        try {
609
            $reader = self::geoip2_city_reader();
610
611
            if ( $reader ) {
612
                $record = $reader->city( $ip_address );
613
                
614
                if ( !empty( $record->country->isoCode ) ) {
615
                    return $record;
616
                }
617
            }
618
        } catch(\InvalidArgumentException $e) {
619
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
620
            
621
            return false;
622
        } catch(\GeoIp2\Exception\AddressNotFoundException $e) {
623
            wpinv_error_log( $e->getMessage(), 'GeoIp2 Lookup( ' . $ip_address . ' )' );
624
            
625
            return false;
626
        } catch( Exception $e ) {
627
            return false;
628
        }
629
        
630
        return false;
631
    }
632
633
    public static function geoip2_country_code( $ip_address ) {
634
        $record = self::geoip2_country_record( $ip_address );
635
        return !empty( $record->country->isoCode ) ? $record->country->isoCode : wpinv_get_default_country();
636
    }
637
638
    // Find country by IP address.
639
    public static function get_country_by_ip( $ip = '' ) {
640
        global $wpinv_ip_address_country;
641
        
642
        if ( !empty( $wpinv_ip_address_country ) ) {
643
            return $wpinv_ip_address_country;
644
        }
645
        
646
        if ( empty( $ip ) ) {
647
            $ip = wpinv_get_ip();
648
        }
649
650
        $ip_country_service = wpinv_get_option( 'vat_ip_lookup' );
651
        $is_default         = empty( $ip_country_service ) || $ip_country_service === 'default' ? true : false;
652
653
        if ( !empty( $ip ) && $ip !== '127.0.0.1' ) { // For 127.0.0.1(localhost) use default country.
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
654
            if ( function_exists( 'geoip_country_code_by_name') && ( $ip_country_service === 'geoip' || $is_default ) ) {
655
                try {
656
                    $wpinv_ip_address_country = geoip_country_code_by_name( $ip );
657
                } catch( Exception $e ) {
658
                    wpinv_error_log( $e->getMessage(), 'GeoIP Lookup( ' . $ip . ' )' );
659
                }
660
            } else if ( self::geoip2_country_dbfile() && ( $ip_country_service === 'geoip2' || $is_default ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::geoip2_country_dbfile() of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
661
                $wpinv_ip_address_country = self::geoip2_country_code( $ip );
662
            } else if ( function_exists( 'simplexml_load_file' ) && ( $ip_country_service === 'geoplugin' || $is_default ) ) {
663
                $load_xml = simplexml_load_file( 'http://www.geoplugin.net/xml.gp?ip=' . $ip );
664
                
665
                if ( !empty( $load_xml ) && !empty( $load_xml->geoplugin_countryCode ) ) {
666
                    $wpinv_ip_address_country = (string)$load_xml->geoplugin_countryCode;
667
                }
668
            }
669
        }
670
671
        if ( empty( $wpinv_ip_address_country ) ) {
672
            $wpinv_ip_address_country = wpinv_get_default_country();
673
        }
674
675
        return $wpinv_ip_address_country;
676
    }
677
    
678
    public static function sanitize_vat_settings( $input ) {
679
        global $wpinv_options;
680
        
681
        $valid      = false;
0 ignored issues
show
Unused Code introduced by
$valid is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
682
        $message    = '';
0 ignored issues
show
Unused Code introduced by
$message is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
683
        
684
        if ( !empty( $wpinv_options['vat_vies_check'] ) ) {
685 View Code Duplication
            if ( empty( $wpinv_options['vat_offline_check'] ) ) {
686
                $valid = self::offline_check( $input['vat_number'] );
687
            } else {
688
                $valid = true;
689
            }
690
            
691
            $message = $valid ? '' : __( 'VAT number not validated', 'invoicing' );
692
        } else {
693
            $result = self::check_vat( $input['vat_number'] );
694
            
695
            if ( empty( $result['valid'] ) ) {
696
                $valid      = false;
697
                $message    = $result['message'];
698
            } else {
699
                $valid      = ( isset( $result['company'] ) && ( $result['company'] == '---' || ( strcasecmp( trim( $result['company'] ), trim( $input['vat_company_name'] ) ) == 0 ) ) ) || !empty( $wpinv_options['vat_disable_company_name_check'] );
700
                $message    = $valid ? '' : __( 'The company name associated with the VAT number provided is not the same as the company name provided.', 'invoicing' );
701
            }
702
        }
703
704
        if ( $message && self::is_vat_validated() != $valid ) {
705
            add_settings_error( 'wpinv-notices', '', $message, ( $valid ? 'updated' : 'error' ) );
706
        }
707
708
        $input['vat_valid'] = $valid;
709
        return $input;
710
    }
711
    
712
    public static function sanitize_vat_rates( $input ) {
713
        if( !current_user_can( 'manage_options' ) ) {
714
            add_settings_error( 'wpinv-notices', '', __( 'Your account does not have permission to add rate classes.', 'invoicing' ), 'error' );
715
            return $input;
716
        }
717
        
718
        $vat_classes = self::get_rate_classes();
719
        $vat_class = !empty( $_REQUEST['wpi_vat_class'] ) && isset( $vat_classes[$_REQUEST['wpi_vat_class']] )? sanitize_text_field( $_REQUEST['wpi_vat_class'] ) : '';
720
        
721
        if ( empty( $vat_class ) ) {
722
            add_settings_error( 'wpinv-notices', '', __( 'No valid VAT rates class contained in the request to save rates.', 'invoicing' ), 'error' );
723
            
724
            return $input;
725
        }
726
727
        $new_rates = ! empty( $_POST['vat_rates'] ) ? array_values( $_POST['vat_rates'] ) : array();
728
729
        if ( $vat_class === '_standard' ) {
730
            // Save the active rates in the invoice settings
731
            update_option( 'wpinv_tax_rates', $new_rates );
732
        } else {
733
            // Get the existing set of rates
734
            $rates = self::get_non_standard_rates();
735
            $rates[$vat_class] = $new_rates;
736
737
            update_option( 'wpinv_vat_rates', $rates );
738
        }
739
        
740
        return $input;
741
    }
742
    
743
    public static function add_class() {        
744
        $response = array();
745
        $response['success'] = false;
746
        
747 View Code Duplication
        if ( !current_user_can( 'manage_options' ) ) {
748
            $response['error'] = __( 'Invalid access!', 'invoicing' );
749
            wp_send_json( $response );
750
        }
751
        
752
        $vat_class_name = !empty( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : false;
753
        $vat_class_desc = !empty( $_POST['desc'] ) ? sanitize_text_field( $_POST['desc'] ) : false;
754
        
755
        if ( empty( $vat_class_name ) ) {
756
            $response['error'] = __( 'Select the VAT rate name', 'invoicing' );
757
            wp_send_json( $response );
758
        }
759
        
760
        $vat_classes = (array)self::get_rate_classes();
761
762 View Code Duplication
        if ( !empty( $vat_classes ) && in_array( strtolower( $vat_class_name ), array_map( 'strtolower', array_values( $vat_classes ) ) ) ) {
763
            $response['error'] = wp_sprintf( __( 'A VAT Rate name "%s" already exists', 'invoicing' ), $vat_class_name );
764
            wp_send_json( $response );
765
        }
766
        
767
        $rate_class_key = normalize_whitespace( 'wpi-' . $vat_class_name );
768
        $rate_class_key = sanitize_key( str_replace( " ", "-", $rate_class_key ) );
769
        
770
        $vat_classes = (array)self::get_rate_classes( true );
771
        $vat_classes[$rate_class_key] = array( 'name' => $vat_class_name, 'desc' => $vat_class_desc );
772
        
773
        update_option( '_wpinv_vat_rate_classes', $vat_classes );
774
        
775
        $response['success'] = true;
776
        $response['redirect'] = admin_url( 'admin.php?page=wpinv-settings&tab=taxes&section=vat_rates&wpi_sub=' . $rate_class_key );
777
        
778
        wp_send_json( $response );
779
    }
780
    
781
    public static function delete_class() {
782
        $response = array();
783
        $response['success'] = false;
784
        
785 View Code Duplication
        if ( !current_user_can( 'manage_options' ) || !isset( $_POST['class'] ) ) {
786
            $response['error'] = __( 'Invalid access!', 'invoicing' );
787
            wp_send_json( $response );
788
        }
789
        
790
        $vat_class = isset( $_POST['class'] ) && $_POST['class'] !== '' ? sanitize_text_field( $_POST['class'] ) : false;
791
        $vat_classes = (array)self::get_rate_classes();
792
793
        if ( !isset( $vat_classes[$vat_class] ) ) {
794
            $response['error'] = __( 'Requested class does not exists', 'invoicing' );
795
            wp_send_json( $response );
796
        }
797
        
798
        if ( $vat_class == '_new' || $vat_class == '_standard' ) {
799
            $response['error'] = __( 'You can not delete standard rates class', 'invoicing' );
800
            wp_send_json( $response );
801
        }
802
            
803
        $vat_classes = (array)self::get_rate_classes( true );
804
        unset( $vat_classes[$vat_class] );
805
        
806
        update_option( '_wpinv_vat_rate_classes', $vat_classes );
807
        
808
        $response['success'] = true;
809
        $response['redirect'] = admin_url( 'admin.php?page=wpinv-settings&tab=taxes&section=vat_rates&wpi_sub=_new' );
810
        
811
        wp_send_json( $response );
812
    }
813
    
814
    public static function update_eu_rates() {        
815
        $response               = array();
816
        $response['success']    = false;
817
        $response['error']      = null;
818
        $response['data']       = null;
819
        
820 View Code Duplication
        if ( !current_user_can( 'manage_options' ) ) {
821
            $response['error'] = __( 'Invalid access!', 'invoicing' );
822
            wp_send_json( $response );
823
        }
824
        
825
        $group      = !empty( $_POST['group'] ) ? sanitize_text_field( $_POST['group'] ) : '';
826
        $euvatrates = self::request_euvatrates( $group );
827
        
828
        if ( !empty( $euvatrates ) ) {
829
            if ( !empty( $euvatrates['success'] ) && !empty( $euvatrates['rates'] ) ) {
830
                $response['success']        = true;
831
                $response['data']['rates']  = $euvatrates['rates'];
832
            } else if ( !empty( $euvatrates['error'] ) ) {
833
                $response['error']          = $euvatrates['error'];
834
            }
835
        }
836
            
837
        wp_send_json( $response );
838
    }
839
    
840
    public static function hide_vat_fields() {
841
        $hide = wpinv_get_option( 'vat_disable_fields' );
842
        
843
        return apply_filters( 'wpinv_hide_vat_fields', $hide );
844
    }
845
    
846
    public static function same_country_rule() {
847
        $same_country_rule = wpinv_get_option( 'vat_same_country_rule' );
848
        
849
        return apply_filters( 'wpinv_vat_same_country_rule', $same_country_rule );
850
    }
851
    
852
    public static function get_vat_name() {
853
        $vat_name   = wpinv_get_option( 'vat_name' );
854
        $vat_name   = !empty( $vat_name ) ? $vat_name : 'VAT';
855
        
856
        return apply_filters( 'wpinv_get_owner_vat_name', $vat_name );
857
    }
858
    
859
    public static function get_company_name() {
860
        $company_name = wpinv_get_option( 'vat_company_name' );
861
        
862
        return apply_filters( 'wpinv_get_owner_company_name', $company_name );
863
    }
864
    
865
    public static function get_vat_number() {
866
        $vat_number = wpinv_get_option( 'vat_number' );
867
        
868
        return apply_filters( 'wpinv_get_owner_vat_number', $vat_number );
869
    }
870
    
871
    public static function is_vat_validated() {
872
        $validated = self::get_vat_number() && wpinv_get_option( 'vat_valid' );
873
        
874
        return apply_filters( 'wpinv_is_owner_vat_validated', $validated );
875
    }
876
    
877
    public static function sanitize_vat( $vat_number, $country_code = '' ) {        
878
        $vat_number = str_replace( array(' ', '.', '-', '_', ',' ), '', strtoupper( trim( $vat_number ) ) );
879
        
880
        if ( empty( $country_code ) ) {
881
            $country_code = substr( $vat_number, 0, 2 );
882
        }
883
        
884
        if ( strpos( $vat_number , $country_code ) === 0 ) {
885
            $vat = str_replace( $country_code, '', $vat_number );
886
        } else {
887
            $vat = $country_code . $vat_number;
888
        }
889
        
890
        $return                 = array();
891
        $return['vat']          = $vat;
892
        $return['iso']          = $country_code;
893
        $return['vat_number']   = $country_code . $vat;
894
        
895
        return $return;
896
    }
897
    
898
    public static function offline_check( $vat_number, $country_code = '', $formatted = false ) {
899
        $vat            = self::sanitize_vat( $vat_number, $country_code );
900
        $vat_number     = $vat['vat_number'];
901
        $country_code   = $vat['iso'];
902
        $regex          = array();
903
        
904
        switch ( $country_code ) {
905
            case 'AT':
906
                $regex[] = '/^(AT)U(\d{8})$/';                           // Austria
907
                break;
908
            case 'BE':
909
                $regex[] = '/^(BE)(0?\d{9})$/';                          // Belgium
910
                break;
911
            case 'BG':
912
                $regex[] = '/^(BG)(\d{9,10})$/';                         // Bulgaria
913
                break;
914
            case 'CH':
915
            case 'CHE':
916
                $regex[] = '/^(CHE)(\d{9})MWST$/';                       // Switzerland (Not EU)
917
                break;
918
            case 'CY':
919
                $regex[] = '/^(CY)([0-5|9]\d{7}[A-Z])$/';                // Cyprus
920
                break;
921
            case 'CZ':
922
                $regex[] = '/^(CZ)(\d{8,13})$/';                         // Czech Republic
923
                break;
924
            case 'DE':
925
                $regex[] = '/^(DE)([1-9]\d{8})$/';                       // Germany
926
                break;
927
            case 'DK':
928
                $regex[] = '/^(DK)(\d{8})$/';                            // Denmark
929
                break;
930
            case 'EE':
931
                $regex[] = '/^(EE)(10\d{7})$/';                          // Estonia
932
                break;
933
            case 'EL':
934
                $regex[] = '/^(EL)(\d{9})$/';                            // Greece
935
                break;
936
            case 'ES':
937
                $regex[] = '/^(ES)([A-Z]\d{8})$/';                       // Spain (National juridical entities)
938
                $regex[] = '/^(ES)([A-H|N-S|W]\d{7}[A-J])$/';            // Spain (Other juridical entities)
939
                $regex[] = '/^(ES)([0-9|Y|Z]\d{7}[A-Z])$/';              // Spain (Personal entities type 1)
940
                $regex[] = '/^(ES)([K|L|M|X]\d{7}[A-Z])$/';              // Spain (Personal entities type 2)
941
                break;
942
            case 'EU':
943
                $regex[] = '/^(EU)(\d{9})$/';                            // EU-type
944
                break;
945
            case 'FI':
946
                $regex[] = '/^(FI)(\d{8})$/';                            // Finland
947
                break;
948
            case 'FR':
949
                $regex[] = '/^(FR)(\d{11})$/';                           // France (1)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
950
                $regex[] = '/^(FR)[(A-H)|(J-N)|(P-Z)](\d{10})$/';        // France (2)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
951
                $regex[] = '/^(FR)\d[(A-H)|(J-N)|(P-Z)](\d{9})$/';       // France (3)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
952
                $regex[] = '/^(FR)[(A-H)|(J-N)|(P-Z)]{2}(\d{9})$/';      // France (4)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
953
                break;
954
            case 'GB':
955
                $regex[] = '/^(GB)?(\d{9})$/';                           // UK (Standard)
956
                $regex[] = '/^(GB)?(\d{12})$/';                          // UK (Branches)
957
                $regex[] = '/^(GB)?(GD\d{3})$/';                         // UK (Government)
958
                $regex[] = '/^(GB)?(HA\d{3})$/';                         // UK (Health authority)
959
                break;
960
            case 'GR':
961
                $regex[] = '/^(GR)(\d{8,9})$/';                          // Greece
962
                break;
963
            case 'HR':
964
                $regex[] = '/^(HR)(\d{11})$/';                           // Croatia
965
                break;
966
            case 'HU':
967
                $regex[] = '/^(HU)(\d{8})$/';                            // Hungary
968
                break;
969
            case 'IE':
970
                $regex[] = '/^(IE)(\d{7}[A-W])$/';                       // Ireland (1)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
971
                $regex[] = '/^(IE)([7-9][A-Z\*\+)]\d{5}[A-W])$/';        // Ireland (2)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
972
                $regex[] = '/^(IE)(\d{7}[A-Z][AH])$/';                   // Ireland (3) (new format from 1 Jan 2013)
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
973
                break;
974
            case 'IT':
975
                $regex[] = '/^(IT)(\d{11})$/';                           // Italy
976
                break;
977
            case 'LV':
978
                $regex[] = '/^(LV)(\d{11})$/';                           // Latvia
979
                break;
980
            case 'LT':
981
                $regex[] = '/^(LT)(\d{9}|\d{12})$/';                     // Lithuania
982
                break;
983
            case 'LU':
984
                $regex[] = '/^(LU)(\d{8})$/';                            // Luxembourg
985
                break;
986
            case 'MT':
987
                $regex[] = '/^(MT)([1-9]\d{7})$/';                       // Malta
988
                break;
989
            case 'NL':
990
                $regex[] = '/^(NL)(\d{9})B\d{2}$/';                      // Netherlands
991
                break;
992
            case 'NO':
993
                $regex[] = '/^(NO)(\d{9})$/';                            // Norway (Not EU)
994
                break;
995
            case 'PL':
996
                $regex[] = '/^(PL)(\d{10})$/';                           // Poland
997
                break;
998
            case 'PT':
999
                $regex[] = '/^(PT)(\d{9})$/';                            // Portugal
1000
                break;
1001
            case 'RO':
1002
                $regex[] = '/^(RO)([1-9]\d{1,9})$/';                     // Romania
1003
                break;
1004
            case 'RS':
1005
                $regex[] = '/^(RS)(\d{9})$/';                            // Serbia (Not EU)
1006
                break;
1007
            case 'SI':
1008
                $regex[] = '/^(SI)([1-9]\d{7})$/';                       // Slovenia
1009
                break;
1010
            case 'SK':
1011
                $regex[] = '/^(SK)([1-9]\d[(2-4)|(6-9)]\d{7})$/';        // Slovakia Republic
1012
                break;
1013
            case 'SE':
1014
                $regex[] = '/^(SE)(\d{10}01)$/';                         // Sweden
1015
                break;
1016
            default:
1017
                $regex = array();
1018
            break;
1019
        }
1020
        
1021
        if ( empty( $regex ) ) {
1022
            return false;
1023
        }
1024
        
1025
        foreach ( $regex as $pattern ) {
1026
            $matches = null;
1027
            preg_match_all( $pattern, $vat_number, $matches );
1028
            
1029
            if ( !empty( $matches[1][0] ) && !empty( $matches[2][0] ) ) {
1030
                if ( $formatted ) {
1031
                    return array( 'code' => $matches[1][0], 'number' => $matches[2][0] );
1032
                } else {
1033
                    return true;
1034
                }
1035
            }
1036
        }
1037
        
1038
        return false;
1039
    }
1040
    
1041
    public static function vies_check( $vat_number, $country_code = '', $result = false ) {
1042
        $vat            = self::sanitize_vat( $vat_number, $country_code );
1043
        $vat_number     = $vat['vat'];
1044
        $iso            = $vat['iso'];
1045
        
1046
        $url = 'http://ec.europa.eu/taxation_customs/vies/viesquer.do?ms=' . urlencode( $iso ) . '&iso=' . urlencode( $iso ) . '&vat=' . urlencode( $vat_number );
1047
        
1048
        if ( ini_get( 'allow_url_fopen' ) ) {
1049
            $response = file_get_contents( $url );
1050
        } else if ( function_exists( 'curl_init' ) ) {
1051
            $ch = curl_init();
1052
            
1053
            curl_setopt( $ch, CURLOPT_URL, $url );
1054
            curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 30 );
1055
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
1056
            curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
1057
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
1058
            
1059
            $response = curl_exec( $ch );
1060
            
1061
            if ( curl_errno( $ch ) ) {
1062
                wpinv_error_log( curl_error( $ch ), 'VIES CHECK ERROR' );
1063
                $response = '';
1064
            }
1065
            
1066
            curl_close( $ch );
1067
        } else {
1068
            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' );
1069
        }
1070
        
1071
        if ( empty( $response ) ) {
1072
            return $result;
1073
        }
1074
1075
        if ( preg_match( '/invalid VAT number/i', $response ) ) {
1076
            return false;
1077
        } else if ( preg_match( '/valid VAT number/i', $response, $matches ) ) {
1078
            $content = explode( "valid VAT number", htmlentities( $response ) );
1079
            
1080
            if ( !empty( $content[1] ) ) {
1081
                preg_match_all( '/<tr>(.*?)<td.*?>(.*?)<\/td>(.*?)<\/tr>/si', html_entity_decode( $content[1] ), $matches );
1082
                
1083
                if ( !empty( $matches[2] ) && $matches[3] ) {
1084
                    $return = array();
1085
                    
1086
                    foreach ( $matches[2] as $key => $label ) {
1087
                        $label = trim( $label );
1088
                        
1089
                        switch ( strtolower( $label ) ) {
1090
                            case 'member state':
1091
                                $return['state'] = trim( strip_tags( $matches[3][$key] ) );
1092
                            break;
1093
                            case 'vat number':
1094
                                $return['number'] = trim( strip_tags( $matches[3][$key] ) );
1095
                            break;
1096
                            case 'name':
1097
                                $return['company'] = trim( strip_tags( $matches[3][$key] ) );
1098
                            break;
1099
                            case 'address':
1100
                                $address           = str_replace( array( "<br><br>", "<br /><br />", "<br/><br/>" ), "<br>", html_entity_decode( trim( $matches[3][$key] ) ) );
1101
                                $return['address'] = trim( strip_tags( $address, '<br>' ) );
1102
                            break;
1103
                            case 'consultation number':
1104
                                $return['consultation'] = trim( strip_tags( $matches[3][$key] ) );
1105
                            break;
1106
                        }
1107
                    }
1108
                    
1109
                    if ( !empty( $return ) ) {
1110
                        return $return;
1111
                    }
1112
                }
1113
            }
1114
            
1115
            return true;
1116
        } else {
1117
            return $result;
1118
        }
1119
    }
1120
    
1121
    public static function check_vat( $vat_number, $country_code = '' ) {        
1122
        $vat_name           = self::get_vat_name();
1123
        
1124
        $return             = array();
1125
        $return['valid']    = false;
1126
        $return['message']  = wp_sprintf( __( '%s number not validated', 'invoicing' ), $vat_name );
1127
                
1128
        if ( !wpinv_get_option( 'vat_offline_check' ) && !self::offline_check( $vat_number, $country_code ) ) {
1129
            return $return;
1130
        }
1131
            
1132
        $response = self::vies_check( $vat_number, $country_code );
1133
        
1134
        if ( $response ) {
1135
            $return['valid']    = true;
1136
            
1137
            if ( is_array( $response ) ) {
1138
                $return['company'] = isset( $response['company'] ) ? $response['company'] : '';
1139
                $return['address'] = isset( $response['address'] ) ? $response['address'] : '';
1140
                $return['message'] = $return['company'] . '<br/>' . $return['address'];
1141
            }
1142
        } else {
1143
            $return['valid']    = false;
1144
            $return['message']  = wp_sprintf( __( 'Fail to validate the %s number: EU Commission VAT server (VIES) check fails.', 'invoicing' ), $vat_name );
1145
        }
1146
        
1147
        return $return;
1148
    }
1149
    
1150
    public static function request_euvatrates( $group ) {
1151
        $response               = array();
1152
        $response['success']    = false;
1153
        $response['error']      = null;
1154
        $response['eurates']    = null;
1155
        
1156
        $euvatrates_url = 'https://euvatrates.com/rates.json';
1157
        $euvatrates_url = apply_filters( 'wpinv_euvatrates_url', $euvatrates_url );
1158
        $api_response   = wp_remote_get( $euvatrates_url );
1159
    
1160
        try {
1161
            if ( is_wp_error( $api_response ) ) {
1162
                $response['error']      = __( $api_response->get_error_message(), 'invoicing' );
1163
            } else {
1164
                $body = json_decode( $api_response['body'] );
1165
                
1166
                if ( isset( $body->rates ) ) {
1167
                    $rates = array();
1168
                    
1169
                    foreach ( $body->rates as $country_code => $rate ) {
1170
                        $vat_rate = array();
1171
                        $vat_rate['country']        = $rate->country;
1172
                        $vat_rate['standard']       = (float)$rate->standard_rate;
1173
                        $vat_rate['reduced']        = (float)$rate->reduced_rate;
1174
                        $vat_rate['superreduced']   = (float)$rate->super_reduced_rate;
1175
                        $vat_rate['parking']        = (float)$rate->parking_rate;
1176
                        
1177
                        if ( $group !== '' && in_array( $group, array( 'standard', 'reduced', 'superreduced', 'parking' ) ) ) {
1178
                            $vat_rate_group = array();
1179
                            $vat_rate_group['country'] = $rate->country;
1180
                            $vat_rate_group[$group]    = $vat_rate[$group];
1181
                            
1182
                            $vat_rate = $vat_rate_group;
1183
                        }
1184
                        
1185
                        $rates[$country_code] = $vat_rate;
1186
                    }
1187
                    
1188
                    $response['success']    = true;                                
1189
                    $response['rates']      = apply_filters( 'wpinv_process_euvatrates', $rates, $api_response, $group );
1190
                } else {
1191
                    $response['error']      = __( 'No EU rates found!', 'invoicing' );
1192
                }
1193
            }
1194
        } catch ( Exception $e ) {
1195
            $response['error'] = __( $e->getMessage(), 'invoicing' );
1196
        }
1197
        
1198
        return apply_filters( 'wpinv_response_euvatrates', $response, $group );
1199
    }    
1200
    
1201
    public static function requires_vat( $requires_vat = false, $user_id = 0, $is_digital = null ) {
1202
        global $wpi_item_id, $wpi_country;
1203
        
1204
        if ( !empty( $_POST['wpinv_country'] ) ) {
1205
            $country_code = trim( $_POST['wpinv_country'] );
1206
        } else if ( !empty( $_POST['country'] ) ) {
1207
            $country_code = trim( $_POST['country'] );
1208
        } else if ( !empty( $wpi_country ) ) {
1209
            $country_code = $wpi_country;
1210
        } else {
1211
            $country_code = self::get_user_country( '', $user_id );
1212
        }
1213
        
1214
        if ( $is_digital === null && $wpi_item_id ) {
1215
            $is_digital = $wpi_item_id ? self::item_has_digital_rule( $wpi_item_id ) : self::allow_vat_rules();
1216
        }
1217
        
1218
        if ( !empty( $country_code ) ) {
1219
            $requires_vat = ( self::is_eu_state( $country_code ) && ( self::is_eu_state( self::$default_country ) || $is_digital ) ) || ( self::is_gst_country( $country_code ) && self::is_gst_country( self::$default_country ) );
1220
        }
1221
        
1222
        return apply_filters( 'wpinv_requires_vat', $requires_vat, $user_id );
1223
    }
1224
    
1225
    public static function tax_label( $label = '' ) {
1226
        global $wpi_requires_vat;
1227
        
1228
        if ( !( $wpi_requires_vat !== 0 && $wpi_requires_vat ) ) {
1229
            $wpi_requires_vat = self::requires_vat( 0, false );
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1230
        }
1231
        
1232
        return $wpi_requires_vat ? __( self::get_vat_name(), 'invoicing' ) : ( $label ? $label : __( 'Tax', 'invoicing' ) );
1233
    }
1234
    
1235
    public static function standard_rates_label() {
1236
        return __( 'Standard Rates', 'invoicing' );
1237
    }
1238
    
1239
    public static function get_rate_classes( $with_desc = false ) {        
1240
        $rate_classes_option = get_option( '_wpinv_vat_rate_classes', true );
1241
        $classes = maybe_unserialize( $rate_classes_option );
1242
        
1243
        if ( empty( $classes ) || !is_array( $classes ) ) {
1244
            $classes = array();
1245
        }
1246
1247
        $rate_classes = array();
1248
        if ( !array_key_exists( '_standard', $classes ) ) {
1249
            if ( $with_desc ) {
1250
                $rate_classes['_standard'] = array( 'name' => self::standard_rates_label(), 'desc' => __( 'EU member states standard VAT rates', 'invoicing' ) );
1251
            } else {
1252
                $rate_classes['_standard'] = self::standard_rates_label();
1253
            }
1254
        }
1255
        
1256
        foreach ( $classes as $key => $class ) {
1257
            $name = !empty( $class['name'] ) ? __( $class['name'], 'invoicing' ) : $key;
1258
            $desc = !empty( $class['desc'] ) ? __( $class['desc'], 'invoicing' ) : '';
1259
            
1260
            if ( $with_desc ) {
1261
                $rate_classes[$key] = array( 'name' => $name, 'desc' => $desc );
1262
            } else {
1263
                $rate_classes[$key] = $name;
1264
            }
1265
        }
1266
1267
        return $rate_classes;
1268
    }
1269
    
1270
    public static function get_all_classes() {        
1271
        $classes            = self::get_rate_classes();
1272
        $classes['_exempt'] = __( 'Exempt (0%)', 'invoicing' );
1273
        
1274
        return apply_filters( 'wpinv_vat_get_all_classes', $classes );
1275
    }
1276
    
1277
    public static function get_class_desc( $rate_class ) {        
1278
        $rate_classes = self::get_rate_classes( true );
1279
1280
        if ( !empty( $rate_classes ) && isset( $rate_classes[$rate_class] ) && isset( $rate_classes[$rate_class]['desc'] ) ) {
1281
            return $rate_classes[$rate_class]['desc'];
1282
        }
1283
        
1284
        return '';
1285
    }
1286
    
1287
    public static function get_vat_groups() {
1288
        $vat_groups = array(
1289
            'standard'      => 'Standard',
1290
            'reduced'       => 'Reduced',
1291
            'superreduced'  => 'Super Reduced',
1292
            'parking'       => 'Parking',
1293
            'increased'     => 'Increased'
1294
        );
1295
        
1296
        return apply_filters( 'wpinv_get_vat_groups', $vat_groups );
1297
    }
1298
1299
    public static function get_rules() {
1300
        $vat_rules = array(
1301
            'digital' => __( 'Digital Product', 'invoicing' ),
1302
            'physical' => __( 'Physical Product', 'invoicing' )
1303
        );
1304
        return apply_filters( 'wpinv_get_vat_rules', $vat_rules );
1305
    }
1306
1307
    public static function get_vat_rates( $class ) {
1308
        if ( $class === '_standard' ) {
1309
            return wpinv_get_tax_rates();
1310
        }
1311
1312
        $rates = self::get_non_standard_rates();
1313
1314
        return array_key_exists( $class, $rates ) ? $rates[$class] : array();
1315
    }
1316
1317
    public static function get_non_standard_rates() {
1318
        $option = get_option( 'wpinv_vat_rates', array());
1319
        return is_array( $option ) ? $option : array();
1320
    }
1321
    
1322
    public static function allow_vat_rules() {
1323
        return ( wpinv_use_taxes() && wpinv_get_option( 'apply_vat_rules' ) ? true : false );
1324
    }
1325
    
1326
    public static function allow_vat_classes() {
1327
        return false; // TODO
1328
        return ( wpinv_get_option( 'vat_allow_classes' ) ? true : false );
0 ignored issues
show
Unused Code introduced by
// TODO return wpinv_get...asses') ? true : false; does not seem to be 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...
1329
    }
1330
    
1331
    public static function get_item_class( $postID ) {
1332
        $class = get_post_meta( $postID, '_wpinv_vat_class', true );
1333
1334
        if ( empty( $class ) ) {
1335
            $class = '_standard';
1336
        }
1337
        
1338
        return apply_filters( 'wpinv_get_item_vat_class', $class, $postID );
1339
    }
1340
    
1341
    public static function item_class_label( $postID ) {        
1342
        $vat_classes = self::get_all_classes();
1343
        
1344
        $class = self::get_item_class( $postID );
1345
        $class = isset( $vat_classes[$class] ) ? $vat_classes[$class] : __( $class, 'invoicing' );
1346
        
1347
        return apply_filters( 'wpinv_item_class_label', $class, $postID );
1348
    }
1349
    
1350
    public static function get_item_rule( $postID ) {        
1351
        $rule_type = get_post_meta( $postID, '_wpinv_vat_rule', true );
1352
        
1353
        if ( empty( $rule_type ) ) {        
1354
            $rule_type = self::allow_vat_rules() ? 'digital' : 'physical';
1355
        }
1356
        
1357
        return apply_filters( 'wpinv_item_get_vat_rule', $rule_type, $postID );
1358
    }
1359
    
1360
    public static function item_rule_label( $postID ) {
1361
        $vat_rules  = self::get_rules();
1362
        $vat_rule   = self::get_item_rule( $postID );
1363
        $vat_rule   = isset( $vat_rules[$vat_rule] ) ? $vat_rules[$vat_rule] : $vat_rule;
1364
        
1365
        return apply_filters( 'wpinv_item_rule_label', $vat_rule, $postID );
1366
    }
1367
    
1368
    public static function item_has_digital_rule( $item_id = 0 ) {        
1369
        return self::get_item_rule( $item_id ) == 'digital' ? true : false;
1370
    }
1371
    
1372
    public static function invoice_has_digital_rule( $invoice = 0 ) {        
1373
        if ( !self::allow_vat_rules() ) {
1374
            return false;
1375
        }
1376
        
1377
        if ( empty( $invoice ) ) {
1378
            return true;
1379
        }
1380
        
1381
        if ( is_int( $invoice ) ) {
1382
            $invoice = new WPInv_Invoice( $invoice );
0 ignored issues
show
Documentation introduced by
$invoice is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1383
        }
1384
        
1385
        if ( !( is_object( $invoice ) && is_a( $invoice, 'WPInv_Invoice' ) ) ) {
1386
            return true;
1387
        }
1388
        
1389
        $cart_items  = $invoice->get_cart_details();
1390
        
1391
        if ( !empty( $cart_items ) ) {
1392
            $has_digital_rule = false;
1393
            
1394
            foreach ( $cart_items as $key => $item ) {
1395
                if ( self::item_has_digital_rule( $item['id'] ) ) {
1396
                    $has_digital_rule = true;
1397
                    break;
1398
                }
1399
            }
1400
        } else {
1401
            $has_digital_rule = true;
1402
        }
1403
        
1404
        return $has_digital_rule;
1405
    }
1406
    
1407
    public static function item_is_taxable( $item_id = 0, $country = false, $state = false ) {        
1408
        if ( !wpinv_use_taxes() ) {
1409
            return false;
1410
        }
1411
        
1412
        $is_taxable = true;
1413
1414
        if ( !empty( $item_id ) && self::get_item_class( $item_id ) == '_exempt' ) {
1415
            $is_taxable = false;
1416
        }
1417
        
1418
        return apply_filters( 'wpinv_item_is_taxable', $is_taxable, $item_id, $country , $state );
1419
    }
1420
    
1421
    public static function find_rate( $country, $state, $rate, $class ) {
1422
        global $wpi_zero_tax;
1423
1424
        if ( $class === '_exempt' || $wpi_zero_tax ) {
1425
            return 0;
1426
        }
1427
1428
        $tax_rates   = wpinv_get_tax_rates();
1429
        
1430
        if ( $class !== '_standard' ) {
1431
            $class_rates = self::get_vat_rates( $class );
1432
            
1433
            if ( is_array( $class_rates ) ) {
1434
                $indexed_class_rates = array();
1435
                
1436
                foreach ( $class_rates as $key => $cr ) {
1437
                    $indexed_class_rates[$cr['country']] = $cr;
1438
                }
1439
1440
                $tax_rates = array_map( function( $tr ) use( $indexed_class_rates ) {
1441
                    $tr_country = $tr['country'];
1442
                    if ( !isset( $indexed_class_rates[$tr_country] ) ) {
1443
                        return $tr;
1444
                    }
1445
                    $icr = $indexed_class_rates[$tr_country];
1446
                    return ( empty( $icr['rate'] ) && $icr['rate'] !== '0' ) ? $tr : $icr;
1447
1448
                }, $tax_rates, $class_rates );
1449
            }
1450
        }
1451
1452 View Code Duplication
        if ( !empty( $tax_rates ) ) {
1453
            foreach ( $tax_rates as $key => $tax_rate ) {
1454
                if ( $country != $tax_rate['country'] )
1455
                    continue;
1456
1457
                if ( !empty( $tax_rate['global'] ) ) {
1458
                    if ( 0 !== $tax_rate['rate'] || !empty( $tax_rate['rate'] ) ) {
1459
                        $rate = number_format( $tax_rate['rate'], 4 );
1460
                    }
1461
                } else {
1462
                    if ( empty( $tax_rate['state'] ) || strtolower( $state ) != strtolower( $tax_rate['state'] ) )
1463
                        continue;
1464
1465
                    $state_rate = $tax_rate['rate'];
1466
                    if ( 0 !== $state_rate || !empty( $state_rate ) ) {
1467
                        $rate = number_format( $state_rate, 4 );
1468
                    }
1469
                }
1470
            }
1471
        }
1472
        
1473
        return $rate;
1474
    }
1475
    
1476
    public static function get_rate( $rate = 1, $country = '', $state = '', $item_id = 0 ) {
1477
        global $wpinv_options, $wpi_session, $wpi_item_id, $wpi_zero_tax;
1478
        
1479
        $item_id = $item_id > 0 ? $item_id : $wpi_item_id;
1480
        $allow_vat_classes = self::allow_vat_classes();
1481
        $class = $item_id ? self::get_item_class( $item_id ) : '_standard';
1482
1483
        if ( $class === '_exempt' || $wpi_zero_tax ) {
1484
            return 0;
1485
        } else if ( !$allow_vat_classes ) {
1486
            $class = '_standard';
1487
        }
1488
1489
        if( !empty( $_POST['wpinv_country'] ) ) {
1490
            $post_country = $_POST['wpinv_country'];
1491
        } elseif( !empty( $_POST['wpinv_country'] ) ) {
1492
            $post_country = $_POST['wpinv_country'];
1493
        } elseif( !empty( $_POST['country'] ) ) {
1494
            $post_country = $_POST['country'];
1495
        } else {
1496
            $post_country = '';
1497
        }
1498
1499
        $country        = !empty( $post_country ) ? $post_country : wpinv_default_billing_country( $country );
1500
        $base_country   = wpinv_is_base_country( $country );
1501
        
1502
        $requires_vat   = self::requires_vat( 0, false );
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1503
        $is_digital     = self::get_item_rule( $item_id ) == 'digital' ;
1504
        $rate           = $requires_vat && isset( $wpinv_options['eu_fallback_rate'] ) ? $wpinv_options['eu_fallback_rate'] : $rate;
1505
          
1506
        if ( self::same_country_rule() == 'no' && $base_country ) { // Disable VAT to same country
1507
            $rate = 0;
1508
        } else if ( $requires_vat ) {
1509
            $vat_number = self::get_user_vat_number( '', 0, true );
1510
            $vat_info   = self::current_vat_data();
1511
            
1512
            if ( is_array( $vat_info ) ) {
1513
                $vat_number = isset( $vat_info['number'] ) && !empty( $vat_info['valid'] ) ? $vat_info['number'] : "";
1514
            }
1515
            
1516
            if ( $country == 'UK' ) {
1517
                $country = 'GB';
1518
            }
1519
1520
            if ( !empty( $vat_number ) ) {
1521
                $rate = 0;
1522
            } else {
1523
                $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
1524
            }
1525
1526
            if ( empty( $vat_number ) && !$is_digital ) {
1527
                if ( $base_country ) {
1528
                    $rate = self::find_rate( $country, null, $rate, $class );
1529 View Code Duplication
                } else {
1530
                    if ( empty( $country ) && isset( $wpinv_options['eu_fallback_rate'] ) ) {
1531
                        $rate = $wpinv_options['eu_fallback_rate'];
1532
                    } else if( !empty( $country ) ) {
1533
                        $rate = self::find_rate( $country, $state, $rate, $class );
1534
                    }
1535
                }
1536
            } else if ( empty( $vat_number ) || ( self::same_country_rule() == 'always' && $base_country ) ) {
1537 View Code Duplication
                if ( empty( $country ) && isset( $wpinv_options['eu_fallback_rate'] ) ) {
1538
                    $rate = $wpinv_options['eu_fallback_rate'];
1539
                } else if( !empty( $country ) ) {
1540
                    $rate = self::find_rate( $country, $state, $rate, $class );
1541
                }
1542
            }
1543
        } else {
1544
            if ( $is_digital ) {
1545
                $ip_country_code = self::get_country_by_ip();
1546
                
1547
                if ( $ip_country_code && self::is_eu_state( $ip_country_code ) ) {
1548
                    $rate = self::find_rate( $ip_country_code, '', 0, $class );
1549
                } else {
1550
                    $rate = self::find_rate( $country, $state, $rate, $class );
1551
                }
1552
            } else {
1553
                $rate = self::find_rate( $country, $state, $rate, $class );
1554
            }
1555
        }
1556
1557
        return $rate;
1558
    }
1559
    
1560
    public static function current_vat_data() {
1561
        global $wpi_session;
1562
        
1563
        return $wpi_session->get( 'user_vat_data' );
1564
    }
1565
    
1566
    public static function get_user_country( $country = '', $user_id = 0 ) {
1567
        $user_address = wpinv_get_user_address( $user_id, false );
1568
        
1569
        if ( wpinv_get_option( 'vat_ip_country_default' ) ) {
1570
            $country = '';
1571
        }
1572
        
1573
        $country    = empty( $user_address ) || !isset( $user_address['country'] ) || empty( $user_address['country'] ) ? $country : $user_address['country'];
1574
        $result     = apply_filters( 'wpinv_get_user_country', $country, $user_id );
1575
1576
        if ( empty( $result ) ) {
1577
            $result = self::get_country_by_ip();
1578
        }
1579
1580
        return $result;
1581
    }
1582
    
1583
    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.

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

Loading history...
1584
        global $wpi_userID;
1585
        
1586
        if ( empty($country) && !empty($wpi_userID) && get_current_user_id() != $wpi_userID ) {
1587
            $country = wpinv_get_default_country();
1588
        }
1589
        
1590
        return $country;
1591
    }
1592
    
1593
    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.

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

Loading history...
1594
        global $wpi_current_id, $wpi_userID;
1595
        
1596
        if ( !empty( $_POST['new_user'] ) ) {
1597
            return '';
1598
        }
1599
        
1600 View Code Duplication
        if ( empty( $user_id ) ) {
1601
            $user_id = !empty( $wpi_userID ) ? $wpi_userID : ( $wpi_current_id ? wpinv_get_user_id( $wpi_current_id ) : get_current_user_id() );
1602
        }
1603
1604
        $vat_number = empty( $user_id ) ? '' : get_user_meta( $user_id, '_wpinv_vat_number', true );
1605
        
1606
        /* TODO
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1607
        if ( $is_valid && $vat_number ) {
1608
            $adddress_confirmed = empty( $user_id ) ? false : get_user_meta( $user_id, '_wpinv_adddress_confirmed', true );
1609
            if ( !$adddress_confirmed ) {
1610
                $vat_number = '';
1611
            }
1612
        }
1613
        */
1614
1615
        return apply_filters('wpinv_get_user_vat_number', $vat_number, $user_id, $is_valid );
1616
    }
1617
    
1618
    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.

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

Loading history...
1619
        global $wpi_current_id, $wpi_userID;
1620
        
1621 View Code Duplication
        if ( empty( $user_id ) ) {
1622
            $user_id = !empty( $wpi_userID ) ? $wpi_userID : ( $wpi_current_id ? wpinv_get_user_id( $wpi_current_id ) : get_current_user_id() );
1623
        }
1624
1625
        $company = empty( $user_id ) ? "" : get_user_meta( $user_id, '_wpinv_company', true );
1626
1627
        return apply_filters( 'wpinv_user_company', $company, $user_id );
1628
    }
1629
    
1630
    public static function save_user_vat_details( $company = '', $vat_number = '' ) {
1631
        $save = apply_filters( 'wpinv_allow_save_user_vat_details', true );
1632
1633
        if ( is_user_logged_in() && $save ) {
1634
            $user_id = get_current_user_id();
1635
1636
            if ( !empty( $vat_number ) ) {
1637
                update_user_meta( $user_id, '_wpinv_vat_number', $vat_number );
1638
            } else {
1639
                delete_user_meta( $user_id, '_wpinv_vat_number');
1640
            }
1641
1642
            if ( !empty( $company ) ) {
1643
                update_user_meta( $user_id, '_wpinv_company', $company );
1644
            } else {
1645
                delete_user_meta( $user_id, '_wpinv_company');
1646
                delete_user_meta( $user_id, '_wpinv_vat_number');
1647
            }
1648
        }
1649
1650
        do_action('wpinv_save_user_vat_details', $company, $vat_number);
1651
    }
1652
    
1653
    public static function ajax_vat_validate() {
1654
        global $wpinv_options, $wpi_session;
1655
        
1656
        $is_checkout            = ( !empty( $_POST['source'] ) && $_POST['source'] == 'checkout' ) ? true : false;
1657
        $response               = array();
1658
        $response['success']    = false;
1659
        
1660 View Code Duplication
        if ( empty( $_REQUEST['_wpi_nonce'] ) || ( !empty( $_REQUEST['_wpi_nonce'] ) && !wp_verify_nonce( $_REQUEST['_wpi_nonce'], 'vat_validation' ) ) ) {
1661
            $response['error'] = __( 'Invalid security nonce', 'invoicing' );
1662
            wp_send_json( $response );
1663
        }
1664
        
1665
        $vat_name   = self::get_vat_name();
1666
        
1667
        if ( $is_checkout ) {
1668
            $invoice = wpinv_get_invoice_cart();
1669
            
1670
            if ( !self::requires_vat( false, 0, self::invoice_has_digital_rule( $invoice ) ) ) {
0 ignored issues
show
Documentation introduced by
$invoice is of type object<WPInv_Invoice>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1671
                $vat_info = array();
1672
                $wpi_session->set( 'user_vat_data', $vat_info );
1673
1674
                self::save_user_vat_details();
1675
                
1676
                $response['success'] = true;
1677
                $response['message'] = wp_sprintf( __( 'Ignore %s', 'invoicing' ), $vat_name );
1678
                wp_send_json( $response );
1679
            }
1680
        }
1681
        
1682
        $company    = !empty( $_POST['company'] ) ? sanitize_text_field( $_POST['company'] ) : '';
1683
        $vat_number = !empty( $_POST['number'] ) ? sanitize_text_field( $_POST['number'] ) : '';
1684
        
1685
        $vat_info = $wpi_session->get( 'user_vat_data' );
1686
        if ( !is_array( $vat_info ) || empty( $vat_info ) ) {
1687
            $vat_info = array( 'company'=> $company, 'number' => '', 'valid' => true );
1688
        }
1689
        
1690
        if ( empty( $vat_number ) ) {
1691
            if ( $is_checkout ) {
1692
                $response['success'] = true;
1693
                $response['message'] = wp_sprintf( __( 'No %s number has been applied. %s will be added to invoice totals', 'invoicing' ), $vat_name, $vat_name );
1694
                
1695
                $vat_info = $wpi_session->get( 'user_vat_data' );
1696
                $vat_info['number'] = "";
1697
                $vat_info['valid'] = true;
1698
                
1699
                self::save_user_vat_details( $company );
1700
            } else {
1701
                $response['error'] = wp_sprintf( __( 'Please enter your %s number!', 'invoicing' ), $vat_name );
1702
                
1703
                $vat_info['valid'] = false;
1704
            }
1705
1706
            $wpi_session->set( 'user_vat_data', $vat_info );
1707
            wp_send_json( $response );
1708
        }
1709
        
1710 View Code Duplication
        if ( empty( $company ) ) {
1711
            $vat_info['valid'] = false;
1712
            $wpi_session->set( 'user_vat_data', $vat_info );
1713
            
1714
            $response['error'] = __( 'Please enter your registered company name!', 'invoicing' );
1715
            wp_send_json( $response );
1716
        }
1717
        
1718
        if ( !empty( $wpinv_options['vat_vies_check'] ) ) {
1719 View Code Duplication
            if ( empty( $wpinv_options['vat_offline_check'] ) && !self::offline_check( $vat_number ) ) {
1720
                $vat_info['valid'] = false;
1721
                $wpi_session->set( 'user_vat_data', $vat_info );
1722
                
1723
                $response['error'] = wp_sprintf( __( '%s number not validated', 'invoicing' ), $vat_name );
1724
                wp_send_json( $response );
1725
            }
1726
            
1727
            $response['success'] = true;
1728
            $response['message'] = wp_sprintf( __( '%s number validated', 'invoicing' ), $vat_name );
1729
        } else {
1730
            $result = self::check_vat( $vat_number );
1731
            
1732
            if ( empty( $result['valid'] ) ) {
1733
                $response['error'] = $result['message'];
1734
                wp_send_json( $response );
1735
            }
1736
            
1737
            $vies_company = !empty( $result['company'] ) ? $result['company'] : '';
1738
            $vies_company = apply_filters( 'wpinv_vies_company_name', $vies_company );
1739
            
1740
            $valid_company = $vies_company && $company && ( $vies_company == '---' || strcasecmp( trim( $vies_company ), trim( $company ) ) == 0 ) ? true : false;
1741
1742
            if ( !empty( $wpinv_options['vat_disable_company_name_check'] ) || $valid_company ) {
1743
                $response['success'] = true;
1744
                $response['message'] = wp_sprintf( __( '%s number validated', 'invoicing' ), $vat_name );
1745 View Code Duplication
            } else {           
1746
                $vat_info['valid'] = false;
1747
                $wpi_session->set( 'user_vat_data', $vat_info );
1748
                
1749
                $response['success'] = false;
1750
                $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 );
1751
                wp_send_json( $response );
1752
            }
1753
        }
1754
        
1755
        if ( $is_checkout ) {
1756
            self::save_user_vat_details( $company, $vat_number );
1757
1758
            $vat_info = array('company' => $company, 'number' => $vat_number, 'valid' => true );
1759
            $wpi_session->set( 'user_vat_data', $vat_info );
1760
        }
1761
1762
        wp_send_json( $response );
1763
    }
1764
    
1765
    public static function ajax_vat_reset() {
1766
        global $wpi_session;
1767
        
1768
        $company    = is_user_logged_in() ? self::get_user_company() : '';
1769
        $vat_number = self::get_user_vat_number();
1770
        
1771
        $vat_info   = array('company' => $company, 'number' => $vat_number, 'valid' => false );
1772
        $wpi_session->set( 'user_vat_data', $vat_info );
1773
        
1774
        $response                       = array();
1775
        $response['success']            = true;
1776
        $response['data']['company']    = $company;
1777
        $response['data']['number']     = $vat_number;
1778
        
1779
        wp_send_json( $response );
1780
    }
1781
    
1782
    public static function checkout_vat_validate( $valid_data, $post ) {
0 ignored issues
show
Unused Code introduced by
The parameter $valid_data is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $post is not used and could be removed.

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

Loading history...
1783
        global $wpinv_options, $wpi_session;
1784
        
1785
        $vat_name  = __( self::get_vat_name(), 'invoicing' );
1786
        
1787
        if ( !isset( $_POST['_wpi_nonce'] ) || !wp_verify_nonce( $_POST['_wpi_nonce'], 'vat_validation' ) ) {
1788
            wpinv_set_error( 'vat_validation', wp_sprintf( __( "Invalid %s validation request.", 'invoicing' ), $vat_name ) );
1789
            return;
1790
        }
1791
        
1792
        $vat_saved = $wpi_session->get( 'user_vat_data' );
1793
        $wpi_session->set( 'user_vat_data', null );
1794
        
1795
        $invoice        = wpinv_get_invoice_cart();
1796
        $amount         = $invoice->get_total();
1797
        $is_digital     = self::invoice_has_digital_rule( $invoice );
0 ignored issues
show
Documentation introduced by
$invoice is of type object<WPInv_Invoice>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1798
        $no_vat         = !self::requires_vat( 0, false, $is_digital );
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1799
        
1800
        $company        = !empty( $_POST['wpinv_company'] ) ? $_POST['wpinv_company'] : null;
1801
        $vat_number     = !empty( $_POST['wpinv_vat_number'] ) ? $_POST['wpinv_vat_number'] : null;
1802
        $country        = !empty( $_POST['wpinv_country'] ) ? $_POST['wpinv_country'] : $invoice->country;
1803
        if ( empty( $country ) ) {
1804
            $country = wpinv_default_billing_country();
1805
        }
1806
        
1807
        if ( !$is_digital && $no_vat ) {
1808
            return;
1809
        }
1810
            
1811
        $vat_data           = array( 'company' => '', 'number' => '', 'valid' => false );
1812
        
1813
        $ip_country_code    = self::get_country_by_ip();
1814
        $is_eu_state        = self::is_eu_state( $country );
1815
        $is_eu_state_ip     = self::is_eu_state( $ip_country_code );
1816
        $is_non_eu_user     = !$is_eu_state && !$is_eu_state_ip;
1817
        
1818
        if ( $is_digital && !$is_non_eu_user && empty( $vat_number ) && apply_filters( 'wpinv_checkout_requires_country', true, $amount ) ) {
1819
            $vat_data['adddress_confirmed'] = false;
1820
            
1821
            if ( !isset( $_POST['wpinv_adddress_confirmed'] ) ) {
1822
                if ( $ip_country_code != $country ) {
1823
                    wpinv_set_error( 'vat_validation', sprintf( __( 'The country of your current location must be the same as the country of your billing location or you must %s confirm %s the billing address is your home country.', 'invoicing' ), '<a href="#wpinv_adddress_confirm">', '</a>' ) );
1824
                }
1825
            } else {
1826
                $vat_data['adddress_confirmed'] = true;
1827
            }
1828
        }
1829
        
1830
        if ( !empty( $wpinv_options['vat_prevent_b2c_purchase'] ) && !$is_non_eu_user && ( empty( $vat_number ) || $no_vat ) ) {
1831
            if ( $is_eu_state ) {
1832
                wpinv_set_error( 'vat_validation', wp_sprintf( __( 'Please enter and validate your %s number to verify your purchase is by an EU business.', 'invoicing' ), $vat_name ) );
1833
            } else if ( $is_digital && $is_eu_state_ip ) {
1834
                wpinv_set_error( 'vat_validation', wp_sprintf( __( 'Sales to non-EU countries cannot be completed because %s must be applied.', 'invoicing' ), $vat_name ) );
1835
            }
1836
        }
1837
        
1838
        if ( !$is_eu_state || $no_vat || empty( $vat_number ) ) {
1839
            return;
1840
        }
1841
1842
        if ( !empty( $vat_saved ) && isset( $vat_saved['valid'] ) ) {
1843
            $vat_data['valid']  = $vat_saved['valid'];
1844
        }
1845
            
1846
        if ( $company !== null ) {
1847
            $vat_data['company'] = $company;
1848
        }
1849
1850
        $message = '';
1851
        if ( $vat_number !== null ) {
1852
            $vat_data['number'] = $vat_number;
1853
            
1854
            if ( !$vat_data['valid'] || ( $vat_saved['number'] !== $vat_data['number'] ) || ( $vat_saved['company'] !== $vat_data['company'] ) ) {
1855
                if ( !empty( $wpinv_options['vat_vies_check'] ) ) {            
1856 View Code Duplication
                    if ( empty( $wpinv_options['vat_offline_check'] ) && !self::offline_check( $vat_number ) ) {
1857
                        $vat_data['valid'] = false;
1858
                    }
1859
                } else {
1860
                    $result = self::check_vat( $vat_number );
1861
                    
1862
                    if ( !empty( $result['valid'] ) ) {                
1863
                        $vat_data['valid'] = true;
1864
                        $vies_company = !empty( $result['company'] ) ? $result['company'] : '';
1865
                        $vies_company = apply_filters( 'wpinv_vies_company_name', $vies_company );
1866
                    
1867
                        $valid_company = $vies_company && $company && ( $vies_company == '---' || strcasecmp( trim( $vies_company ), trim( $company ) ) == 0 ) ? true : false;
1868
1869 View Code Duplication
                        if ( !( !empty( $wpinv_options['vat_disable_company_name_check'] ) || $valid_company ) ) {         
1870
                            $vat_data['valid'] = false;
1871
                            
1872
                            $message = wp_sprintf( __( 'The company name associated with the %s number provided is not the same as the company name provided.', 'invoicing' ), $vat_name );
1873
                        }
1874
                    } else {
1875
                        $message = wp_sprintf( __( 'Fail to validate the %s number: EU Commission VAT server (VIES) check fails.', 'invoicing' ), $vat_name );
1876
                    }
1877
                }
1878
                
1879
                if ( !$vat_data['valid'] ) {
1880
                    $error = wp_sprintf( __( 'The %s %s number %s you have entered has not been validated', 'invoicing' ), '<a href="#wpi-vat-details">', $vat_name, '</a>' ) . ( $message ? ' ( ' . $message . ' )' : '' );
1881
                    wpinv_set_error( 'vat_validation', $error );
1882
                }
1883
            }
1884
        }
1885
1886
        $wpi_session->set( 'user_vat_data', $vat_data );
1887
    }
1888
    
1889
    public static function checkout_vat_fields( $billing_details ) {
0 ignored issues
show
Unused Code introduced by
The parameter $billing_details is not used and could be removed.

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

Loading history...
1890
        global $wpi_session, $wpinv_options, $wpi_country, $wpi_requires_vat;
1891
        
1892
        $ip_address         = wpinv_get_ip();
1893
        $ip_country_code    = self::get_country_by_ip();
1894
        
1895
        $tax_label          = __( self::get_vat_name(), 'invoicing' );
1896
        $invoice            = wpinv_get_invoice_cart();
1897
        $is_digital         = self::invoice_has_digital_rule( $invoice );
0 ignored issues
show
Documentation introduced by
$invoice is of type object<WPInv_Invoice>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1898
        $wpi_country        = $invoice->country;
1899
        
1900
        $requires_vat       = !self::hide_vat_fields() && $invoice->get_total() > 0 && self::requires_vat( 0, false, $is_digital );
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1901
        $wpi_requires_vat   = $requires_vat;
1902
        
1903
        $company            = self::get_user_company();
1904
        $vat_number         = self::get_user_vat_number();
1905
        
1906
        $validated          = $vat_number ? self::get_user_vat_number( '', 0, true ) : 1;
1907
        $vat_info           = $wpi_session->get( 'user_vat_data' );
1908
1909
        if ( is_array( $vat_info ) ) {
1910
            $company    = isset( $vat_info['company'] ) ? $vat_info['company'] : '';
1911
            $vat_number = isset( $vat_info['number'] ) ? $vat_info['number'] : '';
1912
            $validated  = isset( $vat_info['valid'] ) ? $vat_info['valid'] : false;
1913
        }
1914
        
1915
        $selected_country = $invoice->country ? $invoice->country : wpinv_default_billing_country();
1916
1917
        if ( $ip_country_code == 'UK' ) {
1918
            $ip_country_code = 'GB';
1919
        }
1920
        
1921
        if ( $selected_country == 'UK' ) {
1922
            $selected_country = 'GB';
1923
        }
1924
        
1925
        if ( $requires_vat && ( self::same_country_rule() == 'no' && wpinv_is_base_country( $selected_country ) || !self::allow_vat_rules() ) ) {
1926
            $requires_vat = false;
1927
        }
1928
1929
        $display_vat_details    = $requires_vat ? 'block' : 'none';
1930
        $display_validate_btn   = 'none';
1931
        $display_reset_btn      = 'none';
1932
        
1933
        if ( !empty( $vat_number ) && $validated ) {
1934
            $vat_vailidated_text    = wp_sprintf( __( '%s number validated', 'invoicing' ), $tax_label );
1935
            $vat_vailidated_class   = 'wpinv-vat-stat-1';
1936
            $display_reset_btn      = 'block';
1937
        } else {
1938
            $vat_vailidated_text    = empty( $vat_number ) ? '' : wp_sprintf( __( '%s number not validated', 'invoicing' ), $tax_label );
1939
            $vat_vailidated_class   = empty( $vat_number ) ? '' : 'wpinv-vat-stat-0';
1940
            $display_validate_btn   = 'block';
1941
        }
1942
        
1943
        $show_ip_country        = $is_digital && ( empty( $vat_number ) || !$requires_vat ) && $ip_country_code != $selected_country ? 'block' : 'none';
1944
        ?>
1945
        <div id="wpi-vat-details" class="wpi-vat-details clearfix" style="display:<?php echo $display_vat_details; ?>">
1946
            <div id="wpi_vat_info" class="clearfix panel panel-default">
1947
                <div class="panel-heading"><h3 class="panel-title"><?php echo wp_sprintf( __( '%s Details', 'invoicing' ), $tax_label );?></h3></div>
1948
                <div id="wpinv-fields-box" class="panel-body">
1949
                    <p id="wpi_show_vat_note">
1950
                        <?php echo wp_sprintf( __( 'Validate your registered %s number to exclude tax.', 'invoicing' ), $tax_label ); ?>
1951
                    </p>
1952
                    <div id="wpi_vat_fields" class="wpi_vat_info">
1953
                        <p class="wpi-cart-field wpi-col2 wpi-colf">
1954
                            <label for="wpinv_company" class="wpi-label"><?php _e( 'Company Name', 'invoicing' );?></label>
1955
                            <?php
1956
                            echo wpinv_html_text( array(
1957
                                    'id'            => 'wpinv_company',
1958
                                    'name'          => 'wpinv_company',
1959
                                    'value'         => $company,
1960
                                    'class'         => 'wpi-input form-control',
1961
                                    'placeholder'   => __( 'Company name', 'invoicing' ),
1962
                                ) );
1963
                            ?>
1964
                        </p>
1965
                        <p class="wpi-cart-field wpi-col2 wpi-coll wpi-cart-field-vat">
1966
                            <label for="wpinv_vat_number" class="wpi-label"><?php echo wp_sprintf( __( '%s Number', 'invoicing' ), $tax_label );?></label>
1967
                            <span id="wpinv_vat_number-wrap">
1968
                                <label for="wpinv_vat_number" class="wpinv-label"></label>
1969
                                <input type="text" class="wpi-input form-control" placeholder="<?php echo esc_attr( wp_sprintf( __( '%s number', 'invoicing' ), $tax_label ) );?>" value="<?php esc_attr_e( $vat_number );?>" id="wpinv_vat_number" name="wpinv_vat_number">
1970
                                <span class="wpinv-vat-stat <?php echo $vat_vailidated_class;?>"><i class="fa"></i>&nbsp;<font><?php echo $vat_vailidated_text;?></font></span>
1971
                            </span>
1972
                        </p>
1973
                        <p class="wpi-cart-field wpi-col wpi-colf wpi-cart-field-actions">
1974
                            <button class="btn btn-success btn-sm wpinv-vat-validate" type="button" id="wpinv_vat_validate" style="display:<?php echo $display_validate_btn; ?>"><?php echo wp_sprintf( __("Validate %s Number", 'invoicing'), $tax_label ); ?></button>
1975
                            <button class="btn btn-danger btn-sm wpinv-vat-reset" type="button" id="wpinv_vat_reset" style="display:<?php echo $display_reset_btn; ?>"><?php echo wp_sprintf( __("Reset %s", 'invoicing'), $tax_label ); ?></button>
1976
                            <span class="wpi-vat-box wpi-vat-box-info"><span id="text"></span></span>
1977
                            <span class="wpi-vat-box wpi-vat-box-error"><span id="text"></span></span>
1978
                            <input type="hidden" name="_wpi_nonce" value="<?php echo wp_create_nonce( 'vat_validation' ) ?>" />
1979
                        </p>
1980
                    </div>
1981
                </div>
1982
            </div>
1983
        </div>
1984
        <div id="wpinv_adddress_confirm" class="wpi-vat-info clearfix panel panel-info" value="<?php echo $ip_country_code; ?>" style="display:<?php echo $show_ip_country; ?>;">
1985
            <div id="wpinv-fields-box" class="panel-body">
1986
                <span id="wpinv_adddress_confirmed-wrap">
1987
                    <input type="checkbox" id="wpinv_adddress_confirmed" name="wpinv_adddress_confirmed" value="1">
1988
                    <label for="wpinv_adddress_confirmed"><?php _e('The country of your current location must be the same as the country of your billing location or you must confirm the billing address is your home country.', 'invoicing'); ?></label>
1989
                </span>
1990
            </div>
1991
        </div>
1992
        <?php if ( empty( $wpinv_options['hide_ip_address'] ) ) { 
1993
            $ip_link = '<a title="' . esc_attr( __( 'View more details on map', 'invoicing' ) ) . '" target="_blank" href="' . esc_url( admin_url( 'admin-ajax.php?action=wpinv_ip_geolocation&ip=' . $ip_address ) ) . '" class="wpi-ip-address-link">' . $ip_address . '&nbsp;&nbsp;<i class="fa fa-external-link-square" aria-hidden="true"></i></a>';
1994
        ?>
1995
        <div class="wpi-ip-info clearfix panel panel-info">
1996
            <div id="wpinv-fields-box" class="panel-body">
1997
                <span><?php echo wp_sprintf( __( "Your IP address is: %s", 'invoicing' ), $ip_link ); ?></span>
1998
            </div>
1999
        </div>
2000
        <?php }
2001
    }
2002
    
2003
    public static function show_vat_notice( $invoice ) {
2004
        if ( empty( $invoice ) ) {
2005
            return NULL;
2006
        }
2007
        
2008
        $label      = wpinv_get_option( 'vat_invoice_notice_label' );
2009
        $notice     = wpinv_get_option( 'vat_invoice_notice' );
2010
        if ( $label || $notice ) {
2011
        ?>
2012
        <div class="row wpinv-vat-notice">
2013
            <div class="col-sm-12">
2014
                <?php if ( $label ) { ?>
2015
                <strong><?php _e( $label, 'invoicing' ); ?></strong>
2016
                <?php } if ( $notice ) { ?>
2017
                <?php echo wpautop( wptexturize( __( $notice, 'invoicing' ) ) ) ?>
2018
                <?php } ?>
2019
            </div>
2020
        </div>
2021
        <?php
2022
        }
2023
    }
2024
}
2025
2026
global $wpinv_euvat;
2027
$wpinv_euvat = WPInv_EUVat::get_instance();