<?php

class clsdb
{

  // class clsdb.php
  // database abstraction layer, supports multiple databases

  /*
    usage:
    - activatedb() will be called automatically each time you use runsql() (it will guarantee database availability)
    - you may call deactivatedb() at the end of your work (e.g. at end of script)
    - in normal operation use control constants in your main code and just call runsql() and similar functions

    control variables:
    mstrdbtype declares database type of constants DB_TYPE_*, e.g. "POSTGRESQL", "ODBC", "MYSQL" or others
    each database type needs some more special variables:
    POSTGRESQL:
    - mstrdbhost, e.g. "localhost"
    - mintdbport, e.g. 5432
    - mstrdbname, e.g. "testdatabase"
    - mstrdbuser, e.g. "testuser"
    - mstrdbpass, e.g. "testpass"
    ODBC:
    - mstrdbname, e.g. "testdatabase"
    - mstrdbuser, e.g. "testuser"
    - mstrdbpass, e.g. "testpass"

    property functions:
    - [get/set]dbtype
    - [get/set]dbhost
    - [get/set]dbport
    - [get/set]dbname
    - [get/set]dbuser
    - [get/set]dbpass
    - [get]dbdefaultschemas
    - [get]dbrowcount
    - [get]dblastsql
    - [get]dberror

    methods:
    - __constructor clsdb = initializes class instance
    - initdb = initializes database
    - activatedb = activates/opens database connection, will be called automatically
    - deactivatedb = deactivates/closes database connection, will be done automatically at the end of the parent script
    - changedbdefaultschemas = changes search list of default schemas in database
    - runsql = executes SQL statement on given database connection
    - getdbrow = fetches row on position intdbrow from mintdbresultid as associative array into strdbrow
    - getdbfields = returns list of field names, field type and field length from mintdbresultid as associative arrays into strdbfields
    - dbtimestamp = converts given timestamp into database format
  */

  // class constants are always public and static and can be defined by:  const constantname = constantvalue;
  // refer to them from within or from outside the class by:  classname::constantname
  // e.g.:  tstclass::tstconstant

  const DB_TYPE_NONE = "";
  const DB_TYPE_POSTGRESQL = "POSTGRESQL";
  const DB_TYPE_ODBC = "ODBC";
  const DB_TYPE_MYSQL = "MYSQL";

  const DB_PORT_NONE = 0;
  const DB_PORT_POSTGRESQL = 5432;
  const DB_PORT_ODBC = 0;
  const DB_PORT_MYSQL = 3306;

  const DB_ROW_FIRST = 0;
  const DB_ROW_NEXT = -1;
  const DB_ROW_LAST = -2;

  const DB_FIELD_NAME = 0;
  const DB_FIELD_TYPE = 1;
  const DB_FIELD_LEN = 2;

  private $mstrdbtype;
  private $mstrdbhost;
  private $mintdbport;
  private $mstrdbname;
  private $mstrdbuser;
  private $mstrdbpass;

  private $mintdbconnid;
  private $mintdbresultid;
  private $mstrdbdefaultschemas;
  private $mlngdbrowcount;
  private $mstrdblastsql;
  private $mstrdberror;


  // **********************************************************************
  function __construct ()
  // class constructor, initializes class instance
  {
    // init class
    $this->initdb();
  }


  // **********************************************************************
  // public property functions
  function setdbtype ( $strdbtype ) { $this->mstrdbtype = trim( $strdbtype ); }
  function getdbtype () { return $this->mstrdbtype; }
  function setdbhost ( $strdbhost ) { $this->mstrdbhost = trim( $strdbhost ); }
  function getdbhost () { return $this->mstrdbhost; }
  function setdbport ( $intdbport ) { $this->mintdbport = intval( $intdbport ); }
  function getdbport () { return $this->mintdbport; }
  function setdbname ( $strdbname ) { $this->mstrdbname = trim( $strdbname ); }
  function getdbname () { return $this->mstrdbname; }
  function setdbuser ( $strdbuser ) { $this->mstrdbuser = trim( $strdbuser ); }
  function getdbuser () { return $this->mstrdbuser; }
  function setdbpass ( $strdbpass ) { $this->mstrdbpass = $strdbpass; }
  function getdbpass () { return $this->mstrdbpass; }

