<?php
/**
 * Mnemo storage implementation for PHP's PEAR database abstraction layer.
 *
 * Required values for $params:<pre>
 *      'phptype'       The database type (e.g. 'pgsql', 'mysql, etc.).
 *      'hostspec'      The hostname of the database server.
 *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
 *      'username'      The username with which to connect to the database.
 *      'password'      The password associated with 'username'.
 *      'database'      The name of the database.
 *      'table'         The name of the memos table in 'database'.</pre>
 *
 * Required by some database implementations:
 *      'options'       Additional options to pass to the database.
 *      'tty'           The TTY on which to connect to the database.
 *      'port'          The port on which to connect to the database.
 *
 * The table structure is defined in mnemo/scripts/drivers/mnemo_memos.sql.
 *
 * $Horde: mnemo/lib/Driver/sql.php,v 1.11 2003/06/19 18:47:53 chuck Exp $
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @version $Revision: 1.11 $
 * @since   Mnemo 0.1
 * @package mnemo
 */
class Mnemo_Driver_sql extends Mnemo_Driver {

    /** Hash containing connection parameters. */
    var $_params = array();

    /** Handle for the current database connection.
        @var object DB $db */
    var $_db;

    /** Boolean indicating whether or not we're connected to the SQL server. */
    var $_connected = false;

    /**
     * Constructs a new SQL storage object.
     *
     * @param string $user      The user who owns these memos.
     * @param array  $params    A hash containing connection parameters.
     */
    function Mnemo_Driver_sql($user, $params = array())
    {
        $this->_user = $user;
        $this->_params = $params;
    }

    /**
     * Retrieves the user's memos from the database.
     *
     * @return mixed  True on success, PEAR_Error on failure.
     */
    function retrieve()
    {
        /* Make sure we have a valid database connection. */
        $this->_connect();

        /* Build the SQL query. */
        $query = sprintf('SELECT * FROM %s WHERE memo_owner = %s',
                         $this->_params['table'], $this->_db->quote($this->_user));

        /* Log the query at a DEBUG log level. */
        Horde::logMessage(sprintf('Mnemo_Driver_sql::retrieve(): %s', $query),
                          __FILE__, __LINE__, PEAR_LOG_DEBUG);

        /* Execute the query. */
        $result = $this->_db->query($query);

        if (!is_a($result, 'PEAR_Error')) {
            $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
            if (is_a($row, 'PEAR_Error')) {
                return $row;
            }

            /* Store the retrieved values in a fresh $memos list. */
            $this->_memos = array();
            while ($row && !is_a($row, 'PEAR_Error')) {
                /* Create a new memo based on this row's values. */
                $memo = array();
                $memo['memolist_id'] = $this->_user;
                $memo['memo_id'] = $row['memo_id'];
                $memo['desc'] = String::convertCharset($row['memo_desc'], $this->_params['charset']);
                $memo['body'] = String::convertCharset($row['memo_body'], $this->_params['charset']);
                $memo['category'] = $row['memo_category'];
                $memo['private'] = $row['memo_private'];
                $memo['flags'] = 0;

                /* Add this new memo to the $memos list. */
                $this->_memos[$row['memo_id']] = $memo;

                /* Advance to the new row in the result set. */
                $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
            }
            $result->free();
        } else {
            return $result;
        }

        return true;
    }

    /**
     * Stores the user's memos to SQL server.
     *
     * @return mixed  True on success, PEAR_Error on failure.
     */
    function store()
    {
        /* Build lists of the memos that require pending database operations. */
        $added_memos = $this->listMemos(MEMO_ADDED);
        $modified_memos = $this->listMemos(MEMO_MODIFIED);
        $deleted_memos = $this->listMemos(MEMO_DELETED);

        /* If there are no pending operations, exit successfully now. */
        if ((count($added_memos) == 0) && (count($modified_memos) == 0) &&
            (count($deleted_memos) == 0)) {
            return true;
        }

        /* Make sure we have a valid database connection. */
        $this->_connect();

        /* Perform any pending additions. */
        if (count($added_memos) > 0) {
            foreach ($added_memos as $memo_id => $memo) {
                $query = sprintf(
                    'INSERT INTO %s (memo_owner, memo_id, memo_desc, ' .
                    'memo_body, memo_category, memo_private, memo_modified) ' .
                    'VALUES (%s, %d, %s, %s, %s, %d, %d)',
                    $this->_params['table'],
                    $this->_db->quote($this->_user),
                    $memo_id,
                    String::convertCharset($this->_db->quote($memo['desc']), NLS::getCharset(), $this->_params['charset']),
                    String::convertCharset($this->_db->quote($memo['body']), NLS::getCharset(), $this->_params['charset']),
                    $this->_db->quote($memo['category']),
                    1,
                    time());

                /* Log the query at a DEBUG log level. */
                Horde::logMessage(sprintf('Mnemo_Driver_sql::store(): %s', $query),
                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);

                /* Attempt the insertion query. */
                $result = $this->_db->query($query);

                /* Return an error immediately if the query failed. */
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }

                /* Remove the "added" flag from this memo. */
                $this->setFlag($memo_id, MEMO_ADDED, false);
            }
        }

