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 );
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
?>
 |