  function getdbdefaultschemas () { return $this->mstrdbdefaultschemas; }
  
  function getdbrowcount () { return $this->mlngdbrowcount; }
  function getdblastsql () { return $this->mstrdblastsql; }
  function getdberror () { return $this->mstrdberror; }


  // **********************************************************************
  function initdb()
  // initdb = initializes database
  {
    $this->mstrdbtype = clsdb::DB_TYPE_NONE;
    $this->mstrdbhost = 'localhost';
    $this->mintdbport = clsdb::DB_PORT_NONE;
    $this->mstrdbname = '';
    $this->mstrdbuser = '';
    $this->mstrdbpass = '';
    $this->mstrdefaultschemas = '';

    $this->mintdbconnid = 0;
    $this->mintdbresultid = 0;
    $this->mstrdblastsql = '';
    $this->mlngdbrowcount = 0;
    $this->mstrdberror = '';
  }


  // **********************************************************************
  function activatedb ()
  // activates/opens database connection, will be called automatically
  // updates mintdbconnid and mstrdberror
  // returns true if database had already been opened or has been opened successfully,
  // otherwise returns false and sets mstrdberror
  {
    $bolretcode = true;
    $this->mstrdberror = '';

    if ( $this->mintdbconnid == 0 )
    {
      // database is dead or closed -> (re)open it
      switch ( $this->mstrdbtype )
      {
        case clsdb::DB_TYPE_POSTGRESQL:
          if ( $this->mintdbport == clsdb::DB_PORT_NONE ) { $this->mintdbport = clsdb::DB_PORT_POSTGRESQL; }
          $this->mintdbconnid = pg_connect ("host=" . $this->mstrdbhost . " port=" . strval( $this->mintdbport ) . " dbname=" . $this->mstrdbname . " user=" . $this->mstrdbuser . " password=" . $this->mstrdbpass );
          $this->mstrdberror = pg_last_error( $this->mintdbconnid );
          break;

        case clsdb::DB_TYPE_ODBC:
          if ( $this->mintdbport == clsdb::DB_PORT_NONE ) { $this->mintdbport = clsdb::DB_PORT_ODBC; }
          $this->mintdbconnid = odbc_connect ( $this->mstrdbname, $this->mstrdbuser, $this->mstrdbpass, SQL_CUR_USE_ODBC );
          $this->mstrdberror = odbc_errormsg( $this->mintdbconnid );
          break;

        case clsdb::DB_TYPE_MYSQL:
          if ( $this->mintdbport == clsdb::DB_PORT_NONE ) { $this->mintdbport = clsdb::DB_PORT_MYSQL; }
          $this->mintdbconnid = mysql_connect ( $this->mstrdbhost . ':' . strval( $this->mintdbport ), $this->mstrdbuser, $this->mstrdbpass );
          $this->mstrdberror = mysql_error( $this->mintdbconnid );
          if ( $this->mintdbconnid != 0 )
          {
            if ( !mysql_select_db ( $this->mstrdbname, $this->mintdbconnid )) { $this->mintdbconnid = 0; }
            $this->mstrdberror = mysql_error( $this->mintdbconnid );
          }
          break;

        default:
          // db type not supported!
          $this->mintdbconnid = 0;
          $this->mstrdberror = 'Sorry, database of type "' . $this->mstrdbtype . '" is not supported.';
          break;
      }
			
      // set return code on success
      $bolretcode = ( $this->mintdbconnid != 0 );
      
      if ( $bolretcode and $this->mstrdefaultschemas != '' )
      {
        // reset default schemas of database
        $bolretcode = changedbdefaultschemas( $this->mstrdefaultschemas );
      }
    }

    return $bolretcode;
  }


