<?php

/**
 * Project:
 * Contenido Content Management System
 *
 * Description:
 * MySQL database driver
 *
 * Requirements:
 * @con_php_req 5
 *
 *
 * @package    Contenido database
 * @version    0.2.2
 * @author     Boris Erdmann, Kristian Koehntopp
 * @copyright  four for business AG <www.4fb.de>
 * @license    http://www.contenido.org/license/LIZENZ.txt
 * @link       http://www.4fb.de
 * @link       http://www.contenido.org
 * @since      file available since contenido release <Contenido Version>
 *
 * {@internal
 *   created  2002-07-21
 *   modified 2008-07-04, bilal arslan, added security fix
 *   modified 2009-10-29, Murat Purc, removed deprecated functions (PHP 5.3 ready) extended DB_Sql_Abstract, added/optimized some functioms and some formatting
 *   modified 2009-12-18, Murat Purc, Replaced mysql_list_fields() against a SQL statement, see [#CON-262]
 *   modified 2011-03-03, Murat Purc, Some redesign and improvements (partial adaption to PHP 5 and extending DB_Sql_Abstract).
 *   modified 2011-03-13, Murat Purc, Cleanup and documentation.
 *   modified 2011-04-22, Murat Purc, Connect to DB server without database and more 
 *                                    readable connection settings.
 *   modified 2011-05-17, Ortwin Pinke, bugfix in methode nextid().
 *
 *   $Id$:
 * }}
 *
 */
if (!defined('CON_FRAMEWORK')) {
    die('Illegal call');
}

class DB_Sql extends DB_Sql_Abstract {

    /**
     * Constructor.
     *
     * @param  array  $options  Optional assoziative options
     */
    public function __construct(array $options = array()) {
        $options = array_merge($options, array(
            'type' => 'mysql'
        ));
        parent::__construct($options);
    }

    /**
     * @see DB_Sql_Abstract::_connect()
     */
    protected function _connect() {
        $aCon = $this->_aDbCfg['connection'];
        if (!isset($aCon['host']) || !isset($aCon['user']) || !isset($aCon['password'])) {
            $this->halt('MySQL _connect() Connection settings not complete');
            return null;
        }

        // establish connection, select database
        $dbh = @mysql_connect($aCon['host'], $aCon['user'], $aCon['password']);
        if (!$dbh || !is_resource($dbh)) {
            $this->halt('MySQL _connect() Failed');
            return null;
        }

        // set charset, default utf8
        if (isset($aCon['charset'])) {
            @mysql_set_charset($aCon['charset'], $dbh);
        } else {
            @mysql_set_charset($aCon['charset'], 'utf8');
        }
        //echo mysql_client_encoding($dbh);

        if (isset($aCon['database'])) {
            if (!@mysql_select_db($aCon['database'], $dbh)) {
                $this->halt('MySQL _connect() Cannot use database ' . $aCon['database']);
                return null;
            }
            $this->Database = $aCon['database'];
        }

        $this->User = $aCon['user'];

        return $dbh;
    }

    /**
     * Discard the query result
     */
    public function free() {
        @mysql_free_result($this->Query_ID);
        $this->Query_ID = 0;
    }

    /**
     * @see DB_Sql_Abstract::_query()
     */
    protected function _query($sQuery) {
        $this->Query_ID = @mysql_query($sQuery, $this->Link_ID);
        $this->Row = 0;
        $this->Error = $this->_getErrorMessage();
        $this->Errno = $this->_getErrorNumber();
        if (!$this->Query_ID) {
            $this->halt($sQuery);
        }
    }

    /**
     * @see DB_Sql_Abstract::next_record()
     */
    public function next_record() {
        $this->Record = @mysql_fetch_array($this->Query_ID);
        $this->Row += 1;
        $this->Error = $this->_getErrorMessage();
        $this->Errno = $this->_getErrorNumber();

        $stat = is_array($this->Record);
        if (!$stat && $this->Auto_Free) {
            $this->free();
        }
        return $stat;
    }

    /**
     * @see DB_Sql_Abstract::seek()
     */
    public function seek($pos = 0) {
        $status = @mysql_data_seek($this->Query_ID, $pos);
        if ($status) {
            $this->Row = $pos;
        } else {
            $this->halt("seek($pos) failed: result has " . $this->num_rows() . " rows.");
            // half assed attempt to save the day, but do not consider this
            // documented or even desireable behaviour.
            @mysql_data_seek($this->Query_ID, $this->num_rows());
            $this->Row = $this->num_rows();
            return 0;
        }

        return 1;
    }

