AeliaWCEU_VAT_AssistantExchange_Rates_ECB_Historical_Model

Retrieves the exchange rates from the European Central Bank.

Defined (1)

The class is defined in the following location(s).

/src/lib/classes/exchange_rates/aelia-wc-exchangerates-ecb-historical.php  
  1. class Exchange_Rates_ECB_Historical_Model extends Exchange_Rates_ECB_Model { 
  2. // @var string The date for which the exchange rates should be retrieved, in YYYY-MM-DD format 
  3. protected $target_date; 
  4.  
  5. // @var string The URL template to use to query ECB 
  6. private $ecb_api_rates_last_90_days_url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml'; 
  7.  
  8. /** 
  9. * Tranforms the exchange rates received from ECB into an array of 
  10. * currency code => exchange rate pairs. 
  11. * @param string ecb_rates The XML received from ECB. 
  12. * @retur array 
  13. */ 
  14. protected function decode_rates($ecb_rates) { 
  15. $exchange_rates = array(); 
  16.  
  17. // This loop looks for the node containing the exchange rates for the specified 
  18. // target date. Using xpath would probably be a better approach, but it 
  19. // doesn't seem to work properly with the DOM containing the ECB rates 
  20. foreach($ecb_rates->Cube->Cube as $rates_for_day) { 
  21. if($rates_for_day['time'] != $this->target_date) { 
  22. continue; 
  23.  
  24. // Debug 
  25. //var_dump($rates_for_day);die(); 
  26. foreach($rates_for_day->Cube as $rate) { 
  27. $exchange_rates[(string)$rate['currency']] = (float)$rate['rate']; 
  28. // ECB feed is based against EUR, but it doesn't contain such currency. We 
  29. // can safely add it manually, with an exchange rate of 1 
  30. $exchange_rates['EUR'] = 1; 
  31. return $exchange_rates; 
  32.  
  33. /** 
  34. * Fetches all exchange rates from ECB API. 
  35. * @return object|bool An object containing the response from Open Exchange, or 
  36. * False in case of failure. 
  37. */ 
  38. private function fetch_all_rates() { 
  39. $ecb_api_rates_url = $this->ecb_api_rates_last_90_days_url; 
  40.  
  41. try { 
  42. $response = \Httpful\Request::get($ecb_api_rates_url) 
  43. ->expectsXml() 
  44. ->send(); 
  45.  
  46. // Debug 
  47. //var_dump("ECB RATES RESPONSE:", $response); die(); 
  48. if($response->hasErrors()) { 
  49. // OpenExchangeRates sends error details in response body 
  50. if($response->hasBody()) { 
  51. $response_data = $response->body; 
  52.  
  53. $this->add_error(self::ERR_ERROR_RETURNED,  
  54. sprintf(__('Error returned by ECB. ' . 
  55. 'Error code: %s. Error message: %s - %s.',  
  56. Definitions::TEXT_DOMAIN),  
  57. $response_data->status,  
  58. $response_data->message,  
  59. $response_data->description)); 
  60. return false; 
  61. return $response->body; 
  62. catch(Exception $e) { 
  63. $this->add_error(self::ERR_EXCEPTION_OCCURRED,  
  64. sprintf(__('Exception occurred while retrieving the exchange rates from ECB. ' . 
  65. 'Error message: %s.',  
  66. Definitions::TEXT_DOMAIN),  
  67. $e->getMessage())); 
  68. return null; 
  69.  
  70. /** 
  71. * Stores the exchange rates retrieved from the provider for a specific date. 
  72. * @param string base_currency The base currency to which the exchange rates 
  73. * refer. 
  74. * @param string rates_date The date to which the exchange rates refer. 
  75. * @param array rates An array of exchange rates. 
  76. * @return bool 
  77. */ 
  78. protected function store_rates($base_currency, $rates_date, $rates) { 
  79. global $wpdb; 
  80.  
  81. // If there aren't at least two entries in the exchange rates, then the 
  82. // array was not populated correctly 
  83. if(!is_array($rates) || count($rates) <= 1) { 
  84. return false; 
  85.  
  86. $table_name = $wpdb->prefix . 'aelia_exchange_rates_history'; 
  87. $SQL = " 
  88. INSERT INTO `$table_name` ( 
  89. `provider_name`,  
  90. `base_currency`,  
  91. `rates_date`,  
  92. `rates`,  
  93. `date_updated` 
  94. VALUES ( 
  95. %s, -- Provider name 
  96. %s, -- Base currency 
  97. %s, -- Rates date 
  98. %s, -- Rates (JSON) 
  99. %s -- Date updated 
  100. ON DUPLICATE KEY UPDATE 
  101. `rates` = %s,  
  102. `date_updated` = %s 
  103. "; 
  104.  
  105. $query = $wpdb->prepare( 
  106. $SQL,  
  107. get_class($this),  
  108. $base_currency,  
  109. $rates_date,  
  110. json_encode($rates),  
  111. date('YmdHis'),  
  112. // The two values below are repeated because they will populate the 
  113. // ON DUPLICATE KEY section of the query 
  114. json_encode($rates),  
  115. date('YmdHis') 
  116. ); 
  117.  
  118. // Debug 
  119. //var_dump($query); 
  120.  
  121. // Save to database the IP data for the country 
  122. $rows_affected = $wpdb->query($query); 
  123.  
  124. $result = $rows_affected; 
  125. if($result == false) { 
  126. $error_message = sprintf(__('Could not store exchange rates. Provider: "%s". ' . 
  127. 'Base currency: "%s". Rates date: "%s". Rates (JSON): "%s".', $this->text_domain),  
  128. get_class($this),  
  129. $base_currency,  
  130. date('Y-m-d', $rates_date),  
  131. json_encode($rates) 
  132. ); 
  133. $this->log($error_message, false); 
  134. return $result; 
  135.  
  136. protected function get_stored_rates($rates_date) { 
  137. global $wpdb; 
  138.  
  139. $SQL = " 
  140. SELECT 
  141. ERH.`provider_name` 
  142. , ERH.`base_currency` 
  143. , ERH.`rates_date` 
  144. , ERH.`rates` 
  145. , ERH.`date_updated` 
  146. FROM 
  147. {$wpdb->prefix}aelia_exchange_rates_history ERH 
  148. WHERE 
  149. (ERH.provider_name = %s) AND 
  150. (ERH.rates_date = %s) 
  151. "; 
  152.  
  153. $query = $wpdb->prepare( 
  154. $SQL,  
  155. get_class($this),  
  156. $rates_date 
  157. ); 
  158.  
  159. // Debug 
  160. //var_dump($query); 
  161.  
  162. $rates = array(); 
  163. $dataset = $wpdb->get_results($query); 
  164.  
  165. // Debug 
  166. if(is_array($dataset) && !empty($dataset)) { 
  167. $dataset = array_shift($dataset); 
  168. $rates = json_decode($dataset->rates, true); 
  169.  
  170. // Debug 
  171. //var_dump($rates);die(); 
  172. return $rates; 
  173.  
  174. /** 
  175. * Returns current exchange rates for the specified currency. 
  176. * @param string base_currency The base currency. 
  177. * @return array An array of Currency => Exchange Rate pairs. 
  178. */ 
  179. public function current_rates($base_currency) { 
  180. if(empty($this->_current_rates) || 
  181. $this->_base_currency != $base_currency) { 
  182.  
  183. // Try to get the cached rates for the specified base currency, if any 
  184. $this->_current_rates = $this->get_stored_rates($this->target_date); 
  185. if(is_array($this->_current_rates) && !empty($this->_current_rates) && (count($this->_current_rates) > 1)) { 
  186. $this->_current_rates = $this->rebase_rates($this->_current_rates, $base_currency); 
  187. return $this->_current_rates; 
  188.  
  189. // Fetch exchange rates 
  190. $ecb_exchange_rates = $this->fetch_all_rates(); 
  191. if($ecb_exchange_rates === false) { 
  192. return null; 
  193.  
  194. // Debug 
  195. //var_dump($ecb_exchange_rates);die(); 
  196.  
  197. // ECB rates are returned as JSON representation of an array of objects. 
  198. // We need to transform it into an array of currency => rate pairs 
  199. $exchange_rates = $this->decode_rates($ecb_exchange_rates); 
  200.  
  201. // Debug 
  202. //var_dump($exchange_rates);die(); 
  203.  
  204. if(!is_array($exchange_rates)) { 
  205. $this->add_error(self::ERR_UNEXPECTED_ERROR_FETCHING_EXCHANGE_RATES,  
  206. __('An unexpected error occurred while fetching exchange rates ' . 
  207. 'from ECB. The most common cause of this issue is the ' . 
  208. 'absence of PHP CURL extension. Please make sure that ' . 
  209. 'PHP CURL is installed and configured in your system.',  
  210. Definitions::TEXT_DOMAIN)); 
  211. return array(); 
  212.  
  213. // If the exchange rates array cointains one element, it means that 
  214. // the rates were not retrieved correctly. In such case, there is no point 
  215. // in storing them 
  216. if(count($exchange_rates) > 1) { 
  217. // Cache the exchange rates 
  218. $this->store_rates('EUR', $this->target_date, $exchange_rates); 
  219.  
  220. // Since we didn't get the exchange rates related to the base currency,  
  221. // but in the default base currency used by the ECB, we need to 
  222. // recalculate them against the base currency we would like to use 
  223. $this->_current_rates = $this->rebase_rates($exchange_rates, $base_currency); 
  224. $this->_base_currency = $base_currency; 
  225. return $this->_current_rates; 
  226.  
  227. /** 
  228. * Recaculates the exchange rates using another base currency. This method 
  229. * is invoked because the rates fetched from ECB are relative to BitCoin,  
  230. * but another currency is most likely is used by WooCommerce. 
  231. * @param array exchange_rates The exchange rates retrieved from ECB. 
  232. * @param string base_currency The base currency against which the rates should 
  233. * be recalculated. 
  234. * @return array An array of currency => exchange rate pairs. 
  235. */ 
  236. private function rebase_rates(array $exchange_rates, $base_currency) { 
  237. $recalc_rate = get_value($base_currency, $exchange_rates); 
  238. //var_dump($base_currency, $exchange_rates); 
  239.  
  240. if(empty($recalc_rate)) { 
  241. $this->add_error(self::ERR_BASE_CURRENCY_NOT_FOUND,  
  242. sprintf(__('Could not rebase rates against base currency "%s". ' . 
  243. 'Currency not found in data returned by ECB.',  
  244. Definitions::TEXT_DOMAIN),  
  245. $base_currency)); 
  246. return null; 
  247.  
  248. $result = array(); 
  249. foreach($exchange_rates as $currency => $rate) { 
  250. $result[$currency] = $rate / $recalc_rate; 
  251.  
  252. // Debug 
  253. //var_dump($result); die(); 
  254. return $result; 
  255.  
  256. /** 
  257. * Returns the exchange rate of a currency in respect to a base currency. 
  258. * @param string base_currency The code of the base currency. 
  259. * @param string currency The code of the currency for which to find the 
  260. * Exchange Rate. 
  261. * @return float 
  262. */ 
  263. protected function get_rate($base_currency, $currency) { 
  264. $current_rates = $this->current_rates($base_currency); 
  265. return get_value($currency, $current_rates); 
  266.  
  267. /** 
  268. * Returns the last day of a quarter. 
  269. * @param string last_day_of_quarter A string indicating a quarter. It must 
  270. * have the format YYYY-Q (year-quarter). 
  271. * @return string A string representing the last day of a quarter, in YYYY-MM-DD 
  272. * format. 
  273. * @throws InvalidArgumentException if an invalid string is passed as a 
  274. * parameter. 
  275. */ 
  276. protected function get_last_day_of_quarter($last_of_quarter) { 
  277. $last_of_quarter_elements = explode('-', $last_of_quarter); 
  278.  
  279. $quarter_year = array_shift($last_of_quarter_elements); 
  280. $quarter_number = array_shift($last_of_quarter_elements); 
  281. if(!is_numeric($quarter_year) || 
  282. !is_numeric($quarter_number) || 
  283. (($quarter_number < 1) && ($quarter_number > 4))) { 
  284. throw new \InvalidArgumentException(sprintf(__('Invalid "last day of quarter" argument ' . 
  285. 'received: "%s". The argument must be in ' . 
  286. 'YYYY-Q format, and the quarter number ' . 
  287. 'must be between 1 and 4.',  
  288. Definitions::TEXT_DOMAIN),  
  289. $last_of_quarter)); 
  290. $quarter_last_month = $quarter_number * 3; 
  291. $last_day_of_quarter = date('Y-m-t', strtotime("$quarter_year-{$quarter_last_month}-01")); 
  292.  
  293. // Debug 
  294. //var_dump($last_day_of_quarter);die(); 
  295. return $last_day_of_quarter; 
  296.  
  297. /** 
  298. * Class constructor. 
  299. * @param array An array of Settings that can be used to override the ones 
  300. * currently saved in the configuration. 
  301. */ 
  302. public function __construct($settings = null) { 
  303. parent::__construct($settings); 
  304.  
  305. $this->logger = new Logger(Definitions::PLUGIN_SLUG); 
  306.  
  307. // If a specific target date was passed, take it 
  308. $target_date = get_value('target_date', $settings); 
  309.  
  310. if(empty($target_date)) { 
  311. // Check if the "last of quarter" parameter was passed. If passed, it must 
  312. // have the format YYYY-Q (year-quarter) and it will be used to determine 
  313. // the last day of the quarter 
  314. $last_of_quarter = get_value('last_of_quarter', $settings, ''); 
  315. if(!empty($last_of_quarter)) { 
  316. $target_date = $this->get_last_day_of_quarter($last_of_quarter); 
  317.  
  318. // If no date was passed at all, take today 
  319. if(empty($target_date)) { 
  320. $target_date = date('Y-m-d'); 
  321. $this->target_date = $target_date; 
  322.  
  323. /** 
  324. * Initialises an instance of the model using the last day of the specified 
  325. * quarter as a target date. This is a convenience method, to avoid having to 
  326. * create an array of settings and pass it to the class constructor. 
  327. * @param int year A year, in YYYY format. 
  328. * @param int quarter A quarter. It must be between 1 and 4. 
  329. * @return \Aelia\WC\EU_VAT_Assistant\Exchange_Rates_ECB_Historical_Model. 
  330. */ 
  331. public static function init_for_last_day_of_quarter($year, $quarter) { 
  332. $settings = array( 
  333. 'last_of_quarter' => "{$year}-{$quarter}",  
  334. ); 
  335.  
  336. // Initialise and return the instance 
  337. $instance = new self($settings); 
  338. return $instance; 
  339.  
  340. /** 
  341. * Returns all the exchange rates retrieved by the provider for the specified 
  342. * target date. Rates are rebased against the specified base currency. 
  343. * @param string target_date The target date. 
  344. * @param string base_currency The base currency against which the exchange 
  345. * rates will be rebased. 
  346. * @return array An array of currency => exchange rate pairs. 
  347. */ 
  348. public static function get_rates_for_date($target_date, $base_currency = 'EUR') { 
  349. $instance = new self(array('target_date' => $target_date)); 
  350. return $instance->current_rates($base_currency);