PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.13
VikBooking Hotel Booking Engine & PMS v1.8.13
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.8.13, at site/helpers/currencyconverter.php

1,137 lines 34.8 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 'AED' => array('symbol' => '1583;.&#1573'),
234 'ALL' => array('symbol' => '76'),
235 'AFN' => array('symbol' => '1547'),
236 'ARS' => array('symbol' => '36'),
237 'AWG' => array('symbol' => '402'),
238 'AUD' => array('symbol' => '36'),
239 'AZN' => array('symbol' => '1084'),
240 'BSD' => array('symbol' => '36'),
241 'BBD' => array('symbol' => '36'),
242 'BYR' => array('symbol' => '112', 'decimals' => 0),
243 'BZD' => array('symbol' => '66'),
244 'BMD' => array('symbol' => '36'),
245 'BOB' => array('symbol' => '36'),
246 'BAM' => array('symbol' => '75'),
247 'BWP' => array('symbol' => '80'),
248 'BGN' => array('symbol' => '1083'),
249 'BHD' => array('decimals' => 3),
250 'BRL' => array('symbol' => '82'),
251 'BND' => array('symbol' => '36'),
252 'KHR' => array('symbol' => '6107'),
253 'CAD' => array('symbol' => '36'),
254 'KYD' => array('symbol' => '36'),
255 'CLP' => array('symbol' => '36', 'decimals' => 0),
256 'CNY' => array('symbol' => '165'),
257 'COP' => array('symbol' => '36', 'decimals' => 3),
258 'CRC' => array('symbol' => '8353'),
259 'CVE' => array('symbol' => '36', 'decimals' => 2),
260 'HRK' => array('symbol' => '107'),
261 'CUP' => array('symbol' => '8369'),
262 'CZK' => array('symbol' => '75'),
263 'DKK' => array('symbol' => '107'),
264 'DOP' => array('symbol' => '82'),
265 'XCD' => array('symbol' => '36'),
266 'EGP' => array('symbol' => '163'),
267 'SVC' => array('symbol' => '36'),
268 'EEK' => array('symbol' => '107'),
269 'EUR' => array('symbol' => '8364'),
270 'FKP' => array('symbol' => '163'),
271 'FJD' => array('symbol' => '36'),
272 'GHC' => array('symbol' => '162'),
273 'GIP' => array('symbol' => '163'),
274 'GTQ' => array('symbol' => '81'),
275 'GGP' => array('symbol' => '163'),
276 'GYD' => array('symbol' => '36'),
277 'HNL' => array('symbol' => '76'),
278 'HKD' => array('symbol' => '36'),
279 'HUF' => array('symbol' => '70', 'decimals' => 0),
280 'ISK' => array('symbol' => '107', 'decimals' => 0),
281 'IDR' => array('symbol' => '82'),
282 'INR' => array('symbol' => '8377'),
283 'IRR' => array('symbol' => '65020'),
284 'IMP' => array('symbol' => '163'),
285 'ILS' => array('symbol' => '8362'),
286 'JMD' => array('symbol' => '74'),
287 'JPY' => array('symbol' => '165', 'decimals' => 0),
288 'JEP' => array('symbol' => '163'),
289 'KES' => array('decimals' => 2),
290 'KZT' => array('symbol' => '1083'),
291 'KPW' => array('symbol' => '8361'),
292 'KRW' => array('symbol' => '8361', 'decimals' => 0),
293 'KGS' => array('symbol' => '1083'),
294 'KWD' => array('decimals' => 3),
295 'LAK' => array('symbol' => '8365'),
296 'LVL' => array('symbol' => '76'),
297 'LBP' => array('symbol' => '163'),
298 'LRD' => array('symbol' => '36'),
299 'LTL' => array('symbol' => '76'),
300 'MKD' => array('symbol' => '1076'),
301 'MYR' => array('symbol' => '82'),
302 'MUR' => array('symbol' => '8360'),
303 'MXN' => array('symbol' => '36'),
304 'MNT' => array('symbol' => '8366'),
305 'MZN' => array('symbol' => '77', 'decimals' => 0),
306 'NAD' => array('symbol' => '36'),
307 'NPR' => array('symbol' => '8360'),
308 'ANG' => array('symbol' => '402'),
309 'NZD' => array('symbol' => '36'),
310 'NIO' => array('symbol' => '67'),
311 'NGN' => array('symbol' => '8358'),
312 'NOK' => array('symbol' => '107'),
313 'OMR' => array('symbol' => '65020', 'decimals' => 3),
314 'PKR' => array('symbol' => '8360'),
315 'PAB' => array('symbol' => '66'),
316 'PYG' => array('symbol' => '71', 'decimals' => 0),
317 'PEN' => array('symbol' => '83'),
318 'PHP' => array('symbol' => '8369'),
319 'PLN' => array('symbol' => '122'),
320 'QAR' => array('symbol' => '65020'),
321 'RON' => array('symbol' => '108;&#101;&#105'),
322 'RUB' => array('symbol' => '1088'),
323 'SHP' => array('symbol' => '163'),
324 'SAR' => array('symbol' => '65020'),
325 'RSD' => array('symbol' => '1044'),
326 'SCR' => array('symbol' => '8360'),
327 'SGD' => array('symbol' => '36'),
328 'SBD' => array('symbol' => '36'),
329 'SOS' => array('symbol' => '83'),
330 'ZAR' => array('symbol' => '82'),
331 'LKR' => array('symbol' => '8360'),
332 'SEK' => array('symbol' => '107'),
333 'CHF' => array('symbol' => '8355'),
334 'SRD' => array('symbol' => '36'),
335 'SYP' => array('symbol' => '163'),
336 'TWD' => array('symbol' => '78'),
337 'THB' => array('symbol' => '3647'),
338 'TTD' => array('symbol' => '84'),
339 'TRY' => array('symbol' => '8378', 'decimals' => 0),
340 'UAH' => array('symbol' => '8372'),
341 'GBP' => array('symbol' => '163'),
342 'USD' => array('symbol' => '36'),
343 'UYU' => array('symbol' => '36'),
344 'UZS' => array('symbol' => '1083'),
345 'VEF' => array('symbol' => '66'),
346 'VND' => array('symbol' => '8363'),
347 'XOF' => array('decimals' => 0),
348 'YER' => array('symbol' => '65020'),
349 'ZWD' => array('symbol' => '90'),
350 'TND' => array('decimals' => 3),
351 );
352
353 /**
354 * This is the API's Provider that the Class will use to retrieve the conversion rate.
355 * The method callProvider{$suffix}() will be called by the getConversionRate() method.
356 *
357 * Supported Providers: ECB, FloatRates (used as callback by ECB)
358 * Deprecated Providers: fixer, yahoo
359 *
360 * @see getConversionRate()
361 */
362 $this->api_provider = 'ECB';
363 }
364
365 /**
366 * Deprecated call to the Yahoo Finance APIs (shut down in November 2017).
367 * Retrieve the conversion rate between base currency and symbol currency.
368 *
369 * @return mixed false in case of errors, float in case of success
370 *
371 * @deprecated since November 2017
372 */
373 protected function callProviderYahoo()
374 {
375 //http://finance.yahoo.com/currency-converter
376 $apis_url = 'http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s='. $this->from_currency . $this->to_currency .'=X';
377 $fp = @fopen($apis_url, 'r');
378 if ($fp) {
379 $data = '';
380 while (!feof($fp)) {
381 $data .= fread($fp, 4096);
382 }
383 if (!empty($data)) {
384 $data = str_replace("\"", "", $data);
385 $rate_info = explode(',', $data);
386 if (strlen($rate_info[1]) > 0 && floatval($rate_info[1]) > 0.00) {
387 return (float)$rate_info[1];
388 }
389 }
390 }
391 return false;
392 }
393
394 /**
395 * Call to the Fixer Foreign exchange rates and currency conversion API.
396 * Retrieve the conversion rate between base currency and symbol currency.
397 *
398 * @return mixed false in case of errors, float in case of success
399 *
400 * @deprecated since June 2018
401 */
402 protected function callProviderFixer()
403 {
404 //http://fixer.io/
405 $apis_url = 'https://api.fixer.io/latest?'.($this->from_currency != 'EUR' ? 'base='.$this->from_currency.'&' : '').'symbols='.$this->to_currency;
406 $fp = @fopen($apis_url, 'r');
407 if ($fp) {
408 $data = '';
409 while (!feof($fp)) {
410 $data .= fread($fp, 4096);
411 }
412 $resp = json_decode($data);
413 if (is_object($resp) && property_exists($resp, 'rates') && is_object($resp->rates)) {
414 $prop = $this->to_currency;
415 if (property_exists($resp->rates, $prop) && floatval($resp->rates->$prop) > 0.00) {
416 return (float)$resp->rates->$prop;
417 }
418 }
419 }
420 return false;
421 }
422
423 /**
424 * Call to the European Central Bank exchange rates XML data.
425 * Retrieve the conversion rate between base currency and symbol currency.
426 * The exchange rates data is cached by downloading the file every day.
427 *
428 * @return mixed float exchange rate, or false in case of errors
429 *
430 * @since June 2018
431 */
432 protected function callProviderECB()
433 {
434 // load exchange rates
435 $exchange_rates = $this->loadECBRates();
436 if (!$exchange_rates) {
437 // something went wrong
438 return false;
439 }
440
441 if (!isset($exchange_rates[$this->to_currency]) && $this->to_currency != 'EUR') {
442
443 /**
444 * The ECB only supports a few currencies, so as a fallback we also
445 * use the provider floatrates to see if they support the to_currency
446 *
447 * @since October 2018
448 */
449 $exchange_rates = $this->loadFloatRatesDotComRates();
450 if (!$exchange_rates) {
451 // something went wrong
452 return false;
453 }
454 //
455
456 if (!isset($exchange_rates[$this->to_currency])) {
457 // we do not have the exchange rate to this currency.
458 $this->setError('exchange rates to this currency not supported ('.__LINE__.')');
459 return false;
460 }
461 }
462
463 if ($this->from_currency == 'EUR') {
464 // converting from EUR to a known currency
465 return $this->to_currency == 'EUR' ? 1 : $exchange_rates[$this->to_currency];
466 }
467
468 $from_changed = false;
469 if (!isset($exchange_rates[$this->from_currency])) {
470 /**
471 * The ECB only supports a few currencies, so as a fallback we also
472 * use the provider floatrates to see if they support the from_currency
473 *
474 * @since November 2018
475 */
476 $exchange_rates = $this->loadFloatRatesDotComRates(strtolower($this->from_currency));
477 if (!$exchange_rates) {
478 // something went wrong
479 return false;
480 }
481
482 if (isset($exchange_rates[$this->to_currency])) {
483 // we set the exchange rate for the from_currency to 1
484 $exchange_rates[$this->from_currency] = 1;
485 }
486
487 // the from_currency has changed
488 $from_changed = true;
489 }
490
491 if (!isset($exchange_rates[$this->from_currency])) {
492 // converting from this currency is not allowed as we do not know it
493 $this->setError('converting from this currency not allowed because of missing exchange rates ('.__LINE__.')');
494 return false;
495 }
496
497 if ($this->from_currency == $this->to_currency) {
498 // equal currencies should be stopped before this method
499 return 1;
500 }
501
502 if ($from_changed && isset($exchange_rates[$this->to_currency])) {
503 // we have loaded the exchange rates from the requested from_currency (loadFloatRatesDotComRates())
504 return $exchange_rates[$this->to_currency];
505 }
506
507 if ($this->to_currency == 'EUR' && !$from_changed) {
508 // converting to EUR from a known currency
509 return (1 / $exchange_rates[$this->from_currency]);
510 }
511
512 // conversion is not involving EUR, but both currencies are known (from : to = 1 : x)
513 return ($exchange_rates[$this->to_currency] / $exchange_rates[$this->from_currency]);
514 }
515
516 /**
517 * Call to the European Central Bank exchange rates XML data.
518 * Retrieves the conversion rates of all currencies by caching the results,
519 * and by returning an array map with key-value pairs of currencycode-rate from EUR.
520 *
521 * @return mixed false in case of errors, array in case of success
522 *
523 * @since June 2018
524 */
525 protected function loadECBRates()
526 {
527 $rates_doc = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
528 $cache_rates = dirname(__FILE__) . DIRECTORY_SEPARATOR . date('Y-m-d') . '_ecb.xml';
529 $xml_data = '';
530
531 if (!is_file($cache_rates)) {
532 // cached rates are not available, attempt to download the information
533 $expcache = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . '*_ecb.xml');
534 // remove old expired cache files
535 foreach ($expcache as $expf) {
536 if (is_file($expf)) {
537 @unlink($expf);
538 }
539 }
540
541 /**
542 * Remote files should be fetched using CURL rather than through fopen($foo, 'r');
543 *
544 * @since 1.15.2 (J) - 1.5.5 (WP)
545 */
546 $http = new JHttp;
547
548 try {
549 $http_resp = $http->get($rates_doc);
550 } catch (Exception $e) {
551 // request failed
552 $this->setError($e->getMessage());
553 return false;
554 }
555
556 if ($http_resp->code != 200) {
557 // cannot read exchange rates from external XML file
558 $this->setError('cannot read exchange rates from external XML file ('.__LINE__.')');
559 return false;
560 }
561
562 $xml_data = $http_resp->body;
563
564 // store cache file
565 $fp = @fopen($cache_rates, 'w+');
566 if (!$fp) {
567 // cannot open cache file for writing. Exit.
568 $this->setError('cannot open cache file for writing ('.__LINE__.')');
569 return false;
570 }
571 fwrite($fp, $xml_data);
572 fclose($fp);
573 }
574
575 if (!is_file($cache_rates)) {
576 // could not load rates
577 $this->setError('could not load rates ('.__LINE__.')');
578 return false;
579 }
580
581 if (empty($xml_data)) {
582 // read cached rates from XML file
583 $fp = @fopen($cache_rates, 'r');
584 if (!$fp) {
585 // cannot read exchange rates from cached XML file
586 $this->setError('cannot read exchange rates from cached XML file ('.__LINE__.')');
587 return false;
588 }
589 $xml_data = '';
590 while (!feof($fp)) {
591 $xml_data .= fread($fp, 4096);
592 }
593 fclose($fp);
594 }
595
596 $exchange_obj = simplexml_load_string($xml_data);
597 if (!$exchange_obj instanceof SimpleXMLElement) {
598 // this file does not contain correct XML
599 $this->setError('the file does not contain valid XML ('.__LINE__.')');
600 return false;
601 }
602
603 $exchange_rates = array();
604 foreach ($exchange_obj->Cube->Cube->Cube as $exrate) {
605 $attr = $exrate->attributes();
606 $currency = (string)$attr->currency;
607 $rate = (float)$attr->rate;
608 if ($rate > 0.00) {
609 $exchange_rates[$currency] = $rate;
610 }
611 }
612
613 return $exchange_rates;
614 }
615
616 /**
617 * Call to Floatrates.com exchange rates XML data.
618 * Retrieve the conversion rate between base currency and symbol currency.
619 * The exchange rates data is cached by downloading the file every day.
620 *
621 * If this was used as default and only provider, then the from currency would
622 * be passed to the rates loading method to fetch the exact XML feed for the
623 * from currency. If the feed was not available, false would be returned.
624 *
625 * @return mixed float exchange rate, or false in case of errors
626 *
627 * @since October 2018
628 */
629 protected function callProviderFloatRates()
630 {
631 // load exchange rates
632 $exchange_rates = $this->loadFloatRatesDotComRates($this->from_currency);
633 if (!$exchange_rates) {
634 // something went wrong
635 return false;
636 }
637
638 if (!isset($exchange_rates[$this->to_currency])) {
639 // we do not have the exchange rate to this currency.
640 $this->setError('exchange rates to this currency not supported ('.__LINE__.')');
641 return false;
642 }
643
644 if ($this->from_currency == $this->to_currency) {
645 // equal currencies should be stopped before this method
646 return 1;
647 }
648
649 // conversion is not involving EUR, but both currencies are known (from : to = 1 : x)
650 return $exchange_rates[$this->to_currency];
651 }
652
653 /**
654 * Call to the Floatrates.com XML feed.
655 * Retrieves the conversion rates of all currencies by caching the results, and
656 * by returning an array map with key-value pairs of currencycode-rate from EUR.
657 *
658 * @param [string] $currency the base currency 3 chars code, if specified
659 * attempt to load the exact $currency.xml feed
660 *
661 * @return mixed false in case of errors, array in case of success
662 * key (currency code) = value (exchange rate)
663 *
664 * @since October 2018
665 */
666 protected function loadFloatRatesDotComRates($currency = '')
667 {
668 $feed_name = empty($currency) ? 'eur' : substr(strtolower($currency), 0, 3);
669 $rates_doc = "https://www.floatrates.com/daily/{$feed_name}.xml";
670 $cache_rates = dirname(__FILE__) . DIRECTORY_SEPARATOR . date('Y-m-d') . "{$feed_name}" . '_floatrates.xml';
671 $xml_data = '';
672
673 /**
674 * Add support to always-valid exchange rates XML file that could be uploaded onto the server.
675 * This is useful to overcome server limitations for fetching remote XML files.
676 *
677 * @since 1.18.7 (J) - 1.8.7 (WP)
678 */
679 if (!is_file($cache_rates)) {
680 $always_valid_cache_rates = dirname(__FILE__) . DIRECTORY_SEPARATOR . "cache_{$feed_name}" . '_floatrates.xml';
681 if (is_file($always_valid_cache_rates)) {
682 // overwrite cached rates path
683 $cache_rates = $always_valid_cache_rates;
684 }
685 }
686
687 if (!is_file($cache_rates)) {
688 // cached rates are not available, attempt to download the information
689 $expcache = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . '*_floatrates.xml');
690 // remove old expired cache files
691 foreach ($expcache as $expf) {
692 if (is_file($expf)) {
693 @unlink($expf);
694 }
695 }
696
697 /**
698 * Remote files should be fetched using CURL rather than through fopen($foo, 'r');
699 *
700 * @since 1.15.2 (J) - 1.5.5 (WP)
701 */
702 $http = new JHttp;
703
704 try {
705 $http_resp = $http->get($rates_doc);
706 } catch (Exception $e) {
707 // request failed
708 $this->setError($e->getMessage());
709 return false;
710 }
711
712 if ($http_resp->code != 200) {
713 // cannot read exchange rates from external XML file
714 $this->setError('cannot read exchange rates from external XML file ('.__LINE__.')');
715 return false;
716 }
717
718 $xml_data = $http_resp->body;
719
720 // store cache file
721 $fp = @fopen($cache_rates, 'w+');
722 if (!$fp) {
723 // cannot open cache file for writing. Exit.
724 $this->setError('cannot open cache file for writing ('.__LINE__.')');
725 return false;
726 }
727 fwrite($fp, $xml_data);
728 fclose($fp);
729 }
730
731 if (!is_file($cache_rates)) {
732 // could not load rates
733 $this->setError('could not load rates ('.__LINE__.')');
734 return false;
735 }
736
737 if (empty($xml_data)) {
738 // read cached rates from XML file
739 $fp = @fopen($cache_rates, 'r');
740 if (!$fp) {
741 // cannot read exchange rates from cached XML file
742 $this->setError('cannot read exchange rates from cached XML file ('.__LINE__.')');
743 return false;
744 }
745 $xml_data = '';
746 while (!feof($fp)) {
747 $xml_data .= fread($fp, 4096);
748 }
749 fclose($fp);
750 }
751
752 $exchange_obj = simplexml_load_string($xml_data);
753 if (!$exchange_obj instanceof SimpleXMLElement) {
754 // this file does not contain correct XML
755 $this->setError('this file does not contain correct XML ('.__LINE__.')');
756 return false;
757 }
758
759 $exchange_rates = array();
760 foreach ($exchange_obj->item as $exrate) {
761 if (!property_exists($exrate, 'targetCurrency') || !property_exists($exrate, 'exchangeRate')) {
762 continue;
763 }
764 $currency = (string)$exrate->targetCurrency;
765 /**
766 * All conversion rates exceeding 1 thousand are formatted as strings, with a thousand separator
767 * character (comma ","), and so we must get rid of the formatting to obtain a true float value.
768 *
769 * @since 1.14.3 (J) - 1.4.3 (WP)
770 */
771 $rate = floatval(str_replace(',', '', (string)$exrate->exchangeRate));
772 //
773 if ($rate > 0.00) {
774 $exchange_rates[$currency] = $rate;
775 }
776 }
777
778 if (!count($exchange_rates)) {
779 // invalid XML structure
780 $this->setError('invalid exchange rates structure ('.__LINE__.')');
781 return false;
782 }
783
784 return $exchange_rates;
785 }
786
787 /**
788 * Makes a float number from a given string number.
789 *
790 * @param string $num the formatted number to exchange.
791 *
792 * @return float the number to exchange.
793 */
794 protected function makeFloat($num)
795 {
796 $floated = $num;
797 if (is_array($this->format) && count($this->format) == 3) {
798 $decimals = '';
799 if (strstr($num, $this->format[1]) !== false) {
800 $decimals = substr($num, ((int)$this->format[0] - ((int)$this->format[0] * 2)));
801 }
802 $nosep = str_replace($this->format[1], '', $num);
803 $nosep = str_replace($this->format[2], '', $nosep);
804 $newdecimals = '';
805 if ((int)$this->format[0] > 0 && !empty($decimals)) {
806 $nosep = substr_replace($nosep, '', (strlen($decimals) - (strlen($decimals) * 2)));
807 $decimalsabs = abs($decimals);
808 if ($decimalsabs > 0) {
809 $newdecimals = $decimals;
810 }
811 }
812 $floated = floatval($nosep.(!empty($newdecimals) ? '.'.$newdecimals : ''));
813 }
814
815 return $floated;
816 }
817
818 /**
819 * Finds and returns an appropriate symbol for the currency converted.
820 *
821 * @param string $custom_currency Optional currency ISO name to read.
822 *
823 * @return string The symbol to return to the controller for the exchanged rates.
824 *
825 * @since 1.17.2 (J) - 1.7.2 (WP) added argument $custom_currency.
826 */
827 protected function currencySymbol($custom_currency = null)
828 {
829 // the currency to evaluate
830 $use_currency = $custom_currency ?: $this->to_currency;
831
832 /**
833 * Trigger event to allow third party plugins to return specific information for a currency.
834 *
835 * @since 1.16.0 (J) - 1.6.0 (WP)
836 */
837 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetCurrencyDataVikBooking', [$use_currency]);
838 if (is_array($custom_conv) && !empty($custom_conv[0]['symbol'])) {
839 return $custom_conv[0]['symbol'];
840 }
841
842 if (isset($this->currencymap[$use_currency]['symbol'])) {
843 $symbol = '&#'.$this->currencymap[$use_currency]['symbol'].';';
844 } else {
845 $symbol = $use_currency;
846 }
847
848 return $symbol;
849 }
850
851 /**
852 * Formats a number according to the values passed to the constructor
853 *
854 * @param mixed $num the number to format
855 *
856 * @return string the formatted number for display
857 */
858 protected function currencyFormat($num)
859 {
860 // obtain the "to currency" formatting data options
861 list($num_decimals, $decimals_sep, $thousands_sep) = $this->getCurrencyFormatData();
862
863 // return the formatted amount according to currency settings
864 return number_format($num, $num_decimals, $decimals_sep, $thousands_sep);
865 }
866
867 /**
868 * Returns a list of currency formatting data (number of decimals, separators).
869 *
870 * @param string $custom_currency Optional currency ISO name to check.
871 *
872 * @return array Linear list of formatting data.
873 *
874 * @since 1.17.2 (J) - 1.7.2 (WP)
875 */
876 protected function getCurrencyFormatData($custom_currency = null)
877 {
878 // the currency to evaluate
879 $use_currency = $custom_currency ?: $this->to_currency;
880
881 $num_decimals = (int) $this->format[0];
882
883 if (isset($this->currencymap[$use_currency])) {
884 if (isset($this->currencymap[$use_currency]['decimals'])) {
885 // use the currency related number of decimals
886 $num_decimals = $this->currencymap[$use_currency]['decimals'];
887 } else {
888 // default to 2 decimal points when the currency exists by default
889 $num_decimals = 2;
890 }
891 }
892
893 // decimals and thousands separators for this currency
894 $decimals_sep = $this->format[1];
895 $thousands_sep = $this->format[2];
896
897 /**
898 * Trigger event to allow third party plugins to return specific information for a currency.
899 *
900 * @since 1.16.0 (J) - 1.6.0 (WP)
901 */
902 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetCurrencyDataVikBooking', [$use_currency]);
903 if (is_array($custom_conv) && !empty($custom_conv[0]) && is_array($custom_conv[0])) {
904 // override currency information for formatting the amounts
905 $num_decimals = isset($custom_conv[0]['decimals']) && is_int($custom_conv[0]['decimals']) ? $custom_conv[0]['decimals'] : $num_decimals;
906 $decimals_sep = isset($custom_conv[0]['decimals_separator']) && is_string($custom_conv[0]['decimals_separator']) ? $custom_conv[0]['decimals_separator'] : $decimals_sep;
907 $thousands_sep = isset($custom_conv[0]['thousands_separator']) && is_string($custom_conv[0]['thousands_separator']) ? $custom_conv[0]['thousands_separator'] : $thousands_sep;
908 }
909
910 return [
911 $num_decimals,
912 $decimals_sep,
913 $thousands_sep,
914 ];
915 }
916
917 /**
918 * Obtains a list of currency data options for the requested currency.
919 *
920 * @param string $currency The currency ISO name (3-char) to get.
921 *
922 * @return array Associative currency options, or empty list.
923 *
924 * @since 1.17.2 (J) - 1.7.2 (WP)
925 */
926 public function getCurrencyData($currency)
927 {
928 if (!$this->currencyExists($currency)) {
929 return [];
930 }
931
932 // get the currency symbol
933 $symbol = $this->currencySymbol($currency);
934
935 // obtain the currency formatting data options
936 list($num_decimals, $decimals_sep, $thousands_sep) = $this->getCurrencyFormatData($currency);
937
938 return [
939 'name' => $this->currency_names[$currency] ?? $currency,
940 'symbol' => $symbol,
941 'decimals' => $num_decimals,
942 'decimals_sep' => $decimals_sep,
943 'thousands_sep' => $thousands_sep,
944 ];
945 }
946
947 /**
948 * Main method called by the controller after invoking the class. The same method is also
949 * called by other classes of VBO, and keeps associative arrays of rates to exchange.
950 *
951 * @param bool $get_floats true to only return the plain exchanged rates.
952 *
953 * @return array the array of rates converted or an empty array in case of error
954 *
955 * @since 1.15.0 (J) - 1.5.0 (WP) added argument to return plain exchanged floats.
956 */
957 public function convert($get_floats = false)
958 {
959 $conversion = [];
960
961 if (!is_array($this->prices) || !$this->prices) {
962 return $conversion;
963 }
964
965 // get the conversion rate between the currencies
966 $conv_rate = $this->getConversionRate();
967
968 if ($conv_rate === false) {
969 // return the original array
970 return $conversion;
971 }
972
973 // get the currency symbol of the exchanged currency
974 $conv_symbol = $this->currencySymbol();
975
976 // exchange values
977 foreach ($this->prices as $k => $price) {
978 // get the base price to exchange
979 if ($get_floats) {
980 // given price is expected to be already a raw float
981 $base_price = (float)$price;
982 } else {
983 // given price has been formatted already
984 $base_price = $this->makeFloat($price);
985 }
986
987 // apply conversion
988 $exchanged = $base_price * $conv_rate;
989
990 if ($get_floats) {
991 // inject just the exchanged rate
992 $conversion[$k] = $exchanged;
993 } else {
994 // build the exchanged information
995 $conversion[$k]['symbol'] = $conv_symbol;
996 $conversion[$k]['price'] = $this->currencyFormat($exchanged);
997 }
998 }
999
1000 return $conversion;
1001 }
1002
1003 /**
1004 * Generic method called by convert() to invoke a provider and get
1005 * the necessary exchange rates for applying the conversions.
1006 *
1007 * @see convert()
1008 *
1009 * @return mixed float, the exchange rate for the given from and to currencies
1010 * in case of success. Boolean false otherwise.
1011 *
1012 * @since 1.15.0 (J) - 1.5.0 (WP) the visibility is public, but used to be private before.
1013 */
1014 public function getConversionRate()
1015 {
1016 $session = JFactory::getSession();
1017 $ses_conversions = $session->get('vboCurrencyConversions', '');
1018 $conversions_made = array();
1019 $data = '';
1020 $conv_rate = false;
1021
1022 /**
1023 * Trigger event to allow third party plugins to return a specific conversion rate for these currencies.
1024 *
1025 * @since 1.16.0 (J) - 1.6.0 (WP)
1026 */
1027 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetConversionRateVikBooking', [$this->from_currency, $this->to_currency]);
1028 if (is_array($custom_conv) && !empty($custom_conv[0]) && (is_int($custom_conv[0]) || is_float($custom_conv[0]))) {
1029 return $custom_conv[0];
1030 }
1031
1032 if (is_array($ses_conversions) && count($ses_conversions)) {
1033 $conversions_made = $ses_conversions;
1034 if (isset($ses_conversions[$this->from_currency.'_'.$this->to_currency])) {
1035 if (strlen($ses_conversions[$this->from_currency.'_'.$this->to_currency]) && floatval($ses_conversions[$this->from_currency.'_'.$this->to_currency]) > 0.00) {
1036 $conv_rate = $ses_conversions[$this->from_currency.'_'.$this->to_currency];
1037 }
1038 }
1039 }
1040
1041 if ($conv_rate === false) {
1042 // use an external provider for getting the conversion rate
1043 $api_method = 'callProvider'.str_replace(' ', '', ucwords($this->api_provider));
1044 if (!method_exists($this, $api_method)) {
1045 return false;
1046 }
1047 $conv_rate = $this->{$api_method}();
1048 if ($conv_rate !== false) {
1049 // cache conversion rate into the session
1050 $conversions_made[$this->from_currency.'_'.$this->to_currency] = $conv_rate;
1051 $session->set('vboCurrencyConversions', $conversions_made);
1052 }
1053
1054 }
1055
1056 return $conv_rate;
1057 }
1058
1059 /**
1060 * Tells whether a currency name exists in the map-list.
1061 *
1062 * @param string $currency_name the currency name to check.
1063 *
1064 * @return bool true if available, or false.
1065 *
1066 * @since 1.15.0 (J) - 1.5.0 (WP)
1067 */
1068 public function currencyExists($currency_name)
1069 {
1070 if (isset($this->currencymap[$currency_name]) || array_key_exists($currency_name, $this->currency_names)) {
1071 return true;
1072 }
1073
1074 /**
1075 * Trigger event to allow third party plugins to register a custom currency code.
1076 *
1077 * @since 1.16.0 (J) - 1.6.0 (WP)
1078 */
1079 $existing = VBOFactory::getPlatform()->getDispatcher()->filter('onCheckCurrencyExistsVikBooking', [$currency_name]);
1080 if (is_array($existing) && in_array(true, $existing, true)) {
1081 return true;
1082 }
1083
1084 return false;
1085 }
1086
1087 /**
1088 * Overrides the prices to convert.
1089 *
1090 * @param array $prices the list of prices to convert.
1091 *
1092 * @return array the current list of prices to convert.
1093 *
1094 * @since 1.15.0 (J) - 1.5.0 (WP)
1095 */
1096 public function setPrices(array $prices = [])
1097 {
1098 $this->prices = $prices;
1099
1100 return $this->prices;
1101 }
1102
1103 /**
1104 * Returns an associative list of currencies with code and name.
1105 *
1106 * @return array
1107 *
1108 * @since 1.15.4 (J) - 1.5.10 (WP)
1109 */
1110 public function getCurrencyNames()
1111 {
1112 return $this->currency_names;
1113 }
1114
1115 /**
1116 * Concatenates errors occurred during the execution
1117 *
1118 * @param string $err the error message
1119 *
1120 * @return void
1121 */
1122 protected function setError($err)
1123 {
1124 $this->error .= $err . "\n";
1125 }
1126
1127 /**
1128 * Returns the errors occurred during the execution
1129 *
1130 * @return string the error message(s)
1131 */
1132 public function getError()
1133 {
1134 return rtrim($this->error, "\n");
1135 }
1136 }
1137