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

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