From 24b4c87cc434f9e3bda6902fd705e0d234f13e57 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Thu, 28 Jun 2012 14:56:10 -0400
Subject: [PATCH] Progress on session configuration.

---
 module/VuFind/src/VuFind/Account/Manager.php  |   4 +-
 module/VuFind/src/VuFind/Bootstrap.php        |  18 +-
 .../src/VuFind/Session/AbstractBase.php       | 148 +++++++++++++++++
 module/VuFind/src/VuFind/Session/Database.php | 122 ++++++++++++++
 module/VuFind/src/VuFind/Session/File.php     | 157 ++++++++++++++++++
 module/VuFind/src/VuFind/Session/Memcache.php | 116 +++++++++++++
 6 files changed, 555 insertions(+), 10 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Session/AbstractBase.php
 create mode 100644 module/VuFind/src/VuFind/Session/Database.php
 create mode 100644 module/VuFind/src/VuFind/Session/File.php
 create mode 100644 module/VuFind/src/VuFind/Session/Memcache.php

diff --git a/module/VuFind/src/VuFind/Account/Manager.php b/module/VuFind/src/VuFind/Account/Manager.php
index f2ebed66d7f..4eeb3e9d63c 100644
--- a/module/VuFind/src/VuFind/Account/Manager.php
+++ b/module/VuFind/src/VuFind/Account/Manager.php
@@ -27,7 +27,7 @@
  */
 namespace VuFind\Account;
 use VuFind\Auth\Factory as AuthFactory, VuFind\Config\Reader as ConfigReader,
