<?php

require_once HORDE_BASE . '/lib/Serialize.php';

/**
 * Moment storage implementation for PHP's PEAR database abstraction layer.
 *
 * $Horde: moment/lib/Driver/sql.php,v 1.9 2003/08/23 20:05:37 jan Exp $
 *
 * 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 meetings 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 can be created by the scripts/drivers/moment_meetings.sql
 * script.
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @version $Revision: 1.9 $
 * @since   Moment 1.0
 * @package moment
 */
class Moment_Driver_sql extends Moment_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 $organizer The user who organized these meetings.
     * @param array  $params    A hash containing connection parameters.
     */
    function Moment_Driver_sql($organizer, $params = array())
    {
        $this->_organizer = $organizer;

        /* Merge with defaults from Horde, unless overridden in
         * conf.php */
        $this->_params = $params;
        $this->_params['table'] = array_key_exists('table', $params) ?
            $params['table'] : 'moment_meetings';
    }

    /**
     * Retrieves the orgnaizer's meetings 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 meeting_organizer = %s',
                         $this->_params['table'], $this->_db->quote($this->_organizer));

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

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

        if (isset($result) && !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 $_meetings list. */
            $this->_meetings = array();
            while ($row && !is_a($row, 'PEAR_Error')) {
                /* Create a new meeting based on this row's values. */
                $meeting = array();
                $meeting['id'] = $row['meeting_id'];
                $meeting['organizer']   = $row['meeting_organizer'];
                $meeting['title']       = String::convertCharset($row['meeting_title'], $this->_params['charset']);
                $meeting['description'] = String::convertCharset($row['meeting_description'], $this->_params['charset']);
                $meeting['location']    = String::convertCharset($row['meeting_location'], $this->_params['charset']);
                $meeting['start']       = $row['meeting_start'];
                $meeting['end']         = $row['meeting_end'];
                $meeting['attendees']   = Horde_Serialize::unserialize($row['meeting_attendees'], SERIALIZE_BASIC);
                $meeting['uid']         = $row['meeting_uid'];
                $meeting['sequence']    = $row['meeting_sequence'];
                $meeting['status']      = (is_null($row['meeting_status'])) ? '' : $row['meeting_status'];
                $meeting['flags']       = 0;

                /* Add this new meeting to the $_meetings list. */
                $this->_meetings[$row['meeting_id']] = $meeting;

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

        return true;
    }

    /**
     * Stores the organizer's meetings to SQL server.
     *
     * @return mixed  True on success, PEAR_Error on failure.
     */
    function store()
    {
        /* Build lists of the meetings that require pending database operations. */
        $added_meetings    = $this->listMeetings(MEETING_ADDED);
        $modified_meetings = $this->listMeetings(MEETING_MODIFIED);
        $deleted_meetings  = $this->listMeetings(MEETING_DELETED);

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

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

        /* Perform any pending additions. */
        if (count($added_meetings) > 0) {
            foreach ($added_meetings as $meeting_id => $meeting) {
                $query = sprintf(
                    'INSERT INTO %s (meeting_organizer, meeting_id, meeting_title, ' .
                    'meeting_description, meeting_location, meeting_start, ' .
                    'meeting_end, meeting_attendees, meeting_uid, '.
                    'meeting_sequence, meeting_status) ' .
                    'VALUES (%s, %d, %s, %s, %s, %d, %d, %s, %s, %d, %s)',
                    $this->_params['table'],
                    $this->_db->quote($this->_organizer),
                    $meeting_id,
                    String::convertCharset($this->_db->quote($meeting['title']), NLS::getCharset(), $this->_params['charset']),
                    String::convertCharset($this->_db->quote($meeting['description']), NLS::getCharset(), $this->_params['charset']),
                    String::convertCharset($this->_db->quote($meeting['location']), NLS::getCharset(), $this->_params['charset']),
                    $meeting['start'],
                    $meeting['end'],
                    $this->_db->quote(Horde_Serialize::serialize($meeting['attendees'], SERIALIZE_BASIC)),
                    $this->_db->quote($meeting['uid']),
                    $meeting['sequence'],
                    $this->_db->quote($meeting['status']),
                    time());

                    /* Log the query at a DEBUG log level. */
                    Horde::logMessage(sprintf('Moment_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 meeting. */
                $this->setFlag($meeting_id, MEETING_ADDED, false);
            }
        }

        /* Perform any pending modifications. */
        if (count($modified_meetings) > 0) {
            foreach ($modified_meetings as $meeting_id => $meeting) {
                $query  = sprintf('UPDATE %s SET ', $this->_params['table']);
                $query .= sprintf('meeting_title = %s, ',
                                  String::convertCharset($this->_db->quote($meeting['title']), NLS::getCharset(), $this->_params['charset']));
                $query .= sprintf('meeting_description = %s, ',
                                  String::convertCharset($this->_db->quote($meeting['description']), NLS::getCharset(), $this->_params['charset']));
                $query .= sprintf('meeting_location = %s, ',
                                  String::convertCharset($this->_db->quote($meeting['location']), NLS::getCharset(), $this->_params['charset']));
                $query .= sprintf('meeting_start = %d, ', $meeting['start']);
                $query .= sprintf('meeting_end = %d, ', $meeting['end']);
                $query .= sprintf('meeting_attendees = %s, ', $this->_db->quote(Horde_Serialize::serialize($meeting['attendees'], SERIALIZE_BASIC)));
                $query .= sprintf('meeting_sequence = %d, ', $meeting['sequence']);
                $query .= sprintf('meeting_status = %s ', $this->_db->quote($meeting['status']));
                $query .= sprintf('WHERE meeting_organizer = %s AND meeting_id = %d',
                                  $this->_db->quote($this->_organizer), $meeting_id);

                /* Log the query at a DEBUG log level. */
                Horde::logMessage(sprintf('Moment_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 meeting. */
                $this->setFlag($meeting_id, MEETING_MODIFIED, false);
            }
        }

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

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

            /* Log the query at a DEBUG log level. */
            Horde::logMessage(sprintf('Moment_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 meetings. */
            $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) {
            Horde::assertDriverConfig($this->_params, 'meetings',
                array('phptype', 'hostspec', 'username', 'database'),
                'moment meeting SQL');

            /* Connect to the SQL server using the supplied parameters. */
            include_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;
    }

}
