Advertisement
Guest User

EU VAT

a guest
Dec 30th, 2015
415
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 51.70 KB | None | 0 0
  1. <?php
  2. namespace Aelia\WC\EU_VAT_Assistant;
  3. if(!defined('ABSPATH')) exit; // Exit if accessed directly
  4.  
  5. //define('SCRIPT_DEBUG', 1);
  6. //error_reporting(E_ALL);
  7.  
  8. require_once('lib/classes/definitions/definitions.php');
  9.  
  10. use Aelia\WC\Aelia_Plugin;
  11. use Aelia\WC\IP2Location;
  12. use Aelia\WC\EU_VAT_Assistant\Settings;
  13. use Aelia\WC\EU_VAT_Assistant\Settings_Renderer;
  14. use Aelia\WC\Messages;
  15. use Aelia\WC\EU_VAT_Assistant\Logger as Logger;
  16. use \Exception;
  17.  
  18. /**
  19.  * EU VAT Assistant plugin.
  20.  **/
  21. class WC_Aelia_EU_VAT_Assistant extends Aelia_Plugin {
  22.     public static $version = '1.5.4.151210';
  23.  
  24.     public static $plugin_slug = Definitions::PLUGIN_SLUG;
  25.     public static $text_domain = Definitions::TEXT_DOMAIN;
  26.     public static $plugin_name = 'WooCommerce EU VAT Assistant';
  27.  
  28.     /**
  29.      * A list of countries to which sales are allowed. The list is altered by the
  30.      * plugin if the admin populated the list of countries to which the sale is
  31.      * not allowed.
  32.      * @var array
  33.      */
  34.     protected $allowed_sale_countries;
  35.  
  36.     // @var string The country to which a VAT number applies (if any).
  37.     protected $vat_country;
  38.     // @var string The VAT number entered by the customer at checkout.
  39.     protected $vat_number;
  40.     // @var bool Indicates if the VAT number was validated.
  41.     protected $vat_number_validated;
  42.     // @var EU_VAT_Validation The instance of the EU VAT Number validator.
  43.     protected $_eu_vat_validation;
  44.  
  45.     /** Shop's base country. Used to determine if a VAT exemption can be applied
  46.      * (usually, exemption cannot be applied to customers located in shop's base
  47.      * country).
  48.      * @var string
  49.      * @since 1.4.12.150923
  50.      */
  51.     protected $shop_base_country;
  52.  
  53.     /**
  54.      * Initialises and returns the plugin instance.
  55.      *
  56.      * @return Aelia\WC\EU_VAT_Assistant\WC_Aelia_EU_VAT_Assistant
  57.      */
  58.     public static function factory() {
  59.         // Load Composer autoloader
  60.         require_once(__DIR__ . '/vendor/autoload.php');
  61.  
  62.         // Example on how to initialise a settings controller and a messages controller
  63.         $settings_page_renderer = new Settings_Renderer();
  64.         $settings_controller = new Settings(Settings::SETTINGS_KEY,
  65.                                                                                 self::$text_domain,
  66.                                                                                 $settings_page_renderer);
  67.         $messages_controller = new Messages(self::$text_domain);
  68.  
  69.         $plugin_instance = new self($settings_controller, $messages_controller);
  70.         return $plugin_instance;
  71.     }
  72.  
  73.     /**
  74.      * Constructor.
  75.      *
  76.      * @param \Aelia\WC\Settings settings_controller The controller that will handle
  77.      * the plugin settings.
  78.      * @param \Aelia\WC\Messages messages_controller The controller that will handle
  79.      * the messages produced by the plugin.
  80.      */
  81.     public function __construct($settings_controller = null,
  82.                                                             $messages_controller = null) {
  83.         // Load Composer autoloader
  84.         require_once(__DIR__ . '/vendor/autoload.php');
  85.  
  86.         parent::__construct($settings_controller, $messages_controller);
  87.  
  88.         // Instantiate the logger specific to this plugin
  89.         $this->logger = new Logger(Definitions::PLUGIN_SLUG);
  90.  
  91.         // The commented line below is needed for Codestyling Localization plugin to
  92.         // understand what text domain is used by this plugin
  93.         //load_plugin_textdomain('wc-aelia-eu-vat-assistant', false, $this->path('languages') . '/');
  94.     }
  95.  
  96.     /**
  97.      * Indicates if debug mode is active.
  98.      *
  99.      * @return bool
  100.      */
  101.     public function debug_mode() {
  102.         return self::settings()->get(Settings::FIELD_DEBUG_MODE);
  103.     }
  104.  
  105.     /**
  106.      * Returns an instance of the EU VAT numbers validator.
  107.      *
  108.      * @return EU_VAT_Validation
  109.      * @since 1.3.20.150330
  110.      */
  111.     protected function eu_vat_validation() {
  112.         if(empty($this->_eu_vat_validation)) {
  113.         // Instantiate the the EU VAT numbers validator
  114.             $this->_eu_vat_validation = EU_VAT_Validation::factory();
  115.         }
  116.         return $this->_eu_vat_validation;
  117.     }
  118.  
  119.     /**
  120.      * Returns the country corresponding to visitor's IP address.
  121.      *
  122.      * @return string
  123.      */
  124.     protected function get_ip_address_country() {
  125.         if(empty($this->ip_address_country)) {
  126.             $this->ip_address_country = apply_filters('wc_aelia_eu_vat_assistant_ip_address_country', IP2Location::factory()->get_visitor_country());
  127.         }
  128.         return $this->ip_address_country;
  129.     }
  130.  
  131.     /**
  132.      * Returns an array with all the tax types in use in the European Union, together
  133.      * with their descriptions.
  134.      *
  135.      * @return array
  136.      */
  137.     public function get_eu_vat_rate_types() {
  138.         return array(
  139.             'standard_rate' => __('Standard rates', 'wc-aelia-eu-vat-assistant'),
  140.             'reduced_rate' => __('Reduced rates', 'wc-aelia-eu-vat-assistant'),
  141.             'reduced_rate_alt' => __('Reduced rates (alternative)', 'wc-aelia-eu-vat-assistant'),
  142.             'super_reduced_rate' => __('Super reduced rates', 'wc-aelia-eu-vat-assistant'),
  143.             'parking_rate' => __('Parking rates', 'wc-aelia-eu-vat-assistant'),
  144.         );
  145.     }
  146.  
  147.     /**
  148.      * Returns a list of countries to which EU VAT applies. This method takes into
  149.      * account countries such as Monaco and Isle of Man, which are not returned as
  150.      * part of EU countries by WooCommerce.
  151.      *
  152.      * @return array
  153.      */
  154.     public function get_eu_vat_countries() {
  155.         if(empty($this->eu_vat_countries)) {
  156.             $this->eu_vat_countries = $this->wc()->countries->get_european_union_countries();
  157.             // Add countries that are not strictly EU countries, but to which EU VAT rules apply
  158.             $this->eu_vat_countries[] = 'MC';
  159.             $this->eu_vat_countries[] = 'IM';
  160.         }
  161.         return apply_filters('wc_aelia_eu_vat_assistant_eu_vat_countries', $this->eu_vat_countries);
  162.     }
  163.  
  164.     /**
  165.      * Indicates if the plugin has been configured.
  166.      *
  167.      * @return bool
  168.      */
  169.     public function plugin_configured() {
  170.         $vat_currency = $this->settings_controller()->get(Settings::FIELD_VAT_CURRENCY);
  171.         return !empty($vat_currency);
  172.     }
  173.  
  174.     /**
  175.      * Checks if the VAT rates retrieved by the EU VAT Assistant are valid. Rates
  176.      * are valid when, for each country, they contain at least a standard rate
  177.      * (invalid rates often have a "null" object associated to them).
  178.      *
  179.      * @param array vat_rates An array containing the VAT rates for all EU countries.
  180.      * @return bool
  181.      */
  182.     protected function valid_eu_vat_rates($vat_rates) {
  183.         foreach($vat_rates as $country_code => $rates) {
  184.             if(empty($rates['standard_rate']) ||
  185.                  !is_numeric($rates['standard_rate'])) {
  186.                 return false;
  187.             }
  188.         }
  189.         return true;
  190.     }
  191.  
  192.     /**
  193.      * Retrieves the EU VAT rats from https://euvatrates.com website.
  194.      *
  195.      * @return array|null An array with the details of VAT rates, or null on failure.
  196.      * @link https://euvatrates.com
  197.      */
  198.     public function get_eu_vat_rates() {
  199.         $vat_rates = get_transient(Definitions::TRANSIENT_EU_VAT_RATES);
  200.         if(!empty($vat_rates) && is_array($vat_rates)) {
  201.             return $vat_rates;
  202.         }
  203.  
  204.         $eu_vat_url = 'http://euvatrates.com/rates.json';
  205.         $eu_vat_response = wp_remote_get($eu_vat_url, array(
  206.             'timeout' => 5,
  207.         ));
  208.         if(is_wp_error($eu_vat_response)) {
  209.             $this->log(sprintf(__('Could not fetch EU VAT rates from remote site. Error(s): "%s". Remote site: "%s".', 'wc-aelia-eu-vat-assistant'),
  210.                                                  implode(', ', $eu_vat_response->get_error_messages()),
  211.                                                  $eu_vat_url));
  212.             return null;
  213.         }
  214.  
  215.         // Ensure that the VAT rates are in the correct format
  216.         $vat_rates = json_decode(get_value('body', $eu_vat_response), true);
  217.         if($vat_rates === null) {
  218.             $this->log(sprintf(__('Unexpected response returned by EU VAT rates site. Returned data: "%s". Remote site: "%s".', 'wc-aelia-eu-vat-assistant'),
  219.                                                  get_value('body', $eu_vat_response),
  220.                                                  $eu_vat_url));
  221.             return null;
  222.         }
  223.         // Add rates for countries that use other countries' tax rates
  224.         // Monaco uses French VAT
  225.         $vat_rates['rates']['MC'] = $vat_rates['rates']['FR'];
  226.         $vat_rates['rates']['MC']['country'] = 'Monaco';
  227.  
  228.         // Isle of Man uses UK's VAT
  229.         $vat_rates['rates']['IM'] = $vat_rates['rates']['UK'];
  230.         $vat_rates['rates']['IM']['country'] = 'Isle of Man';
  231.  
  232.         // Fix the country codes received from the feed. Some country codes are
  233.         // actually the VAT country code. We need the ISO Code instead.
  234.         $country_codes_to_fix = array(
  235.             'EL' => 'GR',
  236.             'UK' => 'GB',
  237.         );
  238.         foreach($country_codes_to_fix as $code => $correct_code) {
  239.             $vat_rates['rates'][$correct_code] = $vat_rates['rates'][$code];
  240.             unset($vat_rates['rates'][$code]);
  241.         }
  242.  
  243.         /* Fix the VAT rates for countries that don't have a reduced VAT rate. For
  244.          * those countries, the standard rate should be used as the "reduced" rate.
  245.          *
  246.          * @since 1.4.15.151029
  247.          */
  248.         foreach($vat_rates['rates'] as $country_code => $rates) {
  249.             if(!is_numeric($rates['reduced_rate'])) {
  250.                 $rates['reduced_rate'] = $rates['standard_rate'];
  251.             }
  252.             $vat_rates['rates'][$country_code] = $rates;
  253.         }
  254.  
  255.         ksort($vat_rates['rates']);
  256.  
  257.         // Debug
  258.         //var_dump($vat_rates);die();
  259.  
  260.         // Ensure that the VAT rates are valid before caching them
  261.         if($this->valid_eu_vat_rates($vat_rates['rates'])) {
  262.             // Cache the VAT rates, to prevent unnecessary calls to the remote site
  263.             set_transient(Definitions::TRANSIENT_EU_VAT_RATES, $vat_rates, 2 * HOUR_IN_SECONDS);
  264.         }
  265.         return $vat_rates;
  266.     }
  267.  
  268.     /**
  269.      * Stores the list of countries to which sales are allowed, before the plugin
  270.      * alters it.
  271.      */
  272.     protected function store_allowed_sale_countries() {
  273.         $this->allowed_sale_countries = wc()->countries->get_allowed_countries();
  274.     }
  275.  
  276.     /**
  277.      * Returns the list of countries to which sales are not allowed.
  278.      * @return array
  279.      */
  280.     protected function get_sale_disallowed_countries() {
  281.         return $this->_settings_controller->get(Settings::FIELD_SALE_DISALLOWED_COUNTRIES, array());
  282.     }
  283.  
  284.     /**
  285.      * Performs operation when woocommerce has been loaded.
  286.      */
  287.     public function woocommerce_loaded() {
  288.         if(!is_admin()) {
  289.             // Add logic to filter the countries to which sales are allowed
  290.             $this->store_allowed_sale_countries();
  291.             $restricted_countries = $this->get_sale_disallowed_countries();
  292.             if(!empty($restricted_countries)) {
  293.                 add_filter('pre_option_woocommerce_allowed_countries', array($this, 'pre_option_woocommerce_allowed_countries'), 10, 1);
  294.                 add_filter('woocommerce_countries_allowed_countries', array($this, 'woocommerce_countries_allowed_countries'), 10, 1);
  295.             }
  296.         }
  297.  
  298.         // Store shop base country for later use. This is necessary to prevent a
  299.         // conflict with the Tax Display plugin, which may override the base country
  300.         // at checkout
  301.         $this->shop_base_country = $this->wc()->countries->get_base_country();
  302.     }
  303.  
  304.     /**
  305.      * Sets the hooks required by the plugin.
  306.      */
  307.     protected function set_hooks() {
  308.         parent::set_hooks();
  309.  
  310.         if(is_admin() && !$this->plugin_configured()) {
  311.             add_action('admin_notices', array($this, 'settings_notice'));
  312.             // Don't set any hook until the plugin has been configured
  313.             return;
  314.         }
  315.  
  316.         if(!is_admin() || self::doing_ajax()) {
  317.             // Set hooks that should be used on frontend only
  318.             Frontend_Integration::init();
  319.         }
  320.  
  321.         if(is_admin()) {
  322.             // Set hooks that should be used on backend only
  323.             $this->set_admin_hooks();
  324.         }
  325.  
  326.         // Order actions
  327.         add_action('woocommerce_checkout_update_order_meta', array($this, 'woocommerce_checkout_update_order_meta'), 20, 2);
  328.         add_action('woocommerce_checkout_update_user_meta', array($this, 'woocommerce_checkout_update_user_meta'), 20, 2);
  329.         add_action('woocommerce_checkout_billing', array($this, 'woocommerce_checkout_billing'), 40, 1);
  330.         // Update VAT data for subscription renewals
  331.         add_action('woocommerce_subscriptions_renewal_order_created', array($this, 'woocommerce_subscriptions_renewal_order_created'), 20, 2);
  332.  
  333.         // Checkout hooks
  334.         add_action('woocommerce_checkout_update_order_review', array($this, 'woocommerce_checkout_update_order_review'));
  335.         add_action('woocommerce_checkout_process', array($this, 'woocommerce_checkout_process'));
  336.  
  337.         // Ajax hooks
  338.         add_action('wp_ajax_validate_eu_vat_number', array($this, 'wp_ajax_validate_eu_vat_number'));
  339.         add_action('wp_ajax_nopriv_validate_eu_vat_number', array($this, 'wp_ajax_validate_eu_vat_number'));
  340.  
  341.         // Cron hooks
  342.         // Add hooks to automatically update exchange rates
  343.         add_action($this->_settings_controller->exchange_rates_update_hook(),
  344.                              array($this->_settings_controller, 'scheduled_update_exchange_rates'));
  345.  
  346.         // Integration with Currency Switcher
  347.         add_action('wc_aelia_currencyswitcher_settings_saved', array($this, 'wc_aelia_currencyswitcher_settings_saved'));
  348.  
  349.         // UI
  350.         add_action('add_meta_boxes', array($this, 'add_meta_boxes'));
  351.         add_action('woocommerce_order_formatted_billing_address', array($this, 'woocommerce_order_formatted_billing_address'), 10, 2);
  352.         add_action('woocommerce_formatted_address_replacements', array($this, 'woocommerce_formatted_address_replacements'), 10, 2);
  353.         add_action('woocommerce_localisation_address_formats', array($this, 'woocommerce_localisation_address_formats'), 10, 1);
  354.  
  355.         // Hooks to be called by 3rd party
  356.         // Allow 3rd parties to convert a value from one currency to another
  357.         add_filter('wc_aelia_eu_vat_assistant_convert', array($this, 'convert'), 10, 4);
  358.         add_filter('wc_aelia_eu_vat_assistant_get_order_exchange_rate', array($this, 'wc_aelia_eu_vat_assistant_get_order_exchange_rate'), 10, 2);
  359.         add_filter('wc_aelia_eu_vat_assistant_get_setting', array($this, 'wc_aelia_eu_vat_assistant_get_setting'), 10, 2);
  360.  
  361.         // Reports
  362.         ReportsManager::init();
  363.     }
  364.  
  365.     protected function set_admin_hooks() {
  366.         Tax_Settings_Integration::set_hooks();
  367.         Products_Integration::init();
  368.     }
  369.  
  370.     /**
  371.      * Determines if one of plugin's admin pages is being rendered. Override it
  372.      * if plugin implements pages in the Admin section.
  373.      *
  374.      * @return bool
  375.      */
  376.     protected function rendering_plugin_admin_page() {
  377.         $screen = get_current_screen();
  378.         $page_id = $screen->id;
  379.  
  380.         return ($page_id == 'woocommerce_page_' . Definitions::MENU_SLUG);
  381.     }
  382.  
  383.     /**
  384.      * Registers the script and style files needed by the admin pages of the
  385.      * plugin. Extend in descendant plugins.
  386.      */
  387.     protected function register_plugin_admin_scripts() {
  388.         // Scripts
  389.         wp_register_script('chosen',
  390.                                              '//cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.jquery.min.js',
  391.                                              array('jquery'),
  392.                                              null,
  393.                                              true);
  394.  
  395.         // Styles
  396.         wp_register_style('chosen',
  397.                                                 '//cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.min.css',
  398.                                                 array(),
  399.                                                 null,
  400.                                                 'all');
  401.         // WordPress already includes jQuery UI script, but no CSS for it. Therefore,
  402.         // we need to load it from an external source
  403.         wp_register_style('jquery-ui',
  404.                                             '//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css',
  405.                                             array(),
  406.                                             null,
  407.                                             'all');
  408.  
  409.         wp_enqueue_style('jquery-ui');
  410.         wp_enqueue_style('chosen');
  411.  
  412.         wp_enqueue_script('jquery-ui-tabs');
  413.         wp_enqueue_script('jquery-ui-sortable');
  414.         wp_enqueue_script('chosen');
  415.  
  416.         parent::register_plugin_admin_scripts();
  417.     }
  418.  
  419.     /**
  420.      * Loads the scripts required in the Admin section.
  421.      */
  422.     public function load_admin_scripts() {
  423.         parent::load_admin_scripts();
  424.         $this->localize_admin_scripts();
  425.     }
  426.  
  427.     /**
  428.      * Loads the settings that will be used by the admin scripts.
  429.      */
  430.     protected function localize_admin_scripts() {
  431.         // Prepare parameters for common admin scripts
  432.         $admin_scripts_params = array();
  433.  
  434.         // Prepare parameters for Tax Settings pages
  435.         if((get_value('page', $_GET) == 'wc-settings') &&
  436.              (get_value('tab', $_GET) == 'tax')) {
  437.             $admin_scripts_params = Tax_Settings_Integration::localize_admin_scripts($admin_scripts_params);
  438.         }
  439.  
  440.         // Add localization parameters for EU VAT plugin settings page
  441.         if($this->rendering_plugin_admin_page()) {
  442.             $admin_scripts_params = array_merge($admin_scripts_params, array(
  443.                 'eu_vat_countries' => $this->get_eu_vat_countries(),
  444.                 'user_interface' => array(
  445.                     'add_eu_countries_trigger' => __('Add European Union countries', 'wc-aelia-eu-vat-assistant'),
  446.                 ),
  447.             ));
  448.         }
  449.  
  450.         wp_localize_script(static::$plugin_slug . '-admin-common',
  451.                                              'aelia_eu_vat_assistant_admin_params',
  452.                                              $admin_scripts_params);
  453.     }
  454.  
  455.     /**
  456.      * Loads Styles and JavaScript for the frontend. Extend as needed in
  457.      * descendant classes.
  458.      */
  459.     public function load_frontend_scripts() {
  460.         // Enqueue the required Frontend stylesheets
  461.         wp_enqueue_style(static::$plugin_slug . '-frontend');
  462.  
  463.         // JavaScript
  464.         wp_enqueue_script(static::$plugin_slug . '-frontend');
  465.  
  466.         $this->localize_frontend_scripts();
  467.     }
  468.  
  469.     /**
  470.      * Loads the settings that will be used by the frontend scripts.
  471.      */
  472.     protected function localize_frontend_scripts() {
  473.         // Prepare parameters for common admin scripts
  474.  
  475.         // Build the list of countries for which the EU VAT field will be displayed
  476.         // at checkout
  477.         $eu_vat_countries = $this->get_eu_vat_countries();
  478.  
  479.         $frontend_scripts_params = array(
  480.             'tax_based_on' => get_option('woocommerce_tax_based_on'),
  481.             'eu_vat_countries' => $eu_vat_countries,
  482.             'ajax_url' => admin_url('admin-ajax.php', 'relative'),
  483.             'show_self_cert_field' => self::settings()->get(Settings::FIELD_SHOW_SELF_CERTIFICATION_FIELD),
  484.             'eu_vat_field_required' => self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED),
  485.             'hide_self_cert_field_with_valid_vat' => self::settings()->get(Settings::FIELD_HIDE_SELF_CERTIFICATION_FIELD_VALID_VAT_NUMBER),
  486.             'ip_address_country' => $this->get_ip_address_country(),
  487.             'use_shipping_as_evidence' => self::settings()->get(Settings::FIELD_USE_SHIPPING_ADDRESS_AS_EVIDENCE),
  488.             'user_interface' => array(
  489.                 'self_certification_field_title' => __(self::settings()->get(Settings::FIELD_SELF_CERTIFICATION_FIELD_TITLE), self::$text_domain),
  490.             ),
  491.             'show_eu_vat_number_for_base_country' => (bool)self::settings()->get(Settings::FIELD_SHOW_EU_VAT_FIELD_IF_CUSTOMER_IN_BASE_COUNTRY),
  492.             'shop_base_country' => $this->shop_base_country,
  493.         );
  494.  
  495.         wp_localize_script(static::$plugin_slug . '-frontend',
  496.                                              'aelia_eu_vat_assistant_params',
  497.                                              $frontend_scripts_params);
  498.     }
  499.  
  500.     /**
  501.      * Registers the script and style files required in the backend (even outside
  502.      * of plugin's pages). Extend in descendant plugins.
  503.      */
  504.     protected function register_common_admin_scripts() {
  505.         parent::register_common_admin_scripts();
  506.  
  507.         // The admin styles of this plugin are required throughout the WooCommerce
  508.         // Administration, not just on the plugin settings page
  509.         wp_register_style(static::$plugin_slug . '-admin',
  510.                                             $this->url('plugin') . '/design/css/admin.css',
  511.                                             array(),
  512.                                             null,
  513.                                             'all');
  514.         wp_enqueue_style(static::$plugin_slug . '-admin');
  515.  
  516.         // Load common JavaScript for the Admin section
  517.         wp_enqueue_script(static::$plugin_slug . '-admin-common',
  518.                                             $this->url('js') . '/admin/admin-common.js',
  519.                                             array('jquery'),
  520.                                             null,
  521.                                             true);
  522.  
  523.         if((get_value('page', $_GET) == 'wc-reports') &&
  524.              in_array(get_value('report', $_GET), array('eu_vat_by_country_report'))) {
  525.             // Load JavaScript for reports
  526.             wp_enqueue_script(static::$plugin_slug . '-jquery-bbq',
  527.                                                 $this->url('js') . '/admin/jquery.ba-bbq.min.js',
  528.                                                 array('jquery'),
  529.                                                 null,
  530.                                                 true);
  531.             wp_enqueue_script(static::$plugin_slug . '-admin-reports',
  532.                                                 $this->url('js') . '/admin/admin-reports.js',
  533.                                                 array('jquery'),
  534.                                                 null,
  535.                                                 true);
  536.         }
  537.     }
  538.  
  539.     /**
  540.      * Adds an error to WooCommerce, so that it can be displayed to the customer.
  541.      *
  542.      * @param string error_message The error message to display.
  543.      */
  544.     protected function add_woocommerce_error($error_message) {
  545.         wc_add_notice($error_message, 'error');
  546.     }
  547.  
  548.     /**
  549.      * Converts an amount from a Currency to another.
  550.      *
  551.      * @param float amount The amount to convert.
  552.      * @param string from_currency The source Currency.
  553.      * @param string to_currency The destination Currency.
  554.      * @param int price_decimals The amount of decimals to use when rounding the
  555.      * converted result.
  556.      * @return float The amount converted in the destination currency.
  557.      */
  558.     public function convert($amount, $from_currency, $to_currency, $price_decimals = null) {
  559.         // No need to try converting an amount that is not numeric. This can happen
  560.         // quite easily, as "no value" is passed as an empty string
  561.         if(!is_numeric($amount)) {
  562.             return $amount;
  563.         }
  564.  
  565.         // No need to spend time converting a currency to itself
  566.         if($from_currency == $to_currency) {
  567.             return $amount;
  568.         }
  569.  
  570.         if(empty($price_decimals)) {
  571.             $price_decimals = absint(get_option('woocommerce_price_num_decimals'));
  572.         }
  573.  
  574.         // Retrieve exchange rates from the configuration
  575.         $exchange_rates = $this->_settings_controller->get_exchange_rates();
  576.         //var_dump($exchange_rates);
  577.         try {
  578.             $error_message_template = __('Currency conversion - %s currency not valid or exchange rate ' .
  579.                                                                      'not found for: "%s". Please make sure that the EU VAT assistant '.
  580.                                                                      'plugin is configured correctly and that an Exchange ' .
  581.                                                                      'Rate has been specified for each of the available currencies.', 'wc-aelia-eu-vat-assistant');
  582.             $from_currency_rate = get_value($from_currency, $exchange_rates, null);
  583.             if(empty($from_currency_rate)) {
  584.                 $error_message = sprintf($error_message_template,
  585.                                                                  __('Source', 'wc-aelia-eu-vat-assistant'),
  586.                                                                  $from_currency);
  587.                 $this->log($error_message, false);
  588.                 if($this->debug_mode()) {
  589.                     throw new InvalidArgumentException($error_message);
  590.                 }
  591.             }
  592.  
  593.             $to_currency_rate = get_value($to_currency, $exchange_rates, null);
  594.             if(empty($to_currency_rate)) {
  595.                 $error_message = sprintf($error_message_template,
  596.                                                                  __('Destination', 'wc-aelia-eu-vat-assistant'),
  597.                                                                  $from_currency);
  598.                 $this->log($error_message, false);
  599.                 if($this->debug_mode()) {
  600.                     throw new InvalidArgumentException($error_message);
  601.                 }
  602.             }
  603.  
  604.             $exchange_rate = $to_currency_rate / $from_currency_rate;
  605.         }
  606.         catch(Exception $e) {
  607.             $full_message = $e->getMessage() .
  608.                                             sprintf(__('Stack trace: %s', 'wc-aelia-eu-vat-assistant'),
  609.                                                             $e->getTraceAsString());
  610.             $this->log($full_message, false);
  611.             trigger_error($full_message, E_USER_ERROR);
  612.         }
  613.         return round($amount * $exchange_rate, $price_decimals);
  614.     }
  615.  
  616.     /**
  617.      * Returns the exchange rate used to calculate the VAT for an order in the
  618.      * currency that has to be used for VAT returns.
  619.      *
  620.      * @param int order_id The ID of the order from which to retrieve the exchange
  621.      * rate.
  622.      * @return float|false The exchange rate, if present, or false if it was not
  623.      * saved against the order.
  624.      */
  625.     public function get_order_vat_exchange_rate($order_id) {
  626.         $order = new Order($order_id);
  627.         $vat_exchange_rate = $order->get_vat_data('vat_currency_exchange_rate');
  628.         return $vat_exchange_rate;
  629.     }
  630.  
  631.     /**
  632.      * Returns a full VAT number, inclusive of country prefix.
  633.      *
  634.      * @param string country A country code.
  635.      * @param string vat_number A VAT number.
  636.      * @param string A full VAT number, with the country prefix (e.g. IE1234567X)
  637.      * @since 1.3.20.150330
  638.      */
  639.     public function get_full_vat_number($country, $vat_number) {
  640.         $vat_number = $this->eu_vat_validation()->parse_vat_number($vat_number);
  641.         // If an invalid VAT number was passed, return an empty string
  642.         if(!$vat_number) {
  643.             return false;
  644.         }
  645.         $vat_prefix = $this->eu_vat_validation()->get_vat_prefix($country);
  646.         return $vat_prefix . $vat_number;
  647.     }
  648.  
  649.     /**
  650.      * Saves the evidence used to determine the VAT rate to apply.
  651.      *
  652.      * @param Aelia_Order order The order to process.
  653.      */
  654.     protected function save_eu_vat_data($order_id) {
  655.         // Save the VAT number details
  656.         $order_vat_number = $this->get_full_vat_number($this->vat_country, $this->vat_number);
  657.         if($order_vat_number == false) {
  658.             $this->log(sprintf(__('Order ID "%s". Invalid VAT Number parsed: "%s".', 'wc-aelia-eu-vat-assistant'),
  659.                                                  $order_id,
  660.                                                  $order_vat_number), true);
  661.             $order_vat_number = '';
  662.         }
  663.         update_post_meta($order_id, 'vat_number', $order_vat_number);
  664.         update_post_meta($order_id, '_vat_country', $this->vat_country);
  665.         update_post_meta($order_id, '_vat_number_validated', $this->vat_number_validated);
  666.         // Store customer's self-certification flag
  667.         if(get_value(Definitions::ARG_LOCATION_SELF_CERTIFICATION, $_POST, 0)) {
  668.             $location_self_certified = Definitions::YES;
  669.         }
  670.         else {
  671.             $location_self_certified = Definitions::NO;
  672.         }
  673.         update_post_meta($order_id, '_customer_location_self_certified', $location_self_certified);
  674.  
  675.         // Use plugins' internal Order class, which implements convenience methods
  676.         // to handle EU VAT reguirements
  677.         $order = new Order($order_id);
  678.         // Generate and store details about order VAT
  679.         $order->update_vat_data();
  680.         // Save EU VAT compliance evidence
  681.         $order->store_vat_evidence();
  682.     }
  683.  
  684.     /**
  685.      * Renders the EU VAT field using the appropriate view.
  686.      */
  687.     protected function render_eu_vat_field() {
  688.         include_once($this->path('views') . '/frontend/checkout-eu-vat-field.php');
  689.     }
  690.  
  691.     /**
  692.      * Renders the self-certification field using the appropriate view.
  693.      */
  694.     protected function render_self_certification_field() {
  695.         include_once($this->path('views') . '/frontend/checkout-self-certification-field.php');
  696.     }
  697.  
  698.     /**
  699.      * Validates an EU VAT number.
  700.      *
  701.      * @param string country A country code.
  702.      * @param string vat_number A VAT number.
  703.      * @return array An array with the result of the validation.
  704.      */
  705.     protected function validate_eu_vat_number($country, $vat_number) {
  706.         $validation_result = $this->eu_vat_validation()->validate_vat_number($country, $vat_number);
  707.         // TODO Refactor the communication with the EU_VAT_Validation class to make it easier to replace it with other classes
  708.  
  709.         // A validation result of "false" indicates a failure in sending the SOAP
  710.         // request
  711.         if($validation_result == false) {
  712.             $response = array(
  713.                 'valid' => false,
  714.                 'vies_response' => null,
  715.                 'errors' => $this->eu_vat_validation()->get_errors(),
  716.             );
  717.         }
  718.         else {
  719.             $vat_number_valid = false;
  720.  
  721.             // Extract the error message, if any
  722.             $validation_error = get_value('FaultString', $validation_result);
  723.             if(!empty($validation_error)) {
  724.                 // If server was busy, we may have to accept the VAT number as valid,
  725.                 // depending on plugin settings
  726.                 if(strcasecmp($validation_error, 'SERVER_BUSY') == 0) {
  727.                     $vat_number_valid = self::settings()->get(Settings::FIELD_ACCEPT_VAT_NUMBER_WHEN_VALIDATION_SERVER_BUSY, true);
  728.                 }
  729.             }
  730.             else {
  731.                 // The VAT number is valid if the response contains 'valid' = 'true'
  732.                 $vat_number_valid = (strcasecmp(get_value('valid', $validation_result), 'true') == 0);
  733.             }
  734.  
  735.             if($vat_number_valid) {
  736.                 // With a valid VAT number, the response contains the validation result
  737.                 $response = array(
  738.                     'valid' => true,
  739.                     'vies_response' => $validation_result,
  740.                 );
  741.             }
  742.             else {
  743.                 // With an invalid VAT number, the response will contain the errors
  744.                 // returned by the VIES server
  745.                 $response = array(
  746.                     'valid' => false,
  747.                     'errors' => $validation_result,
  748.                 );
  749.             }
  750.         }
  751.  
  752.         $this->log(sprintf(__('EU VAT validation response (JSON): "%s".', 'wc-aelia-eu-vat-assistant'),
  753.                                              json_encode($response)),
  754.                              false);
  755.         return $response;
  756.     }
  757.  
  758.     /**
  759.      * Determines if a country is part of the EU.
  760.      *
  761.      * @param string country The country code to check.
  762.      * @return bool
  763.      */
  764.     public function is_eu_country($country) {
  765.         return in_array($country, $this->get_eu_vat_countries());
  766.     }
  767.  
  768.     /**
  769.      * Determines if there is enough evidence about customer's location to satisfy
  770.      * EU requirements. The method collects all the country codes that can be derived
  771.      * from the data posted at checkout, and looks for one that occurs twice or
  772.      * more. As per EU regulations, we only need two matching pieces of evidence.
  773.      *
  774.      * @param array posted_data The data posted at checkout.
  775.      * @return string|bool The code of the country with the most entries, if one
  776.      * with at least two occurrences is found, or false if no country appears more
  777.      * than once.
  778.      */
  779.     protected function sufficient_location_evidence($posted_data) {
  780.         // Collect all the countries we can get from the posted data
  781.         $countries = array();
  782.  
  783.         $countries[] = $billing_country = get_value('billing_country', $posted_data);
  784.         $countries[] = $this->get_ip_address_country();
  785.  
  786.         // Take shipping country as evidence only if explicitly told so
  787.         if(self::settings()->get(Settings::FIELD_USE_SHIPPING_ADDRESS_AS_EVIDENCE)) {
  788.             if(get_value('ship_to_different_address', $posted_data)) {
  789.                 $countries[] = get_value('shipping_country', $posted_data);
  790.             }
  791.             else {
  792.                 // If shipping to the same address as billing, add the billing country again
  793.                 // as a further proof of location
  794.                 $countries[] = $billing_country;
  795.             }
  796.         }
  797.  
  798.         $countries = array_filter(apply_filters('wc_aelia_eu_vat_assistant_location_evidence_countries', $countries));
  799.  
  800.         // Calculate how many times each country appears in the list and sort the
  801.         // array by count, in descending order, so that the country with most matches
  802.         // is at the top
  803.         $country_count = array_count_values($countries);
  804.         arsort($country_count);
  805.         reset($country_count);
  806.  
  807.         // We only need at least two matching entries in the array. If that is the
  808.         // case, return the country code (it may be useful later, and it's better than
  809.         // a simple "true")
  810.         $top_country = key($country_count);
  811.         if($country_count[$top_country] >= 2) {
  812.             return $top_country;
  813.         }
  814.         // If the top country doesn't have at least a count of two, then the other
  815.         // entries won't have it either. In such case, inform the caller that we
  816.         // don't have sufficient evidence
  817.         return false;
  818.     }
  819.  
  820.     /**
  821.      * Sets customer's VAT exemption, depending on his country and the VAT number
  822.      * he entered.
  823.      *
  824.      * @param string country The country against which the VAT exemption will be checked.
  825.      * @param string vat_number The VAT number entered by the customer.
  826.      * @return int A numeric result code indicating if the check succeeded, whether
  827.      * the customer is VAT exempt or not, or failed (i.e. when an EU customer
  828.      * entered an invalid EU VAT number).
  829.      */
  830.     protected function set_customer_vat_exemption($customer_country, $vat_number) {
  831.         $this->log(sprintf(__('Setting customer VAT exemption. Customer country: "%s". Number: "%s".', 'wc-aelia-eu-vat-assistant'),
  832.                                              $customer_country, $vat_number), false);
  833.         $result = Definitions::RES_OK;
  834.         // Assume that customer is not VAT exempt. This is a sensible default, as
  835.         // exemption applies only in specific cases, and only for EU customers.
  836.         // Customers outside the EU are not technically "VAT exempt". To them, a
  837.         // special "Zero VAT" rate applies
  838.         $customer_vat_exemption = false;
  839.         // Clear VAT information before validation
  840.         $this->vat_country = '';
  841.         $this->vat_number = '';
  842.         $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_NO_NUMBER;
  843.  
  844.         // If VAT number was hidden, customer cannot be made VAT exempt. We can skip
  845.         // the checks, in such case
  846.         if(self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED) != Settings::OPTION_EU_VAT_NUMBER_FIELD_HIDDEN) {
  847.             // No need to check the VAT number if either the country or the number are empty
  848.             if(!empty($customer_country) && !empty($vat_number)) {
  849.                 // Store the VAT information. They will be used later, to update the order
  850.                 // when it's finalised
  851.                 $this->vat_country = $customer_country;
  852.                 $this->vat_number = $vat_number;
  853.  
  854.                 // Customer is based in the European Union, therefore EU VAT rules and validations apply
  855.                 if($this->is_eu_country($customer_country)) {
  856.                     // Validate the VAT number for EU customers
  857.                     $validation_result = $this->validate_eu_vat_number($customer_country, $vat_number);
  858.  
  859.                     // Debug
  860.                     //var_dump($validation_result);
  861.  
  862.                     // If the EU VAT number is valid, we must determine if the customer should
  863.                     // be considered exempt from VAT
  864.                     if($validation_result['valid'] == true) {
  865.                         /* An EU customer will be considered exempt from VAT if:
  866.                          * - He is located in a country different from shop's base country.
  867.                          * - He is located in the same country as the shop, and option "remove VAT
  868.                          *   when customer in located in shop's base country" is enabled.
  869.                          */
  870.                         if(($customer_country != $this->shop_base_country) ||
  871.                              self::settings()->get(Settings::FIELD_REMOVE_VAT_IF_CUSTOMER_IN_BASE_COUNTRY)) {
  872.                             $customer_vat_exemption = true;
  873.                         }
  874.                         $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_VALID;
  875.                     }
  876.                     else {
  877.                         // An empty VIES response means that nothing was returned by the
  878.                         // server. In such case, log an error
  879.                         if(empty($validation_result['vies_response'])) {
  880.                             $this->log(sprintf(__('EU VAT Number could not be validated due to errors in ' .
  881.                                                                         'the communication with the VIES service. Error details ' .
  882.                                                                         '(JSON): "%s".', 'wc-aelia-eu-vat-assistant'),
  883.                                                                  json_encode($validation_result['errors'])));
  884.                             $this->vat_number_validated = Definitions::VAT_NUMBER_COULD_NOT_BE_VALIDATED;
  885.                             $result = Definitions::ERR_COULD_NOT_VALIDATE_VAT_NUMBER;
  886.                         }
  887.                         else {
  888.                             $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_NOT_VALID;
  889.                             $result = Definitions::ERR_INVALID_EU_VAT_NUMBER;
  890.                         }
  891.                     }
  892.                 }
  893.                 else {
  894.                     $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_NON_EU;
  895.                 }
  896.             }
  897.         }
  898.         else {
  899.             $this->log(__('EU VAT Number field was hidden by plugin configuration. Customer ' .
  900.                                         'cannot be made VAT exempt.', 'wc-aelia-eu-vat-assistant'));
  901.         }
  902.         $this->log(sprintf(__('VAT exemption check completed. Customer exemption: %d. Result: %d.', 'wc-aelia-eu-vat-assistant'),
  903.                                              (int)$customer_vat_exemption,
  904.                                              $result));
  905.  
  906.         $this->wc()->customer->set_is_vat_exempt($customer_vat_exemption);
  907.         return $result;
  908.     }
  909.  
  910.     /**
  911.      * Updates the order meta after it has been created, or updated, at checkout.
  912.      *
  913.      * @param int order_id The order just created, or updated.
  914.      * @param array posted_data The data posted to create the order.
  915.      */
  916.     public function woocommerce_checkout_update_order_meta($order_id, $posted_data) {
  917.         $this->save_eu_vat_data($order_id);
  918.     }
  919.  
  920.     /**
  921.      * Updates the metadata of a renewal order created by the Subscriptions plugin.
  922.      *
  923.      * @param WC_Order $renewal_order The renewal order.
  924.      * @param WC_Order $original_order The original order used to create the renewal order.
  925.      */
  926.     public function woocommerce_subscriptions_renewal_order_created($renewal_order, $original_order) {
  927.         // If we are processing checkout, no action needs to be taken. Handler for
  928.         // "woocommerce_checkout_update_order_meta" hook will take care of saving the
  929.         // necessary data
  930.         if(is_checkout()) {
  931.             return;
  932.         }
  933.  
  934.         // Use the internal Order classes
  935.         $original_order = new Order($original_order->id);
  936.         $renewal_order = new Order($renewal_order->id);
  937.  
  938.         // Copy the VAT evidence from the original subscription order. A renewal order
  939.         // is created without customer's intervention, therefore
  940.         $original_order_vat_info = array(
  941.             Order::META_EU_VAT_EVIDENCE => $original_order->eu_vat_evidence,
  942.             'vat_number' => $original_order->get_customer_vat_number(),
  943.             '_vat_country' => $original_order->vat_country,
  944.             '_vat_number_validated' => $original_order->vat_number_validated,
  945.             '_customer_location_self_certified' => $original_order->customer_location_self_certified,
  946.         );
  947.         $original_order_vat_info = apply_filters('wc_aelia_eu_vat_assistant_subscription_original_order_vat_info', $original_order_vat_info, $original_order, $renewal_order);
  948.         foreach($original_order_vat_info as $meta_key => $value) {
  949.             $renewal_order->set_meta($meta_key, $value) ;
  950.         }
  951.         // Generate and store details about order VAT
  952.         $renewal_order->update_vat_data();
  953.     }
  954.  
  955.     /**
  956.      * Updates the customer meta at checkout.
  957.      *
  958.      * @param int user_id The user ID who placed the order.
  959.      * @param array posted_data The data posted to create the order.
  960.      */
  961.     public function woocommerce_checkout_update_user_meta($user_id, $posted_data) {
  962.         update_user_meta($user_id, 'vat_number', $this->get_full_vat_number($this->vat_country, $this->vat_number));
  963.         update_user_meta($user_id, '_vat_country', $this->vat_country);
  964.         update_user_meta($user_id, '_vat_number_validated', $this->vat_number_validated);
  965.     }
  966.  
  967.     /**
  968.      * Renders the EU VAT field on the checkout form.
  969.      */
  970.     public function woocommerce_checkout_billing() {
  971.         // Render EU VAT Number element, unless it's hidden
  972.         if(self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED) != Settings::OPTION_EU_VAT_NUMBER_FIELD_HIDDEN) {
  973.             $this->render_eu_vat_field();
  974.         }
  975.  
  976.         // Render self-certification element, unless it's hidden
  977.         if(self::settings()->get(Settings::FIELD_SHOW_SELF_CERTIFICATION_FIELD) != Settings::OPTION_SELF_CERTIFICATION_FIELD_NO) {
  978.             $this->render_self_certification_field();
  979.         }
  980.     }
  981.  
  982.     /**
  983.      * Ajax handler. Validates an EU VAT number using VIES service and outputs
  984.      * a JSON response with the validation result.
  985.      */
  986.     public function wp_ajax_validate_eu_vat_number() {
  987.         // Validate EU VAT number and return the result as JSON
  988.         wp_send_json($this->validate_eu_vat_number(
  989.             get_value(Definitions::ARG_COUNTRY, $_GET),
  990.             get_value(Definitions::ARG_VAT_NUMBER, $_GET)
  991.         ));
  992.     }
  993.  
  994.     /**
  995.      * Performs VAT validation operations during order review.
  996.      *
  997.      * @param array form_data The data posted from the order review page.
  998.      */
  999.     public function woocommerce_checkout_update_order_review($form_data) {
  1000.         // $form_data contains an HTTP query string. Let's "explode" it into an
  1001.         // array
  1002.         parse_str($form_data, $posted_data);
  1003.  
  1004.         // Debug
  1005.         //var_dump("POSTED DATA", $posted_data);die();
  1006.  
  1007.         // Cannot continue without the billing country
  1008.         if(empty($posted_data['billing_country'])) {
  1009.             return;
  1010.         }
  1011.  
  1012.         // Determine which country will be used to calculate taxes
  1013.         switch(get_option('woocommerce_tax_based_on')) {
  1014.             case 'billing' :
  1015.             case 'base' :
  1016.                 $country = !empty($posted_data['billing_country']) ? $posted_data['billing_country'] : '';
  1017.             break;
  1018.             case 'shipping' :
  1019.                 if(get_value('ship_to_different_address', $posted_data) && !empty($posted_data['shipping_country'])) {
  1020.                     $country = $posted_data['shipping_country'];
  1021.                 }
  1022.                 else {
  1023.                     $country = $posted_data['billing_country'];
  1024.                 }
  1025.             break;
  1026.         }
  1027.  
  1028.         // Check if customer is VAT exempt
  1029.         $country = woocommerce_clean($country);
  1030.         if(empty($country)) {
  1031.             $this->log(sprintf(__('Unexpected condition occurred: no customer country was posted ' .
  1032.                                                         'to "checkout update order review" event. Full posted data ' .
  1033.                                                         '(JSON): "%s".', 'wc-aelia-eu-vat-assistant'),
  1034.                                                  json_encode($posted_data)), false);
  1035.         }
  1036.         $vat_number = woocommerce_clean(get_value('vat_number', $posted_data));
  1037.         $this->set_customer_vat_exemption($country, $vat_number);
  1038.     }
  1039.  
  1040.     /**
  1041.      * Performs validations to make sure that either there is enough evidence to
  1042.      * determine customer's location, or customer had self-certified his location.
  1043.      * This method automatically adds a checkout error when needed.
  1044.      *
  1045.      * @return void
  1046.      */
  1047.     protected function validate_self_certification() {
  1048.         // If the self-certification element was forcibly hidden, it doesn't make
  1049.         // sense to perform this validation
  1050.         if(self::settings()->get(Settings::FIELD_SHOW_SELF_CERTIFICATION_FIELD) == Settings::OPTION_SELF_CERTIFICATION_FIELD_NO) {
  1051.             return;
  1052.         }
  1053.  
  1054.         /* We need to check the available gelocation evidence in two cases:
  1055.          * - When customer is not VAT exempt.
  1056.          * - Regardless of the VAT number, if option "hide self certification field
  1057.          *   with valid VAT number" is DISABLED.
  1058.          *
  1059.          * In all other cases, there must be sufficient evidence for the order to
  1060.          * go through, or customer must self-certify.
  1061.          */
  1062.         if(($this->wc()->customer->is_vat_exempt == false) ||
  1063.              (self::settings()->get(Settings::FIELD_HIDE_SELF_CERTIFICATION_FIELD_VALID_VAT_NUMBER) == false)) {
  1064.             // Check if we have sufficient evidence about customer's location
  1065.             $sufficient_location_evidence_result = $this->sufficient_location_evidence($_POST);
  1066.             if($sufficient_location_evidence_result == false) {
  1067.                 // Convenience variable, to better understand the check below
  1068.                 $ignore_insufficient_evidence = ($this->vat_number_validated == Definitions::VAT_NUMBER_VALIDATION_VALID);
  1069.                 /* If insufficient location evidence has been provided, check if self-certification
  1070.                  * is required to accept the order. If it is required, and it was not provided,
  1071.                  * stop the checkout and inform the customer.
  1072.                  */
  1073.                 if(!$ignore_insufficient_evidence &&
  1074.                      self::settings()->get(Settings::FIELD_SELF_CERTIFICATION_FIELD_REQUIRED_WHEN_CONFLICT) &&
  1075.                     (get_value(Definitions::ARG_LOCATION_SELF_CERTIFICATION, $_POST) == false)) {
  1076.                     // Inform the customer that he must self-certify to proceed with the purchase
  1077.                     $error = __('Unfortunately, we could not collect sufficient information to confirm ' .
  1078.                                             'your location. To proceed with the order, please tick the box below the ' .
  1079.                                             'billing details to confirm that you will be using the product(s) in ' .
  1080.                                             'country you selected.', 'wc-aelia-eu-vat-assistant');
  1081.                     $this->add_woocommerce_error($error);
  1082.                 }
  1083.             }
  1084.         }
  1085.     }
  1086.  
  1087.     /**
  1088.      * Indicates if a valid EU VAT number is required to complete checkout. A VAT
  1089.      * number is required in the following cases:
  1090.      * - When the requirement setting is "always required".
  1091.      * - When the requirement setting is "required for EU only" and customer
  1092.      *   selected a billing country that is part of the EU.
  1093.      *
  1094.      * @param string customer_country The country for which the check should be
  1095.      * performed.
  1096.      * @return bool
  1097.      */
  1098.     protected function is_eu_vat_number_required($customer_country) {
  1099.         $eu_vat_number_required = self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED);
  1100.         /* The EU VAT field is required in one of the following cases
  1101.          * 1- If the related option is set to "required".
  1102.          * 2- If the option is set to "only if company name is filled" and the
  1103.          *    customer entered a company name.
  1104.          * 3- If the option is set "only for EU customers" and the customer selected
  1105.          *    a EU country.
  1106.          */
  1107.         $result = ($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED) ||
  1108.                             // Check if option is "required only if company has been entered"
  1109.                             // and the company field is not empty
  1110.                             (($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED_IF_COMPANY_FILLED) &&
  1111.                               !empty($_POST['billing_company']) && (trim($_POST['billing_company']) != '')) ||
  1112.                             // Check if option is "required only if company has been entered
  1113.                             // and address is in the EU", and the company field is not empty
  1114.                             (($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED_IF_COMPANY_FILLED_EU_ONLY) &&
  1115.                                 $this->is_eu_country($customer_country) &&
  1116.                               !empty($_POST['billing_company']) && (trim($_POST['billing_company']) != '')) ||
  1117.                             // Check if option is "required only if company is in EU" and the
  1118.                             // company is in the EU
  1119.                             (($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED_EU_ONLY) && $this->is_eu_country($customer_country));
  1120.         return apply_filters('wc_aelia_euva_order_is_eu_vat_number_required', $result, $customer_country);
  1121.     }
  1122.  
  1123.     /**
  1124.      * Performs validations related to the EU VAT Number, to ensure that customer
  1125.      * has entered all the information required to complete the checkout.
  1126.      * This method automatically adds a checkout error when needed.
  1127.      *
  1128.      * @return void
  1129.      */
  1130.     protected function validate_vat_exemption() {
  1131.         // Check if customer is VAT exempt and set him as such
  1132.         $customer_country = $this->wc()->customer->get_country();
  1133.  
  1134.         // If customer's country is empty on customer object, take it from the data
  1135.         // posted at checkout
  1136.         if(empty($customer_country)) {
  1137.             $this->log(__('Billing country on Customer object is empty. Retrieving ' .
  1138.                                         'it from posted data.', 'wc-aelia-eu-vat-assistant'), false);
  1139.             $customer_country = get_value('billing_country', $_POST, '');
  1140.  
  1141.             if(empty($customer_country)) {
  1142.                 $this->log(sprintf(__('Unexpected condition occurred: no customer country was posted  ' .
  1143.                                                             'during checkout. VAT exemption cannot be applied correctly. ' .
  1144.                                                             'Full posted data (JSON): "%s".', 'wc-aelia-eu-vat-assistant'),
  1145.                                                      json_encode($_POST)), false);
  1146.             }
  1147.         }
  1148.  
  1149.         $vat_number = get_value(Definitions::ARG_VAT_NUMBER, $_POST, '');
  1150.         // Check if customer is VAT exempt and set his exemption accordingly
  1151.         $exemption_check_result = $this->set_customer_vat_exemption($customer_country, $vat_number);
  1152.  
  1153.         // If VAT Number is required, but it was not entered or it's not valid,
  1154.         // display the appropriate error message and stop the checkout
  1155.         if($this->is_eu_vat_number_required($customer_country) &&
  1156.              ($this->vat_number_validated != Definitions::VAT_NUMBER_VALIDATION_VALID)) {
  1157.             $error = sprintf(__('You must enter a valid EU VAT number to complete the purchase.', 'wc-aelia-eu-vat-assistant'), $vat_number);
  1158.             $this->add_woocommerce_error($error);
  1159.             return;
  1160.         }
  1161.  
  1162.         // If VAT number is optional, check if customer may be allowed to complete
  1163.         // the purchase anyway
  1164.         if($exemption_check_result == Definitions::ERR_INVALID_EU_VAT_NUMBER &&
  1165.              // If "store invalid VAT numbers" option is enabled, don't show any error
  1166.              // The VAT number will be recorded with the order, but the customer won't
  1167.              // be made exempt from VAT
  1168.              self::settings()->get(Settings::FIELD_STORE_INVALID_VAT_NUMBERS) == false) {
  1169.             // Show an error when VAT number validation fails at checkout
  1170.             $error = sprintf(__('VAT number "%s" is not valid for your country.', 'wc-aelia-eu-vat-assistant'), $vat_number);
  1171.             $this->add_woocommerce_error($error);
  1172.         }
  1173.     }
  1174.  
  1175.     /**
  1176.      * Performs VAT validation operations during checkout.
  1177.      *
  1178.      * @param array posted_data The data posted from the order review page.
  1179.      */
  1180.     public function woocommerce_checkout_process() {
  1181.         $this->validate_vat_exemption();
  1182.         $this->validate_self_certification();
  1183.     }
  1184.  
  1185.     /**
  1186.      * Triggered when the Currency Switcher settings are updated. It updates
  1187.      * exchange rates automatically, so that they will include any new currency
  1188.      * that might have been added.
  1189.      */
  1190.     public function wc_aelia_currencyswitcher_settings_saved() {
  1191.         self::settings()->scheduled_update_exchange_rates();
  1192.     }
  1193.  
  1194.     /**
  1195.      * Adds meta boxes to the admin interface.
  1196.      *
  1197.      * @see add_meta_boxes().
  1198.      */
  1199.     public function add_meta_boxes() {
  1200.         add_meta_box('wc_aelia_eu_vat_assistant_order_vat_info_box',
  1201.                                  __('VAT information', 'wc-aelia-eu-vat-assistant'),
  1202.                                  array($this, 'render_order_vat_info_box'),
  1203.                                  'shop_order',
  1204.                                  'side',
  1205.                                  'default');
  1206.     }
  1207.  
  1208.     /**
  1209.      * Renders the currency selector widget in "new order" page.
  1210.      */
  1211.     public function render_order_vat_info_box() {
  1212.         global $post;
  1213.  
  1214.         $order = new Order($post->ID);
  1215.         include_once($this->path('views') . '/admin/order-vat-info-box.php');
  1216.     }
  1217.  
  1218.     /**
  1219.      * Handler for "wc_aelia_eu_vat_assistant_get_order_exchange_rate" hook. It
  1220.      * just acts as a wrapper for WC_Aelia_EU_VAT_Assistant::get_order_vat_exchange_rate()
  1221.      * method.
  1222.      *
  1223.      * @param float default_rate The default exchange rate to return if the order
  1224.      * doesn't have any.
  1225.      * @param int order_id The ID of the order from which the exchange rate should
  1226.      * be retrieved.
  1227.      * @return float
  1228.      */
  1229.     public function wc_aelia_eu_vat_assistant_get_order_exchange_rate($default_rate, $order_id) {
  1230.         $vat_exchange_rate = $this->get_order_vat_exchange_rate($order_id);
  1231.         if($vat_exchange_rate === false) {
  1232.             $vat_exchange_rate = $default_rate;
  1233.         }
  1234.         return $vat_exchange_rate;
  1235.     }
  1236.  
  1237.     /**
  1238.      * Retrieves the value of a plugin's setting.
  1239.      *
  1240.      * @param mixed default_value The default value to return if the setting is
  1241.      * not found.
  1242.      * @param string setting_key The setting to retrieved.
  1243.      * @return mixed
  1244.      */
  1245.     public function wc_aelia_eu_vat_assistant_get_setting($defaut_value, $setting_key) {
  1246.         return self::settings()->get($setting_key, $defaut_value);
  1247.     }
  1248.  
  1249.     /**
  1250.      * Displays a notice when the plugin has not yet been configured.
  1251.      */
  1252.     public function settings_notice() {
  1253.     ?>
  1254.         <div id="message" class="updated woocommerce-message">
  1255.             <p><?php echo __('<strong>EU VAT Assistant</strong> is almost ready! Please go to ' .
  1256.                                              '<code>WooCommerce > EU VAT Assistant</code> settings page to complete the ' .
  1257.                                              'configuration and start collecting the information required for ' .
  1258.                                              'EU VAT compliance.', 'wc-aelia-eu-vat-assistant'); ?></p>
  1259.             <p class="submit">
  1260.                 <a href="<?php echo admin_url('admin.php?page=' . Definitions::MENU_SLUG); ?>"
  1261.                      class="button-primary"><?php echo __('Go to EU VAT Assistant settings', 'wc-aelia-eu-vat-assistant'); ?></a>
  1262.             </p>
  1263.         </div>
  1264.     <?php
  1265.     }
  1266.  
  1267.     /**
  1268.      * Adds elements to the billing address.
  1269.      *
  1270.      * @param array address_parts The various parts of the address (name, company,
  1271.      * address, etc).
  1272.      * @param WC_Order order The order from which the address was taken.
  1273.      * @return array
  1274.      * @see \WC_Order::get_formatted_billing_address()
  1275.      */
  1276.     public function woocommerce_order_formatted_billing_address($address_parts, $order) {
  1277.         $order = new Order($order->id);
  1278.         $address_parts['vat_number'] = $order->get_customer_vat_number();
  1279.         return $address_parts;
  1280.     }
  1281.  
  1282.     /**
  1283.      * Adds tags that will be replaced with additional information on the address,
  1284.      * such as customers' VAT number.
  1285.      *
  1286.      * @param array replacements The replacement tokens passed by WooCommerce.
  1287.      * @param array values The values from the billing address.
  1288.      * @return array
  1289.      * @see \WC_Countries::get_formatted_address()
  1290.      */
  1291.     public function woocommerce_formatted_address_replacements($replacements, $values) {
  1292.         if(!empty($values['vat_number'])) {
  1293.             $replacements['{vat_number}'] = __('VAT #:', 'wc-aelia-eu-vat-assistant') . ' ' . $values['vat_number'];
  1294.         }
  1295.         else {
  1296.             $replacements['{vat_number}'] = '';
  1297.         }
  1298.         return $replacements;
  1299.     }
  1300.  
  1301.     /**
  1302.      * Alters the address formats and adds new tokens, such as the VAT number.
  1303.      *
  1304.      * @param array formats An array of address formats.
  1305.      * @return array
  1306.      * @see \WC_Countries::get_address_formats()
  1307.      */
  1308.     public function woocommerce_localisation_address_formats($formats) {
  1309.         foreach($formats as $format_idx => $address_format) {
  1310.             $formats[$format_idx] .= "\n{vat_number}";
  1311.         }
  1312.         return $formats;
  1313.     }
  1314.  
  1315.     /**
  1316.      * Overrides the value of the "woocommerce_allowed_countries" setting when
  1317.      * admin entered a list of countries to which sales are disallowed.
  1318.      *
  1319.      * @param mixed value The original value of the parameter.
  1320.      * @return string
  1321.      */
  1322.     public function pre_option_woocommerce_allowed_countries($value) {
  1323.         /* If we reach this point, it means that the Admin placed some restrictions
  1324.          * on the list of countries to which he wishes to sell. In such case, the
  1325.          * "allowed countries" option must be forced to "specific", so that the list
  1326.          * of available countries can be filtered.
  1327.          */
  1328.         return 'specific';
  1329.     }
  1330.  
  1331.     /**
  1332.      * Filters the list of countries to which sale is allowed, taking into account
  1333.      * the ones explicitly disallowed by the administrator.
  1334.      *
  1335.      * @param array countries A list of countries passed by WooCommerce.
  1336.      * @return array The list of countries, with the disallowed ones removed from
  1337.      * it.
  1338.      */
  1339.     public function woocommerce_countries_allowed_countries($countries) {
  1340.         $allowed_sale_countries = $this->allowed_sale_countries;
  1341.         $disallowed_sale_countries = $this->get_sale_disallowed_countries();
  1342.  
  1343.         if(!empty($disallowed_sale_countries)) {
  1344.             foreach($disallowed_sale_countries as $disallowed_country) {
  1345.                 if(isset($allowed_sale_countries[$disallowed_country])) {
  1346.                     unset($allowed_sale_countries[$disallowed_country]);
  1347.                 }
  1348.             }
  1349.         }
  1350.         // Debug
  1351.         //var_dump($allowed_sale_countries);
  1352.         return $allowed_sale_countries;
  1353.     }
  1354. }
  1355.  
  1356. $GLOBALS[WC_Aelia_EU_VAT_Assistant::$plugin_slug] = WC_Aelia_EU_VAT_Assistant::factory();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement