Location: PHPKode > scripts > Session Handler classes > session-handler-classes/cSession.inc
<?
/*
CLASS NAME
SessionManager

VERSION
1.0

AUTHOR
Scott Christensen (hide@address.com)

LICENCE
I am providing this class as is with NO WARRANTY and I take no 
responsibility for any problems that occur as a result of using this 
software.  You may modify this code as you like as long as this notice
remains in tact. Please let me know via email what changes you
have made so that I may update my code if it makes sense to do so.

If you have any problems with the class, please let me know what they are so that I
may fix them promptly.  I may or may not come out with updates to this
code depending on the issues that arise and time permitting.

Finally, please also let me know if you are using this in any production envrionment
(or for that matter, any environment at all). I am extremely interested in finding 
out if the classes I write are useful to others or just to me.  If you are using them,
please send the link when you write.

If you redistribute this code please mention in your script my info (Name & Email) as it's creator.
If you are using this code please send me your feedback and your improvements to it.

CHANGE LOG

Version  Name                  Date        Description
--------+---------------------+-----------+---------------------------------------------
1.0      Scott Christensen     05/02/2001  Creation
--------+---------------------+-----------+---------------------------------------------
*/


/*
This class requires the IniFileReader class that can be found on
phpclasses.upperdesign.com.  It also requires some utility functions
I have written.  These can be also found on phpclasses.upperdesign.com
in the same location that this class was found
*/
require($DOCUMENT_ROOT."/library/headers/hIniFileReader.inc");
require($DOCUMENT_ROOT."/library/headers/hUtility.inc");

define("MINUTE", 60);

/**
<p>This class implements a session but does not use MySQL as its storage mechanism
like other session handling classes do.  Instead it uses a directory structure
where the session is identified as a directory and keys are files inside of that
directory.  Each file contains the value assoiciated with the key identified by
the file name.

<p>This session handler requires the use of non-persistent cookies which means it
will create cookies on the user's machine, but the cookies will be destroyed 
when the user closes their browser.  This could be an issue because there are
those people who have cookies turned off.  If you use this class, you may want
to detect whether the user has cookies enabled or disabled first.  Then use
the <code>getURLSessionString</code> function and place what is returned into
your URL query string if cookies are turned off.

<p>Example:
<pre>
if (!cookies_enabled) {
    myquerystring .= "?".$SESSION->getURLSessionString();
}
</pre>

<p>I have added a global variable outside of this class that creates a new instance
of the session class.  This was an attempt to make the session handling "hidden".
Instead of creating an instance of this class, you can use the global <code>$SESSION</code>
variable.  The only time you may need to create a new instance of the class is if
you manually <code>destroy</code> the current session.  If that is the case, you may want
to <code>unset</code> the global <code>$SESSION</code> object and then reinitialize it
with a call to <code>new Session()</code>.

<p>Note that this class is not necessary if you are using PHP4 because there is
already session handling incorporated into PHP4. See the <a 
href = "http://www.php.net/manual/en/ref.session.php">session handling</a> section
on <a href="http://www.php.net/">http://www.php.net</a> but you can certainly use
it if you would like.

<p>This class requires an ini file reader that can be found on 
<a href="http://phpclasses.upperdesign.com">http://phpclasses.upperdesign.com</a>.  It
also requires some utility functions that I have written and is another file in this
class.  Here is the directory structure hierarchy if you do not change anything.

web root
|
-- library
|  |
|  -- classes
|  |  |
|  |  + cIniFileReader.inc
|  |  + cSession.inc
|  | 
|  -- common
|  |  |
|  |  + mUtility.inc
|  |
|  -- headers
|     |
|     + hIniFileReader.inc
|     + hSession.inc
|     + hUtility.inc
|
-- php
|  |
|  + Session.ini
|
-- sessiondata
   |
   + [session data stored here if not overridden by Session.ini file]

<p>Always include the header files in the headers subdirectory instead of the class
files themselves.  PHP3 does not have the <code>require_once</code> directive, so
the classes must be embedded in an "if not defined" statement which is the reason
for the header files.

<p>Usage of this class is pretty simple.  Since the global <code>$SESSION</code>
object is created for you, there is no reason to create another instance of the 
object.  Use <code>$SESSION->register</code> to register a key/value pair with
the session handler for the correct session.  Use <code>$SESSION->retrieve</code>
to retrieve a value from the session for a given key.  If you need to get rid
of everything in the session, call <code>$SESSION->destroy</code> which deletes
the existing session and creates a new one.  If you want to only delete a key
from the existing session, call the <code>$SESSION->deleteKey</code> function.


@access public
*/
class Session {
    var $_sessionId;
    var $_sessionIdKey;
    var $_timeout;
    var $_timeoutKey;
    var $_sessionTraverseTimeout;
    var $_rootDir;
    var $_objID;
    var $_urlSessionString;
    var $_sessionPath;
    var $_autoDestroy;

    // default values if data not found in Session.ini file.
    var $DEFAULT_TIMEOUT = 30;
    var $DEFAULT_SESSION_PATH = $DOCUMENT_ROOT."/sessiondata";
    var $DEFAULT_SESSION_PREFIX = "cSession";
    var $DEFAULT_AUTO_DESTROY = 1;
    var $SESSION_SECTION = "sessiondata";

    // path to the Session.ini file.
    var $LIBRARY_PATH = $DOCUMENT_ROOT."/php";

    /**
    This is the constructor for the Session object

    @access public
    */
    function Session() {
        $ini = new IniFileReader(BuildPath($this->LIBRARY_PATH, "Session.ini"));

        $this->_timeout = $ini->getIniFile("session", "timeout", $this->DEFAULT_TIMEOUT);
        $this->_sessionTraverseTimeout = $ini->getIniFile("session", "traverseTimeout", $this->DEFAULT_TIMEOUT);
        $this->_objID = $ini->getIniFile("session", "objectID", $this->DEFAULT_SESSION_PREFIX);
        $this->_rootDir = $ini->getIniFile("session", "rootDir", $this->DEFAULT_SESSION_PATH);
        $this->_autoDestroy = (integer)$ini->getIniFile("session", "deleteTimedOutSessions", $this->DEFAULT_AUTO_DESTROY);
        
        $this->_sessionIdKey = $this->_objID."sessionID";
        $this->_timeoutKey = $this->_objID."timeout";

        $this->_sessionId = $GLOBALS[$this->_sessionIdKey];
        
        if (!(empty($this->_sessionId))) {
            $this->_sessionPath = BuildPath($this->_rootDir, $this->_sessionId);
            
            if ($this->hasTimedOut()) {
                // delete directory and create a new session
                $this->destroy();
                $this->_sessionId = $this->createNew();
                
            } else {
                $this->updateTimeoutFile();
                
            }
            
        } else {
            $this->_sessionId = $this->createNew();
            
        }
        
        $this->_sessionPath = BuildPath($this->_rootDir, $this->_sessionId);

        // Traverse all sessions and delete those old ones if it is time
        if ($this->_autoDestroy) {
            $this->destroyOldSessions();
        }
    }


    /**
    This function deletes all old sessions that have expired but have not been
    deleted.  This may occur if the user closed the browser without ending the
    session.  This function is only called internally.

    @access private
    */
    function destroyOldSessions() {
        $timeoutfile = BuildPath($this->_rootDir, "traverseTimeout");
        $lockfile = $timeoutfile."lock";

        clearstatcache();
        if (!file_exists($lockfile)) {
            if (!$fp = fopen($lockfile, "w")) {
                return true;
            }
            fclose($fp);

            clearstatcache();
            $timediff = time() - filemtime($timeoutfile);
            
            if ($timediff > ($this->_sessionTraverseTimeout * MINUTE)) {
                // delete all sessions that have expired
                $dh = opendir($this->_rootDir);

                rewinddir($dh);
                while ($sess = readdir($dh)) {
                    clearstatcache();
                    $filename = BuildPath($this->_rootDir, $sess);
                    if ($sess != "." && $sess != ".." && is_dir($filename)) {
                        if ($this->hasTimedOut($sess)) {
                            $this->destroy($sess);
                        }
                    }
                }

                closedir($dh);

                $this->updateTimeoutFile($timeoutfile);
            }
                
            @unlink($lockfile);
        }
    }


    /**
    This function determines if a session given by <code>$sess</code> has
    timed out.  It returns true if the session has timed out or false otherwise

    @param $sess An optional paramater that identifies the session to check.
    If not passed in, the session associated with this <code>$SESSION</code>
    object is assumed, otherwise this function will check the session given
    by the paramater.

    @return boolean True if the session has timed out, false otherwise.

    @access private
    */
    function hasTimedOut($sess = "") {
        if (empty($sess)) {
            $timeoutfile = BuildPath($this->_sessionPath, $this->_timeoutKey);
        } else {
            $timeoutfile = BuildPath(BuildPath($this->_rootDir, $sess), $this->_timeoutKey);
        }

        clearstatcache();
        if (file_exists($timeoutfile)) {
            clearstatcache();
            $timediff = time() - filemtime($timeoutfile);
            
            if ($timediff > ($this->_timeout * MINUTE)) {
                return true;
            }
        } else {
            return true;
        }

        return false;
    }


    /**
    This function creates a new session and initializes it.  This function uses
    cookies, so if you know a new session is going to be created, like in the 
    case of <code>destroy</code>, make sure you have not sent out any other
    headers beforehand

    @return string A unique id that can be used for a session directory

    @access private
    */
    function createNew() {
        $uniqueID = $this->generateSafeID("c");
        
        $this->_sessionPath = BuildPath($this->_rootDir, $uniqueID);

        @setcookie($this->_sessionIdKey, $uniqueID);

        $this->_urlSessionString = $this->_sessionIdKey."=".$uniqueID;

        $oldmask = umask(0);
        mkdir($this->_sessionPath, 0777);
        umask($oldmask);
        
        $this->updateTimeoutFile();
        
        return $uniqueID;
    }

    
    /**
    This functions allows the user to write the session key/value pair in
    the url string in case cookies are not enabled.

    @return string String of the form 'key=value' where key is the key
    name of the session identifier and value is the session identifier iteself.

    @access public
    */
    function getURLSessionString() {
        return $this->_urlSessionString;
    }

    
    /**
    This function creates an ID that will be used as the session name.
    The ID created will be unique because it is checked against all
    existing sessions to make sure it does not already exist.  If it
    does exist, a new ID is generated.

    @return string A unique ID suitable for using as a session id.

    @access private
    */
    function generateSafeID($prefix) {
        do {
            $safeid = md5(uniqid(uniqid($prefix)));
            clearstatcache();
        } while (is_dir(BuildPath($this->_rootDir, $safeid)));

        return $safeid;
    }