  // **********************************************************************
  function deactivatedb ()
  // deactivates/closes database connection, will be done automatically at the end of the parent script
  // updates mintdbconnid and mstrdberror
  // returns true if database had already been closed or has been closed successfully,
  // otherwise returns false and sets mstrdberror
  {
    $bolretcode = true;
    $this->mstrdberror = '';

    if ( $this->mintdbconnid != 0 )
    {
      // database is still opened -> close it
      switch ( $this->mstrdbtype )
      {
        case clsdb::DB_TYPE_POSTGRESQL:
          $bolretcode = pg_close ( $this->mintdbconnid );
          $this->mstrdberror = pg_last_error( $this->mintdbconnid );
          break;

        case clsdb::DB_TYPE_ODBC:
          $bolretcode = odbc_close ( $this->mintdbconnid );
          $this->mstrdberror = odbc_errormsg( $this->mintdbconnid );
          break;

        case clsdb::DB_TYPE_MYSQL:
          $bolretcode = mysql_close ( $this->mintdbconnid );
          $this->mstrdberror = mysql_error( $this->mintdbconnid );
          break;

        default:
          // db type not supported!
          $bolretcode = false;
          $this->mstrdberror = 'Sorry, database of type "' . $this->mstrdbtype . '" is not supported.';
          break;
      }
    }

    // init/clear database connection on success
    if ( $bolretcode )
    {
      initdb();
    }
    return $bolretcode;
  }


  // **********************************************************************
  function changedbdefaultschemas ( $strdbdefaultschemas )
  // changes search list of default schemas in database
  // expects comma separated list of schema names, but ignores empty list
  // returns true if no schemas were given or default schemas have been updated
  // and false if some error occured when updating
  {
    $bolretcode = true;
    
    if ( $strdbdefaultschemas != '' )
    {
      // do not change to empty value

      $bolretcode = false;

      if ( $this->activatedb() )
      {
        // database is ok
        // change default schemas

        switch ( $this->mstrdbtype )
        {
          case clsdb::DB_TYPE_POSTGRESQL:
            if ( $this->runsql( "set session search_path to " . $strdbdefaultschemas ))
            {
              $bolretcode = true;
            }
            else
            {
              $this->mstrdberror = pg_last_error( $this->mintdbconnid );
            }
            break;

          case clsdb::DB_TYPE_ODBC:
            $this->mstrdberror = 'Sorry, changing the default database schema generically is not supported on ODBC databases.';
            break;

          case clsdb::DB_TYPE_MYSQL:
            $this->mstrdberror = 'Sorry, database schemas are not supported on MYSQL databases.';
            break;

          default:
            // db type not supported!
            $this->mstrdberror = 'Sorry, database of type "' . $this->mstrdbtype . '" is not supported.';
            break;
        }
      }
      else
      {
        // database is dead or closed ->
        $this->mstrdberror = 'Sorry, database is currently not available.';
      }

      // remember current database default schemas
      if ( $bolretcode )
      {
        $this->mstrdbdefaultschemas = $strdbdefaultschemas;
      }
    }

    return $bolretcode;
  }


