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

1,037 lines 32.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 '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 $http_resp = $http->get($rates_doc);
545 if ($http_resp->code != 200) {
546 // cannot read exchange rates from external XML file
547 $this->setError('cannot read exchange rates from external XML file ('.__LINE__.')');
548 return false;
549 }
550 $xml_data = $http_resp->body;
551
552 // store cache file
553 $fp = @fopen($cache_rates, 'w+');
554 if (!$fp) {
555 // cannot open cache file for writing. Exit.
556 $this->setError('cannot open cache file for writing ('.__LINE__.')');
557 return false;
558 }
559 fwrite($fp, $xml_data);
560 fclose($fp);
561 }
562
563 if (!is_file($cache_rates)) {
564 // could not load rates
565 $this->setError('could not load rates ('.__LINE__.')');
566 return false;
567 }
568
569 if (empty($xml_data)) {
570 // read cached rates from XML file
571 $fp = @fopen($cache_rates, 'r');
572 if (!$fp) {
573 // cannot read exchange rates from cached XML file
574 $this->setError('cannot read exchange rates from cached XML file ('.__LINE__.')');
575 return false;
576 }
577 $xml_data = '';
578 while (!feof($fp)) {
579 $xml_data .= fread($fp, 4096);
580 }
581 fclose($fp);
582 }
583
584 $exchange_obj = simplexml_load_string($xml_data);
585 if (!$exchange_obj instanceof SimpleXMLElement) {
586 // this file does not contain correct XML
587 $this->setError('the file does not contain valid XML ('.__LINE__.')');
588 return false;
589 }
590
591 $exchange_rates = array();
592 foreach ($exchange_obj->Cube->Cube->Cube as $exrate) {
593 $attr = $exrate->attributes();
594 $currency = (string)$attr->currency;
595 $rate = (float)$attr->rate;
596 if ($rate > 0.00) {
597 $exchange_rates[$currency] = $rate;
598 }
599 }
600
601 return $exchange_rates;
602 }
603
604 /**
605 * Call to Floatrates.com exchange rates XML data.
606 * Retrieve the conversion rate between base currency and symbol currency.
607 * The exchange rates data is cached by downloading the file every day.
608 *
609 * If this was used as default and only provider, then the from currency would
610 * be passed to the rates loading method to fetch the exact XML feed for the
611 * from currency. If the feed was not available, false would be returned.
612 *
613 * @return mixed float exchange rate, or false in case of errors
614 *
615 * @since October 2018
616 */
617 protected function callProviderFloatRates()
618 {
619 // load exchange rates
620 $exchange_rates = $this->loadFloatRatesDotComRates($this->from_currency);
621 if (!$exchange_rates) {
622 // something went wrong
623 return false;
624 }
625
626 if (!isset($exchange_rates[$this->to_currency])) {
627 // we do not have the exchange rate to this currency.
628 $this->setError('exchange rates to this currency not supported ('.__LINE__.')');
629 return false;
630 }
631
632 if ($this->from_currency == $this->to_currency) {
633 // equal currencies should be stopped before this method
634 return 1;
635 }
636
637 // conversion is not involving EUR, but both currencies are known (from : to = 1 : x)
638 return $exchange_rates[$this->to_currency];
639 }
640
641 /**
642 * Call to the Floatrates.com XML feed.
643 * Retrieves the conversion rates of all currencies by caching the results, and
644 * by returning an array map with key-value pairs of currencycode-rate from EUR.
645 *
646 * @param [string] $currency the base currency 3 chars code, if specified
647 * attempt to load the exact $currency.xml feed
648 *
649 * @return mixed false in case of errors, array in case of success
650 * key (currency code) = value (exchange rate)
651 *
652 * @since October 2018
653 */
654 protected function loadFloatRatesDotComRates($currency = '')
655 {
656 $feed_name = empty($currency) ? 'eur' : substr(strtolower($currency), 0, 3);
657 $rates_doc = "http://www.floatrates.com/daily/{$feed_name}.xml";
658 $cache_rates = dirname(__FILE__) . DIRECTORY_SEPARATOR . date('Y-m-d') . "{$feed_name}" . '_floatrates.xml';
659 $xml_data = '';
660
661 if (!is_file($cache_rates)) {
662 // cached rates are not available, attempt to download the information
663 $expcache = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . '*_floatrates.xml');
664 // remove old expired cache files
665 foreach ($expcache as $expf) {
666 if (is_file($expf)) {
667 @unlink($expf);
668 }
669 }
670
671 /**
672 * Remote files should be fetched using CURL rather than through fopen($foo, 'r');
673 *
674 * @since 1.15.2 (J) - 1.5.5 (WP)
675 */
676 $http = new JHttp;
677 $http_resp = $http->get($rates_doc);
678 if ($http_resp->code != 200) {
679 // cannot read exchange rates from external XML file
680 $this->setError('cannot read exchange rates from external XML file ('.__LINE__.')');
681 return false;
682 }
683 $xml_data = $http_resp->body;
684
685 // store cache file
686 $fp = @fopen($cache_rates, 'w+');
687 if (!$fp) {
688 // cannot open cache file for writing. Exit.
689 $this->setError('cannot open cache file for writing ('.__LINE__.')');
690 return false;
691 }
692 fwrite($fp, $xml_data);
693 fclose($fp);
694 }
695
696 if (!is_file($cache_rates)) {
697 // could not load rates
698 $this->setError('could not load rates ('.__LINE__.')');
699 return false;
700 }
701
702 if (empty($xml_data)) {
703 // read cached rates from XML file
704 $fp = @fopen($cache_rates, 'r');
705 if (!$fp) {
706 // cannot read exchange rates from cached XML file
707 $this->setError('cannot read exchange rates from cached XML file ('.__LINE__.')');
708 return false;
709 }
710 $xml_data = '';
711 while (!feof($fp)) {
712 $xml_data .= fread($fp, 4096);
713 }
714 fclose($fp);
715 }
716
717 $exchange_obj = simplexml_load_string($xml_data);
718 if (!$exchange_obj instanceof SimpleXMLElement) {
719 // this file does not contain correct XML
720 $this->setError('this file does not contain correct XML ('.__LINE__.')');
721 return false;
722 }
723
724 $exchange_rates = array();
725 foreach ($exchange_obj->item as $exrate) {
726 if (!property_exists($exrate, 'targetCurrency') || !property_exists($exrate, 'exchangeRate')) {
727 continue;
728 }
729 $currency = (string)$exrate->targetCurrency;
730 /**
731 * All conversion rates exceeding 1 thousand are formatted as strings, with a thousand separator
732 * character (comma ","), and so we must get rid of the formatting to obtain a true float value.
733 *
734 * @since 1.14.3 (J) - 1.4.3 (WP)
735 */
736 $rate = floatval(str_replace(',', '', (string)$exrate->exchangeRate));
737 //
738 if ($rate > 0.00) {
739 $exchange_rates[$currency] = $rate;
740 }
741 }
742
743 if (!count($exchange_rates)) {
744 // invalid XML structure
745 $this->setError('invalid exchange rates structure ('.__LINE__.')');
746 return false;
747 }
748
749 return $exchange_rates;
750 }
751
752 /**
753 * Makes a float number from a given string number.
754 *
755 * @param string $num the formatted number to exchange.
756 *
757 * @return float the number to exchange.
758 */
759 protected function makeFloat($num)
760 {
761 $floated = $num;
762 if (is_array($this->format) && count($this->format) == 3) {
763 $decimals = '';
764 if (strstr($num, $this->format[1]) !== false) {
765 $decimals = substr($num, ((int)$this->format[0] - ((int)$this->format[0] * 2)));
766 }
767 $nosep = str_replace($this->format[1], '', $num);
768 $nosep = str_replace($this->format[2], '', $nosep);
769 $newdecimals = '';
770 if ((int)$this->format[0] > 0 && !empty($decimals)) {
771 $nosep = substr_replace($nosep, '', (strlen($decimals) - (strlen($decimals) * 2)));
772 $decimalsabs = abs($decimals);
773 if ($decimalsabs > 0) {
774 $newdecimals = $decimals;
775 }
776 }
777 $floated = floatval($nosep.(!empty($newdecimals) ? '.'.$newdecimals : ''));
778 }
779
780 return $floated;
781 }
782
783 /**
784 * Finds and returns an appropriate symbol for the currency converted
785 *
786 * @return string the symbol to return to the controller for the exchanged rates
787 */
788 protected function currencySymbol()
789 {
790 /**
791 * Trigger event to allow third party plugins to return specific information for a currency.
792 *
793 * @since 1.16.0 (J) - 1.6.0 (WP)
794 */
795 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetCurrencyDataVikBooking', [$this->to_currency]);
796 if (is_array($custom_conv) && !empty($custom_conv[0]) && is_array($custom_conv[0]) && !empty($custom_conv[0]['symbol'])) {
797 return $custom_conv[0]['symbol'];
798 }
799
800 if (isset($this->currencymap[$this->to_currency]) && isset($this->currencymap[$this->to_currency]['symbol'])) {
801 $symbol = '&#'.$this->currencymap[$this->to_currency]['symbol'].';';
802 } else {
803 $symbol = $this->to_currency;
804 }
805
806 return $symbol;
807 }
808
809 /**
810 * Formats a number according to the values passed to the constructor
811 *
812 * @param mixed $num the number to format
813 *
814 * @return string the formatted number for display
815 */
816 protected function currencyFormat($num)
817 {
818 $num_decimals = (int)$this->format[0];
819 if (array_key_exists($this->to_currency, $this->currencymap)) {
820 if (array_key_exists('decimals', $this->currencymap[$this->to_currency])) {
821 $num_decimals = $this->currencymap[$this->to_currency]['decimals'];
822 } else {
823 $num_decimals = 2;
824 }
825 }
826
827 // decimals and thousands separators for this currency
828 $decimals_sep = $this->format[1];
829 $thousands_sep = $this->format[2];
830
831 /**
832 * Trigger event to allow third party plugins to return specific information for a currency.
833 *
834 * @since 1.16.0 (J) - 1.6.0 (WP)
835 */
836 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetCurrencyDataVikBooking', [$this->to_currency]);
837 if (is_array($custom_conv) && !empty($custom_conv[0]) && is_array($custom_conv[0])) {
838 // override currency information for formatting the amounts
839 $num_decimals = isset($custom_conv[0]['decimals']) && is_int($custom_conv[0]['decimals']) ? $custom_conv[0]['decimals'] : $num_decimals;
840 $decimals_sep = isset($custom_conv[0]['decimals_separator']) && is_string($custom_conv[0]['decimals_separator']) ? $custom_conv[0]['decimals_separator'] : $decimals_sep;
841 $thousands_sep = isset($custom_conv[0]['thousands_separator']) && is_string($custom_conv[0]['thousands_separator']) ? $custom_conv[0]['thousands_separator'] : $thousands_sep;
842 }
843
844 return number_format($num, $num_decimals, $decimals_sep, $thousands_sep);
845 }
846
847 /**
848 * Main method called by the controller after invoking the class. The same method is also
849 * called by other classes of VBO, and keeps associative arrays of rates to exchange.
850 *
851 * @param bool $get_floats true to only return the plain exchanged rates.
852 *
853 * @return array the array of rates converted or an empty array in case of error
854 *
855 * @since 1.15.0 (J) - 1.5.0 (WP) added argument to return plain exchanged floats.
856 */
857 public function convert($get_floats = false)
858 {
859 $conversion = [];
860
861 if (!is_array($this->prices) || !$this->prices) {
862 return $conversion;
863 }
864
865 // get the conversion rate between the currencies
866 $conv_rate = $this->getConversionRate();
867
868 if ($conv_rate === false) {
869 // return the original array
870 return $conversion;
871 }
872
873 // get the currency symbol of the exchanged currency
874 $conv_symbol = $this->currencySymbol();
875
876 // exchange values
877 foreach ($this->prices as $k => $price) {
878 // get the base price to exchange
879 if ($get_floats) {
880 // given price is expected to be already a raw float
881 $base_price = (float)$price;
882 } else {
883 // given price has been formatted already
884 $base_price = $this->makeFloat($price);
885 }
886
887 // apply conversion
888 $exchanged = $base_price * $conv_rate;
889
890 if ($get_floats) {
891 // inject just the exchanged rate
892 $conversion[$k] = $exchanged;
893 } else {
894 // build the exchanged information
895 $conversion[$k]['symbol'] = $conv_symbol;
896 $conversion[$k]['price'] = $this->currencyFormat($exchanged);
897 }
898 }
899
900 return $conversion;
901 }
902
903 /**
904 * Generic method called by convert() to invoke a provider and get
905 * the necessary exchange rates for applying the conversions.
906 *
907 * @see convert()
908 *
909 * @return mixed float, the exchange rate for the given from and to currencies
910 * in case of success. Boolean false otherwise.
911 *
912 * @since 1.15.0 (J) - 1.5.0 (WP) the visibility is public, but used to be private before.
913 */
914 public function getConversionRate()
915 {
916 $session = JFactory::getSession();
917 $ses_conversions = $session->get('vboCurrencyConversions', '');
918 $conversions_made = array();
919 $data = '';
920 $conv_rate = false;
921
922 /**
923 * Trigger event to allow third party plugins to return a specific conversion rate for these currencies.
924 *
925 * @since 1.16.0 (J) - 1.6.0 (WP)
926 */
927 $custom_conv = VBOFactory::getPlatform()->getDispatcher()->filter('onGetConversionRateVikBooking', [$this->from_currency, $this->to_currency]);
928 if (is_array($custom_conv) && !empty($custom_conv[0]) && (is_int($custom_conv[0]) || is_float($custom_conv[0]))) {
929 return $custom_conv[0];
930 }
931
932 if (is_array($ses_conversions) && count($ses_conversions)) {
933 $conversions_made = $ses_conversions;
934 if (isset($ses_conversions[$this->from_currency.'_'.$this->to_currency])) {
935 if (strlen($ses_conversions[$this->from_currency.'_'.$this->to_currency]) && floatval($ses_conversions[$this->from_currency.'_'.$this->to_currency]) > 0.00) {
936 $conv_rate = $ses_conversions[$this->from_currency.'_'.$this->to_currency];
937 }
938 }
939 }
940
941 if ($conv_rate === false) {
942 // use an external provider for getting the conversion rate
943 $api_method = 'callProvider'.str_replace(' ', '', ucwords($this->api_provider));
944 if (!method_exists($this, $api_method)) {
945 return false;
946 }
947 $conv_rate = $this->{$api_method}();
948 if ($conv_rate !== false) {
949 // cache conversion rate into the session
950 $conversions_made[$this->from_currency.'_'.$this->to_currency] = $conv_rate;
951 $session->set('vboCurrencyConversions', $conversions_made);
952 }
953
954 }
955
956 return $conv_rate;
957 }
958
959 /**
960 * Tells whether a currency name exists in the map-list.
961 *
962 * @param string $currency_name the currency name to check.
963 *
964 * @return bool true if available, or false.
965 *
966 * @since 1.15.0 (J) - 1.5.0 (WP)
967 */
968 public function currencyExists($currency_name)
969 {
970 if (isset($this->currencymap[$currency_name])) {
971 return true;
972 }
973
974 /**
975 * Trigger event to allow third party plugins to register a custom currency code.
976 *
977 * @since 1.16.0 (J) - 1.6.0 (WP)
978 */
979 $existing = VBOFactory::getPlatform()->getDispatcher()->filter('onCheckCurrencyExistsVikBooking', [$currency_name]);
980 if (is_array($existing) && in_array(true, $existing, true)) {
981 return true;
982 }
983
984 return false;
985 }
986
987 /**
988 * Overrides the prices to convert.
989 *
990 * @param array $prices the list of prices to convert.
991 *
992 * @return array the current list of prices to convert.
993 *
994 * @since 1.15.0 (J) - 1.5.0 (WP)
995 */
996 public function setPrices(array $prices = [])
997 {
998 $this->prices = $prices;
999
1000 return $this->prices;
1001 }
1002
1003 /**
1004 * Returns an associative list of currencies with code and name.
1005 *
1006 * @return array
1007 *
1008 * @since 1.15.4 (J) - 1.5.10 (WP)
1009 */
1010 public function getCurrencyNames()
1011 {
1012 return $this->currency_names;
1013 }
1014
1015 /**
1016 * Concatenates errors occurred during the execution
1017 *
1018 * @param string $err the error message
1019 *
1020 * @return void
1021 */
1022 protected function setError($err)
1023 {
1024 $this->error .= $err . "\n";
1025 }
1026
1027 /**
1028 * Returns the errors occurred during the execution
1029 *
1030 * @return string the error message(s)
1031 */
1032 public function getError()
1033 {
1034 return rtrim($this->error, "\n");
1035 }
1036 }
1037