348 lines
13 KiB
PHP
348 lines
13 KiB
PHP
|
<?php
|
||
|
/* vim: set expandtab tabstop=4 shiftwidth=4: */
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | PHP version 4 |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | Copyright (c) 1997-2003 The PHP Group |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | This source file is subject to version 2.0 of the PHP license, |
|
||
|
// | that is bundled with this package in the file LICENSE, and is |
|
||
|
// | available through the world-wide-web at |
|
||
|
// | http://www.php.net/license/2_02.txt. |
|
||
|
// | If you did not receive a copy of the PHP license and are unable to |
|
||
|
// | obtain it through the world-wide-web, please send a note to |
|
||
|
// | license@php.net so we can mail you a copy immediately. |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
// | Author: Marshall Roch <marshall@exclupen.com> |
|
||
|
// +----------------------------------------------------------------------+
|
||
|
//
|
||
|
// $Id: ExchangeRates.php,v 1.7 2005/06/23 20:29:20 cross Exp $
|
||
|
|
||
|
/**
|
||
|
* @package Services_ExchangeRates
|
||
|
* @category Services
|
||
|
*/
|
||
|
|
||
|
/**#@+
|
||
|
* Error codes
|
||
|
*/
|
||
|
define('SERVICES_EXCHANGERATES_ERROR_RETURN', 1);
|
||
|
define('SERVICES_EXCHANGERATES_ERROR_DIE', 8);
|
||
|
define('SERVICES_EXCHANGERATES_ERROR_INVALID_DRIVER', 101);
|
||
|
define('SERVICES_EXCHANGERATES_ERROR_INVALID_CURRENCY', 102);
|
||
|
define('SERVICES_EXCHANGERATES_ERROR_CONVERSION_ERROR', 103);
|
||
|
define('SERVICES_EXCHANGERATES_ERROR_RETRIEVAL_FAILED', 104);
|
||
|
/**#@-*/
|
||
|
|
||
|
/**
|
||
|
* Exchange Rate package
|
||
|
*
|
||
|
* This package converts back and forth between different currencies, in any
|
||
|
* combination. All data used is updated automatically from interchangable
|
||
|
* sources. That is, if there is a source publishing exchange rates that
|
||
|
* isn't supported yet, you could write a driver and use that source
|
||
|
* seamlessly with the rest of the package.
|
||
|
*
|
||
|
* Disclaimer: The rates are nominal quotations - neither buying nor
|
||
|
* selling rates - and are intended for statistical or analytical
|
||
|
* purposes. Rates available from financial institutions will differ.
|
||
|
*
|
||
|
* The United Nations Economic Commission for Europe is implementing new web
|
||
|
* services. Keep an eye on progress here: http://www.unemed.org/edocs/index.htm
|
||
|
*
|
||
|
* @todo Add locale support for different currency formatting
|
||
|
*
|
||
|
* @example ExchangeRates/docs/example.php
|
||
|
*
|
||
|
* @author Marshall Roch <marshall@exclupen.com>
|
||
|
* @author Colin Ross <cross@php.net>
|
||
|
* @copyright Copyright 2003 Marshall Roch
|
||
|
* @license http://www.php.net/license/2_02.txt PHP License 2.0
|
||
|
* @package Services_ExchangeRates
|
||
|
*/
|
||
|
class Services_ExchangeRates {
|
||
|
|
||
|
/**
|
||
|
* Sets the number of places to round the currencies to at the end
|
||
|
* @access private
|
||
|
* @var int
|
||
|
*/
|
||
|
var $_roundToDecimal = 2;
|
||
|
|
||
|
/**
|
||
|
* Determines whether the returned conversion is rounded or not
|
||
|
* @access private
|
||
|
* @var bool
|
||
|
*/
|
||
|
var $_roundAutomatically = true;
|
||
|
|
||
|
/**
|
||
|
* Defines single character used to separate each group of thousands in returned conversion
|
||
|
* @access private
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_thousandsSeparator = ",";
|
||
|
|
||
|
/**
|
||
|
* Defines single character to use as a decimal place in returned conversion
|
||
|
* @access private
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_decimalCharacter = ".";
|
||
|
|
||
|
/**
|
||
|
* Sets the path to where cache files are stored (don't forget the trailing slash!)
|
||
|
* @access private
|
||
|
* @var string
|
||
|
*/
|
||
|
var $_cacheDirectory = '/tmp/';
|
||
|
|
||
|
/**
|
||
|
* Sets the length (in seconds) to cache the exchange rate data. This information
|
||
|
* is updated daily. Default is 1 hour.
|
||
|
* @access private
|
||
|
* @var int
|
||
|
*/
|
||
|
var $_cacheLengthRates = 3600;
|
||
|
|
||
|
/**
|
||
|
* Sets the length (in seconds) to cache the list of currencies. This information
|
||
|
* is very rarely updated. Default is 4 weeks.
|
||
|
* @access private
|
||
|
* @var int
|
||
|
*/
|
||
|
var $_cacheLengthCurrencies = 2419200;
|
||
|
|
||
|
/**
|
||
|
* Sets the length (in seconds) to cache the list of countries. This information
|
||
|
* is very rarely updated. Default is 4 weeks.
|
||
|
* @access private
|
||
|
* @var int
|
||
|
*/
|
||
|
var $_cacheLengthCountries = 2419200;
|
||
|
|
||
|
/**
|
||
|
* PEAR error mode (when raiseError is called)
|
||
|
*
|
||
|
* @see setToDebug()
|
||
|
* @access private
|
||
|
* @var int
|
||
|
*/
|
||
|
var $_pearErrorMode = SERVICES_EXCHANGERATES_ERROR_RETURN;
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* This method overrides any default settings based on the $options
|
||
|
* parameter and retrieves feed data from the cache or their sources.
|
||
|
*
|
||
|
* $options is an associative array:
|
||
|
* <code>
|
||
|
* $options = array(
|
||
|
* 'roundToDecimal' => number of decimal places to round to (int),
|
||
|
* 'roundAutomatically' => whether to automatically round to
|
||
|
* $roundToDecimal digits (bool),
|
||
|
* 'thousandsSeparator' => character to separate every 1000 (string),
|
||
|
* 'decimalCharacter' => character for decimal place (string),
|
||
|
* 'cacheDirectory' => path (with trailing slash) to store cache
|
||
|
* files (string),
|
||
|
* 'cacheLengthRates' => length of time to cache exchange rates
|
||
|
* file (int),
|
||
|
* 'cacheLengthCurrencies' => length of time to cache currency
|
||
|
* list (int),
|
||
|
* 'cacheLengthCountries' => length of time to cache country list (int),
|
||
|
* 'pearErrorMode' => pear error mode (int));
|
||
|
* </code>
|
||
|
*
|
||
|
* @param string Driver name (filename minus 'Rates_' and .php) for exchange rate feed
|
||
|
* @param string Driver name for currency code list
|
||
|
* @param string Driver name for country code list (not yet used for anything)
|
||
|
* @param array Array to override default settings
|
||
|
*/
|
||
|
function __construct($ratesSource = 'ECB',
|
||
|
$currencySource = 'UN',
|
||
|
$countrySource = 'UN',
|
||
|
$options = array(NULL)) {
|
||
|
|
||
|
$availableOptions = array('roundToDecimal',
|
||
|
'roundAutomatically',
|
||
|
'thousandsSeparator',
|
||
|
'decimalCharacter',
|
||
|
'cacheDirectory',
|
||
|
'cacheLengthRates',
|
||
|
'cacheLengthCurrencies',
|
||
|
'cacheLengthCountries');
|
||
|
|
||
|
foreach($options as $key => $value) {
|
||
|
if(in_array($key, $availableOptions)) {
|
||
|
$property = '_'.$key;
|
||
|
$this->$property = $value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$rateData = $this->retrieveData('Rates_' . $ratesSource, $this->_cacheLengthRates);
|
||
|
$this->rates = $rateData['rates'];
|
||
|
$this->ratesUpdated = $rateData['date'];
|
||
|
$this->ratesSource = $rateData['source'];
|
||
|
|
||
|
$this->currencies = $this->retrieveData('Currencies_' . $currencySource, $this->_cacheLengthCurrencies);
|
||
|
|
||
|
// not yet implimented, here for future features:
|
||
|
// $this->countries = $this->retrieveData('Countries_' . $countriesSource, $this->_cacheLengthCountries);
|
||
|
|
||
|
$this->validCurrencies = $this->getValidCurrencies($this->currencies, $this->rates);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Factory
|
||
|
*
|
||
|
* Includes the necessary driver, instantiates the class, retrieves the feed,
|
||
|
* and returns an associative array.
|
||
|
*
|
||
|
* @param string Driver filename (minus .php; this includes 'Rates_', etc.)
|
||
|
* @param int Cache length
|
||
|
* @return array Associative array containing the data requested
|
||
|
*/
|
||
|
function retrieveData($source, $cacheLength) {
|
||
|
include_once("Services/ExchangeRates/${source}.php");
|
||
|
$classname = "Services_ExchangeRates_${source}";
|
||
|
if (!class_exists($classname)) {
|
||
|
return $this->raiseError("No driver exists for the source ${source}... aborting.", SERVICES_EXCHANGERATES_ERROR_INVALID_DRIVER);
|
||
|
}
|
||
|
$class = new $classname;
|
||
|
|
||
|
return $class->retrieve($cacheLength, $this->_cacheDirectory);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get list of currencies with known exchange rates
|
||
|
*
|
||
|
* Creates an array of currency codes and their names, based on
|
||
|
* overlapping elements in $rates and $currencies.
|
||
|
*
|
||
|
* @param array Array of currency codes to currency names
|
||
|
* @param array Array of currency codes to exchange rates
|
||
|
* @return array Array of currency codes to currency names that have a known exchange rate (sorted alphabetically)
|
||
|
*/
|
||
|
function getValidCurrencies($currencies, $rates) {
|
||
|
// loop through list of currencies
|
||
|
$validCurrencies = array();
|
||
|
foreach ($currencies as $code => $currency) {
|
||
|
// check to see if that currency has a known exchange rate
|
||
|
if (in_array($code, array_keys($rates))) {
|
||
|
// if so, add it to the array to return
|
||
|
$validCurrencies[$code] = $currency;
|
||
|
}
|
||
|
}
|
||
|
asort($validCurrencies);
|
||
|
return $validCurrencies;
|
||
|
}
|
||
|
|
||
|
function isValidCurrency($code) {
|
||
|
if (!in_array($code, array_keys($this->validCurrencies))) {
|
||
|
$this->raiseError('Error: Invalid currency: ' . $code, SERVICES_EXCHANGERATES_ERROR_INVALID_CURRENCY);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert currencies
|
||
|
*
|
||
|
* @param string Currency code of original currency
|
||
|
* @param string Currency code of target currency
|
||
|
* @param double Amount of original currency to convert
|
||
|
* @param boolean Format the final currency (add commas, round, etc.)
|
||
|
* @return mixed Currency converted to $to
|
||
|
*/
|
||
|
function convert($from, $to, $amount, $format = true) {
|
||
|
|
||
|
if ($this->isValidCurrency($from) && $this->isValidCurrency($to)) {
|
||
|
|
||
|
// Convert $from to whatever the base currency of the
|
||
|
// exchange rate feed is.
|
||
|
$base = (1 / $this->rates[$from]) * $amount;
|
||
|
// Convert from base currency to $to
|
||
|
$final = $this->rates[$to] * $base;
|
||
|
return ($format) ? $this->format($final) : $final;
|
||
|
}
|
||
|
$this->raiseError('Unable to convert!', SERVICES_EXCHANGERATES_ERROR_CONVERSION_ERROR);
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Formats the converted currency
|
||
|
*
|
||
|
* This method adds $this->_thousandsSeparator between every group of thousands,
|
||
|
* and rounds to $this->_roundToDecimal decimal places. Use the $options parameter
|
||
|
* on the constructor to set these values.
|
||
|
*
|
||
|
* @param double Number to format
|
||
|
* @param mixed Number of decimal places to round to (null for default)
|
||
|
* @param mixed Character to use for decimal point (null for default)
|
||
|
* @param mixed Character to use for thousands separator (null for default)
|
||
|
* @return string Formatted currency
|
||
|
*/
|
||
|
function format($amount, $roundTo = null, $decChar = null, $sep = null) {
|
||
|
$roundTo = (($this->_roundAutomatically) ?
|
||
|
(($roundTo == null) ? $this->_roundToDecimal : $roundTo) :
|
||
|
'');
|
||
|
$decChar = ($decChar == null) ? $this->_decimalCharacter : $decChar;
|
||
|
$sep = ($sep == null) ? $this->_thousandsSeparator : $sep;
|
||
|
|
||
|
return number_format($amount, $roundTo, $decChar, $sep);
|
||
|
}
|
||
|
/**
|
||
|
* Get all rates as compared to a reference currency
|
||
|
*
|
||
|
* Returns an associative array with currency codes as keys and
|
||
|
* formated rates as values, as computed against a reference currency.
|
||
|
*
|
||
|
* @param string $referenceCurrency Reference currency code
|
||
|
* @return array List of currencies => rates
|
||
|
* @see Services_ExchangeRates::convert()
|
||
|
* @access public
|
||
|
*/
|
||
|
function getRates ($referenceCurrency)
|
||
|
{
|
||
|
$rates = array();
|
||
|
foreach ($this->validCurrencies as $code => $name) {
|
||
|
$rates[$code] = $this->convert($referenceCurrency, $code, 1, false);
|
||
|
}
|
||
|
ksort($rates);
|
||
|
return $rates;
|
||
|
}
|
||
|
/**
|
||
|
* Set to debug mode
|
||
|
*
|
||
|
* When an error is found, the script will stop and the message will be displayed
|
||
|
* (in debug mode only).
|
||
|
*/
|
||
|
function setToDebug()
|
||
|
{
|
||
|
self::$_pearErrorMode = SERVICES_EXCHANGERATES_ERROR_DIE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Trigger a PEAR error
|
||
|
*
|
||
|
* To improve performances, the PEAR.php file is included dynamically.
|
||
|
* The file is so included only when an error is triggered. So, in most
|
||
|
* cases, the file isn't included and performance is much better.
|
||
|
*
|
||
|
* @param string error message
|
||
|
* @param int error code
|
||
|
*/
|
||
|
static function raiseError($msg, $code)
|
||
|
{
|
||
|
include_once('PEAR.php');
|
||
|
PEAR::raiseError($msg, $code, ( isset(self::$_pearErrorMode) ) ? self::$_pearErrorMode : SERVICES_EXCHANGERATES_ERROR_RETURN );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
?>
|