<?php
/**
 * Turba directory driver implementation for PHP's PEAR database abstraction
 * layer.
 *
 * $Horde: turba/lib/Driver/sql.php,v 1.34 2003/06/19 18:48:11 chuck Exp $
 *
 * @author  Jon Parise <jon@csh.rit.edu>
 * @version $Revision: 1.34 $
 * @since   Turba 0.0.1
 * @package turba
 */
class Turba_Driver_sql extends Turba_Driver {

    /** Handle for the current database connection. */
    var $_db = null;

    /** String containg the name of the table in which the entries are
        stored. */
    var $_table = '';

    /**
     * Constructs a new Turba SQL driver object.
     *
     * @param $params       Hash containing additional configuration parameters.
     */
    function Turba_Driver_sql($params)
    {
        $this->type = 'sql';
        $this->params = $params;

        include_once 'DB.php';
        $this->_db = &DB::connect($params,
                                  array('persistent' => !empty($this->_params['persistent'])));
        if (!is_a($this->_db, 'PEAR_Error')) {
            $this->_db->setOption('optimize', 'portability');

            if ($params['phptype'] == 'oci8') {
                $this->_db->query('ALTER SESSION SET NLS_DATE_FORMAT = \'YYYY-MM-DD\'');
            }
        } else {
            $this->errno = -1;
            $this->errstr = $this->_db->getMessage();
        }

        $this->_table = $params['table'];
    }

    /**
     * Searches the SQL database with the given criteria and returns a
     * filtered list of results. If the criteria parameter is an empty
     * array, all records will be returned.
     *
     * @param $criteria      Array containing the search criteria.
     * @param $fields        List of fields to return.
     * @param $strict_fields (optional) Array of fields which must be matched
     *                       exactly.
     * @param const $match   Do an 'and' or an 'or' search (defaults to TURBA_SEARCH_AND).
     *
     * @return               Hash containing the search results.
     */
    function search($criteria, $fields, $strict_fields = array(), $match = TURBA_SEARCH_AND)
    {
        if ($match === TURBA_SEARCH_AND) {
            $glue = ' AND ';
        } else {
            $glue = ' OR ';
        }

        $where = '';
        if (count($criteria) > 0) {
            /* Sort into strict and non-strict fields. */
            $strict = array();
            $loose = array();
            $strict_where = '';
            $loose_where = '';
            foreach ($criteria as $key => $val) {
                $val = String::convertCharset($val, NLS::getCharset(), $this->params['charset']);
                if (in_array($key, $strict_fields)) {
                    $strict[$key] = $val;
                } else {
                    $loose[$key] = $val;
                }
            }

            /* Add strict sub-query. */
            if (count($strict) > 0) {
                $strict_where = ' (';
                foreach ($strict as $key => $val) {
                    if ($strict_where != ' (') {
                        $strict_where .= ' AND ';
                    }
                    $strict_where .= $key . ' = ' . $this->_db->quote($val);
                }
                $strict_where .= ')';
            }

            /* Add loose sub-query. */
            if (count($loose) > 0) {
                $loose_where = ' (';
                foreach ($loose as $key => $val) {
                    if ($loose_where != ' (') {
                        $loose_where .= ' ' . $glue;
                    }
                    $loose_where .= 'LOWER(' . $key . ') LIKE LOWER(' . $this->_db->quote('%' . $val . '%') . ')';
                }
                $loose_where .= ')';
            }

            if (!empty($strict_where) && !empty($loose_where)) {
                $where = ' WHERE ' . $strict_where . ' AND ' . $loose_where;
            } elseif (!empty($strict_where) || !empty($loose_where)) {
                $where = ' WHERE ' . ($strict_where ? $strict_where : $loose_where);
            }
        }

        $query  = 'SELECT ' . implode(', ', $fields) . ' ';
        $query .= 'FROM ' . $this->_table;
        $query .= $where;

        /* Log the query at a DEBUG log level. */
        Horde::logMessage(sprintf('SQL search by %s: table = %s; query = "%s"',
                                  Auth::getAuth(), $this->_table, $query),
                          __FILE__, __LINE__, PEAR_LOG_DEBUG);

        /* Run query. */
        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
        }