-    Zend\Registry, Zend\Session, Zend\Session\Container as SessionContainer;
+    Zend\Registry, Zend\Session\Container as SessionContainer;
 
 /**
  * Wrapper class for handling logged-in user in session.
@@ -164,7 +164,7 @@ class Manager
 
         // Destroy the session for good measure, if requested.
         if ($destroy) {
-            Session::destroy();
+            Registry::getInstance()->get('Zend_Session')->destroy();
         } else {
             // If we don't want to destroy the session, we still need to empty it.
             // There should be a way to do this through Zend\Session, but there
diff --git a/module/VuFind/src/VuFind/Bootstrap.php b/module/VuFind/src/VuFind/Bootstrap.php
index d3f2e9483db..6c23596a2d3 100644
--- a/module/VuFind/src/VuFind/Bootstrap.php
+++ b/module/VuFind/src/VuFind/Bootstrap.php
@@ -32,7 +32,7 @@ use VuFind\Account\Manager as AccountManager,
     VuFind\Theme\Initializer as ThemeInitializer,
     VuFind\Translator\Factory as TranslatorFactory,
     Zend\Mvc\MvcEvent, Zend\Registry, Zend\Mvc\Router\Http\RouteMatch,
-    Zend\Translator\Translator;
+    Zend\Session\SessionManager, Zend\Translator\Translator;
 /**
  * VuFind Bootstrapper
  *
@@ -307,12 +307,15 @@ class Bootstrap
             return;
         }
 
-        /* TODO:
         // Get session configuration:
         if (!isset($this->config->Session->type)) {
             throw new Exception('Cannot initialize session; configuration missing');
         }
 
+        // Register a session manager:
+        $sessionManager = new SessionManager();
+        Registry::getInstance()->set('Zend_Session', $sessionManager);
+
         // Set up session handler (after manipulating the type setting for legacy
         // compatibility -- VuFind 1.x used MySQL instead of Database and had
         // "Session" as part of the configuration string):
@@ -322,19 +325,18 @@ class Bootstrap
         if ($type == 'Mysql') {
             $type = 'Database';
         }
-        $class = 'VF_Session_' . $type;
-        Zend_Session::setSaveHandler(new $class($this->config->Session));
+        $class = 'VuFind\\Session\\' . $type;
+        $sessionManager->setSaveHandler(new $class($this->config->Session));
 
         // Start up the session:
-        Zend_Session::start();
+        $sessionManager->start();
 
         // According to the PHP manual, session_write_close should always be
         // registered as a shutdown function when using an object as a session
         // handler: http://us.php.net/manual/en/function.session-set-save-handler.php
-        register_shutdown_function(array('Zend_Session', 'writeClose'));
+        register_shutdown_function(array($sessionManager, 'writeClose'));
 
         // Check user credentials:
-        VF_Account_Manager::getInstance()->checkForExpiredCredentials();
-         */
+        AccountManager::getInstance()->checkForExpiredCredentials();
     }
 }
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Session/AbstractBase.php b/module/VuFind/src/VuFind/Session/AbstractBase.php
new file mode 100644
index 00000000000..d36d4a0ff56
--- /dev/null
+++ b/module/VuFind/src/VuFind/Session/AbstractBase.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Base class for session handling
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+namespace VuFind\Session;
+use Zend\Session\SaveHandler\SaveHandlerInterface;
+
+/**
+ * Base class for session handling
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+abstract class AbstractBase implements SaveHandlerInterface
+{
+    public $lifetime = 3600;
+
+    /**
+     * Constructor.
+     *
+     * @param Zend_Config $config Session configuration ([Session] section of
+     * config.ini)
+     */
+    public function __construct($config)
+    {
+        if (isset($config->lifetime)) {
+            $this->lifetime = $config->lifetime;
+        }
+    }
+
+    /**
+     * Open function, this works like a constructor in classes and is executed
+     * when the session is being opened.
+     *
+     * @param string $sess_path Session save path
+     * @param string $sess_name Session name
+     *
+     * @return void
+     */
+    public function open($sess_path, $sess_name)
+    {
+        return true;
+    }
+
+    /**
+     * Close function, this works like a destructor in classes and is executed
+     * when the session operation is done.
+     *
+     * @return void
+     */
+    public function close()
+    {
+        return true;
+    }
+
+    /**
+     * Read function must return string value always to make save handler work as
+     * expected. Return empty string if there is no data to read.
+     *
+     * @param string $sess_id The session ID to read
+     *
+     * @return string
+     */
+    public function read($sess_id)
+    {
+    }
+
+    /**
+     * Write function that is called when session data is to be saved.
+     *
+     * @param string $sess_id The current session ID
+     * @param string $data    The session data to write
+     *
+     * @return void
+     */
+    public function write($sess_id, $data)
+    {
+    }
+
+    /**
+     * The destroy handler, this is executed when a session is destroyed with
+     * session_destroy() and takes the session id as its only parameter.
+     *
+     * IMPORTANT:  The functionality defined in this method is global to all session
+     *             mechanisms.  If you override this method, be sure to still call
+     *             parent::destroy() in addition to any new behavior.
+     *
+     * @param string $sess_id The session ID to destroy
+     *
+     * @return void
+     */
+    public function destroy($sess_id)
+    {
+        /* TODO
+        $table = new VuFind_Model_Db_Search();
+        $table->destroySession($sess_id);
+         */
+    }
+
+    /**
+     * The garbage collector, this is executed when the session garbage collector
+     * is executed and takes the max session lifetime as its only parameter.
+     *
+     * @param int $sess_maxlifetime Maximum session lifetime.
+     *
+     * @return void
+     */
+    public function gc($sess_maxlifetime)
+    {
+        // how often does this get called (if at all)?
+
+        // *** 08/Oct/09 - Greg Pendlebury
+        // Clearly this is being called. Production installs with
+        //   thousands of sessions active are showing no old sessions.
+        // What I can't do is reproduce for testing. It might need the
+        //   search delete code from 'destroy()' if it is not calling it.
+        // *** 09/Oct/09 - Greg Pendlebury
+        // Anecdotal testing Today and Yesterday seems to indicate destroy()
+        //   is called by the garbage collector and everything is good.
+        // Something to keep in mind though.
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Session/Database.php b/module/VuFind/src/VuFind/Session/Database.php
new file mode 100644
index 00000000000..cdec8176735
--- /dev/null
+++ b/module/VuFind/src/VuFind/Session/Database.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Database session handler
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+namespace VuFind\Session;
+use VuFind\Exception\SessionExpired as SessionExpiredException;
+
+/**
+ * Database session handler
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+class Database extends AbstractBase
+{
+    protected $table;
+
+    /**
+     * Constructor.
+     *
+     * @param Zend_Config $config Session configuration ([Session] section of
+     * config.ini)
+     */
+    public function __construct($config)
+    {
+        // Create database connection:
+        /* TODO:
+        $this->table = new VuFind_Model_Db_Session();
+         */
+
+        // Call standard session initialization from this point.
+        parent::__construct($config);
+    }
+
+    /**
+     * Read function must return string value always to make save handler work as
+     * expected. Return empty string if there is no data to read.
+     *
+     * @param string $sess_id The session ID to read
+     *
+     * @return string
+     */
+    public function read($sess_id)
+    {
+        // Try to read the session, but destroy it if it has expired:
+        try {
+            return $this->table->readSession($sess_id, $this->lifetime);
+        } catch (SessionExpiredException $e) {
+            $this->destroy($sess_id);
+            return;
+        }
+    }
+
+    /**
+     * Write function that is called when session data is to be saved.
+     *
+     * @param string $sess_id The current session ID
+     * @param string $data    The session data to write
+     *
+     * @return void
+     */
+    public function write($sess_id, $data)
+    {
+        $this->table->writeSession($sess_id, $data);
+    }
+
+    /**
+     * The destroy handler, this is executed when a session is destroyed with
+     * session_destroy() and takes the session id as its only parameter.
+     *
+     * @param string $sess_id The session ID to destroy
+     *
+     * @return void
+     */
+    public function destroy($sess_id)
+    {
+        // Perform standard actions required by all session methods:
+        parent::destroy($sess_id);
+
+        // Now do database-specific destruction:
+        $this->table->destroySession($sess_id);
+    }
+
+    /**
+     * The garbage collector, this is executed when the session garbage collector
+     * is executed and takes the max session lifetime as its only parameter.
+     *
+     * @param int $sess_maxlifetime Maximum session lifetime.
+     *
+     * @return void
+     */
+    public function gc($sess_maxlifetime)
+    {
+        $this->table->garbageCollect($sess_maxlifetime);
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Session/File.php b/module/VuFind/src/VuFind/Session/File.php
new file mode 100644
index 00000000000..e190e123da6
--- /dev/null
+++ b/module/VuFind/src/VuFind/Session/File.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * File-based session handler
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+namespace VuFind\Session;
+
+/**
+ * File-based session handler
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+class File extends AbstractBase
+{
+    protected $path;
+
+    /**
+     * Constructor.
+     *
+     * @param Zend_Config $config Session configuration ([Session] section of
+     * config.ini)
+     */
+    public function __construct($config)
+    {
+        // Set defaults if nothing set in config file.
+        if (isset($config->file_save_path)) {
+            $this->path = $config->file_save_path;
+        } else {
+            $tempdir = function_exists('sys_get_temp_dir')
+                ? sys_get_temp_dir() : DIRECTORY_SEPARATOR . 'tmp';
+            $this->path = $tempdir . DIRECTORY_SEPARATOR . 'vufind_sessions';
+        }
+
+        // Die if the session directory does not exist and cannot be created.
+        if (!file_exists($this->path) || !is_dir($this->path)) {
+            if (!@mkdir($this->path)) {
+                throw new \Exception(
+                    "Cannot access session save path: " . $this->path
+                );
+            }
+        }
+
+        // Call standard session initialization from this point.
+        parent::__construct($config);
+    }
+
+    /**
+     * Read function must return string value always to make save handler work as
+     * expected. Return empty string if there is no data to read.
+     *
+     * @param string $sess_id The session ID to read
+     *
+     * @return string
+     */
+    public function read($sess_id)
+    {
+        $sess_file = $this->path . '/sess_' . $sess_id;
+        if (!file_exists($sess_file)) {
+            return '';
+        }
+
+        // enforce lifetime of this session data
+        if (filemtime($sess_file) + $this->lifetime <= time()) {
+            $this->destroy($sess_id);
+            return '';
+        }
+
+        return (string)@file_get_contents($sess_file);
+    }
+
+    /**
+     * Write function that is called when session data is to be saved.
+     *
+     * @param string $sess_id The current session ID
+     * @param string $data    The session data to write
+     *
+     * @return void
+     */
+    public function write($sess_id, $data)
+    {
+        $sess_file = $this->path . '/sess_' . $sess_id;
+        if ($fp = @fopen($sess_file, "w")) {
+            $return = fwrite($fp, $data);
+            fclose($fp);
+            if ($return) {
+                return;
+            }
+        }
+        // If we got this far, something went wrong with the file output...
+        // It is tempting to throw an exception here, but this code is called
+        // outside of the context of exception handling, so all we can do is
+        // echo a message.
+        echo 'Cannot write session to ' . $sess_file . "\n";
+    }
+
+    /**
+     * The destroy handler, this is executed when a session is destroyed with
+     * session_destroy() and takes the session id as its only parameter.
+     *
+     * @param string $sess_id The session ID to destroy
+     *
+     * @return void
+     */
+    public function destroy($sess_id)
+    {
+        // Perform standard actions required by all session methods:
+        parent::destroy($sess_id);
+
+        // Perform file-specific cleanup:
+        $sess_file = $this->path . '/sess_' . $sess_id;
+        return(@unlink($sess_file));
+    }
+
+    /**
+     * The garbage collector, this is executed when the session garbage collector
+     * is executed and takes the max session lifetime as its only parameter.
+     *
+     * @param int $maxlifetime Maximum session lifetime.
+     *
+     * @return void
+     */
+    public function gc($maxlifetime)
+    {
+        foreach (glob($this->path . "/sess_*") as $filename) {
+            if (filemtime($filename) + $maxlifetime < time()) {
+                @unlink($filename);
+            }
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Session/Memcache.php b/module/VuFind/src/VuFind/Session/Memcache.php
new file mode 100644
index 00000000000..1dde4673270
--- /dev/null
+++ b/module/VuFind/src/VuFind/Session/Memcache.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * MemCache session handler
+ *
+ * Note: This relies on PHP's Memcache extension
+ * (see http://us.php.net/manual/en/book.memcache.php)
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+namespace VuFind\Session;
+
+/**
+ * Memcache session handler
+ *
+ * @category VuFind2
+ * @package  Session_Handlers
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/creating_a_session_handler Wiki
+ */
+class Memcache extends AbstractBase
+{
+    protected $connection;
+
+    /**
+     * Constructor.
+     *
+     * @param Zend_Config $config Session configuration ([Session] section of
+     * config.ini)
+     */
+    public function __construct($config)
+    {
+        // Set defaults if nothing set in config file.
+        $host = isset($config->memcache_host) ? $config->memcache_host : 'localhost';
+        $port = isset($config->memcache_port) ? $config->memcache_port : 11211;
+        $timeout = isset($config->memcache_connection_timeout)
+            ? $config->memcache_connection_timeout : 1;
+
+        // Connect to Memcache:
+        $this->connection = new \Memcache();
+        if (!@$this->connection->connect($host, $port, $timeout)) {
+            throw new \Exception(
+                "Could not connect to Memcache (host = {$host}, port = {$port})."
+            );
+        }
+
+        // Call standard session initialization from this point.
+        parent::__construct($config);
+    }
+
+    /**
+     * Read function must return string value always to make save handler work as
+     * expected. Return empty string if there is no data to read.
+     *
+     * @param string $sess_id The session ID to read
+     *
+     * @return string
+     */
+    public function read($sess_id)
+    {
+        return $this->connection->get("vufind_sessions/{$sess_id}");
+    }
+
+    /**
+     * Write function that is called when session data is to be saved.
+     *
+     * @param string $sess_id The current session ID
+     * @param string $data    The session data to write
+     *
+     * @return void
+     */
+    public function write($sess_id, $data)
+    {
+        return $this->connection->set(
+            "vufind_sessions/{$sess_id}", $data, 0, $this->lifetime
+        );
+    }
+
+    /**
+     * The destroy handler, this is executed when a session is destroyed with
+     * session_destroy() and takes the session id as its only parameter.
+     *
+     * @param string $sess_id The session ID to destroy
+     *
+     * @return void
+     */
+    public function destroy($sess_id)
+    {
+        // Perform standard actions required by all session methods:
+        parent::destroy($sess_id);
+
+        // Perform Memcache-specific cleanup:
+        return $this->connection->delete("vufind_sessions/{$sess_id}");
+    }
+}
\ No newline at end of file
-- 
GitLab