  // **********************************************************************
  function runsql ( $strdbsql )
  // executes SQL statement on given database connection
  // updates mlngdbrowcount, mintdbresultid and mstrdberror
  // returns true and sets mlngdbrowcount and mintdbresultid if SQL statement had been executed successfully,
  // otherwise returns false and sets mstrdberror
  {
    $bolretcode = false;
    $this->mlngdbrowcount = 0;
    $this->mintdbresultid = 0;
    $this->mstrdberror = '';

    if ( $this->activatedb() )
    {
      // database is ok

      switch ( $this->mstrdbtype )
      {
        case clsdb::DB_TYPE_POSTGRESQL:
          $this->mintdbresultid = pg_query ( $this->mintdbconnid, $strdbsql );
          $this->mstrdberror = pg_last_error( $this->mintdbconnid );
          if ( $this->mintdbresultid != 0 )
          {
						if ( strtolower(substr( trim( $strdbsql ), 0, 6 ) == 'select' ) )
						{
						$this->mlngdbrowcount = pg_num_rows( $this->mintdbresultid );
						}
						else
						{
						$this->mlngdbrowcount = pg_affected_rows( $this->mintdbresultid );
						}
						$bolretcode = true;
          }
          break;

        case clsdb::DB_TYPE_ODBC:
          $this->mintdbresultid = odbc_exec ( $this->mintdbconnid, $strdbsql );
          $this->mstrdberror = odbc_errormsg( $this->mintdbconnid );
          if ( $this->mintdbresultid != 0 )
          {
            // Attention: odbc_num_rows may return wrong record count in SELECT-statements!
            // to retrieve exact number of records in SELECT statements you should walk through the recordset
            $this->mlngdbrowcount = odbc_num_rows( $this->mintdbresultid );
            $bolretcode = true;
          }
          break;

        case clsdb::DB_TYPE_MYSQL:
          $this->mintdbresultid = mysql_query ( $strdbsql, $this->mintdbconnid );
          $this->mstrdberror = mysql_error( $this->mintdbconnid );
          if ( $this->mintdbresultid != 0 )
          {
						if ( strtolower(substr( trim( $strdbsql ), 0, 6 ) == 'select' ) )
						{
						  $this->mlngdbrowcount = mysql_num_rows( $this->mintdbresultid );
						}
						else
						{
						  $this->mlngdbrowcount = mysql_affected_rows( $this->mintdbconnid );
						}
						$bolretcode = true;
          }
          break;

        default:
          // db type not supported!
          $bolretcode = false;
          $this->mstrdberror = 'Sorry, database of type "' . $this->mstrdbtype . '" is not supported.';
          break;
      }
    }

    return $bolretcode;
  }