        $results = array();
        $iMax = count($fields);
        if (!is_a($result, 'PEAR_Error')) {
            while ($row = $result->fetchRow()) {
                if (is_a($row, 'PEAR_Error')) {
                    return $row;
                }

                $entry = array();
                for ($i = 0; $i < $iMax; $i++) {
                    $field = $fields[$i];
                    $entry[$field] = String::convertCharset($row[$i], $this->params['charset']);
                }

                $results[] = $entry;
            }
        }

        return $results;
    }

    /**
     * Read the given data from the SQL database and returns
     * the result's fields.
     *
     * @param $criteria      Search criteria.
     * @param $id            Data identifier.
     * @param $fields        List of fields to return.
     *
     * @return               Hash containing the search results.
     */
    function read($criteria, $id, $fields)
    {
        $where = $criteria . ' = ' . $this->_db->quote(String::convertCharset($id, NLS::getCharset(), $this->params['charset']));

        $query  = 'SELECT ' . implode(', ', $fields) . ' ';
        $query .= 'FROM ' . $this->_table . ' WHERE ' . $where;

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
        }

        $results = array();
        $iMax = count($fields);
        if (!is_a($result, 'PEAR_Error')) {
            $row = $result->fetchRow();
            if (isset($row) && !is_a($row, 'PEAR_Error')) {
                $entry = array();

                for ($i = 0; $i < $iMax; $i++) {
                    $field = $fields[$i];
                    $entry[$field] = String::convertCharset($row[$i], $this->params['charset']);
                }

                $results[] = $entry;
            }
        }

        return $results;
    }

    /**
     * Closes the current database connection.
     *
     * @return          The result of the disconnect() call.
     */
    function close()
    {
        return $this->_db->disconnect();
    }

    /**
     * Adds the specified object to the SQL database.
     */
    function addObject($attributes)
    {
        $fields = array();
        $values = array();
        foreach ($attributes as $field => $value) {
            $fields[] = $field;
            $values[] = $this->_db->quote(String::convertCharset($value, NLS::getCharset(), $this->params['charset']));
        }

        $query  = 'INSERT INTO ' . $this->_table . ' (' . implode(', ', $fields) . ')';
        $query .= ' VALUES (' . implode(', ', $values) . ')';

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
        }

        return is_a($result, 'PEAR_Error') ? $result : true;
    }

    /**
     * Deletes the specified object from the SQL database.
     */
    function removeObject($object_key, $object_id)
    {
        $where = $object_key . ' = ' . $this->_db->quote($object_id);
        $query = 'DELETE FROM ' . $this->_table . ' WHERE ' . $where;

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
        }

        return !is_a($result, 'PEAR_Error');
    }

    /**
     * Saves the specified object in the SQL database.
     */
    function setObject($object_key, $object_id, $attributes)
    {
        $where = $object_key . ' = ' . $this->_db->quote($object_id);
        unset($attributes[$object_key]);

        $set = array();
        foreach ($attributes as $field => $value) {
            $set[] = $field . ' = ' . $this->_db->quote(String::convertCharset($value, NLS::getCharset(), $this->params['charset']));
        }

        $query  = 'UPDATE ' . $this->_table . ' SET ' . implode(', ', $set) . ' ';
        $query .= 'WHERE ' . $where;

        /* Log the query at a DEBUG log level. */
        Horde::logMessage(sprintf('SQL setObject by %s: table = %s; query = "%s"',
                                  Auth::getAuth(), $this->_table, $query),
                          __FILE__, __LINE__, PEAR_LOG_DEBUG);

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
        }

        return !is_a($result, 'PEAR_Error');
    }

    /**
     * Create an object key for a new object.
     *
     * @param array $attributes  The attributes (in driver keys) of the
     *                           object being added.
     *
     * @return string  A unique ID for the new object.
     */
    function makeKey($attributes)
    {
        return md5(mt_rand());
    }

}
