<?php

	/**
	 * \defgroup Database
	 *
	 * <p>This module includes all database-related objects.</p>
	 *
	 * <p>The most important one is the Db object, which provides one centralized way to get
	 * access to the current database connection. In fact, there is no reason for classes that require
	 * database access to use the PDb framework directly, as the Db::getDb() singleton method will
	 * transparently load the database information from the configuration file, connect to it and return
	 * a working connection.</p>
	 *
	 * <p>In order to get a working database connection, this is all you need to do:</p>
	 *
	 * <pre>
	 * $db =& Db::getDb();
	 * </pre>
	 *
	 * <p>For those classes that extend the Model base class, the private attribute Model::_db is a database
	 * connection so there is no need to initialize a new one.</p>
	 */
	
    include_once( PLOG_CLASS_PATH."class/config/configfilestorage.class.php" );
    include_once( PLOG_CLASS_PATH."class/database/pdb/pdb.class.php" );

    /**
	 * \ingroup Database
	 *
     * Provides a singleton for accessing the db and interfaces with PDb. Please use the 
     * getDb() singleton method to get access to the global database object instead of creating
 	 * new objects every time.
     */
	class Db  
	{
        var $_prefix;

		/**
		 * Constructor of the class
		 */
		function Db()
		{
        	
		}

		/**
		 * Singleto method that should be used to get access to the global database connection. This method
		 * will load the database information from the database configuration file (config/config.properties.php) and
		 * will initialize a connection based on its information. If it is unable to start a new database connection, this
		 * method wil stop all processing and display an error message.
		 *
		 * @return Returns a reference to a PDb driver, with a working connection to the database.
		 * @see PDb::getDriver()
		 */
		function &getDb()
		{
			static $db;

            if( !isset( $db )) {
            	// we need to connect to the db
                $fileConfig = new ConfigFileStorage();

				//$db = NewADOConnection('mysql');
                $db = PDb::getDriver('mysql');

                $username  = $fileConfig->getValue( "db_username" );
                $password  = $fileConfig->getValue( "db_password" );
                $host      = $fileConfig->getValue( "db_host" );
                $dbname    = $fileConfig->getValue( "db_database" );
                $dbcharset = $fileConfig->getValue( "db_character_set" );
                $dbpersistent   = $fileConfig->getValue( "db_persistent" );
                if($dbpersistent == true) {
	            	if( !$db->PConnect( $host, $username, $password, $dbname, $dbcharset )) {
	            	    $message = "Fatal error: could not connect to the database!".
	            	               " Error: ".$db->ErrorMsg();
	            		throw( new Exception( $message ));
	            		die();
	            	}
            	}
            	else {
	            	if( !$db->Connect( $host, $username, $password, $dbname, $dbcharset )) {
	            	    $message = "Fatal error: could not connect to the database!".
	            	               " Error: ".$db->ErrorMsg();
	            		throw( new Exception( $message ));
	            		die();
	            	}
            	}
	           	// just in case, forcing to use indexing by field name instead of
    	       	// by field number
        	   	//$db->SetFetchMode( ADODB_FETCH_ASSOC );       	
        	   	// comment out the line above if you're planning to test pdb
            }
            
            return $db;
		}
		
		/**
		 * Creates a new database instance based on the information provided
		 *
		 * @param host The database host where to connect
		 * @param username The username used for the connection
		 * @param password The password used for the connection
		 * @param dbname The name of the database
		 * @param returns a reference to a PDb driver or dies if there was an error connecting
		 */
		function &getNewDb( $host, $username, $password, $dbname ) 
		{
			static $dbs;
			
			if( !is_array( $dbs ))
				$dbs = Array();
				
			$key = $username.$password.$host.$dbname;
				
			if( !isset( $dbs[$key] )) {
                $dbs[$key] = PDb::getDriver('mysql');
            	if( !$dbs[$key]->PConnect( $host, $username, $password, $dbname )) {
            		throw( new Exception( "getNewDb: Fatal error: could not connect to the database!" ));
                	die();
            	}

            	// just in case, forcing to use indexing by field name instead of
            	// by field number
            	//$db->SetFetchMode( ADODB_FETCH_ASSOC );				
			}		
			
			return( $dbs[$key] );				
		}

		/**
		 * returns the prefix as configured for this database connection
		 *
		 * @return A string containing the database prefix
		 * @static
		 */
		function getPrefix()
		{
            static $prefix;

            if ( isset($prefix) ) {
                return $prefix;
            } else {
			    $fileConfig = new ConfigFileStorage();
			    $prefix = $fileConfig->getValue( "db_prefix" );
			    return( $prefix );	
            }
		}


		/**
		 * Builds a SELECT query
		 *
		 * @param tableName The table from where we want to build the SELECT query
		 * @param fieldsToFetch An array with the list of fields we'd like to fetch
		 * @param whereColumn Column used in the WHERE clause
		 * @param whereValue Value used in the WHERE clause
		 * @param orderColumn Column used to sort the data
		 * @param limit Number of records to return
		 * @param whereGlue Logical operator used to glue the different WHERE conditions, 'AND'
		 * by default.
		 * @return Returns the SELECT query defined by the provided parameters
		 */
        function buildSelectQuery( $tableName, 
                                   $fieldsToFetch = array(),
                                   $whereColumn   = null, 
                                   $whereValue    = null,
                                   $orderColumn   = null,
                                   $limit         = null,
                                   $whereGlue     = ' AND ')
        {
            $db = Db::getDb();

            $tableName = Db::addTablePrefixToTableName( $tableName );

            $query = $db->buildSelectQuery( $tableName,
                                            $fieldsToFetch,
                                            $whereColumn,
                                            $whereValue,
                                            $orderColumn,
                                            $limit,
                                            $whereGlue );

            return $query;
        }

		/**
		 * Builds an INSERT query
		 *
		 * @param tableName Name of the table where data is to be inserted
		 * @param keyValuePairs An associative array where the key is the name of the column and the
		 * value is the value of the column in the query
		 * @return Returns the INSERT query
		 */
        function buildInsertQuery( $tableName,
                                   $keyValuePairs )
        {
            $tableName = Db::addTablePrefixToTableName( $tableName );

            $query  = 'INSERT INTO ' . $tableName;

            foreach( $keyValuePairs as $key => $value ) {
                $keys[]   = $key;
                $values[] = Db::quoteValue( $value );
            }
            $query .= ' (' . implode( ",", $keys ) . ')';
            $query .= ' VALUES (';
            $query .= implode( ",", $values );
            $query .= ');';

            return $query;
        }

		/**
		 * Builds an UPDATE query
		 *
		 * @param tableName Name of the table where data is to be inserted
		 * @param keyValuePairs An associative array where the key is the name of the column and the
		 * value is the value of the column in the query		
		 * @param whereColumn Column used in the WHERE clause
		 * @param whereValue Value used in the WHERE clause		
		 * @return Returns the UPDATE query
		 */
        function buildUpdateQuery( $tableName,
                                   $keyValuePairs,
                                   $whereColumn   = null, 
                                   $whereValue    = null,
                                   $whereGlue     = ' AND ')
        {
            $tableName = Db::addTablePrefixToTableName( $tableName );

            $query  = 'UPDATE ' . $tableName . ' SET ';

            $valuesToSet = array();
            foreach( $keyValuePairs as $key => $value ) {
                $queryPart  = $key . ' = ';
                $queryPart .= Db::quoteValue( $value );
                $valuesToSet[] = $queryPart;
            }
            $query .= implode( ",", $valuesToSet );
            
            if( $whereColumn != null )
                $query .= Db::buildWhereConditions( $whereColumn, $whereValue, $whereGlue );

            return $query;
        }

		/**
		 * Builds a DELETE query
		 * 
		 * @param tableName Name of the table where data is to be inserted		
		 * @param whereColumn Column used in the WHERE clause
		 * @param whereValue Value used in the WHERE clause		
		 * @param whereGlue Logical operator used to glue the different WHERE conditions, 'AND'
		 * by default.
		 */
        function buildDeleteQuery( $tableName,
                                   $whereColumn = null,
                                   $whereValue  = null,
                                   $whereGlue   = ' AND ')
        {
            $tableName = Db::addTablePrefixToTableName( $tableName );

            $query  = 'DELETE FROM ' . $tableName;

            if( $whereColumn != null )
                $query .= Db::buildWhereConditions( $whereColumn, $whereValue, $whereGlue );

            return $query;
        }

		/**
		 * @private
		 * Adds the current database prefix to the given table name. If the table name does not have
		 * the prefix yet, it will be added in front of the table name but if it's already there, the table
		 * name will not be modified
		 *
		 * @param tableName Name of the table, without prefix. 
		 * @return The table name with the prefix
		 */
        function addTablePrefixToTableName( $tableName ) {
            if ( !preg_match( "/^" . Db::getPrefix() . "/", $tableName) ) {
                $tableName = Db::getPrefix() . $tableName;
            }
            return $tableName;
        }

		/**
		 * Builds a WHERE clause for SELECT and DELETE queries
		 *
		 * @param whereColumn The column, it can either be a single column or an array of columns. If it
		 * is an array of columns, it should be an associative array where the key is the name of the column
		 * and the value is the value assigned to this column.
		 * @param whereValue The value assigned to the column, in case $whereColumn is only one column
		 * @param whereGlue The glue used to link the different conditions
		 * @return A string that can be used as a WHERE clause in a query
		 */
        function buildWhereConditions( $whereColumn, $whereValue, $whereGlue )
        {
            $queryPart = ' WHERE ';
            if( is_array($whereColumn) ) {
                $conditions = array();
                foreach( $whereColumn as $column => $value ) {
                    if($column && $value){
                        $conditions[] = Db::buildWhereCondition( $column, $value );
                    }
                }
                $imploded = implode( $whereGlue , $conditions );
                if(!$imploded)
                    return null;
                $queryPart .= $imploded;
            } elseif( !empty($whereColumn) ) {
                $queryPart .= Db::buildWhereCondition( $whereColumn, $whereValue );
            }

            return $queryPart;
        }

		/**
		 * Builds one single condition for a WHERE clause
		 *
		 * @param columnName the column name
		 * @param columnValue the value for the column
		 * @return A string as part of a WHERE clause
		 */
        function buildWhereCondition( $columnName, $columnValue )
        {
            $db =& Db::getDb();
            return $db->buildWhereCondition( $columnName, $columnValue );
        }

		/**
		 * @private
		 */
        function _buildOrderCondition( $orderColumn )
        {
            preg_match( '/^(.)(.*)$/', $orderColumn, $matches );

            $queryPart = ' ORDER BY '; 
            $firstValueCharacter = $matches[1]; 
            switch( $firstValueCharacter ) {
                case '-':
                    $orderColumn = $matches[2];
                    $direction = ' DESC';
                case '+':
                    $orderColumn = $matches[2];
                default:
                    $direction  = '';
                    $queryPart .= $orderColumn . $direction;
            }
            return $queryPart;
        }

        /**
         * Quote a value for a SQL query. Depending on the type of the
         * value, this method will return the correct string to store
         * the value in the database.
         *
         * - if the value is numeric, it will return the numeric value
         * - if the value is a Timestamp(), it will return an isoDate
         * - if the value is a string, it will return a quoted string
         * - if the value starts with an '@', it will return the
         *   unquoted value. this is used e.g. for adding something
         *   like "date = date" in Articles()->deleteArticle().
         *
         * @param value a value to be stored in the database
         * @return string the correctly quoted value
         * @access public
         */
        function quoteValue( $value )
        {
            if( is_numeric($value) )
                return $value;
            if( preg_match('/^@(.*)/', $value, $matches) )
                return $matches[1];
            elseif( is_object($value) ) {
                switch (get_class($value)) {
                    case 'timestamp':
                        return Db::quoteValue($value->getIsoDate());
                        break;
                }
            }
            else
                return '\'' . Db::qstr( $value ) . '\'';
        }

		/**
		 * Prepares a string for an SQL query by escaping apostrophe
		 * characters. If the PHP configuration setting 'magic_quotes_gpc'
		 * is set to ON, it will first strip the added slashes. Apostrophe
		 * characters are doubled, conforming with the ANSI SQL standard.
		 * The SQL parser makes sure that the escape token is not entered
		 * in the database so there is no need to modify the data when it
		 * is read from the database.
		 *
		 * @param  string $string
		 * @return string
		 * @access public
		 */
		function qstr($string) {

 			if (get_magic_quotes_gpc()) {
				$string = stripslashes($string);
				//$string = stripslashes($string);
		                $string = str_replace('\\\\', '\\', $string);
                		$string = str_replace("\\'", "'", $string);
         		        $string = str_replace('\\"', '"', $string);
 			}
 
			$string = str_replace("\\", "\\\\", $string);
 			$string = str_replace("'", "''", $string);

			return $string;
		}
    }
?>