Abstract storage portion of sessionhandler into a separate class
authorMichael M Slusarz <slusarz@curecanti.org>
Fri, 12 Nov 2010 20:22:14 +0000 (13:22 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Fri, 12 Nov 2010 20:57:29 +0000 (13:57 -0700)
14 files changed:
framework/Core/lib/Horde/Core/Factory/SessionHandler.php
framework/SessionHandler/lib/Horde/SessionHandler.php
framework/SessionHandler/lib/Horde/SessionHandler/Builtin.php [deleted file]
framework/SessionHandler/lib/Horde/SessionHandler/External.php [deleted file]
framework/SessionHandler/lib/Horde/SessionHandler/Memcache.php [deleted file]
framework/SessionHandler/lib/Horde/SessionHandler/Sql.php [deleted file]
framework/SessionHandler/lib/Horde/SessionHandler/Stack.php [deleted file]
framework/SessionHandler/lib/Horde/SessionHandler/Storage.php [new file with mode: 0644]
framework/SessionHandler/lib/Horde/SessionHandler/Storage/Builtin.php [new file with mode: 0644]
framework/SessionHandler/lib/Horde/SessionHandler/Storage/External.php [new file with mode: 0644]
framework/SessionHandler/lib/Horde/SessionHandler/Storage/Memcache.php [new file with mode: 0644]
framework/SessionHandler/lib/Horde/SessionHandler/Storage/Sql.php [new file with mode: 0644]
framework/SessionHandler/lib/Horde/SessionHandler/Storage/Stack.php [new file with mode: 0644]
framework/SessionHandler/package.xml

index c65d48b..927e82f 100644 (file)
@@ -35,53 +35,57 @@ class Horde_Core_Factory_SessionHandler
         }
         $params = Horde::getDriverConfig('sessionhandler', $driver);
 
-        if (strcasecmp($driver, 'Sql') === 0) {
-            $params['db'] = $injector->getInstance('Horde_Db_Adapter');
-        } elseif (strcasecmp($driver, 'Memcache') === 0) {
-            $params['memcache'] = $injector->getInstance('Horde_Memcache');
-        } elseif (strcasecmp($driver, 'Ldap') === 0) {
+        $driver = basename(Horde_String::lower($driver));
+        $noset = false;
+
+        switch ($driver) {
+        case 'builtin':
+            $noset = true;
+            break;
+
+        case 'ldap':
             $params['ldap'] = $injector->getInstances('Horde_Core_Factory_Ldap')->getLdap('horde', 'sessionhandler');
+            break;
+
+        case 'memcache':
+            $params['memcache'] = $injector->getInstance('Horde_Memcache');
+            break;
+
+        case 'sql':
+            $params['db'] = $injector->getInstance('Horde_Db_Adapter');
+            break;
         }
 
-        $logger = $injector->getInstance('Horde_Log_Logger');
+        $class = 'Horde_SessionHandler_Storage_' . Horde_String::ucfirst($driver);
+        if (!class_exists($class)) {
+            throw new Horde_SessionHandler_Exception('Driver not found: ' . $class);
+        }
+        $storage = new $class($params);
 
         if (!empty($conf['sessionhandler']['memcache']) &&
-            (strcasecmp($driver, 'Builtin') != 0) &&
-            (strcasecmp($driver, 'Memcache') != 0)) {
-            $params = array(
+            !in_array($driver, array('builtin', 'memcache'))) {
+            $storage = new Horde_SessionHandler_Storage_Stack(array(
                 'stack' => array(
-                    array(
-                        'driver' => 'Memcache',
-                        'params' => array(
-                            'memcache' => $injector->getInstance('Horde_Memcache'),
-                            'logger' => $logger
-                        )
-                    ),
-                    array(
-                        'driver' => $driver,
-                        'params' => array_merge($params, array(
-                            'logger' => $logger
-                        ))
-                    )
+                    new Horde_SessionHandler_Storage_Memcache(array(
+                        'memcache' => $injector->getInstance('Horde_Memcache')
+                    )),
+                    $storage
                 )
-            );
-            $driver = 'Stack';
+            ));
         }
 
-        $params['logger'] = $logger;
-        // TODO: Uncomment once all session data is saved through
-        //  Horde_Session.
-        //$params['no_md5'] = true
-        $params['parse'] = array($this, 'readSessionData');
-
-        $driver = basename(strtolower($driver));
-        $class = 'Horde_SessionHandler_' . ucfirst($driver);
-
-        if (class_exists($class)) {
-            return new $class($params);
-        }
 
-        throw new Horde_SessionHandler_Exception('Driver not found: ' . $driver);
+        return new Horde_SessionHandler(
+            $storage,
+            array(
+                'logger' => $injector->getInstance('Horde_Log_Logger'),
+                // TODO: Uncomment once all session data is saved through
+                //  Horde_Session.
+                //'no_md5' => true,
+                'noset' => $noset,
+                'parse' => array($this, 'readSessionData')
+            )
+        );
     }
 
     /**
index a943715..696e231 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * This is the abstract class that all drivers inherit from.
+ * This class provides the interface to the session storage backend.
  *
  * Copyright 2002-2010 The Horde Project (http://www.horde.org/)
  *
@@ -12,7 +12,7 @@
  * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
  * @package  SessionHandler
  */
