PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.7.1
VikBooking Hotel Booking Engine & PMS v1.7.1
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / site / helpers / currencyconverter.php

currencyconverter.php in VikBooking Hotel Booking Engine & PMS 1.7.1, at site/helpers/currencyconverter.php

1,055 lines 32.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 class VboCurrencyConverter
14 {
15 /**
16 * The currency code on which the rates are based
17 *
18 * @var string
19 */
20 protected $from_currency;
21
22 /**
23 * The desired currency code for conversion
24 *
25 * @var string
26 */
27 protected $to_currency;
28
29 /**
30 * A list of numbers (rates) to convert
31 *
32 * @var array
33 */
34 protected $prices;
35
36 /**
37 * The formatting values for numbers
38 *
39 * @var array
40 */
41 protected $format;
42
43 /**
44 * A map of symbols and decimals for the various currency codes
45 *
46 * @var array
47 */
48 protected $currencymap;
49
50 /**
51 * The error message(s) occurred during the processes
52 *
53 * @var string
54 */
55 protected $error;
56
57 /**
58 * The list of supported currencies.
59 *
60 * @var array
61 * @since 1.15.4 (J) - 1.5.10 (WP)
62 */
63 protected $currency_names = array(
64 'AED' => 'AED - United Arab Emirates Dirham',
65 'AFN' => 'AFN - Afghan Afghani',
66 'ALL' => 'ALL - Albanian Lek',
67 'AMD' => 'AMD - Armenian Dram',
68 'ANG' => 'ANG - Netherlands Antillean Gulden',
69 'AOA' => 'AOA - Angolan Kwanza',
70 'ARS' => 'ARS - Argentine Peso',
71 'AUD' => 'AUD - Australian Dollar',
72 'AWG' => 'AWG - Aruban Florin',
73 'AZN' => 'AZN - Azerbaijani Manat',
74 'BAM' => 'BAM - Bosnia and Herzegovina Mark',
75 'BBD' => 'BBD - Barbadian Dollar',
76 'BDT' => 'BDT - Bangladeshi Taka',
77 'BGN' => 'BGN - Bulgarian Lev',
78 'BHD' => 'BHD - Bahraini Dinar',
79 'BIF' => 'BIF - Burundian Franc',
80 'BMD' => 'BMD - Bermudian Dollar',
81 'BND' => 'BND - Brunei Dollar',
82 'BOB' => 'BOB - Bolivian Boliviano',
83 'BRL' => 'BRL - Brazilian Real',
84 'BSD' => 'BSD - Bahamian Dollar',
85 'BWP' => 'BWP - Botswana Pula',
86 'BZD' => 'BZD - Belize Dollar',
87 'CAD' => 'CAD - Canadian Dollar',
88 'CDF' => 'CDF - Congolese Franc',
89 'CHF' => 'CHF - Swiss Franc',
90 'CLP' => 'CLP - Chilean Peso',
91 'CNY' => 'CNY - Chinese Renminbi Yuan',
92 'COP' => 'COP - Colombian Peso',
93 'CRC' => 'CRC - Costa Rican Colón',
94 'CVE' => 'CVE - Cape Verdean Escudo',
95 'CZK' => 'CZK - Czech Koruna',
96 'DJF' => 'DJF - Djiboutian Franc',
97 'DKK' => 'DKK - Danish Krone',
98 'DOP' => 'DOP - Dominican Peso',
99 'DZD' => 'DZD - Algerian Dinar',
100 'EEK' => 'EEK - Estonian Kroon',
101 'EGP' => 'EGP - Egyptian Pound',
102 'ETB' => 'ETB - Ethiopian Birr',
103 'EUR' => 'EUR - Euro',
104 'FJD' => 'FJD - Fijian Dollar',
105 'FKP' => 'FKP - Falkland Islands Pound',
106 'GBP' => 'GBP - British Pound',
107 'GEL' => 'GEL - Georgian Lari',
108 'GIP' => 'GIP - Gibraltar Pound',
109 'GMD' => 'GMD - Gambian Dalasi',
110 'GNF' => 'GNF - Guinean Franc',
111 'GTQ' => 'GTQ - Guatemalan Quetzal',
112 'GYD' => 'GYD - Guyanese Dollar',
113 'HKD' => 'HKD - Hong Kong Dollar',
114 'HNL' => 'HNL - Honduran Lempira',
115 'HRK' => 'HRK - Croatian Kuna',
116 'HTG' => 'HTG - Haitian Gourde',
117 'HUF' => 'HUF - Hungarian Forint',
118 'IDR' => 'IDR - Indonesian Rupiah',
119 'ILS' => 'ILS - Israeli New Sheqel',
120 'INR' => 'INR - Indian Rupee',
121 'ISK' => 'ISK - Icelandic Króna',
122 'JMD' => 'JMD - Jamaican Dollar',
123 'JPY' => 'JPY - Japanese Yen',
124 'JOD' => 'JOD - Jordanian Dinar',
125 'KES' => 'KES - Kenyan Shilling',
126 'KGS' => 'KGS - Kyrgyzstani Som',
127 'KHR' => 'KHR - Cambodian Riel',
128 'KMF' => 'KMF - Comorian Franc',
129 'KRW' => 'KRW - South Korean Won',
130 'KYD' => 'KYD - Cayman Islands Dollar',
131 'KZT' => 'KZT - Kazakhstani Tenge',
132 'KWD' => 'KWD - Kuwaiti Dinar',
133 'LAK' => 'LAK - Lao Kip',
134 'LBP' => 'LBP - Lebanese Pound',
135 'LKR' => 'LKR - Sri Lankan Rupee',
136 'LRD' => 'LRD - Liberian Dollar',
137 'LSL' => 'LSL - Lesotho Loti',
138 'LTL' => 'LTL - Lithuanian Litas',
139 'LVL' => 'LVL - Latvian Lats',
140 'MAD' => 'MAD - Moroccan Dirham',
141 'MDL' => 'MDL - Moldovan Leu',
142 'MGA' => 'MGA - Malagasy Ariary',
143 'MKD' => 'MKD - Macedonian Denar',
144 'MNT' => 'MNT - Mongolian Tögrög',
145 'MOP' => 'MOP - Macanese Pataca',
146 'MRU' => 'MRU - Mauritanian Ouguiya',
147 'MUR' => 'MUR - Mauritian Rupee',
148 'MVR' => 'MVR - Maldivian Rufiyaa',
149 'MWK' => 'MWK - Malawian Kwacha',
150 'MXN' => 'MXN - Mexican Peso',
151 'MYR' => 'MYR - Malaysian Ringgit',
152 'MZN' => 'MZN - Mozambican Metical',
153 'NAD' => 'NAD - Namibian Dollar',
154 'NGN' => 'NGN - Nigerian Naira',
155 'NIO' => 'NIO - Nicaraguan Córdoba',
156 'NOK' => 'NOK - Norwegian Krone',
157 'NPR' => 'NPR - Nepalese Rupee',
158 'NZD' => 'NZD - New Zealand Dollar',
159 'OMR' => 'OMR - Omani Rial',
160 'PAB' => 'PAB - Panamanian Balboa',
161 'PEN' => 'PEN - Peruvian Nuevo Sol',
162 'PGK' => 'PGK - Papua New Guinean Kina',
163 'PHP' => 'PHP - Philippine Peso',
164 'PKR' => 'PKR - Pakistani Rupee',
165 'PLN' => 'PLN - Polish Złoty',
166 'PYG' => 'PYG - Paraguayan Guaraní',
167 'QAR' => 'QAR - Qatari Riyal',
168 'RON' => 'RON - Romanian Leu',
169 'RSD' => 'RSD - Serbian Dinar',
170 'RUB' => 'RUB - Russian Ruble',
171 'RWF' => 'RWF - Rwandan Franc',
172 'SAR' => 'SAR - Saudi Riyal',
173 'SBD' => 'SBD - Solomon Islands Dollar',
174 'SCR' => 'SCR - Seychellois Rupee',
175 'SEK' => 'SEK - Swedish Krona',
176 'SGD' => 'SGD - Singapore Dollar',
177 'SHP' => 'SHP - Saint Helenian Pound',
178 'SLL' => 'SLL - Sierra Leonean Leone',
179 'SOS' => 'SOS - Somali Shilling',
180 'SRD' => 'SRD - Surinamese Dollar',
181 'STD' => 'STD - São Tomé and Príncipe Dobra',
182 'SVC' => 'SVC - Salvadoran Colón',
183 'SZL' => 'SZL - Swazi Lilangeni',
184 'THB' => 'THB - Thai Baht',
185 'TJS' => 'TJS - Tajikistani Somoni',
186 'TOP' => 'TOP - Tongan Paʻanga',
187 'TRY' => 'TRY - Turkish Lira',
188 'TTD' => 'TTD - Trinidad and Tobago Dollar',
189 'TWD' => 'TWD - New Taiwan Dollar',
190 'TZS' => 'TZS - Tanzanian Shilling',
191 'UAH' => 'UAH - Ukrainian Hryvnia',
192 'UGX' => 'UGX - Ugandan Shilling',
193 'USD' => 'USD - United States Dollar',
194 'UYU' => 'UYU - Uruguayan Peso',
195 'UZS' => 'UZS - Uzbekistani Som',
196 'VEF' => 'VEF - Venezuelan Bolívar',
197 'VND' => 'VND - Vietnamese Đồng',
198 'VUV' => 'VUV - Vanuatu Vatu',
199 'WST' => 'WST - Samoan Tala',
200 'XAF' => 'XAF - Central African Cfa Franc',
201 'XCD' => 'XCD - East Caribbean Dollar',
202 'XOF' => 'XOF - West African Cfa Franc',
203 'XPF' => 'XPF - Cfp Franc',
204 'YER' => 'YER - Yemeni Rial',
205 'ZAR' => 'ZAR - South African Rand',
206 'ZMW' => 'ZMW - Zambian Kwacha',
207 'TND' => 'TND - Tunisian dinar',
208 );
209
210 /**
211 * The suffix of the method for exchaning the rates
212 *
213 * @var string
214 */
215 public $api_provider;
216
217 /**
218 * Class constructor
219 *
220 * @param string $from current currency
221 * @param string $to desired currency
222 * @param array $numbers list of numbers
223 * @param array $format formatting options
224 */
225 public function __construct($from, $to, $numbers, $format)
226 {
227 $this->from_currency = $from;
228 $this->to_currency = $to;
229 $this->prices = $numbers;
230 $this->format = $format;
231 $this->error = '';
232 $this->currencymap = array(
233 'ALL' => array('symbol' => '76'),
234 'AFN' => array('symbol' => '1547'),
235 'ARS' => array('symbol' => '36'),
236 'AWG' => array('symbol' => '402'),
237 'AUD' => array('symbol' => '36'),
238 'AZN' => array('symbol' => '1084'),
239 'BSD' => array('symbol' => '36'),
240 'BBD' => array('symbol' => '36'),
241 'BYR' => array('symbol' => '112', 'decimals' => 0),
242 'BZD' => array('symbol' => '66'),
243 'BMD' => array('symbol' => '36'),
244 'BOB' => array('symbol' => '36'),
245 'BAM' => array('symbol' => '75'),
246 'BWP' => array('symbol' => '80'),
247 'BGN' => array('symbol' => '1083'),
248 'BHD' => array('decimals' => 3),
249 'BRL' => array('symbol' => '82'),
250 'BND' => array('symbol' => '36'),
251 'KHR' => array('symbol' => '6107'),
252 'CAD' => array('symbol' => '36'),
253 'KYD' => array('symbol' => '36'),
254 'CLP' => array('symbol' => '36', 'decimals' => 0),
255 'CNY' => array('symbol' => '165'),
256 'COP' => array('symbol' => '36', 'decimals' => 3),
257 'CRC' => array('symbol' => '8353'),
258 'CVE' => array('symbol' => '36', 'decimals' => 2),
259 'HRK' => array('symbol' => '107'),
260 'CUP' => array('symbol' => '8369'),
261 'CZK' => array('symbol' => '75'),
262 'DKK' => array('symbol' => '107'),
263 'DOP' => array('symbol' => '82'),
264 'XCD' => array('symbol' => '36'),
265 'EGP' => array('symbol' => '163'),
266 'SVC' => array('symbol' => '36'),
267 'EEK' => array('symbol' => '107'),
268 'EUR' => array('symbol' => '8364'),
269 'FKP' => array('symbol' => '163'),
270 'FJD' => array('symbol' => '36'),
271 'GHC' => array('symbol' => '162'),
272 'GIP' => array('symbol' => '163'),
273 'GTQ' => array('symbol' => '81'),
274 'GGP' => array('symbol' => '163'),
275 'GYD' => array('symbol' => '36'),
276 'HNL' => array('symbol' => '76'),
277 'HKD' => array('symbol' => '36'),
278 'HUF' => array('symbol' => '70', 'decimals' => 0),
279 'ISK' => array('symbol' => '107', 'decimals' => 0),
280 'IDR' => array('symbol' => '82'),
281 'INR' => array('symbol' => '8377'),
282 'IRR' => array('symbol' => '65020'),
283 'IMP' => array('symbol' => '163'),
284 'ILS' => array('symbol' => '8362'),
285 'JMD' => array('symbol' => '74'),
286 'JPY' => array('symbol' => '165', 'decimals' => 0),
287 'JEP' => array('symbol' => '163'),
288 'KZT' => array('symbol' => '1083'),
289 'KPW' => array('symbol' => '8361'),
290 'KRW' => array('symbol' => '8361', 'decimals' => 0),
291 'KGS' => array('symbol' => '1083'),
292 'KWD' => array('decimals' => 3),
293 'LAK' => array('symbol' => '8365'),
294 'LVL' => array('symbol' => '76'),
295 'LBP' => array('symbol' => '163'),
296 'LRD' => array('symbol' => '36'),
297 'LTL' => array('symbol' => '76'),
298 'MKD' => array('symbol' => '1076'),
299 'MYR' => array('symbol' => '82'),
300 'MUR' => array('symbol' => '8360'),
301 'MXN' => array('symbol' => '36'),
302 'MNT' => array('symbol' => '8366'),
303 'MZN' => array('symbol' => '77', 'decimals' => 0),
304 'NAD' => array('symbol' => '36'),
305 'NPR' => array('symbol' => '8360'),
306 'ANG' => array('symbol' => '402'),
307 'NZD' => array('symbol' => '36'),
308 'NIO' => array('symbol' => '67'),
309 'NGN' => array('symbol' => '8358'),
310 'NOK' => array('symbol' => '107'),
311 'OMR' => array('symbol' => '65020', 'decimals' => 3),
312 'PKR' => array('symbol' => '8360'),
313 'PAB' => array('symbol' => '66'),
314 'PYG' => array('symbol' => '71', 'decimals' => 0),
315 'PEN' => array('symbol' => '83'),
316 'PHP' => array('symbol' => '8369'),
317 'PLN' => array('symbol' => '122'),
318 'QAR' => array('symbol' => '65020'),
319 'RON' => array('symbol' => '108;&#101;&#105'),
320 'RUB' => array('symbol' => '1088'),
321 'SHP' => array('symbol' => '163'),
322 'SAR' => array('symbol' => '65020'),
323 'RSD' => array('symbol' => '1044'),
324 'SCR' => array('symbol' => '8360'),
325 'SGD' => array('symbol' => '36'),
326 'SBD' => array('symbol' => '36'),
327 'SOS' => array('symbol' => '83'),
328 'ZAR' => array('symbol' => '82'),
329 'LKR' => array('symbol' => '8360'),
330 'SEK' => array('symbol' => '107'),
331 'CHF' => array('symbol' => '8355'),
332 'SRD' => array('symbol' => '36'),
333 'SYP' => array('symbol' => '163'),
334 'TWD' => array('symbol' => '78'),
335 'THB' => array('symbol' => '3647'),
336 'TTD' => array('symbol' => '84'),
337 'TRY' => array('symbol' => '8378', 'decimals' => 0),
338 'UAH' => array('symbol' => '8372'),
339 'GBP' => array('symbol' => '163'),
340 'USD' => array('symbol' => '36'),
341 'UYU' => array('symbol' => '36'),
342 'UZS' => array('symbol' => '1083'),
343 'VEF' => array('symbol' => '66'),
344 'VND' => array('symbol' => '8363'),
345 'YER' => array('symbol' => '65020'),
346 'ZWD' => array('symbol' => '90'),
347 'TND' => array('decimals' => 3),
348 );
349
350 /**
351 * This is the API's Provider that the Class will use to retrieve the conversion rate.
352 * The method callProvider{$suffix}() will be called by the getConversionRate() method.
353 *
354 * Supported Providers: ECB, FloatRates (used as callback by ECB)
355 * Deprecated Providers: fixer, yahoo
356 *
357 * @see getConversionRate()
358 */
359 $this->api_provider = 'ECB';
360 }
361
362 /**
363 * Deprecated call to the Yahoo Finance APIs (shut down in November 2017).
364 * Retrieve the conversion rate between base currency and symbol currency.
365 *
366 * @return mixed false in case of errors, float in case of success
367 *
368 * @deprecated since November 2017
369 */
370 protected function callProviderYahoo()
371 {
372 //http://finance.yahoo.com/currency-converter
373 $apis_url = 'http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s='. $this->from_currency . $this->to_currency .'=X';
374 $fp = @fopen($apis_url, 'r');
375 if ($fp) {
376 $data = '';
377 while (!feof($fp)) {
378 $data .= fread($fp, 4096);
379 }
380 if (!empty($data)) {
381 $data = str_replace("\"", "", $data);
382 $rate_info = explode(',', $data);
383 if (strlen($rate_info[1]) > 0 && floatval($rate_info[1]) > 0.00) {
384 return (float)$rate_info[1];
385 }
386 }
387 }
388 return false;
389 }
390
391 /**
392 * Call to the Fixer Foreign exchange rates and currency conversion API.
393 * Retrieve the conversion rate between base currency and symbol currency.
394 *
395 * @return mixed false in case of errors, float in case of success
396 *
397 * @deprecated since June 2018
398 */
399 protected function callProviderFixer()
400 {
401 //http://fixer.io/
402 $apis_url = 'https://api.fixer.io/latest?'.($this->from_currency != 'EUR' ? 'base='.$this->from_currency.'&' : '').'symbols='.$this->to_currency;
403 $fp = @fopen($apis_url, 'r');
404 if ($fp) {
405 $data = '';
406 while (!feof($fp)) {
407 $data .= fread($fp, 4096);
408 }
409 $resp = json_decode($data);
410 if (is_object($resp) && property_exists($resp, 'rates') && is_object($resp->rates)) {
411 $prop = $this->to_currency;
412 if (property_exists($resp->rates, $prop) && floatval($resp->rates->$prop) > 0.00) {
413 return (float)$resp->rates->$prop;
414 }
415 }
416 }
417 return false;
418 }
419
420 /**
421 * Call to the European Central Bank exchange rates XML data.
422 * Retrieve the conversion rate between base currency and symbol currency.
423 * The exchange rates data is cached by downloading the file every day.
424 *
425 * @return mixed float exchange rate, or false in case of errors
426 *
427 * @since June 2018
428 */
429 protected function callProviderECB()
430 {
431 // load exchange rates
432 $exchange_rates = $this->loadECBRates();
433 if (!$exchange_rates) {
434 // something went wrong
435 return false;
436 }
437
438 if (!isset($exchange_rates[$this->to_currency]) && $this->to_currency != 'EUR') {
439
440 /**
441 * The ECB only supports a few currencies, so as a fallback we also
442 * use the provider floatrates to see if they support the to_currency
443 *
444 * @since October 2018
445 */
446 $exchange_rates = $this->loadFloatRatesDotComRates();
447 if (!$exchange_rates) {
448 // something went wrong
449 return false;
450 }
451 //
452
453 if (!isset($exchange_rates[$this->to_currency])) {
454 // we do not have the exchange rate to this currency.
455 $this->setError('exchange rates to this currency not supported ('.__LINE__.')');
456 return false;
457 }
458 }
459
460 if ($this->from_currency == 'EUR') {
461 // converting from EUR to a known currency
462 return $this->to_currency == 'EUR' ? 1 : $exchange_rates[$this->to_currency];
463 }
464
465 $from_changed = false;
466 if (!isset($exchange_rates[$this->from_currency])) {
467 /**
468 * The ECB only supports a few currencies, so as a fallback we also
469 * use the provider floatrates to see if they support the from_currency
470 *
471 * @since November 2018
472 */
473 $exchange_rates = $this->loadFloatRatesDotComRates(strtolower($this->from_currency));
474 if (!$exchange_rates) {
475 // something went wrong
476 return false;
477 }
478
479 if (isset($exchange_rates[$this->to_currency])) {
480 // we set the exchange rate for the from_currency to 1
481 $exchange_rates[$this->from_currency] = 1;
482 }
483
484 // the from_currency has changed
485 $from_changed = true;
486 }
487
488 if (!isset($exchange_rates[$this->from_currency])) {
489 // converting from this currency is not allowed as we do not know it
490 $this->setError('converting from this currency not allowed because of missing exchange rates ('.__LINE__.')');
491 return false;
492 }
493
494 if ($this->from_currency == $this->to_currency) {
495 // equal currencies should be stopped before this method
496 return 1;
497 }
498
499 if ($from_changed && isset($exchange_rates[$this->to_currency])) {
500 // we have loaded the exchange rates from the requested from_currency (loadFloatRatesDotComRates())
501 return $exchange_rates[$this->to_currency];
502 }
503
504 if ($this->to_currency == 'EUR' && !$from_changed) {
505 // converting to EUR from a known currency
506 return (1 / $exchange_rates[$this->from_currency]);
507 }
508
509 // conversion is not involving EUR, but both currencies are known (from : to = 1 : x)
510 return ($exchange_rates[$this->to_currency] / $exchange_rates[$this->from_currency]);
511 }
512
513 /**
514 * Call to the European Central Bank exchange rates XML data.
515 * Retrieves the conversion rates of all currencies by caching the results,
516 * and by returning an array map with key-value pairs of currencycode-rate from EUR.
517 *
518 * @return mixed false in case of errors, array in case of success
519 *
520 * @since June 2018
521 */
522 protected function loadECBRates()
523 {
524 $rates_doc = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
525 $cache_rates = dirname(__FILE__) . DIRECTORY_SEPARATOR . date('Y-m-d') . '_ecb.xml';
526 $xml_data = '';
527
528 if (!is_file($cache_rates)) {
529 // cached rates are not available, attempt to download the information
530 $expcache = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . '*_ecb.xml');
531 // remove old expired cache files
532 foreach ($expcache as $expf) {
533 if (is_file($expf)) {
534 @unlink($expf);
535 }
536 }
537
538 /**
539 * Remote files should be fetched using CURL rather than through fopen($foo, 'r');
540 *
541 * @since 1.15.2 (J) - 1.5.5 (WP)
542 */
543 $http = new JHttp;
544
545 try {
546 $http_resp = $http->get($rates_doc);
547 } catch (Exception $e) {
548 // request failed
549 $this->setError($e->getMessage());
550 return false;
551 }
552
553 if ($http_resp->code != 200) {
554 // cannot read exchange rates from external XML file
555 $this->setError('cannot read exchange rates from external XML file ('.__LINE__.')');
556 return false;
557 }
558
559 $xml_data = $http_resp->body;
560
561 // store cache file
562 $fp = @fopen($cache_rates, 'w+');
563 if (!$fp) {
564 // cannot open cache file for writing. Exit.
565 $this->setError('cannot open cache file for writing ('.__LINE__.')');
566 return false;
567 }
568 fwrite($fp, $xml_data);
569 fclose($fp);
570 }
571
572 if (!is_file($cache_rates)) {
573 // could not load rates
574 $this->setError('could not load rates ('.__LINE__.')');
575 return false;
576 }
577
578 if (empty($xml_data)) {
579 // read cached rates from XML file
580 $fp = @fopen($cache_rates, 'r');
581 if (!$fp) {
582 // cannot read exchange rates from cached XML file
583 $this->setError('cannot read exchange rates from cached XML file ('.__LINE__.')');
584 return false;
585 }
586 $xml_data = '';
587 while (!feof($fp)) {
588 $xml_data .= fread($fp, 4096);
589 }
590 fclose($fp);
591 }
592
593 $exchange_obj = simplexml_load_string($xml_data);
594 if (!$exchange_obj instanceof SimpleXMLElement) {
595 // this file does not contain correct XML
596 $this->setError('the file does not contain valid XML ('.__LINE__.')');
597 return false;
598 }
599
600 $exchange_rates = array();
601 foreach ($exchange_obj->Cube->Cube->Cube as $exrate) {
602 $attr = $exrate->attributes();
603 $currency = (string)$attr->currency;
604 $rate = (float)$attr->rate;
605 if ($rate > 0.00) {
606 $exchange_rates[$currency] = $rate;
607 }
608 }
609
610 return $exchange_rates;
611 }
612
613 /**
614 * Call to Floatrates.com exchange rates XML data.
615 * Retrieve the conversion rate between base currency and symbol currency.
616 * The exchange rates data is cached by downloading the file every day.
617 *
618 * If this was used as default and only provider, then the from currency would
619 * be passed to the rates loading method to fetch the exact XML feed for the
620 * from currency. If the feed was not available, false would be returned.
621 *
622 * @return mixed float exchange rate, or false in case of errors
623 *
624 * @since October 2018
625 */
626 protected function callProviderFloatRates()
627 {
628 // load exchange rates
629 $exchange_rates = $this->loadFloatRatesDotComRates($this->from_currency);
630 if (!$exchange_rates) {
631 // something went wrong
632 return false;
633 }
634
635 if (!isset($exchange_rates[$this->to_currency])) {
636 // we do not have the exchange rate to this currency.
637 $this->setError('exchange rates to this currency not supported ('.__LINE__.')');
638 return false;
639 }
640
641 if ($this->from_currency == $this->to_currency) {
642 // equal currencies should be stopped before this method
643 return 1;
644 }
645
646 // conversion is not involving EUR, but both currencies are known (from : to = 1 : x)
647 return $exchange_rates[$this->to_currency];
648 }
649
650 /**
651 * Call to the Floatrates.com XML feed.
652 * Retrieves the conversion rates of all currencies by caching the results, and
653 * by returning an array map with key-value pairs of currencycode-rate from EUR.
654 *
655 * @param [string] $currency the base currency 3 chars code, if specified
656 * attempt to load the exact $currency.xml feed
657 *
658 * @return mixed false in case of errors, array in case of success
659 * key (currency code) = value (exchange rate)
660 *
661 * @since October 2018
662 */
663 protected function loadFloatRatesDotComRates($currency = '')
664 {
665 $feed_name = empty($currency) ? 'eur' : substr(strtolower($currency), 0, 3);
666 $rates_doc = "http://www.floatrates.com/daily/{$feed_name}.xml";
667 $cache_rates = dirname(__FILE__) . DIRECTORY_SEPARATOR . date('Y-m-d') . "{$feed_name}" . '_floatrates.xml';
668 $xml_data = '';
669
670 if (!is_file($cache_rates)) {
671 // cached rates are not available, attempt to download the information
672 $expcache = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . '*_floatrates.xml');
673 // remove old expired cache files
674 foreach ($expcache as $expf) {
675 if (is_file($expf)) {
676 @unlink($expf);
677 }
678 }
679
680 /**
681 * Remote files should be fetched using CURL rather than through fopen($foo, 'r');
682 *
683 * @since 1.15.2 (J) - 1.5.5 (WP)
684 */
685 $http = new JHttp;
686
687 try {
688 $http_resp = $http->get($rates_doc);
689 } catch (Exception $e) {
690 // request failed
691 $this->setError($e->getMessage());
692 return false;
693 }
694
695 if ($http_resp->code != 200) {
696 // cannot read exchange rates from external XML file
697 $this->setError('cannot read exchange rates from external XML file ('.__LINE__.')');
698 return false;
699 }
700
701 $xml_data = $http_resp->body;
702
703 // store cache file
704 $fp = @fopen($cache_rates, 'w+');
705 if (!$fp) {
706 // cannot open cache file for writing. Exit.
707 $this->setError('cannot open cache file for writing ('.__LINE__.')');
708 return false;
709 }
710 fwrite($fp, $xml_data);
711 fclose($fp);
712 }
713
714 if (!is_file($cache_rates)) {
715 // could not load rates
716 $this->setError('could not load rates ('.__LINE__.')');
717 return false;
718 }
719
720 if (empty($xml_data)) {
721 // read cached rates from XML file
722 $fp = @fopen($cache_rates, 'r');
723 if (!$fp) {
724 // cannot read exchange rates from cached XML file
725 $this->setError('cannot read exchange rates from cached XML file ('.__LINE__.')');
726 return false;
727 }
728 $xml_data = '';
729 while (!feof($fp)) {
730 $xml_data .= fread($fp, 4096);
731 }
732 fclose($fp);
733 }
734
735 $exchange_obj = simplexml_load_string($xml_data);
736 if (!$exchange_obj instanceof SimpleXMLElement) {
737 // this file does not contain correct XML
738 $this->setError('this file does not contain correct XML ('.__LINE__.')');
739 return false;
740 }
741
742 $exchange_rates = array();
743 foreach ($exchange_obj->item as $exrate) {
744 if (!property_exists($exrate, 'targetCurrency') || !property_exists($exrate, 'exchangeRate')) {
745 continue;
746 }
747 $currency = (string)$exrate->targetCurrency;
748 /**
749 * All conversion rates exceeding 1 thousand are formatted as strings, with a thousand separator
750 * character (comma ","), and so we must get rid of the formatting to obtain a true float value.
751 *
752 * @since 1.14.3 (J) - 1.4.3 (WP)
753 */
754 $rate = floatval(str_replace(',', '', (string)$exrate->exchangeRate));
755 //
756 if ($rate > 0.00) {
757 $exchange_rates[$currency] = $rate;
758 }
759 }
760
761 if (!count($exchange_rates)) {
762 // invalid XML structure
763 $this->setError('invalid exchange rates structure ('.__LINE__.')');
764 return false;
765 }
766
767 return $exchange_rates;
768 }
769
770 /**
771 * Makes a float number from a given string number.
772 *
773 * @param string $num the formatted number to exchange.
774 *
775 * @return float the number to exchange.
776 */
777 protected function makeFloat($num)
778 {
779 $floated = $num;
780 if (is_array($this->format) && count($this->format) == 3) {
781 $decimals = '';
782 if (strstr($num, $this->format[1]) !== false) {
783 $decimals = substr($num, ((int)$this->format[0] - ((int)$this->format[0] * 2)));
784 }
785 $nosep = str_replace($this->format[1], '', $num);
786 $nosep = str_replace($this->format[2], '', $nosep);
787 $newdecimals = '';
788 if ((int)$this->format[0] > 0 && !empty($decimals)) {
789 $nosep = substr_replace($nosep, '', (strlen($decimals) - (strlen($decimals) * 2)));
790 $decimalsabs = abs($decimals);
791 if ($decimalsabs > 0) {
792 $newdecimals = $decimals;
793 }
794 }
795 $floated = floatval($nosep.(!empty($newdecimals) ? '.'.$newdecimals : ''));
796 }
797
798 return $floated;
799 }
800
801 /**
802 * Finds and returns an appropriate symbol for the currency converted
803 *
804 * @return string the symbol to return to the controller for the exchanged rates
805 */
806 protected function currencySymbol()
807 {
808 /**
809 * Trigger event to allow third party plugins to return specific information for a currency.
810 *
811 * @since 1.16.0 (J) - 1.6.0 (WP)
812 */
813 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetCurrencyDataVikBooking', [$this->to_currency]);
814 if (is_array($custom_conv) && !empty($custom_conv[0]) && is_array($custom_conv[0]) && !empty($custom_conv[0]['symbol'])) {
815 return $custom_conv[0]['symbol'];
816 }
817
818 if (isset($this->currencymap[$this->to_currency]) && isset($this->currencymap[$this->to_currency]['symbol'])) {
819 $symbol = '&#'.$this->currencymap[$this->to_currency]['symbol'].';';
820 } else {
821 $symbol = $this->to_currency;
822 }
823
824 return $symbol;
825 }
826
827 /**
828 * Formats a number according to the values passed to the constructor
829 *
830 * @param mixed $num the number to format
831 *
832 * @return string the formatted number for display
833 */
834 protected function currencyFormat($num)
835 {
836 $num_decimals = (int)$this->format[0];
837 if (array_key_exists($this->to_currency, $this->currencymap)) {
838 if (array_key_exists('decimals', $this->currencymap[$this->to_currency])) {
839 $num_decimals = $this->currencymap[$this->to_currency]['decimals'];
840 } else {
841 $num_decimals = 2;
842 }
843 }
844
845 // decimals and thousands separators for this currency
846 $decimals_sep = $this->format[1];
847 $thousands_sep = $this->format[2];
848
849 /**
850 * Trigger event to allow third party plugins to return specific information for a currency.
851 *
852 * @since 1.16.0 (J) - 1.6.0 (WP)
853 */
854 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetCurrencyDataVikBooking', [$this->to_currency]);
855 if (is_array($custom_conv) && !empty($custom_conv[0]) && is_array($custom_conv[0])) {
856 // override currency information for formatting the amounts
857 $num_decimals = isset($custom_conv[0]['decimals']) && is_int($custom_conv[0]['decimals']) ? $custom_conv[0]['decimals'] : $num_decimals;
858 $decimals_sep = isset($custom_conv[0]['decimals_separator']) && is_string($custom_conv[0]['decimals_separator']) ? $custom_conv[0]['decimals_separator'] : $decimals_sep;
859 $thousands_sep = isset($custom_conv[0]['thousands_separator']) && is_string($custom_conv[0]['thousands_separator']) ? $custom_conv[0]['thousands_separator'] : $thousands_sep;
860 }
861
862 return number_format($num, $num_decimals, $decimals_sep, $thousands_sep);
863 }
864
865 /**
866 * Main method called by the controller after invoking the class. The same method is also
867 * called by other classes of VBO, and keeps associative arrays of rates to exchange.
868 *
869 * @param bool $get_floats true to only return the plain exchanged rates.
870 *
871 * @return array the array of rates converted or an empty array in case of error
872 *
873 * @since 1.15.0 (J) - 1.5.0 (WP) added argument to return plain exchanged floats.
874 */
875 public function convert($get_floats = false)
876 {
877 $conversion = [];
878
879 if (!is_array($this->prices) || !$this->prices) {
880 return $conversion;
881 }
882
883 // get the conversion rate between the currencies
884 $conv_rate = $this->getConversionRate();
885
886 if ($conv_rate === false) {
887 // return the original array
888 return $conversion;
889 }
890
891 // get the currency symbol of the exchanged currency
892 $conv_symbol = $this->currencySymbol();
893
894 // exchange values
895 foreach ($this->prices as $k => $price) {
896 // get the base price to exchange
897 if ($get_floats) {
898 // given price is expected to be already a raw float
899 $base_price = (float)$price;
900 } else {
901 // given price has been formatted already
902 $base_price = $this->makeFloat($price);
903 }
904
905 // apply conversion
906 $exchanged = $base_price * $conv_rate;
907
908 if ($get_floats) {
909 // inject just the exchanged rate
910 $conversion[$k] = $exchanged;
911 } else {
912 // build the exchanged information
913 $conversion[$k]['symbol'] = $conv_symbol;
914 $conversion[$k]['price'] = $this->currencyFormat($exchanged);
915 }
916 }
917
918 return $conversion;
919 }
920
921 /**
922 * Generic method called by convert() to invoke a provider and get
923 * the necessary exchange rates for applying the conversions.
924 *
925 * @see convert()
926 *
927 * @return mixed float, the exchange rate for the given from and to currencies
928 * in case of success. Boolean false otherwise.
929 *
930 * @since 1.15.0 (J) - 1.5.0 (WP) the visibility is public, but used to be private before.
931 */
932 public function getConversionRate()
933 {
934 $session = JFactory::getSession();
935 $ses_conversions = $session->get('vboCurrencyConversions', '');
936 $conversions_made = array();
937 $data = '';
938 $conv_rate = false;
939
940 /**
941 * Trigger event to allow third party plugins to return a specific conversion rate for these currencies.
942 *
943 * @since 1.16.0 (J) - 1.6.0 (WP)
944 */
945 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetConversionRateVikBooking', [$this->from_currency, $this->to_currency]);
946 if (is_array($custom_conv) && !empty($custom_conv[0]) && (is_int($custom_conv[0]) || is_float($custom_conv[0]))) {
947 return $custom_conv[0];
948 }
949
950 if (is_array($ses_conversions) && count($ses_conversions)) {
951 $conversions_made = $ses_conversions;
952 if (isset($ses_conversions[$this->from_currency.'_'.$this->to_currency])) {
953 if (strlen($ses_conversions[$this->from_currency.'_'.$this->to_currency]) && floatval($ses_conversions[$this->from_currency.'_'.$this->to_currency]) > 0.00) {
954 $conv_rate = $ses_conversions[$this->from_currency.'_'.$this->to_currency];
955 }
956 }
957 }
958
959 if ($conv_rate === false) {
960 // use an external provider for getting the conversion rate
961 $api_method = 'callProvider'.str_replace(' ', '', ucwords($this->api_provider));
962 if (!method_exists($this, $api_method)) {
963 return false;
964 }
965 $conv_rate = $this->{$api_method}();
966 if ($conv_rate !== false) {
967 // cache conversion rate into the session
968 $conversions_made[$this->from_currency.'_'.$this->to_currency] = $conv_rate;
969 $session->set('vboCurrencyConversions', $conversions_made);
970 }
971
972 }
973
974 return $conv_rate;
975 }
976
977 /**
978 * Tells whether a currency name exists in the map-list.
979 *
980 * @param string $currency_name the currency name to check.
981 *
982 * @return bool true if available, or false.
983 *
984 * @since 1.15.0 (J) - 1.5.0 (WP)
985 */
986 public function currencyExists($currency_name)
987 {
988 if (isset($this->currencymap[$currency_name])) {
989 return true;
990 }
991
992 /**
993 * Trigger event to allow third party plugins to register a custom currency code.
994 *
995 * @since 1.16.0 (J) - 1.6.0 (WP)
996 */
997 $existing = VBOFactory::getPlatform()->getDispatcher()->filter('onCheckCurrencyExistsVikBooking', [$currency_name]);
998 if (is_array($existing) && in_array(true, $existing, true)) {
999 return true;
1000 }
1001
1002 return false;
1003 }
1004
1005 /**
1006 * Overrides the prices to convert.
1007 *
1008 * @param array $prices the list of prices to convert.
1009 *
1010 * @return array the current list of prices to convert.
1011 *
1012 * @since 1.15.0 (J) - 1.5.0 (WP)
1013 */
1014 public function setPrices(array $prices = [])
1015 {
1016 $this->prices = $prices;
1017
1018 return $this->prices;
1019 }
1020
1021 /**
1022 * Returns an associative list of currencies with code and name.
1023 *
1024 * @return array
1025 *
1026 * @since 1.15.4 (J) - 1.5.10 (WP)
1027 */
1028 public function getCurrencyNames()
1029 {
1030 return $this->currency_names;
1031 }
1032
1033 /**
1034 * Concatenates errors occurred during the execution
1035 *
1036 * @param string $err the error message
1037 *
1038 * @return void
1039 */
1040 protected function setError($err)
1041 {
1042 $this->error .= $err . "\n";
1043 }
1044
1045 /**
1046 * Returns the errors occurred during the execution
1047 *
1048 * @return string the error message(s)
1049 */
1050 public function getError()
1051 {
1052 return rtrim($this->error, "\n");
1053 }
1054 }
1055