  // **********************************************************************
  function getdbrow ( &$strdbrow, $intdbrow = clsdb::DB_ROW_NEXT )
  // fetches row on position intdbrow from mintdbresultid as associative array into strdbrow
  // position intdbrow may be DB_RESULT_NEXTROW to fetch the next row of mintdbresultid
  // updates mstrdberror
  // returns true and sets strdbrow if row has been fetched successfully,
  // otherwise returns false and sets mstrdberror
  {
    $bolretcode = false;
    $strdbrow = array();
    $intindex = 0;
    $this->mstrdberror = '';

    if ( $this->mintdbresultid != 0 )
    {
      // resultset is ok

      switch ( $this->mstrdbtype )
      {
        case clsdb::DB_TYPE_POSTGRESQL:
          switch ( $intdbrow )
          {
            case clsdb::DB_ROW_FIRST:
              $bolretcode = ( $strdbrow = pg_fetch_assoc( $this->mintdbresultid, 0 ));
              break;

            case clsdb::DB_ROW_NEXT:
              $bolretcode = ( $strdbrow = pg_fetch_assoc( $this->mintdbresultid ));
              break;

            case clsdb::DB_ROW_LAST:
              $bolretcode = ( $strdbrow = pg_fetch_assoc( $this->mintdbresultid, pg_num_rows( $this->mintdbresultid ) - 1 ));
              break;

            default:
              // specific $intdbrow given -> use it directly, offer start at position 1
              $bolretcode = ( $strdbrow = pg_fetch_assoc( $this->mintdbresultid, $intdbrow - 1 ));
              break;
          }
          $this->mstrdberror = pg_result_error( $this->mintdbresultid );

          if ( $bolretcode )
          {
            // cast special field types to common string values
            $intindex = 0;
            while ( $intindex < pg_num_fields( $this->mintdbresultid ))
            {
              if ( pg_field_type( $this->mintdbresultid, $intindex ) == 'bool' )
              {
                // boolean type of postgresql is "f" or "t"
                // -> convert to "0" or "1"
                switch ( $strdbrow[pg_field_name( $this->mintdbresultid, $intindex )] )
                {
                  case 'f':
                    $strdbrow[pg_field_name( $this->mintdbresultid, $intindex )] = '0';
                    break;
                  case 't':
                    $strdbrow[pg_field_name( $this->mintdbresultid, $intindex )] = '1';
                    break;
                }
              }
              $intindex++;
            }
          }
          
          break;

        case clsdb::DB_TYPE_ODBC:
          switch ( $intdbrow )
          {
            case clsdb::DB_ROW_FIRST:
              $bolretcode = ( $strdbrow = odbc_fetch_array( $this->mintdbresultid, 0 ));
              break;

            case clsdb::DB_ROW_NEXT:
              $bolretcode = ( $strdbrow = odbc_fetch_array( $this->mintdbresultid ));
              break;

            case clsdb::DB_ROW_LAST:
              // Attention: odbc_num_rows may return wrong record count in SELECT-statements!
              // to retrieve exact number of records in SELECT statements you should walk through the recordset
              $bolretcode = ( $strdbrow = odbc_fetch_array( $this->mintdbresultid, odbc_num_rows( $this->mintdbresultid ) - 1 ));
              break;

            default:
              // specific $intdbrow given -> use it directly, offer start at position 1
              $bolretcode = ( $strdbrow = odbc_fetch_array( $this->mintdbresultid, $intdbrow - 1));
              break;
          }
          // ODBC does not support error messages on resultset level
          if ( !$bolretcode )
          {
            $this->mstrdberror = 'Sorry, could not read result row on position ' . $intdbrow ;
          }
          break;

        case clsdb::DB_TYPE_MYSQL:
          switch ( $intdbrow )
          {
            case clsdb::DB_ROW_FIRST:
              $bolretcode = mysql_data_seek( $this->mintdbresultid, 0 );
              $this->mstrdberror = mysql_error( $this->mintdbconnid );
              break;

            case clsdb::DB_ROW_NEXT:
              // just stay where the pointer is
              $bolretcode = true;
              break;

            case clsdb::DB_ROW_LAST:
              $bolretcode = mysql_data_seek( $this->mintdbresultid, mysql_num_rows( $this->mintdbresultid ) - 1 );
              $this->mstrdberror = mysql_error( $this->mintdbconnid );
              break;

            default:
              // specific $intdbrow given -> use it directly, offer start at position 1
              $bolretcode = mysql_data_seek( $this->mintdbresultid, $intdbrow - 1 );
              $this->mstrdberror = mysql_error( $this->mintdbconnid );
              break;
          }
          if ( $bolretcode )
          {
            $bolretcode = ( $strdbrow = mysql_fetch_assoc( $this->mintdbresultid ));
            $this->mstrdberror = mysql_error( $this->mintdbconnid );
          }
        	break;

        default:
          // db type not supported!
          $bolretcode = false;
          $this->mstrdberror = 'Sorry, database of type "' . $this->mstrdbtype . '" is not supported.';
          break;
      }
    }
    else
    {
      $bolretcode = false;
      $this->mstrdberror = 'Sorry, no data available to read.';
    }

    if ( $bolretcode )
    {
      // convert boolean string ('f', 't') manually to ('0', '1')
    }

    return $bolretcode;
  }