-abstract class Horde_SessionHandler
+class Horde_SessionHandler
 {
     /**
      * If true, indicates the session data has changed.
@@ -36,7 +36,7 @@ abstract class Horde_SessionHandler
     protected $_logger;
 
     /**
-     * Hash containing connection parameters.
+     * Configuration parameters.
      *
      * @var array
      */
@@ -50,9 +50,17 @@ abstract class Horde_SessionHandler
     protected $_sig;
 
     /**
+     * The storage object.
+     *
+     * @var Horde_SessionHandler_Storage
+     */
+    protected $_storage;
+
+    /**
      * Constructor.
      *
-     * @param array $params  Parameters:
+     * @param Horde_SessionHandler_Storage $storage  The storage object.
+     * @param array $params                          Configuration parameters:
      * <pre>
      * logger - (Horde_Log_Logger) A logger instance.
      *          DEFAULT: No logging
@@ -70,16 +78,20 @@ abstract class Horde_SessionHandler
      *         DEFAULT: No
      * </pre>
      */
-    public function __construct(array $params = array())
+    public function __construct(Horde_SessionHandler_Storage $storage,
+                                array $params = array())
     {
         $params = array_merge($this->_params, $params);
 
         if (isset($params['logger'])) {
             $this->_logger = $params['logger'];
             unset($params['logger']);
+
+            $storage->setLogger($this->_logger);
         }
 
         $this->_params = $params;
+        $this->_storage = $storage;
 
         if (empty($this->_params['noset'])) {
             ini_set('session.save_handler', 'user');
@@ -117,7 +129,7 @@ abstract class Horde_SessionHandler
     {
         if (!$this->_connected) {
             try {
-                $this->_open($save_path, $session_name);
+                $this->_storage->open($save_path, $session_name);
             } catch (Horde_SessionHandler_Exception $e) {
                 if ($this->_logger) {
                     $this->_logger->log($e, 'ERR');
@@ -132,16 +144,6 @@ abstract class Horde_SessionHandler
     }
 
     /**
-     * Open the backend.
-     *
-     * @param string $save_path     The path to the session object.
-     * @param string $session_name  The name of the session.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    abstract protected function _open($save_path = null, $session_name = null);
-
-    /**
      * Close the backend.
      *
      * @return boolean  True on success, false otherwise.
@@ -149,7 +151,7 @@ abstract class Horde_SessionHandler
     public function close()
     {
         try {
-            $this->_close();
+            $this->_storage->close();
         } catch (Horde_SessionHandler_Exception $e) {
             if ($this->_logger) {
                 $this->_logger->log($e, 'ERR');
@@ -162,13 +164,6 @@ abstract class Horde_SessionHandler
     }
 
     /**
-     * Close the backend.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    abstract protected function _close();
-
-    /**
      * Read the data for a particular session identifier from the backend.
      * This method should only be called internally by PHP via
      * session_set_save_handler().
@@ -179,7 +174,7 @@ abstract class Horde_SessionHandler
      */
     public function read($id)
     {
-        $result = $this->_read($id);
+        $result = $this->_storage->read($id);
         if (empty($this->_params['no_md5'])) {
             $this->_sig = md5($result);
         }
@@ -187,15 +182,6 @@ abstract class Horde_SessionHandler
     }
 
     /**
-     * Read the data for a particular session identifier from the backend.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    abstract protected function _read($id);
-
-    /**
      * Write session data to the backend.
      * This method should only be called internally by PHP via
      * session_set_save_handler().
@@ -210,7 +196,7 @@ abstract class Horde_SessionHandler
         if ($this->changed ||
             (empty($this->_params['no_md5']) &&
              ($this->_sig != md5($session_data)))) {
-            return $this->_write($id, $session_data);
+            return $this->_storage->write($id, $session_data);
         }
 
         if ($this->_logger) {
@@ -221,16 +207,6 @@ abstract class Horde_SessionHandler
     }
 
     /**
-     * Write session data to the backend.
-     *
-     * @param string $id            The session identifier.
-     * @param string $session_data  The session data.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    abstract protected function _write($id, $session_data);
-
-    /**
      * Destroy the data for a particular session identifier in the backend.
      * This method should only be called internally by PHP via
      * session_set_save_handler().
@@ -239,7 +215,10 @@ abstract class Horde_SessionHandler
      *
      * @return boolean  True on success, false otherwise.
      */
-    abstract public function destroy($id);
+    public function destroy($id)
+    {
+        return $this->_storage->destroy($id);
+    }
 
     /**
      * Garbage collect stale sessions from the backend.
@@ -250,18 +229,9 @@ abstract class Horde_SessionHandler
      *
      * @return boolean  True on success, false otherwise.
      */
-    abstract public function gc($maxlifetime = 300);
-
-    /**
-     * Get session data read-only.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _readOnly($id)
+    public function gc($maxlifetime = 300)
     {
-        return $this->read($id);
+        return $this->_storage->gc($id);
     }
 
     /**
@@ -270,7 +240,10 @@ abstract class Horde_SessionHandler
      * @return array  A list of valid session identifiers.
      * @throws Horde_SessionHandler_Exception
      */
-    abstract public function getSessionIDs();
+    public function getSessionIDs()
+    {
+        return $this->_storage->getSessionIDs();
+    }
 
     /**
      * Returns a list of authenticated users and data about their session.
@@ -291,9 +264,11 @@ abstract class Horde_SessionHandler
 
         $sessions = $this->getSessionIDs();
 
+        $this->_storage->readonly = true;
+
         foreach ($sessions as $id) {
             try {
-                $data = $this->_readOnly($id);
+                $data = $this->read($id);
             } catch (Horde_SessionHandler_Exception $e) {
                 continue;
             }
@@ -304,6 +279,8 @@ abstract class Horde_SessionHandler
             }
         }
 
+        $this->_storage->readonly = false;
+
         return $info;
     }
 
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Builtin.php b/framework/SessionHandler/lib/Horde/SessionHandler/Builtin.php
deleted file mode 100644 (file)
index 72a77d0..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-/**
- * Horde_SessionHandler implementation for PHP's built-in session handler.
- * This doesn't do any session handling itself - instead, it exists to allow
- * utility features to be used with the built-in PHP handler.
- *
- * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
- *
- * 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   Matt Selsky <selsky@columbia.edu>
- * @category Horde
- * @package  SessionHandler
- */
-class Horde_SessionHandler_Builtin extends Horde_SessionHandler
-{
-    /**
-     * Directory with session files.
-     *
-     * @var string
-     */
-    protected $_path;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  Parameters.
-     */
-    public function __construct(array $params = array())
-    {
-        parent::__construct(array_merge($params, array(
-            'noset' => true
-        )));
-
-        $this->_path = session_save_path();
-        if (!$this->_path) {
-            $this->_path = Horde_Util::getTempDir();
-        }
-    }
-
-    /**
-     * Open the backend.
-     *
-     * @param string $save_path     The path to the session object.
-     * @param string $session_name  The name of the session.
-     */
-     protected function _open($save_path = null, $session_name = null)
-     {
-     }
-
-     /**
-      * Close the backend.
-      *
-      * @throws Horde_Exception
-      */
-    protected function _close()
-    {
-    }
-
-    /**
-     * Read the data for a particular session identifier from the backend.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _read($id)
-    {
-        $file = $this->_path . '/sess_' . $id;
-        $session_data = @file_get_contents($file);
-        if (($session_data === false) && $this->_logger) {
-            $this->_logger->log('Unable to read file: ' . $file, 'ERR');
-        }
-
-        return strval($session_data);
-    }
-
-    /**
-     * Write session data to the backend.
-     *
-     * @param string $id            The session identifier.
-     * @param string $session_data  The session data.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    protected function _write($id, $session_data)
-    {
-        return false;
-    }
-
-    /**
-     * Destroy the data for a particular session identifier in the backend.
-     * This method should only be called internally by PHP via
-     * session_set_save_handler().
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function destroy($id)
-    {
-        return false;
-    }
-
-    /**
-     * Garbage collect stale sessions from the backend.
-     * This method should only be called internally by PHP via
-     * session_set_save_handler().
-     *
-     * @param integer $maxlifetime  The maximum age of a session.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function gc($maxlifetime = 300)
-    {
-        return false;
-    }
-
-    /**
-     * Get a list of the valid session identifiers.
-     *
-     * @return array  A list of valid session identifiers.
-     */
-    public function getSessionIDs()
-    {
-        $sessions = array();
-
-        try {
-            $di = new DirectoryIterator($this->_path);
-        } catch (UnexpectedValueException $e) {
-            return $sessions;
-        }
-
-        foreach ($di as $val) {
-            /* Make sure we're dealing with files that start with sess_. */
-            if ($val->isFile() &&
-                (strpos($val->getFilename(), 'sess_') === 0)) {
-                $sessions[] = substr($val->getFilename(), strlen('sess_'));
-            }
-        }
-
-        return $sessions;
-    }
-
-}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/External.php b/framework/SessionHandler/lib/Horde/SessionHandler/External.php
deleted file mode 100644 (file)
index 414e310..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * Horde_SessionHandler_External implements an external save handler defined
- * via driver configuration parameters.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * 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   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @package  SessionHandler
- */
-class Horde_SessionHandler_External extends Horde_SessionHandler
-{
-    /**
-     * Constructor.
-     *
-     * @param array $params  Required parameters:
-     * <pre>
-     * 'close' - (callback) See session_set_save_handler().
-     * 'destroy' - (callback) See session_set_save_handler().
-     * 'gc' - (callback) See session_set_save_handler().
-     * 'open' - (callback) See session_set_save_handler().
-     * 'read' - (callback) See session_set_save_handler().
-     * 'write' - (callback) See session_set_save_handler().
-     * </pre>
-     *
-     * @throws InvalidArgumentException
-     */
-    public function __construct(array $params = array())
-    {
-        foreach (array('open', 'close', 'read', 'write', 'destroy', 'gc') as $val) {
-            if (!isset($params[$val])) {
-                throw new InvalidArgumentException('Missing parameter: ' . $val);
-            }
-        }
-
-        parent::__construct($params);
-    }
-
-    /**
-     * Open the backend.
-     *
-     * @param string $save_path     The path to the session object.
-     * @param string $session_name  The name of the session.
-     */
-    protected function _open($save_path = null, $session_name = null)
-    {
-        call_user_func($this->_params['open'], $save_path, $session_name);
-    }
-
-    /**
-     * Close the backend.
-     */
-    protected function _close()
-    {
-        call_user_func($this->_params['close']);
-    }
-
-    /**
-     * Read the data for a particular session identifier from the backend.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _read($id)
-    {
-        return call_user_func($this->_params['read']);
-    }
-
-    /**
-     * Write session data to the backend.
-     *
-     * @param string $id            The session identifier.
-     * @param string $session_data  The session data.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    protected function _write($id, $session_data)
-    {
-        return call_user_func($this->_params['write'], $id, $session_data);
-    }
-
-    /**
-     * Destroy the data for a particular session identifier in the backend.
-     * This method should only be called internally by PHP via
-     * session_set_save_handler().
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function destroy($id)
-    {
-        return call_user_func($this->_params['destroy'], $id);
-    }
-
-    /**
-     * Garbage collect stale sessions from the backend.
-     * This method should only be called internally by PHP via
-     * session_set_save_handler().
-     *
-     * @param integer $maxlifetime  The maximum age of a session.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function gc($maxlifetime = 300)
-    {
-        return call_user_func($this->_params['gc'], $maxlifetime);
-    }
-
-    /**
-     * Get a list of the valid session identifiers.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    public function getSessionIDs()
-    {
-        throw new Horde_SessionHandler_Exception('Driver does not support listing session IDs.');
-    }
-
-}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Memcache.php b/framework/SessionHandler/lib/Horde/SessionHandler/Memcache.php
deleted file mode 100644 (file)
index 34344b7..0000000
+++ /dev/null
@@ -1,300 +0,0 @@
-<?php
-/**
- * Horde_SessionHandler implementation for memcache.
- *
- * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
- *
- * 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   Rong-En Fan <rafan@infor.org>
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @package  SessionHandler
- */
-class Horde_SessionHandler_Memcache extends Horde_SessionHandler
-{
-    /**
-     * Memcache object.
-     *
-     * @var Horde_Memcache
-     */
-    protected $_memcache;
-
-    /**
-     * Current session ID.
-     *
-     * @var string
-     */
-    protected $_id;
-
-    /**
-     * Do read-only get?
-     *
-     * @var boolean
-     */
-    protected $_readonly = false;
-
-    /**
-     * The ID used for session tracking.
-     *
-     * @var string
-     */
-    protected $_trackID = 'horde_memcache_sessions_track';
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  Parameters:
-     * <pre>
-     * 'memcache' - (Horde_Memcache) [REQUIRED] A memcache object.
-     * 'track' - (boolean) Track active sessions?
-     * 'track_lifetime' - (integer) The number of seconds after which tracked
-     *                    sessions will be treated as expired.
-     * </pre>
-     *
-     * @throws InvalidArgumentException
-     */
-    public function __construct(array $params = array())
-    {
-        if (empty($params['memcache'])) {
-            throw new InvalidArgumentException('Missing memcache argument.');
-        }
-
-        $this->_memcache = $params['memcache'];
-        unset($params['memcache']);
-
-        parent::__construct($params);
-
-        if (empty($this->_params['track_lt'])) {
-            $this->_params['track_lt'] = ini_get('session.gc_maxlifetime');
-        }
-
-        if (!empty($this->_params['track']) && (rand(0, 999) == 0)) {
-            register_shutdown_function(array($this, 'trackGC'));
-        }
-    }
-
-    /**
-     * Open the backend.
-     *
-     * @param string $save_path     The path to the session object.
-     * @param string $session_name  The name of the session.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    protected function _open($save_path = null, $session_name = null)
-    {
-    }
-
-    /**
-     * Close the backend.
-     */
-    protected function _close()
-    {
-        if (isset($this->_id)) {
-            $this->_memcache->unlock($this->_id);
-        }
-    }
-
-    /**
-     * Read the data for a particular session identifier.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _read($id)
-    {
-        if (!$this->_readonly) {
-            $this->_memcache->lock($id);
-        }
-        $result = $this->_memcache->get($id);
-
-        if ($result === false) {
-            if (!$this->_readonly) {
-                $this->_memcache->unlock($id);
-            }
-
-            if ($result === false) {
-                if ($this->_logger) {
-                    $this->_logger->log('Error retrieving session data (id = ' . $id . ')', 'DEBUG');
-                }
-                return false;
-            }
-        }
-
-        if (!$this->_readonly) {
-            $this->_id = $id;
-        }
-
-        if ($this->_logger) {
-            $this->_logger->log('Read session data (id = ' . $id . ')', 'DEBUG');
-        }
-
-        return $result;
-    }
-
-    /**
-     * Write session data to the backend.
-     *
-     * @param string $id            The session identifier.
-     * @param string $session_data  The session data.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    protected function _write($id, $session_data)
-    {
-        if (!empty($this->_params['track'])) {
-            // Do a replace - the only time it should fail is if we are
-            // writing a session for the first time.  If that is the case,
-            // update the session tracker.
-            $res = $this->_memcache->replace($id, $session_data);
-            $track = !$res;
-        } else {
-            $res = $track = false;
-        }
-
-        if (!$res &&
-            !$this->_memcache->set($id, $session_data)) {
-            if ($this->_logger) {
-                $this->_logger->log('Error writing session data (id = ' . $id . ')', 'ERR');
-            }
-            return false;
-        }
-
-        if ($track) {
-            $this->_memcache->lock($this->_trackID);
-            $ids = $this->_memcache->get($this->_trackID);
-            if ($ids === false) {
-                $ids = array();
-            }
-
-            $ids[$id] = time();
-            $this->_memcache->set($this->_trackID, $ids);
-            $this->_memcache->unlock($this->_trackID);
-        }
-
-        if ($this->_logger) {
-            $this->_logger->log('Wrote session data (id = ' . $id . ')', 'DEBUG');
-        }
-
-        return true;
-    }
-
-    /**
-     * Destroy the data for a particular session identifier.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function destroy($id)
-    {
-        $result = $this->_memcache->delete($id);
-        $this->_memcache->unlock($id);
-
-        if ($result === false) {
-            if ($this->_logger) {
-                $this->_logger->log('Failed to delete session (id = ' . $id . ')', 'DEBUG');
-            }
-            return false;
-        }
-
-        if (!empty($this->_params['track'])) {
-            $this->_memcache->lock($this->_trackID);
-            $ids = $this->_memcache->get($this->_trackID);
-            if ($ids !== false) {
-                unset($ids[$id]);
-                $this->_memcache->set($this->_trackID, $ids);
-            }
-            $this->_memcache->unlock($this->_trackID);
-        }
-
-        if ($this->_logger) {
-            $this->_logger->log('Deleted session data (id = ' . $id . ')', 'DEBUG');
-        }
-
-        return true;
-    }
-
-    /**
-     * Garbage collect stale sessions from the backend.
-     *
-     * @param integer $maxlifetime  The maximum age of a session.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function gc($maxlifetime = 300)
-    {
-        // Memcache does its own garbage collection.
-        return true;
-    }
-
-    /**
-     * Get a list of (possibly) valid session identifiers.
-     *
-     * @return array  A list of session identifiers.
-     * @throws Horde_SessionHandler_Exception
-     */
-    public function getSessionIDs()
-    {
-        if (empty($this->_params['track'])) {
-            throw new Horde_SessionHandler_Exception('Memcache session tracking not enabled.');
-        }
-
-        $this->trackGC();
-
-        $ids = $this->_memcache->get($this->_trackID);
-
-        return ($ids === false)
-            ? array()
-            : array_keys($ids);
-    }
-
-    /**
-     * Get session data read-only.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _readOnly($id)
-    {
-        $this->_readonly = true;
-        $result = $this->_memcache->get($id);
-        $this->_readonly = false;
-
-        return $result;
-    }
-
-    /**
-     * Do garbage collection for session tracking information.
-     */
-    public function trackGC()
-    {
-        $this->_memcache->lock($this->_trackID);
-        $ids = $this->_memcache->get($this->_trackID);
-        if (empty($ids)) {
-            $this->_memcache->unlock($this->_trackID);
-            return;
-        }
-
-        $tstamp = time() - $this->_params['track_lt'];
-        $alter = false;
-
-        foreach ($ids as $key => $val) {
-            if ($tstamp > $val) {
-                unset($ids[$key]);
-                $alter = true;
-            }
-        }
-
-        if ($alter) {
-            $this->_memcache->set($this->_trackID, $ids);
-        }
-
-        $this->_memcache->unlock($this->_trackID);
-    }
-
-}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Sql.php b/framework/SessionHandler/lib/Horde/SessionHandler/Sql.php
deleted file mode 100644 (file)
index bf8839b..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-<?php
-/**
- * Horde_SessionHandler implementation for SQL databases.
- *
- * The table structure can be found in:
- *   horde/scripts/sql/horde_sessionhandler.sql.
- *
- * Copyright 2002-2010 The Horde Project (http://www.horde.org/)
- *
- * 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   Mike Cochrane <mike@graftonhall.co.nz>
- * @category Horde
- * @package  SessionHandler
- */
-class Horde_SessionHandler_Sql extends Horde_SessionHandler
-{
-    /**
-     * Handle for the current database connection.
-     *
-     * @var Horde_Db_Adapter
-     */
-    protected $_db;
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  Parameters:
-     * <pre>
-     * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
-     * 'table' - (string) The name of the sessions table.
-     *           DEFAULT: 'horde_sessionhandler'
-     * </pre>
-     *
-     * @throws InvalidArgumentException
-     */
-    public function __construct(array $params = array())
-    {
-        if (!isset($params['db'])) {
-            throw new InvalidArgumentException('Missing db parameter.');
-        }
-        $this->_db = $params['db'];
-        unset($params['db']);
-
-        parent::__construct(array_merge(array(
-            'table' => 'horde_sessionhandler'
-        ), $params));
-    }
-
-    /**
-     * Close the backend.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    protected function _close()
-    {
-        /* Close any open transactions. */
-        try {
-            $this->_db->commitDbTransaction();
-        } catch (Horde_Db_Exception $e) {
-            throw new Horde_SessionHandler_Exception($e);
-        }
-    }
-
-    /**
-     * Read the data for a particular session identifier from the backend.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _read($id)
-    {
-        /* Begin a transaction. */
-        // TODO: Rowlocking in Mysql
-        $this->_db->beginDbTransaction();
-
-        /* Build query. */
-        $query = sprintf('SELECT session_data FROM %s WHERE session_id = ?',
-                         $this->_params['table']);
-        $values = array($id);
-
-        /* Execute the query. */
-        try {
-            return $this->_db->selectValue($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            return false;
-        }
-    }
-
-    /**
-     * Write session data to the backend.
-     *
-     * @param string $id            The session identifier.
-     * @param string $session_data  The session data.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    protected function _write($id, $session_data)
-    {
-        /* Build the SQL query. */
-        $query = sprintf('SELECT session_id FROM %s WHERE session_id = ?',
-                         $this->_params['table']);
-        $values = array($id);
-
-        /* Execute the query. */
-        try {
-            $result = $this->_db->selectValue($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            return false;
-        }
-
-        /* Build the replace SQL query. */
-        $query = sprintf('REPLACE INTO %s ' .
-                         '(session_id, session_data, session_lastmodified) ' .
-                         'VALUES (?, ?, ?)',
-                         $this->_params['table']);
-        $values = array(
-            $id,
-            $session_data,
-            time()
-        );
-
-        /* Execute the replace query. */
-        try {
-            $this->_db->update($query, $values);
-            $this->_db->commitDbTransaction();
-        } catch (Horde_Db_Exception $e) {
-            $this->_db->rollbackDbTransaction();
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Destroy the data for a particular session identifier in the backend.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function destroy($id)
-    {
-        /* Build the SQL query. */
-        $query = sprintf('DELETE FROM %s WHERE session_id = ?',
-                         $this->_params['table']);
-        $values = array($id);
-
-        /* Execute the query. */
-        try {
-            $this->_db->delete($query, $values);
-            $this->_db->commitDbTransaction();
-        } catch (Horde_Db_Exception $e) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Garbage collect stale sessions from the backend.
-     *
-     * @param integer $maxlifetime  The maximum age of a session.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function gc($maxlifetime = 300)
-    {
-        /* Build the SQL query. */
-        $query = sprintf('DELETE FROM %s WHERE session_lastmodified < ?',
-                         $this->_params['table']);
-        $values = array(time() - $maxlifetime);
-
-        /* Execute the query. */
-        try {
-            $this->_db->delete($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Get a list of the valid session identifiers.
-     *
-     * @return array  A list of valid session identifiers.
-     */
-    public function getSessionIDs()
-    {
-        $this->open();
-
-        /* Build the SQL query. */
-        $query = sprintf('SELECT session_id FROM %s' .
-                         ' WHERE session_lastmodified >= ?',
-                         $this->_params['table']);
-        $values = array(time() - ini_get('session.gc_maxlifetime'));
-
-        /* Execute the query. */
-        try {
-            return $this->_db->selectValues($query, $values);
-        } catch (Horde_Db_Exception $e) {
-            return array();
-        }
-    }
-
-}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Stack.php b/framework/SessionHandler/lib/Horde/SessionHandler/Stack.php
deleted file mode 100644 (file)
index 0050257..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-<?php
-/**
- * Horde_SessionHandler_Stack is an implementation that will loop through a
- * given list of Horde_SessionHandler drivers to return the session
- * information.  This driver allows for use of caching backends on top of
- * persistent backends.
- *
- * Copyright 2010 The Horde Project (http://www.horde.org/)
- *
- * 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   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @package  SessionHandler
- */
-class Horde_SessionHandler_Stack extends Horde_SessionHandler
-{
-    /**
-     * Stack of sessionhandlers.
-     *
-     * @var string
-     */
-    protected $_stack = array();
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  Parameters:
-     * <pre>
-     * 'stack' - (array) [REQUIRED] A list of sessionhandlers to loop
-     *           through, in order of priority. The last entry is considered
-     *           the "master" server.
-     *           Each value should contain an array with two keys: 'driver', a
-     *           string value with the SessionHandler driver to use, and
-     *           'params', containing any parameters needed by this driver.
-     * </pre>
-     *
-     * @throws InvalidArgumentException
-     */
-    public function __construct(array $params = array())
-    {
-        if (!isset($params['stack'])) {
-            throw new InvalidArgumentException('Missing stack parameter.');
-        }
-
-        foreach ($params['stack'] as $val) {
-            $this->_stack[] = Horde_SessionHandler::factory($val['driver'], $val['params']);
-        }
-
-        unset($params['stack']);
-
-        parent::__construct($params);
-    }
-
-    /**
-     * Open the backend.
-     *
-     * @param string $save_path     The path to the session object.
-     * @param string $session_name  The name of the session.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    protected function _open($save_path = null, $session_name = null)
-    {
-        foreach ($this->_stack as $val) {
-            $val->open($save_path, $session_name);
-        }
-    }
-
-    /**
-     * Close the backend.
-     *
-     * @throws Horde_SessionHandler_Exception
-     */
-    protected function _close()
-    {
-        foreach ($this->_stack as $val) {
-            $val->close();
-        }
-    }
-
-    /**
-     * Read the data for a particular session identifier from the backend.
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return string  The session data.
-     */
-    protected function _read($id)
-    {
-        foreach ($this->_stack as $val) {
-            $result = $val->read($id);
-            if ($result === false) {
-                break;
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Write session data to the backend.
-     *
-     * @param string $id            The session identifier.
-     * @param string $session_data  The session data.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    protected function _write($id, $session_data)
-    {
-        /* Do writes in *reverse* order - it is OK if a write to one of the
-         * non-master backends fails. */
-        $master = true;
-
-        foreach (array_reverse($this->_stack) as $val) {
-            $result = $val->write($id, $session_data);
-            if ($result === false) {
-                if ($master) {
-                    return false;
-                }
-                /* Attempt to invalidate cache if write failed. */
-                $val->destroy($id);
-            }
-            $master = false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Destroy the data for a particular session identifier in the backend.
-     * This method should only be called internally by PHP via
-     * session_set_save_handler().
-     *
-     * @param string $id  The session identifier.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function destroy($id)
-    {
-        /* Only report success from master. */
-        $master = $success = true;
-
-        foreach (array_reverse($this->_stack) as $val) {
-            $result = $val->destroy($id);
-            if ($master && ($result === false)) {
-                $success = false;
-            }
-            $master = false;
-        }
-
-        return $success;
-    }
-
-    /**
-     * Garbage collect stale sessions from the backend.
-     * This method should only be called internally by PHP via
-     * session_set_save_handler().
-     *
-     * @param integer $maxlifetime  The maximum age of a session.
-     *
-     * @return boolean  True on success, false otherwise.
-     */
-    public function gc($maxlifetime = 300)
-    {
-        /* Only report GC results from master. */
-        foreach ($this->_stack as $val) {
-            $result = $val->gc($maxlifetime);
-        }
-
-        return $result;
-    }
-
-    /**
-     * Get a list of the valid session identifiers.
-     *
-     * @return array  A list of valid session identifiers.
-     * @throws Horde_SessionHandler_Exception
-     */
-    public function getSessionIDs()
-    {
-        /* Grab session ID list from the master. */
-        $ob = end($this->_stack);
-        return $ob->getSessionIDs();
-    }
-
-}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage.php
new file mode 100644 (file)
index 0000000..78fe885
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+/**
+ * This is the abstract class that all storage drivers inherit from.
+ *
+ * Copyright 2002-2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  SessionHandler
+ */
+abstract class Horde_SessionHandler_Storage
+{
+    /**
+     * Access session read-only?
+     *
+     * @var boolean
+     */
+    public $readonly = false;
+
+    /**
+     * A logger instance.
+     *
+     * @var Horde_Log_Logger
+     */
+    protected $_logger;
+
+    /**
+     * Hash containing connection parameters.
+     *
+     * @var array
+     */
+    protected $_params = array();
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Configuration parameters.
+     */
+    public function __construct(array $params = array())
+    {
+        $params = array_merge($this->_params, $params);
+    }
+
+    /**
+     * Set the logger object.
+     *
+     * @param Horde_Log_Logger $log  The logger instance.
+     */
+    public function setLogger(Horde_Log_Logger $log)
+    {
+        $this->_logger = $log;
+    }
+
+    /**
+     * Open the backend.
+     *
+     * @param string $save_path     The path to the session object.
+     * @param string $session_name  The name of the session.
+     *
+     * @throws Horde_SessionHandler_Exception
+     */
+    abstract public function open($save_path = null, $session_name = null);
+
+    /**
+     * Close the backend.
+     *
+     * @throws Horde_SessionHandler_Exception
+     */
+    abstract public function close();
+
+    /**
+     * Read the data for a particular session identifier from the backend.
+     *
+     * @param string $id  The session identifier.
+     *
+     * @return string  The session data.
+     */
+    abstract public function read($id);
+
+    /**
+     * Write session data to the backend.
+     *
+     * @param string $id            The session identifier.
+     * @param string $session_data  The session data.
+     *
+     * @return boolean  True on success, false otherwise.
+     */
+    abstract public function write($id, $session_data);
+
+    /**
+     * Destroy the data for a particular session identifier in the backend.
+     * This method should only be called internally by PHP via
+     * session_set_save_handler().
+     *
+     * @param string $id  The session identifier.
+     *
+     * @return boolean  True on success, false otherwise.
+     */
+    abstract public function destroy($id);
+
+    /**
+     * Garbage collect stale sessions from the backend.
+     * This method should only be called internally by PHP via
+     * session_set_save_handler().
+     *
+     * @param integer $maxlifetime  The maximum age of a session.
+     *
+     * @return boolean  True on success, false otherwise.
+     */
+    abstract public function gc($maxlifetime = 300);
+
+    /**
+     * Get a list of the valid session identifiers.
+     *
+     * @return array  A list of valid session identifiers.
+     * @throws Horde_SessionHandler_Exception
+     */
+    abstract public function getSessionIDs();
+
+}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Builtin.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Builtin.php
new file mode 100644 (file)
index 0000000..5bf7477
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * SessionHandler storage implementation for PHP's built-in session handler.
+ * This doesn't do any session handling itself - instead, it exists to allow
+ * utility features to be used with the built-in PHP handler.
+ *
+ * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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   Matt Selsky <selsky@columbia.edu>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  SessionHandler
+ */
+class Horde_SessionHandler_Storage_Builtin extends Horde_SessionHandler_Storage
+{
+    /**
+     * Directory with session files.
+     *
+     * @var string
+     */
+    protected $_path;
+
+    /**
+     */
+    public function __construct(array $params = array())
+    {
+        parent::__construct($params);
+
+        $this->_path = session_save_path();
+        if (!$this->_path) {
+            $this->_path = Horde_Util::getTempDir();
+        }
+    }
+
+    /**
+     */
+    public function open($save_path = null, $session_name = null)
+    {
+    }
+
+     /**
+      */
+    public function close()
+    {
+    }
+
+    /**
+     */
+    public function read($id)
+    {
+        $file = $this->_path . '/sess_' . $id;
+        $session_data = @file_get_contents($file);
+        if (($session_data === false) && $this->_logger) {
+            $this->_logger->log('Unable to read file: ' . $file, 'ERR');
+        }
+
+        return strval($session_data);
+    }
+
+    /**
+     */
+    public function write($id, $session_data)
+    {
+        return false;
+    }
+
+    /**
+     */
+    public function destroy($id)
+    {
+        return false;
+    }
+
+    /**
+     */
+    public function gc($maxlifetime = 300)
+    {
+        return false;
+    }
+
+    /**
+     */
+    public function getSessionIDs()
+    {
+        $sessions = array();
+
+        try {
+            $di = new DirectoryIterator($this->_path);
+        } catch (UnexpectedValueException $e) {
+            return $sessions;
+        }
+
+        foreach ($di as $val) {
+            /* Make sure we're dealing with files that start with sess_. */
+            if ($val->isFile() &&
+                (strpos($val->getFilename(), 'sess_') === 0)) {
+                $sessions[] = substr($val->getFilename(), strlen('sess_'));
+            }
+        }
+
+        return $sessions;
+    }
+
+}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/External.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/External.php
new file mode 100644 (file)
index 0000000..7731921
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * SessionHandler storage implementation for an external save handler defined
+ * via configuration parameters.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  SessionHandler
+ */
+class Horde_SessionHandler_Storage_External extends Horde_SessionHandler_Storage
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  Required parameters:
+     * <pre>
+     * close - (callback) See session_set_save_handler().
+     * destroy - (callback) See session_set_save_handler().
+     * gc - (callback) See session_set_save_handler().
+     * open - (callback) See session_set_save_handler().
+     * read - (callback) See session_set_save_handler().
+     * write - (callback) See session_set_save_handler().
+     * </pre>
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $params = array())
+    {
+        foreach (array('open', 'close', 'read', 'write', 'destroy', 'gc') as $val) {
+            if (!isset($params[$val])) {
+                throw new InvalidArgumentException('Missing parameter: ' . $val);
+            }
+        }
+
+        parent::__construct($params);
+    }
+
+    /**
+     */
+    public function open($save_path = null, $session_name = null)
+    {
+        call_user_func($this->_params['open'], $save_path, $session_name);
+    }
+
+    /**
+     */
+    public function close()
+    {
+        call_user_func($this->_params['close']);
+    }
+
+    /**
+     */
+    public function read($id)
+    {
+        return call_user_func($this->_params['read']);
+    }
+
+    /**
+     */
+    public function write($id, $session_data)
+    {
+        return call_user_func($this->_params['write'], $id, $session_data);
+    }
+
+    /**
+     */
+    public function destroy($id)
+    {
+        return call_user_func($this->_params['destroy'], $id);
+    }
+
+    /**
+     */
+    public function gc($maxlifetime = 300)
+    {
+        return call_user_func($this->_params['gc'], $maxlifetime);
+    }
+
+    /**
+     * Get a list of the valid session identifiers.
+     *
+     * @throws Horde_SessionHandler_Exception
+     */
+    public function getSessionIDs()
+    {
+        throw new Horde_SessionHandler_Exception('Driver does not support listing session IDs.');
+    }
+
+}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Memcache.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Memcache.php
new file mode 100644 (file)
index 0000000..9dd1c28
--- /dev/null
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Horde_SessionHandler implementation for memcache.
+ *
+ * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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   Rong-En Fan <rafan@infor.org>
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @package  SessionHandler
+ */
+class Horde_SessionHandler_Storage_Memcache extends Horde_SessionHandler_Storage
+{
+    /**
+     * Memcache object.
+     *
+     * @var Horde_Memcache
+     */
+    protected $_memcache;
+
+    /**
+     * Current session ID.
+     *
+     * @var string
+     */
+    protected $_id;
+
+    /**
+     * The ID used for session tracking.
+     *
+     * @var string
+     */
+    protected $_trackID = 'horde_memcache_sessions_track';
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameters:
+     * <pre>
+     * 'memcache' - (Horde_Memcache) [REQUIRED] A memcache object.
+     * 'track' - (boolean) Track active sessions?
+     * 'track_lifetime' - (integer) The number of seconds after which tracked
+     *                    sessions will be treated as expired.
+     * </pre>
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $params = array())
+    {
+        if (empty($params['memcache'])) {
+            throw new InvalidArgumentException('Missing memcache argument.');
+        }
+
+        $this->_memcache = $params['memcache'];
+        unset($params['memcache']);
+
+        parent::__construct($params);
+
+        if (empty($this->_params['track_lt'])) {
+            $this->_params['track_lt'] = ini_get('session.gc_maxlifetime');
+        }
+
+        if (!empty($this->_params['track']) && (rand(0, 999) == 0)) {
+            register_shutdown_function(array($this, 'trackGC'));
+        }
+    }
+
+    /**
+     */
+    public function open($save_path = null, $session_name = null)
+    {
+    }
+
+    /**
+     */
+    public function close()
+    {
+        if (isset($this->_id)) {
+            $this->_memcache->unlock($this->_id);
+        }
+    }
+
+    /**
+     */
+    public function read($id)
+    {
+        if (!$this->readonly) {
+            $this->_memcache->lock($id);
+        }
+        $result = $this->_memcache->get($id);
+
+        if ($result === false) {
+            if (!$this->readonly) {
+                $this->_memcache->unlock($id);
+            }
+
+            if ($result === false) {
+                if ($this->_logger) {
+                    $this->_logger->log('Error retrieving session data (id = ' . $id . ')', 'DEBUG');
+                }
+                return false;
+            }
+        }
+
+        if (!$this->readonly) {
+            $this->_id = $id;
+        }
+
+        if ($this->_logger) {
+            $this->_logger->log('Read session data (id = ' . $id . ')', 'DEBUG');
+        }
+
+        return $result;
+    }
+
+    /**
+     */
+    public function write($id, $session_data)
+    {
+        if (!empty($this->_params['track'])) {
+            // Do a replace - the only time it should fail is if we are
+            // writing a session for the first time.  If that is the case,
+            // update the session tracker.
+            $res = $this->_memcache->replace($id, $session_data);
+            $track = !$res;
+        } else {
+            $res = $track = false;
+        }
+
+        if (!$res &&
+            !$this->_memcache->set($id, $session_data)) {
+            if ($this->_logger) {
+                $this->_logger->log('Error writing session data (id = ' . $id . ')', 'ERR');
+            }
+            return false;
+        }
+
+        if ($track) {
+            $this->_memcache->lock($this->_trackID);
+            $ids = $this->_memcache->get($this->_trackID);
+            if ($ids === false) {
+                $ids = array();
+            }
+
+            $ids[$id] = time();
+            $this->_memcache->set($this->_trackID, $ids);
+            $this->_memcache->unlock($this->_trackID);
+        }
+
+        if ($this->_logger) {
+            $this->_logger->log('Wrote session data (id = ' . $id . ')', 'DEBUG');
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    public function destroy($id)
+    {
+        $result = $this->_memcache->delete($id);
+        $this->_memcache->unlock($id);
+
+        if ($result === false) {
+            if ($this->_logger) {
+                $this->_logger->log('Failed to delete session (id = ' . $id . ')', 'DEBUG');
+            }
+            return false;
+        }
+
+        if (!empty($this->_params['track'])) {
+            $this->_memcache->lock($this->_trackID);
+            $ids = $this->_memcache->get($this->_trackID);
+            if ($ids !== false) {
+                unset($ids[$id]);
+                $this->_memcache->set($this->_trackID, $ids);
+            }
+            $this->_memcache->unlock($this->_trackID);
+        }
+
+        if ($this->_logger) {
+            $this->_logger->log('Deleted session data (id = ' . $id . ')', 'DEBUG');
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    public function gc($maxlifetime = 300)
+    {
+        // Memcache does its own garbage collection.
+        return true;
+    }
+
+    /**
+     */
+    public function getSessionIDs()
+    {
+        if (empty($this->_params['track'])) {
+            throw new Horde_SessionHandler_Exception('Memcache session tracking not enabled.');
+        }
+
+        $this->trackGC();
+
+        $ids = $this->_memcache->get($this->_trackID);
+
+        return ($ids === false)
+            ? array()
+            : array_keys($ids);
+    }
+
+    /**
+     * Do garbage collection for session tracking information.
+     */
+    public function trackGC()
+    {
+        $this->_memcache->lock($this->_trackID);
+        $ids = $this->_memcache->get($this->_trackID);
+        if (empty($ids)) {
+            $this->_memcache->unlock($this->_trackID);
+            return;
+        }
+
+        $tstamp = time() - $this->_params['track_lt'];
+        $alter = false;
+
+        foreach ($ids as $key => $val) {
+            if ($tstamp > $val) {
+                unset($ids[$key]);
+                $alter = true;
+            }
+        }
+
+        if ($alter) {
+            $this->_memcache->set($this->_trackID, $ids);
+        }
+
+        $this->_memcache->unlock($this->_trackID);
+    }
+
+}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Sql.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Sql.php
new file mode 100644 (file)
index 0000000..39cda3c
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+/**
+ * SessionHandler storage implementation for SQL databases.
+ *
+ * Uses the following SQL table structure:
+ * <pre>
+ * CREATE TABLE horde_sessionhandler (
+ *     VARCHAR(32) NOT NULL,
+ *     session_lastmodified   INT NOT NULL,
+ *     session_data           LONGBLOB,
+ *     -- Or, on some DBMS systems:
+ *     --  session_data           IMAGE,
+ *
+ *     PRIMARY KEY (session_id)
+ * );
+ *
+ * CREATE INDEX session_lastmodified_idx ON horde_sessionhandler (session_lastmodified);
+ * </pre>
+ *
+ * Copyright 2002-2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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   Mike Cochrane <mike@graftonhall.co.nz>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  SessionHandler
+ */
+class Horde_SessionHandler_Storage_Sql extends Horde_SessionHandler_Storage
+{
+    /**
+     * Handle for the current database connection.
+     *
+     * @var Horde_Db_Adapter
+     */
+    protected $_db;
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameters:
+     * <pre>
+     * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
+     * 'table' - (string) The name of the sessions table.
+     *           DEFAULT: 'horde_sessionhandler'
+     * </pre>
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $params = array())
+    {
+        if (!isset($params['db'])) {
+            throw new InvalidArgumentException('Missing db parameter.');
+        }
+        $this->_db = $params['db'];
+        unset($params['db']);
+
+        parent::__construct(array_merge(array(
+            'table' => 'horde_sessionhandler'
+        ), $params));
+    }
+
+    /**
+     */
+    public function open($save_path = null, $session_name = null)
+    {
+    }
+
+    /**
+     */
+    public function close()
+    {
+        /* Close any open transactions. */
+        try {
+            $this->_db->commitDbTransaction();
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_SessionHandler_Exception($e);
+        }
+    }
+
+    /**
+     */
+    public function read($id)
+    {
+        /* Begin a transaction. */
+        // TODO: Rowlocking in Mysql
+        $this->_db->beginDbTransaction();
+
+        /* Build query. */
+        $query = sprintf('SELECT session_data FROM %s WHERE session_id = ?',
+                         $this->_params['table']);
+        $values = array($id);
+
+        /* Execute the query. */
+        try {
+            return $this->_db->selectValue($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     */
+    public function write($id, $session_data)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT session_id FROM %s WHERE session_id = ?',
+                         $this->_params['table']);
+        $values = array($id);
+
+        /* Execute the query. */
+        try {
+            $result = $this->_db->selectValue($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            return false;
+        }
+
+        /* Build the replace SQL query. */
+        $query = sprintf('REPLACE INTO %s ' .
+                         '(session_id, session_data, session_lastmodified) ' .
+                         'VALUES (?, ?, ?)',
+                         $this->_params['table']);
+        $values = array(
+            $id,
+            $session_data,
+            time()
+        );
+
+        /* Execute the replace query. */
+        try {
+            $this->_db->update($query, $values);
+            $this->_db->commitDbTransaction();
+        } catch (Horde_Db_Exception $e) {
+            $this->_db->rollbackDbTransaction();
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    public function destroy($id)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('DELETE FROM %s WHERE session_id = ?',
+                         $this->_params['table']);
+        $values = array($id);
+
+        /* Execute the query. */
+        try {
+            $this->_db->delete($query, $values);
+            $this->_db->commitDbTransaction();
+        } catch (Horde_Db_Exception $e) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    public function gc($maxlifetime = 300)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('DELETE FROM %s WHERE session_lastmodified < ?',
+                         $this->_params['table']);
+        $values = array(time() - $maxlifetime);
+
+        /* Execute the query. */
+        try {
+            $this->_db->delete($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    public function getSessionIDs()
+    {
+        $this->open();
+
+        /* Build the SQL query. */
+        $query = sprintf('SELECT session_id FROM %s' .
+                         ' WHERE session_lastmodified >= ?',
+                         $this->_params['table']);
+        $values = array(time() - ini_get('session.gc_maxlifetime'));
+
+        /* Execute the query. */
+        try {
+            return $this->_db->selectValues($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            return array();
+        }
+    }
+
+}
diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Stack.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Stack.php
new file mode 100644 (file)
index 0000000..b3f0e56
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+/**
+ * SessionHandler storage implementation that will loop through a list of
+ * storage drivers to handle the session information.
+ * This driver allows for use of caching backends on top of persistent
+ * backends, for example.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * 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   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  SessionHandler
+ */
+class Horde_SessionHandler_Storage_Stack extends Horde_SessionHandler_Storage
+{
+    /**
+     * Stack of storage objects.
+     *
+     * @var array
+     */
+    protected $_stack = array();
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameters:
+     * <pre>
+     * stack - (array) [REQUIRED] A list of storage objects to loop
+     *         through, in order of priority. The last entry is considered
+     *         the "master" server.
+     * </pre>
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $params = array())
+    {
+        if (!isset($params['stack'])) {
+            throw new InvalidArgumentException('Missing stack parameter.');
+        }
+
+        $this->_stack = $params['stack'];
+        unset($params['stack']);
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Set the logger object.
+     *
+     * @param Horde_Log_Logger $log  The logger instance.
+     */
+    public function setLogger(Horde_Log_Logger $log)
+    {
+        parent::setLogger($log);
+
+        foreach ($this->_stack as $ob) {
+            $ob->setLogger($log);
+        }
+    }
+
+    /**
+     */
+    public function open($save_path = null, $session_name = null)
+    {
+        foreach ($this->_stack as $val) {
+            $val->open($save_path, $session_name);
+        }
+    }
+
+    /**
+     */
+    public function close()
+    {
+        foreach ($this->_stack as $val) {
+            $val->close();
+        }
+    }
+
+    /**
+     */
+    public function read($id)
+    {
+        foreach ($this->_stack as $val) {
+            $result = $val->read($id);
+            if ($result === false) {
+                break;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     */
+    public function write($id, $session_data)
+    {
+        /* Do writes in *reverse* order - it is OK if a write to one of the
+         * non-master backends fails. */
+        $master = true;
+
+        foreach (array_reverse($this->_stack) as $val) {
+            $result = $val->write($id, $session_data);
+            if ($result === false) {
+                if ($master) {
+                    return false;
+                }
+                /* Attempt to invalidate cache if write failed. */
+                $val->destroy($id);
+            }
+            $master = false;
+        }
+
+        return true;
+    }
+
+    /**
+     */
+    public function destroy($id)
+    {
+        /* Only report success from master. */
+        $master = $success = true;
+
+        foreach (array_reverse($this->_stack) as $val) {
+            $result = $val->destroy($id);
+            if ($master && ($result === false)) {
+                $success = false;
+            }
+            $master = false;
+        }
+
+        return $success;
+    }
+
+    /**
+     */
+    public function gc($maxlifetime = 300)
+    {
+        /* Only report GC results from master. */
+        foreach ($this->_stack as $val) {
+            $result = $val->gc($maxlifetime);
+        }
+
+        return $result;
+    }
+
+    /**
+     */
+    public function getSessionIDs()
+    {
+        /* Grab session ID list from the master. */
+        $ob = end($this->_stack);
+        return $ob->getSessionIDs();
+    }
+
+}
index 4a0295b..e86b7ff 100644 (file)
@@ -34,7 +34,7 @@
   <api>beta</api>
  </stability>
  <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>
+ <notes>* Abstracted storage-specific code into &apos;Storage&apos; drivers.
 * Removed LDAP driver
 * Abstracted memcache persistent-backend code into &apos;Stack&apos; driver.
 * Renamed &apos;none&apos; driver to &apos;Builtin&apos;.
    <dir name="lib">
     <dir name="Horde">
      <dir name="SessionHandler">
-      <file name="Builtin.php" role="php" />
+      <dir name="Storage">
+       <file name="Builtin.php" role="php" />
+       <file name="External.php" role="php" />
+       <file name="Memcache.php" role="php" />
+       <file name="Sql.php" role="php" />
+       <file name="Stack.php" role="php" />
+      </dir> <!-- //lib/Horde/SessionHandler/Storage -->
       <file name="Exception.php" role="php" />
-      <file name="External.php" role="php" />
-      <file name="Memcache.php" role="php" />
-      <file name="Sql.php" role="php" />
-      <file name="Stack.php" role="php" />
+      <file name="Storage.php" role="php" />
      </dir> <!-- //lib/Horde/SessionHandler -->
      <file name="SessionHandler.php" role="php" />
     </dir> <!-- //lib/Horde -->
  <phprelease>
   <filelist>
    <install as="Horde/SessionHandler.php" name="lib/Horde/SessionHandler.php" />
-   <install as="Horde/SessionHandler/Builtin.php" name="lib/Horde/SessionHandler/Builtin.php" />
+   <install as="Horde/SessionHandler/Storage/Builtin.php" name="lib/Horde/SessionHandler/Storage/Builtin.php" />
+   <install as="Horde/SessionHandler/Storage/External.php" name="lib/Horde/SessionHandler/Storage/External.php" />
+   <install as="Horde/SessionHandler/Storage/Memcache.php" name="lib/Horde/SessionHandler/Storage/Memcache.php" />
+   <install as="Horde/SessionHandler/Storage/Sql.php" name="lib/Horde/SessionHandler/Storage/Sql.php" />
+   <install as="Horde/SessionHandler/Storage/Stack.php" name="lib/Horde/SessionHandler/Storage/Stack.php" />
    <install as="Horde/SessionHandler/Exception.php" name="lib/Horde/SessionHandler/Exception.php" />
-   <install as="Horde/SessionHandler/External.php" name="lib/Horde/SessionHandler/External.php" />
-   <install as="Horde/SessionHandler/Memcache.php" name="lib/Horde/SessionHandler/Memcache.php" />
-   <install as="Horde/SessionHandler/Sql.php" name="lib/Horde/SessionHandler/Sql.php" />
-   <install as="Horde/SessionHandler/Stack.php" name="lib/Horde/SessionHandler/Stack.php" />
+   <install as="Horde/SessionHandler/Storage.php" name="lib/Horde/SessionHandler/Storage.php" />
   </filelist>
  </phprelease>
  <changelog>