* Copyright 2001-2012 Strangecode, LLC
*
* This file is part of The Strangecode Codebase.
*
* The Strangecode Codebase is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* The Strangecode Codebase is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* The Strangecode Codebase. If not, see .
*/
/**
* Currency.inc.php
*
* Class to convert currency values.
*
* @author Quinn Comendant
* @version 1.5
*
* Example of use:
---------------------------------------------------------------------
$currency = new Currency();
$currency->setParam(array('cache_dir' => COMMON_BASE . '/tmp/xcache'));
echo $currency->getRage('eur', 'usd');
echo $currency->getValue(125.50, 'eur', 'usd');
---------------------------------------------------------------------
*/
class Currency
{
// Configuration parameters for this object.
protected $_params = array(
'cache_result' => true,
'cache_dir' => '',
'cache_age' => 86400, // 24 hours.
'api' => 'ecb', // 'xurrency' or 'ecb'. Add other APIs under the _performAPICall() method.
'api_key' => '', // Used only by xurrency API.
);
// Static runtime cache of values.
private static $rates = array();
/**
* Cart constructor.
*/
public function __construct($params=array())
{
$app =& App::getInstance();
// Set custom parameters.
$this->setParam($params);
// Setup cache directory.
if ($this->getParam('cache_result')) {
if ('' == $this->getParam('cache_dir')) {
// Use a sane default cache directory.
$this->setParam(array('cache_dir' => '/tmp/xcache_' . md5(COMMON_BASE)));
}
if (!is_dir($this->getParam('cache_dir'))) {
$app->logMsg(sprintf('Creating cache_dir: %s', $this->getParam('cache_dir')), LOG_INFO, __FILE__, __LINE__);
if (!mkdir($this->getParam('cache_dir'))) {
$app->logMsg(sprintf('Could not create cache_dir: %s', $this->getParam('cache_dir')), LOG_WARNING, __FILE__, __LINE__);
}
}
}
}
/**
* Set the params of this object.
*
* @param array $params Array of param keys and values to set.
*/
public function setParam($params=null)
{
if (isset($params) && is_array($params)) {
// Merge new parameters with old overriding only those passed.
$this->_params = array_merge($this->_params, $params);
}
}
/**
* Return the value of a parameter, if it exists.
*
* @access public
* @param string $param Which parameter to return.
* @return mixed Configured parameter value.
*/
public function getParam($param)
{
$app =& App::getInstance();
if (array_key_exists($param, $this->_params)) {
return $this->_params[$param];
} else {
$app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
return null;
}
}
/*
* Return the exchange value between the two given currencies for given amount.
*
* @access public
* @param float $amount Base amount to convert from.
* @param string $base 3-letter currency code to convert from.
* @param string $target 3-letter currency code to convert to.
* @return mixed Float converted currency value, or false on error.
* @author Quinn Comendant
* @version 1.0
* @since 05 May 2008 23:50:59
*/
public function getValue($amount, $base, $target)
{
if (false !== ($rate = $this->getRate($base, $target))) {
return abs($rate * $amount);
} else {
return false;
}
}
/*
* Return the currency conversion rate as a ratio.
*
* @access public
* @param string $base 3-letter currency code to convert from.
* @param string $target 3-letter currency code to convert to.
* @return mixed Float exchange rate value, or false on error.
* @author Quinn Comendant
* @version 1.0
* @since 25 May 2011 01:26:24
*/
public function getRate($base, $target)
{
$app =& App::getInstance();
// If we've looked-up this rate during this run, return it from the runtime cache.
$rate_key = sprintf('%s-to-%s', $base, $target);
if (isset(self::$rates[$rate_key])) {
$app->logMsg(sprintf('Found %s in runtime cache: %s', $rate_key, self::$rates[$rate_key]), LOG_DEBUG, __FILE__, __LINE__);
return self::$rates[$rate_key];
}
$cache_file_path = sprintf('%s/%s-to-%s', $this->getParam('cache_dir'), $base, $target);
if (!is_dir(dirname($cache_file_path))) {
mkdir(dirname($cache_file_path));
}
$cache_file_mtime = @filemtime($cache_file_path);
if (!$this->getParam('cache_result') || !$cache_file_mtime || $cache_file_mtime < time() - $this->getParam('cache_age')) {
// Get fresh data and create cached file if missing or expired.
$app->logMsg(sprintf('Getting fresh currency exchange rate: %s-to-%s', $base, $target), LOG_DEBUG, __FILE__, __LINE__);
$value = $this->_performAPICall(array(
'amount' => '1',
'base' => $base,
'target' => $target
));
if (false === $value || !is_numeric($value)) {
// Failed retrieving value. Use cached copy for now.
$app->logMsg(sprintf('Failed getting currency exchange rate: %s-to-%s, using cached copy', $base, $target), LOG_NOTICE, __FILE__, __LINE__);
if (!$value = @file_get_contents($cache_file_path)) {
$app->logMsg(sprintf('Failed reading cached exchange rate file: %s', $cache_file_path), LOG_ERR, __FILE__, __LINE__);
return false;
}
} else if ($this->getParam('cache_result') && !filePutContents($cache_file_path, $value)) {
$app->logMsg(sprintf('Failed writing to target rate file: %s', $cache_file_path), LOG_ERR, __FILE__, __LINE__);
return false;
}
} else {
$app->logMsg(sprintf('Getting cached currency exchange rate: %s-to-%s', $base, $target), LOG_DEBUG, __FILE__, __LINE__);
if (!$value = file_get_contents($cache_file_path)) {
$app->logMsg(sprintf('Failed reading target rate file: %s', $cache_file_path), LOG_ERR, __FILE__, __LINE__);
return false;
}
}
$app->logMsg(sprintf('Found currency exchange rate: %s-to-%s = %s', $base, $target, $value), LOG_DEBUG, __FILE__, __LINE__);
// Store the value in the runtime cache.
self::$rates[$rate_key] = trim($value);
return trim($value);
}
/**
* @param string
* @param array
* @return mixed
* @access private
*/
protected function _performAPICall($parameters=null)
{
$app =& App::getInstance();
switch ($this->getParam('api')) {
case 'xurrency' :
$api_url = 'http://xurrency.com/api/%s/%s/%s';
if ('' != $this->getParam('api_key')) {
$api_url .= '?key=' . $this->getParam('api_key');
}
$json_response = file_get_contents(sprintf($api_url, $parameters['base'], $parameters['target'], $parameters['amount']));
$json = json_decode($json_response);
if (null === $json) {
$app->logMsg(sprintf('Could not decode JSON response: %s', getDump($json_response)), LOG_WARNING, __FILE__, __LINE__);
return false;
} else if ($json->status === 'fail') {
if ($json->code == 3) {
$app->logMsg(sprintf('Xurrency error LimitReachedException: %s', $json->message), LOG_WARNING, __FILE__, __LINE__);
} elseif ($json->code == 2) {
$app->logMsg(sprintf('Xurrency error InvalidCurrencies: %s', $json->message), LOG_WARNING, __FILE__, __LINE__);
} elseif ($json->code == 4 || $json->code == 5) {
$app->logMsg(sprintf('Xurrency error InvalidKey: %s', $json->message), LOG_WARNING, __FILE__, __LINE__);
} else {
$app->logMsg(sprintf('Xurrency unknown error: %s', $json->message), LOG_WARNING, __FILE__, __LINE__);
}
return false;
} else {
return $json->result->value;
}
break;
case 'ecb' :
// Fetch XML from ECB
$api_url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';
if (false === ($sXML = file_get_contents($api_url))) {
$app->logMsg(sprintf('Failed to load ECB XML data from: %s', $api_url), LOG_WARNING, __FILE__, __LINE__);
return false;
}
if (false === ($oXML = simplexml_load_string($sXML))) {
$app->logMsg(sprintf('Failed to decode ECB XML data: %s', truncate($sXML, 200, 'end')), LOG_WARNING, __FILE__, __LINE__);
return false;
}
// Populate Array
foreach ($oXML->Cube->Cube->Cube as $oRate) {
foreach ($oRate->attributes() as $sKey => $sAttribute) {
if ($sKey == 'currency') {
$sCurrency = strtolower((string) $sAttribute);
} else if ($sKey == 'rate') {
$nRate = (string) $sAttribute;
}
}
$aCurrencies['eur'][$sCurrency] = $nRate;
$aCurrencies[$sCurrency]['eur'] = 1 / $nRate;
}
// Check if requested rates are available.
if (isset($aCurrencies[$parameters['base']][$parameters['target']])) {
return (float) $aCurrencies[$parameters['base']][$parameters['target']];
} else {
$app->logMsg(sprintf('API %s does not have base %s or target %s', $this->getParam('api'), $parameters['base'], $parameters['target']), LOG_ALERT, __FILE__, __LINE__);
return false;
}
default :
$app->logMsg(sprintf('Unknown currency API: %s', $this->getParam('api')), LOG_ERR, __FILE__, __LINE__);
return false;
}
}
}