    /**
     * @see DB_Sql_Abstract::lock()
     */
    public function lock($table, $mode = 'write') {
        if ($this->_bNolock == true) {
            return true;
        }
        $query = 'LOCK TABLES ';
        if (is_array($table)) {
            while (list($key, $value) = each($table)) {
                if (!is_int($key)) {
                    // texts key are "read", "read local", "write", "low priority write"
                    $query .= "$value $key, ";
                } else {
                    $query .= "$value $mode, ";
                }
            }
            $query = substr($query, 0, -2);
        } else {
            $query .= "$table $mode";
        }
        $res = $this->query($query);
        if (!$res) {
            $this->halt('lock() failed.');
            return 0;
        }
        return $res;
    }

    /**
     * @see DB_Sql_Abstract::unlock()
     */
    public function unlock() {
        if ($this->_bNolock == true) {
            return true;
        }

        $res = $this->query('UNLOCK TABLES');
        if (!$res) {
            $this->halt('unlock() failed.');
        }
        return $res;
    }

    /**
     * @see DB_Sql_Abstract::affected_rows()
     */
    public function affected_rows() {
        return ($this->Link_ID) ? mysql_affected_rows($this->Link_ID) : 0;
    }

    /**
     * @see DB_Sql_Abstract::num_rows()
     */
    public function num_rows() {
        return ($this->Query_ID) ? mysql_num_rows($this->Query_ID) : 0;
    }

    /**
     * @see DB_Sql_Abstract::num_fields()
     */
    public function num_fields() {
        return ($this->Query_ID) ? mysql_num_fields($this->Query_ID) : 0;
    }

    /**
     * get next possible id for requested db-table and update sequence table
     *
     * @param string $seq_name name of db-table to get nextid
     * @return int  nextid for requested table or 0 if an error occured
     */
    public function nextid($seq_name) {
        $this->connect();

        if ($this->lock($this->Seq_Table)) {
            /* get sequence number (locked) and increment */
            $q = sprintf("SELECT nextid FROM `%s` WHERE seq_name = '%s'", $this->Seq_Table, $seq_name);
            $id = @mysql_query($q, $this->Link_ID);
            $res = @mysql_fetch_array($id);

            /* No current value, make one */
            if (!is_array($res)) {
                $currentid = 0;
                $q = sprintf("INSERT INTO `%s` VALUES('%s', %s)", $this->Seq_Table, $seq_name, $currentid);
                $id = @mysql_query($q, $this->Link_ID);
            } else {
                $currentid = $res['nextid'];
            }
            $nextid = $currentid + 1;
            $q = sprintf("UPDATE `%s` SET nextid = '%s' WHERE seq_name = '%s'", $this->Seq_Table, $nextid, $seq_name);
            $id = @mysql_query($q, $this->Link_ID);
            $this->unlock();
        } else {
            $this->halt('Cannot lock ' . $this->Seq_Table . ' - has it been created?');
            return 0;
        }
        return $nextid;
    }

    /**
     * @see DB_Sql_Abstract::disconnect()
     */
    public function disconnect() {
        $this->_debug("Debug: Disconnecting $this->Link_ID...");
        if (is_resource($this->Link_ID)) {
            mysql_close($this->Link_ID);
            $this->_removeConnection($this->Link_ID);
        }
        $this->Link_ID = 0;
        $this->Query_ID = 0;
    }

