<?php
/**
 * YANA library
 *
 * @package yana
 * @licence http://creativecommons.org/licenses/by-nc-sa/2.5/
 */

/**
 * Connection to a database server
 *
 * @access      public
 * @name        class_dbserver
 * @package     yana
 * @subpackage  io
 */
class DbServer
{

    /**#@+
     * @ignore
     * @access  private
     */

    /** @var mixed */ var $database = null;
    /** @var array */ var $dsn      = array();
    /** @var array */ var $options  = array();

    /**#@-*/

    /**
     * create a new instance
     *
     * The $dsn parameter became available in version 2.8
     * It is an array containing the following information (all entries are optional):
     *
     * <ul>
     *     <li><pre> string  ( DBMS )     default =  mysql      name of the php-dbms api to be used
     *                                            e.g. mysql, mysqli, db2, ... </pre></li>
     *     <li><pre> string  ( HOST )     default =  localhost  adress of the host e.g. localhost,
     *                                            123.456.789.0,
     *                                            COMPUTER-NAME\DB-INSTANCE (windows+MS-SQL)</pre></li>
     *     <li><pre> integer ( PORT )     default =  0          port number </pre></li>
     *     <li><pre> string  ( USERNAME ) default =  root </pre></li>
     *     <li><pre> string  ( PASSWORD ) default =  n/a </pre></li>
     *     <li><pre> string  ( DATABASE ) default =  yana       name of the database </pre></li>
     * </ul>
     *
     * The default settings may be changed by editing file config/system.config in key DEFAULT.DATABASE.DSN.
     * The parameter $dsn has the following fall-back behaviour: $dsn -> global user settings -> yana default settings
     *
     * {@internal {
     *
     * Note on PEAR-DB connection parameters
     *
     * <ul>
     * <li> phptype:    Database backend used in PHP (i.e. mysql  , odbc etc.) </li>
     * <li> dbsyntax:   Database used with regards to SQL syntax etc. When using ODBC as the phptype, set this to the DBMS type the ODBC driver is connecting to. Examples: access, db2, mssql, navision, solid, etc. </li>
     * <li> protocol:   Communication protocol to use ( i.e. tcp, unix etc.) </li>
     * <li> hostspec:   Host specification (hostname[:port]) </li>
     * <li> database:   Database to use on the DBMS server </li>
     * <li> username:   User name for login </li>
     * <li> password:   Password for login </li>
     * <li> proto_opts: Maybe used with protocol </li>
     * <li> option:     Additional connection options in URI query string format. options get separated by & </li>
     * </ul>
     *
     * Note on PEAR-DB connection options
     *
     * <ul>
     * <li> autofree:       should results be freed automatically when there are no more rows? </li>
     * <li> debug:          debug level </li>
     * <li> persistent:     should the connection be persistent? </li>
     * <li> portability:    portability mode constant. These constants are bitwised, so they can be combined using | and removed using ^. See the examples below and the "Intro - Portability" for more information. </li>
     * <li> seqname_format: the sprintf() format string used on sequence names. This format is applied to sequence names passed to createSequence(), nextID() and dropSequence(). </li>
     * <li> ssl:            use ssl to connect? </li>
     * </ul>
     *
     * }}
     *
     * @name     dbServer::__consruct()
     * @param    array     $dsn    for a description of the $dsn parameter see the text above

     */
    function DbServer($dsn = null)
    {
        global $YANA;

        /* 1 retrieve default settings for fall back */
        if (isset($YANA)) {
            $require_odbc = $YANA->getDefault('database.require_odbc');
            $this->options= $YANA->getDefault('database.options');
            assert('is_array($require_odbc);');
            assert('is_array($this->options);');
            $system_dsn = array('DBMS'     => YANA_DATABASE_DBMS,
                                'HOST'     => YANA_DATABASE_HOST,
                                'PORT'     => YANA_DATABASE_PORT,
                                'USERNAME' => YANA_DATABASE_USER,
                                'PASSWORD' => YANA_DATABASE_PASSWORD,
                                'DATABASE' => YANA_DATABASE_NAME);
            $this->dsn = mergeArrays($YANA->getDefault('database.dsn'), $system_dsn);
            assert('is_array($this->dsn);');
            unset($system_dsn);
        } else {
            $require_odbc = array();
            $this->options= array();
        }
        /* 1.1 there are some static options that always have to be there and can't be changed */
        $this->options['portability'] = DB_PORTABILITY_ALL;


        /* 2 process settings provided by the user */
        if (is_array($dsn)) {
            $this->dsn = mergeArrays($this->dsn, changeCaseArray($dsn,CASE_UPPER));
        }
        assert('is_array($this->dsn);');
        unset($dsn);

        /* 3 create PEAR-DB compatible dsn-array */
        /* 3.1 determine if odbc is required to connect to this dbms */
        if (!empty($this->dsn['DBMS'])) {
            if ($this->dsn['USE_ODBC'] == true || in_array(strtolower($this->dsn['DBMS']),$require_odbc)) {
                $dsn['phptype']  = 'ODBC';
                $dsn['dbsyntax'] = $this->dsn['DBMS'];
            } else {
                $dsn['phptype']  = $this->dsn['DBMS'];
            }
        }
        /* 3.2 now for the database host */
        if (!empty($this->dsn['HOST'])) {
            $dsn['hostspec'] = $this->dsn['HOST'];
            if (is_int($this->dsn['PORT']) && $this->dsn['PORT'] > 0) {
                $dsn['hostspec'] .= ':'.$this->dsn['PORT'];
            }
        }
        /* 3.3 database name */
        if (!empty($this->dsn['DATABASE'])) {
            $dsn['database'] = $this->dsn['DATABASE'];
        }
        /* 3.4 collect login information */
        if (!empty($this->dsn['USERNAME'])) {
            $dsn['username'] = $this->dsn['USERNAME'];
        }
        if (!empty($this->dsn['PASSWORD'])) {
            $dsn['password'] = $this->dsn['PASSWORD'];
        }

        /* 4 if PEAR-DB is available try to connect using the collected settings */
        if (class_exists("DB")) {

            assert('is_array($dsn);');    
            /* the returned object is whether an instance of DB_common or DB_Error */
            $this->database = DB::connect($dsn);

            /* error handling */
            if (DB::isError($this->database)) {
                $err_msg = 'DATABASE NOT AVAILABLE: Unable to establish connection with database server. Open administration panel and choose "database administration" to edit settings';
                if (isset($YANA)) {
                    $YANA->report( new log($err_msg, $this->database->getMessage()) );
                } else {
                    trigger_error($err_msg.': '.$this->database->getMessage(),E_USER_WARNING);
                }
            }
        } else {
            /* error handling */
            if (isset($YANA)) {
                $YANA->report( new error('PEARDB_FAILURE') );
            }
            $this->database = null;
        }
    }

