<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Database container for session data
 *
 * PEAR::MDB2 database container
 *
 * PHP version 4
 *
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   HTTP
 * @package    HTTP_Session
 * @author     Alexander Radivanovich <info@wwwlab.net>
 * @author     David Costa <gurugeek@php.net>
 * @author     Michael Metz <pear.metz@speedpartner.de>
 * @author     Stefan Neufeind <pear.neufeind@speedpartner.de>
 * @author     Torsten Roehr <torsten.roehr@gmx.de>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    CVS: $Id: MDB2.php,v 1.3 2005/09/03 14:42:09 troehr Exp $
 * @link       http://pear.php.net/package/HTTP_Session
 * @since      File available since Release 0.5.0
 */

require_once 'HTTP/Session/Container.php';
require_once 'MDB2.php';

/**
 * Database container for session data
 *
 * Create the following table to store session data
 * <code>
 * CREATE TABLE `sessiondata` (
 *     `id` CHAR(32) NOT NULL,
 *     `expiry` INT UNSIGNED NOT NULL DEFAULT 0,
 *     `data` TEXT NOT NULL,
 *     PRIMARY KEY (`id`)
 * );
 * </code>
 *
 * @category   HTTP
 * @package    HTTP_Session
 * @author     David Costa <gurugeek@php.net>
 * @author     Michael Metz <pear.metz@speedpartner.de>
 * @author     Stefan Neufeind <pear.neufeind@speedpartner.de>
 * @author     Torsten Roehr <torsten.roehr@gmx.de>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: @package_version@
 * @link       http://pear.php.net/package/HTTP_Session
 * @since      Class available since Release 0.5.0
 */
class HTTP_Session_Container_MDB2 extends HTTP_Session_Container
{

    /**
     * MDB2 connection object
     *
     * @var object MDB2
     * @access private
     */
    var $db = null;

    /**
     * Session data cache id
     *
     * @var mixed
     * @access private
     */
    var $crc = false;

    /**
     * Constructor method
     *
     * $options is an array with the options.<br>
     * The options are:
     * <ul>
     * <li>'dsn' - The DSN string</li>
     * <li>'table' - Table with session data, default is 'sessiondata'</li>
     * <li>'autooptimize' - Boolean, 'true' to optimize
     * the table on garbage collection, default is 'false'.</li>
     * </ul>
     *
     * @access public
     * @param  array  $options The options
     * @return void
     */
    function HTTP_Session_Container_MDB2($options)
    {
        $this->_setDefaults();
        if (is_array($options)) {
            $this->_parseOptions($options);
        } else {
            $this->options['dsn'] = $options;
        }
    }

    /**
     * Connect to database by using the given DSN string
     *
     * @access private
     * @param  string DSN string
     * @return mixed  Object on error, otherwise bool
     */
    function _connect($dsn)
    {
        if (is_string($dsn) || is_array($dsn)) {
            $this->db = MDB2::connect($dsn);
        } else if (is_object($dsn) && is_a($dsn, 'MDB2_Driver_Common')) {
            $this->db = $dsn;
        } else if (is_object($dsn) && MDB2::isError($dsn)) {
            return $dsn;
        } else {
            return new PEAR_Error("The given dsn was not valid in file " . __FILE__ . " at line " . __LINE__,
                                  41,
                                  PEAR_ERROR_RETURN,
                                  null,
                                  null
                                  );

        }

        if (MDB2::isError($this->db)) {
            return new MDB2_Error($this->db->code, PEAR_ERROR_DIE);
        }

        return true;
    }

    /**
     * Set some default options
     *
     * @access private
     */
    function _setDefaults()
    {
        $this->options['dsn']           = null;
        $this->options['table']         = 'sessiondata';
        $this->options['autooptimize']  = false;
    }

    /**
     * Establish connection to a database
     *
     */
    function open($save_path, $session_name)
    {
        if (MDB2::isError($this->_connect($this->options['dsn']))) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Free resources
     *
     */
    function close()
    {
        return true;
    }

    /**
     * Read session data
     *
     */
    function read($id)
    {
        $query = sprintf("SELECT data FROM %s WHERE id = %s AND expiry >= %d",
                         $this->options['table'],
                         $this->db->quote(md5($id), 'text'),
                         time()
                         );
        $result = $this->db->queryOne($query);
        if (MDB2::isError($result)) {
            $this->db->raiseError($result->code, PEAR_ERROR_DIE);
            return false;
        }
        $this->crc = strlen($result) . crc32($result);
        return $result;
    }

    /**
     * Write session data
     *
     */
    function write($id, $data)
    {
        if ((false !== $this->crc) && ($this->crc === strlen($data) . crc32($data))) {
            // $_SESSION hasn't been touched, no need to update the blob column
            $query = sprintf("UPDATE %s SET expiry = %d WHERE id = %s",
                             $this->options['table'],
                             time() + ini_get('session.gc_maxlifetime'),
                             $this->db->quote(md5($id), 'text')
                             );
        } else {
            // Check if table row already exists
            $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s",
                             $this->options['table'],
                             $this->db->quote(md5($id), 'text')
                             );
            $result = $this->db->queryOne($query);
            if (MDB2::isError($result)) {
                $this->db->raiseError($result->code, PEAR_ERROR_DIE);
                return false;
            }
            if (0 == intval($result)) {
                // Insert new row into table
                $query = sprintf("INSERT INTO %s (id, expiry, data) VALUES (%s, %d, %s)",
                                 $this->options['table'],
                                 $this->db->quote(md5($id), 'text'),
                                 time() + ini_get('session.gc_maxlifetime'),
                                 $this->db->quote($data, 'text')
                                 );
            } else {
                // Update existing row
                $query = sprintf("UPDATE %s SET expiry = %d, data = %s WHERE id = %s",
                                 $this->options['table'],
                                 time() + ini_get('session.gc_maxlifetime'),
                                 $this->db->quote($data, 'text'),
                                 $this->db->quote(md5($id), 'text')
                                 );
            }
        }
        $result = $this->db->query($query);
        if (MDB2::isError($result)) {
            $this->db->raiseError($result->code, PEAR_ERROR_DIE);
            return false;
        }

        return true;
    }

    /**
     * Destroy session data
     *
     */
    function destroy($id)
    {
        $query = sprintf("DELETE FROM %s WHERE id = %s",
                         $this->options['table'],
                         $this->db->quote(md5($id), 'text')
                         );
        $result = $this->db->query($query);
        if (MDB2::isError($result)) {
            $this->db->raiseError($result->code, PEAR_ERROR_DIE);
            return false;
        }

        return true;
    }

    /**
     * Garbage collection
     *
     */
    function gc($maxlifetime)
    {
        $query = sprintf("DELETE FROM %s WHERE expiry < %d",
                         $this->options['table'],
                         time()
                         );
        $result = $this->db->query($query);
        if (MDB2::isError($result)) {
            $this->db->raiseError($result->code, PEAR_ERROR_DIE);
            return false;
        }
        if ($this->options['autooptimize']) {
            switch($this->db->phptype) {
                case 'mysql':
                    $query = sprintf("OPTIMIZE TABLE %s", $this->options['table']);
                    break;
                case 'pgsql':
                    $query = sprintf("VACUUM %s", $this->options['table']);
                    break;
                default:
                    $query = null;
                    break;
            }
            if (isset($query)) {
                $result = $this->db->query($query);
                if (MDB2::isError($result)) {
                    $this->db->raiseError($result->code, PEAR_ERROR_DIE);
                    return false;
                }
            }
        }

        return true;
    }

}

?>