  // **********************************************************************
  function getdbfields ( &$strdbfields )
  // returns list of field names, field type and field length from mintdbresultid as associative arrays into strdbfields
  // strdbfields will contain following partial arrays: DB_FIELD_NAME, DB_FIELD_TYPE, DB_FIELD_LEN
  // use as following: $strdbfields[clsdb::DB_FIELD_*][$intdbfield or $strdbfieldname]
  // example: $intdbfieldtype[$strdbfieldname] = $strdbfields[DB_FIELD_TYPE][$strdbfieldname]
  // updates parameter strdbfields, mstrdberror
  // returns true and sets strdbfields if field names have been retrieved successfully,
  // otherwise returns false and sets mstrdberror
  {
    $bolretcode = false;
    $intindex = 0;
    $strfieldname = '';
    $strdbfields = array (clsdb::DB_FIELD_NAME => array(),
                          clsdb::DB_FIELD_TYPE => array(),
                          clsdb::DB_FIELD_LEN => array());
    $this->mstrdberror = '';

    if ( $this->mintdbresultid != 0 )
    {
      // resultset is ok

      //no errors expected at all
      $bolretcode = true;

      switch ( $this->mstrdbtype )
      {
        case clsdb::DB_TYPE_POSTGRESQL:
          // field numeration in PostgreSQL starts with 0
          $intindex = 0;
          while ( $intindex < pg_num_fields( $this->mintdbresultid ))
          {
            array_push ( $strdbfields[clsdb::DB_FIELD_NAME], pg_field_name( $this->mintdbresultid, $intindex ));
            array_push ( $strdbfields[clsdb::DB_FIELD_TYPE], pg_field_type( $this->mintdbresultid, $intindex ));
            array_push ( $strdbfields[clsdb::DB_FIELD_LEN], pg_field_prtlen( $this->mintdbresultid, $intindex ));
            $intindex++;
          }
          $this->mstrdberror = pg_result_error( $this->mintdbresultid );
          break;

        case clsdb::DB_TYPE_ODBC:
          // field numeration in ODBC starts with 1
          $intindex = 1;
          while ( $intindex <= odbc_num_fields( $this->mintdbresultid ))
          {
            $strfieldname = odbc_field_name( $this->mintdbresultid, $intindex );
            array_push ( $strdbfields[clsdb::DB_FIELD_NAME], $strfieldname );
            array_push ( $strdbfields[clsdb::DB_FIELD_TYPE], odbc_field_type( $this->mintdbresultid, $intindex ));
            array_push ( $strdbfields[clsdb::DB_FIELD_LEN], odbc_field_len( $this->mintdbresultid, $intindex ));
            $intindex++;
          }
          // ODBC does not support error messages on resultset level
          if ( !$bolretcode )
          {
            $this->mstrdberror = 'Sorry, could not read field names.';
          }
          break;

        case clsdb::DB_TYPE_MYSQL:
          // field numeration in MYSQL starts with 0
          $intindex = 0;
          while ( $intindex < mysql_num_fields( $this->mintdbresultid ))
          {
            array_push ( $strdbfields[clsdb::DB_FIELD_NAME], mysql_field_name( $this->mintdbresultid, $intindex ));
            array_push ( $strdbfields[clsdb::DB_FIELD_TYPE], mysql_field_type( $this->mintdbresultid, $intindex ));
            array_push ( $strdbfields[clsdb::DB_FIELD_LEN], mysql_field_len( $this->mintdbresultid, $intindex ));
            $intindex++;
          }
          $this->mstrdberror = mysql_error( $this->mintdbconnid );
          break;

        default:
          // db type not supported!
          $bolretcode = false;
          $this->mstrdberror = 'Sorry, database of type "' . $this->mstrdbtype . '" is not supported.';
          break;
      }
    }
    else
    {
      $bolretcode = false;
      $this->mstrdberror = 'Sorry, no data available to read.';
    }

    return $bolretcode;
  }

  // **********************************************************************
  function dbtimestamp( $vartimestamp )
  // converts given timestamp into database format
  {
    $strretcode = '';
    $inttimestamp = 0;
    
    if ( is_numeric( $vartimestamp ) )
    {
      // UNIX timestamp assumed
      $inttimestamp = $vartimestamp;
    }
    else
    {
      // time string assumed
      $inttimestamp = strtotime( $vartimestamp );
    }
    
    // inttimestamp now contains UNIX timestamp
    switch ( $this->mstrdbtype )
    {
      case clsdb::DB_TYPE_POSTGRESQL:
        $strretcode = "TIMESTAMP '" . date( "Y-m-d H:i:s", $vartimestamp) . "'";
        break;

      case clsdb::DB_TYPE_ODBC:
        $strretcode = "'" . date( "Y-m-d H:i:s", $vartimestamp) . "'";
        break;

      case clsdb::DB_TYPE_MYSQL:
        $strretcode = "'" . date( "YmdHis", $vartimestamp ) . "'";
        break;

      default:
        // db type not supported!
        // -> return original string
        $strretcode = $vartimestamp;
        break;
    }

    return $strretcode;
  }

}

?>