        /* Perform any pending modifications. */
        if (count($modified_memos) > 0) {
            foreach ($modified_memos as $memo_id => $memo) {
                $query  = sprintf('UPDATE %s SET', $this->_params['table']);
                $query .= sprintf(' memo_desc = %s,',
                                  String::convertCharset($this->_db->quote($memo['desc']), NLS::getCharset(), $this->_params['charset']));
                $query .= sprintf(' memo_body = %s,',
                                  String::convertCharset($this->_db->quote($memo['body']), NLS::getCharset(), $this->_params['charset']));
                $query .= sprintf(' memo_category = %s,', $this->_db->quote($memo['category']));
                $query .= sprintf(' memo_modified = %d', time());
                $query .= sprintf(' WHERE memo_owner = %s AND memo_id = %d',
                                  $this->_db->quote($this->_user), $memo_id);

                /* Log the query at a DEBUG log level. */
                Horde::logMessage(sprintf('Mnemo_Driver_sql::store(): %s', $query),
                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);

                /* Attempt the update query. */
                $result = $this->_db->query($query);

                /* Return an error immediately if the query failed. */
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }

                /* Remove the "modified" flag from this memo. */
                $this->setFlag($memo_id, MEMO_MODIFIED, false);
            }
        }

        /* Perform any pending deletions. */
        if (count($deleted_memos) > 0) {
            $memo_ids = array_keys($deleted_memos);
            $where = 'memo_id = ' . $memo_ids[0];
            if (count($memo_ids) > 1) {
                array_shift($memo_ids);
                $where .= ' OR memo_id = ' . implode(' OR memo_id =', $memo_ids);
            }

            $query = sprintf('DELETE FROM %s WHERE memo_owner = %s AND (%s)',
                             $this->_params['table'],
                             $this->_db->quote($this->_user),
                             $where);

            /* Log the query at a DEBUG log level. */
            Horde::logMessage(sprintf('Mnemo_Driver_sql::store(): %s', $query),
                              __FILE__, __LINE__, PEAR_LOG_DEBUG);

            /* Attempt the delete query. */
            $result = $this->_db->query($query);

            /* Return an error immediately if the query failed. */
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }

            /* Purge the deleted memos. */
            $this->purgeDeleted();
        }

        return true;
    }

    /**
     * Attempts to open a persistent connection to the SQL server.
     *
     * @return boolean    True on success; exits (Horde::fatal()) on error.
     */
    function _connect()
    {
        if (!$this->_connected) {
            if (!is_array($this->_params)) {
                Horde::fatal(PEAR::raiseError(_("No configuration information specified for SQL Memos.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['phptype'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'phptype' not specified in memos configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['hostspec'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'hostspec' not specified in memos configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['username'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'username' not specified in memos configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['password'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'password' not specified in memos configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['database'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'database' not specified in memos configuration.")), __FILE__, __LINE__);
            }
            if (!isset($this->_params['table'])) {
                Horde::fatal(PEAR::raiseError(_("Required 'table' not specified in memos configuration.")), __FILE__, __LINE__);
            }

            /* Connect to the SQL server using the supplied parameters. */
            require_once 'DB.php';
            $this->_db = &DB::connect($this->_params,
                                      array('persistent' => !empty($this->_params['persistent'])));
            if (is_a($this->_db, 'PEAR_Error')) {
                Horde::fatal($this->_db, __FILE__, __LINE__);
            }

            /* Enable the "portability" option. */
            $this->_db->setOption('optimize', 'portability');

            $this->_connected = true;
        }

        return true;
    }

    /**
     * Disconnect from the SQL server and clean up the connection.
     *
     * @return boolean     true on success, false on failure.
     */
    function _disconnect()
    {
        if ($this->_connected) {
            $this->_connected = false;
            return $this->_db->disconnect();
        }

        return true;
    }

}