    /**
     * @see DB_Sql_Abstract::_metaData()
     */
    protected function _metaData($table = '', $full = false) {
        $count = 0;
        $id = 0;
        $res = array();

        /*
         * Due to compatibility problems with Table we changed the behavior
         * of metadata();
         * depending on $full, metadata returns the following values:
         *
         * - full is false (default):
         * $result[]:
         *   [0]["table"]  table name
         *   [0]["name"]   field name
         *   [0]["type"]   field type
         *   [0]["len"]    field length
         *   [0]["flags"]  field flags
         *
         * - full is true
         * $result[]:
         *   ["num_fields"] number of metadata records
         *   [0]["table"]  table name
         *   [0]["name"]   field name
         *   [0]["type"]   field type
         *   [0]["len"]    field length
         *   [0]["flags"]  field flags
         *   ["meta"][field name]  index of field named "field name"
         *   This last one could be used if you have a field name, but no index.
         *   Test:  if (isset($result['meta']['myfield'])) { ...
         */

        // if no $table specified, assume that we are working with a query
        // result
        if (!empty($table)) {
            $this->connect();
            $id = mysql_query(sprintf('SELECT * FROM `%s` LIMIT 1', $table), $this->Link_ID); // PHP 5.3 fix 07.2009 O.Pinke, PHP 5.3 crashes with deprecated mysql_list_fields()
            if (!$id) {
                $this->halt('Metadata query failed.');
                return false;
            }
        } else {
            $id = $this->Query_ID;
            if (!$id) {
                $this->halt('No query specified.');
                return false;
            }
        }

        $count = @mysql_num_fields($id);

        // made this IF due to performance (one if is faster than $count if's)
        for ($i = 0; $i < $count; $i++) {
            $res[$i]['table'] = @mysql_field_table($id, $i);
            $res[$i]['name'] = @mysql_field_name($id, $i);
            $res[$i]['type'] = @mysql_field_type($id, $i);
            $res[$i]['len'] = @mysql_field_len($id, $i);
            $res[$i]['flags'] = @mysql_field_flags($id, $i);
            if ($full) {
                $res['meta'][$res[$i]['name']] = $i;
            }
        }
        if ($full) {
            $res['num_fields'] = $count;
        }

        // free the result only if we were called on a table
        if ($table) {
            @mysql_free_result($id);
        }
        return $res;
    }

    /**
     * @see DB_Sql_Abstract::_tableNames()
     */
    protected function _tableNames() {
        $return = array();
        $this->connect();
        $h = @mysql_query('SHOW TABLES', $this->Link_ID);
        $i = 0;
        if (isset($h) && @mysql_num_rows($h) > 0) {
            while ($info = @mysql_fetch_row($h)) {
                $return[$i]['table_name'] = $info[0];
                $return[$i]['tablespace_name'] = $this->Database;
                $return[$i]['database'] = $this->Database;
                $i++;
            }
            @mysql_free_result($h);
        }
        return $return;
    }

    /**
     * @see DB_Sql_Abstract::escape()
     */
    public function escape($sString) {
        if (!is_string($sString))
            return $sString;
        $sResult = '';
        if (is_resource($this->Link_ID) || $this->connect()) {
            $sResult = mysql_real_escape_string($sString, $this->Link_ID);
        }
        return $sResult;
    }

    /**
     * @see DB_Sql_Abstract::_serverInfo()
     */
    protected function _serverInfo() {
        $arr['description'] = mysql_get_server_info($this->Link_ID);
        return $arr;
    }

    /**
     * @see DB_Sql_Abstract::_getErrorMessage()
     */
    protected function _getErrorMessage() {
        if (is_resource($this->Link_ID)) {
            return mysql_error($this->Link_ID);
        } else {
            return mysql_error();
        }
    }

    /**
     * @see DB_Sql_Abstract::_getErrorNumber()
     */
    protected function _getErrorNumber() {
        if (is_resource($this->Link_ID)) {
            return mysql_errno($this->Link_ID);
        } else {
            return mysql_errno();
        }
    }

    /**
     * This method equates to mysql_fetch_object(). It returns the current
     * result set as object or null if no result set is left. If optional
     * param $sClassName is set, the result object is an instance of class
     * $sClassName.
     *
     * @return object
     *
     * @author Holger Librenz <holger.librenz@4fb.de>
     * @version 1.0
     */
    public function getResultObject($sClassName = null) {
        $oResult = null;

        if (is_resource($this->Link_ID) && is_resource($this->Query_ID)) {
            if ($sClassName == null) {
                $oResult = mysql_fetch_object($this->Query_ID);
            } else {
                $oResult = mysql_fetch_object($this->Query_ID, $sClassName);
            }
        }

        return $oResult;
    }

    public function getServerInfo() {
        return mysql_get_server_info($this->Link_ID);
    }

    public function getClientEncoding() {
        return mysql_client_encoding($this->Link_ID);
    }

    public function getClientInfo() {
        return mysql_get_client_info();
    }
}