    /**
     * get a PEAR-DB connection object
     *
     * This function returns an open database connection via PEAR-DB.
     * The returned values are:
     *
     * <ul>
     *   <li><pre> null               = if PEAR-DB was not found </pre></li>
     *   <li><pre> (boolean) false    = if the connection failed </pre></li>
     *   <li><pre> (object) DB_common = if the connection was established successfully </pre></li>
     * </ul>
     *
     * @access  public
     * @name    dbServer::get()
     * @return  mixed
     */
    function &get()
    {
        if (!class_exists("DB")) {
            return null;
        } else if (DB::isError($this->database)) {
            trigger_error($this->database->getMessage(), E_USER_ERROR);
            $this->database = null;
            return $this->database;
        } else {
            return $this->database;
        }
    }

    /**
     * test if a connection is available
     *
     * returns (boolean) true if a connection to a db-server could be established via
     * the provided parameters.
     * See the constructor method for details on the $dsn parameter
     *
     * @access  public
     * @static
     * @name    dbServer::test()
     * @param   string  $dsn  array
     * @return  boolean
     */
    function test($dsn)
    {
        if (!class_exists("DB")) {
            return false;
        }
        $db =& new dbServer($dsn);
        /*
         * NOTE: the constructor already created a log-entry if the connection failed,
         * so there's no need to do this again
         */
        if ($db->database === false || DB::isError($db->database)) {
            return false;
        } else {
            return true;
        }
    }

}

?>