/src/plugin-main.php

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