    /**
    This function registers a variable with a given key to
    the session.

    @param key This is a string key value to reference the
    variable that is being registered

    @param item This is the item that is being registered.  This
    function uses the serialize function to write out the contents
    to a string which can be stored in a file.

    @return boolean True if the register succeeded, false otherwise.

    @access public
    */
    function register($key, $item) {
        $keyfile = BuildPath($this->_sessionPath, $key);

        if (!($fp = fopen($keyfile, "w"))) {
            //error out
            return false;
        } else {
            $str = serialize($item);
            fputs($fp, $str);
            fclose($fp);
            @chmod($keyfile, 0777);
        }

        return true;
    }


    /**
    This function is used to retrieve a session value for the given
    key.  If the key is not found, false is returned otherwise
    true is returned.  The value of the retrieve is returned in the
    <code>$loc</code> parameter.  If the value is an object and 
    the <code>$func</code> function is defined, this function is
    called on the object.  This is intended to reset values in the
    object while retaining the functions of the object which are 
    not stored by serialize.

    @param $key The key to use when looking for the value in the
    current session.

    @param &$loc This is the item to put the data into.

    @param $func This is the name of a function that will restore
    the data if the item identified by <code>$loc</code> is an 
    object.  It must be a function defined in the class and must
    accept the object returned by the <code>unserialize</code> function.  
    If it is not passed in, the <code>$loc</code> object will be 
    overridden by the object returned by the <code>unserialize</code> 
    function and all functions of that object will be lost.

    @return boolean True if the retrieve completed successfully, false
    if not.

    @access public
    */
    function retrieve($key, &$loc, $func="") {
        $keyfile = BuildPath($this->_sessionPath, $key);

        clearstatcache();
        if (file_exists($keyfile)) {
            $arrFile = file($keyfile);
            $line = trim($arrFile[0]);
            $tmp = unserialize($line);
            if (is_object($tmp) && is_object($loc) && !(empty($func))) {
                $loc->$func($tmp);
            } else {
                $loc = $tmp;
            }

            return true;
        } else {
            return false;
        }
    }

    
    /**
    This function deletes a key from the session.  This is the
    function that should be called by outside scripts

    @param $key The key to delete from the current session

    @return boolean True if the delete was successful, false otherwise

    @access public
    */
    function deleteKey($key) {
        $keyfile = BuildPath($this->_sessionPath, $key);

        clearstatcache();
        if (file_exists($keyfile) && $key != "." && $key != "..") {
            return @unlink($keyfile);
        } else {
            return true;
        }
    }

    
    /**
    This function should be used to delete the entire session.  If the
    session is deleted, the global <code>$SESSION</code> variable is
    unset and will need to be recreated before new information can be
    added to the session.

    @param $sess This is an optional parameter that identifies the 
    session to delete.  If not passed in, the current session is
    assumed.

    @return boolean True if the delete was successful, false otherwise

    @access public
    */
    function destroy($sess = "") {
       if (empty($sess)) {
           $currSess = true;
           $sess = $this->_sessionPath;
       } else {
           $currSess = false;
           $sess = BuildPath($this->_rootDir, $sess);
       }

       clearstatcache();
       if (is_dir($sess)) {
            $dh = opendir($sess);

            rewinddir($dh);
            while ($keyfile = readdir($dh)) {
                if ($keyfile != "." && $keyfile != "..") {
                    $file = BuildPath($sess, $keyfile);
                    if (!@unlink($file)) {
                        return false;
                    }
                }
            }

            closedir($dh);

            rmdir($sess);

            if ($currSess) {
                @setcookie($this->_sessionIdKey);
            }

            return true;

        } else {
            return false;
        }
    }
    
    
    /**
    This function is called when the session is accessed and has not
    timed out.  It is used to update the time of the timeout file that
    signifies the last time the session was active.

    @param $file The file to update.

    @access private
    */
    function updateTimeoutFile($file = "") {
        // create the timeout file so we know the date and time
        // this session was last updated.
        if (empty($file)) {
            $file = BuildPath($this->_sessionPath, $this->_timeoutKey);
        }

        $fp = fopen($file, "w");
        fputs($fp, date("m/d/Y G:i:s",time()));
        fclose($fp);
        @chmod($file, 0777);
    }
}

$SESSION = new Session();
?>
Return current item: Session Handler classes