<?php
/**
 * Session
 *
 * $Id$
 *
 * Copyright (c) 2007, DM Solutions Group Inc.
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*****************************************************************************
 * Purpose: Directory-based PHP session capability
 *****************************************************************************
 *
 * This code was taken from the php_utils cvs module maintained on 
 * cvs.maptools.org/cvs/maptools/cvsroot. Version 1.1 of this file was an 
 * exact copy of 1.29 except for the comment block at the top.
 *
 * Developer's notes:
 *
 * This file has been updated to work with PHP 5.3 it includes a more robust locking
 * for cross platform.
 *
 * Include this file and call installSessionDirectoryHandler() before starting
 * a session.
 *
 * Call initializeSession() to start or restart a session.
 *
 * Session-based resources can be saved in the directory returned from
 * getSessionSavePath().
 *
 * Note the old GC cleanup does not function correctly, Honestly I don't think we should cleanup with php
 * a support script should be used on the server to cleanup old sessions. 
 *
 */

global $gszSessSavePath;
global $gszSessName;
global $gSzSessionDebug;
global $gszLockFile;
global $gszSessId;
global $bLockSession;

$gSzSessionDebug = FALSE;
$bLockSession = FALSE;

if (!function_exists('time_sleep_until')) {
   function time_sleep_until($future) {
       if ($future < time()) {
           trigger_error("Time in past", E_USER_WARNING);
           return false;
       }

       usleep(($future - microtime(1))*1000000);
       return true;
   }
}


if (!function_exists("_open")) {
  /**
   * _open Called by PHP session manager when a session is opened.
   * We just set the save path and session name into global variable.
   *
   * @param szSavePath String containing the absolute path where to
   *        save session info.
   * @param szSessionName String contqaining the session name.
   */
    function _open($szSavePath, $szSessionName){
        
        debug_msg(" _open: ".$_SERVER['PHP_SELF']);
        $GLOBALS['gszSessSavePath'] = $szSavePath;
        $GLOBALS['gszSessName'] = $szSessionName;
        $GLOBALS['gszSessId'] = session_id();
        $GLOBALS['gszLockFile'] = $GLOBALS['gszSessSavePath']."/sess_".$GLOBALS["gszSessId"]."/lock";
        
        // Check to see if the session locked already.
        _checkLock();

        // Create the session folder if it doesn't already exists.
        $szSessionDir = $GLOBALS['gszSessSavePath']."/sess_".$GLOBALS['gszSessId'];
        clearstatcache();
        if (!file_exists($szSessionDir)){
            mkdir($szSessionDir);
        }

        // If the session is locked wait.
        if ($GLOBALS['bLockSession']) {
            $start_time = microtime(true);
            $end_time = $start_time + microtime(true) + (ini_get("max_execution_time") -2); // end time is 2 seconds less then the max_execution_time
            while ($start_time < $end_time  ){ 
                debug_msg("BLOCKING");
                if (!_isLocked()){
                    // Free now set the lock.
                    _setLock();
                    break;
                }
                time_sleep_until(microtime(true)+0.01); // sleep for 10ms
                $start_time = microtime(true);
            }
        }
        else
        {
        // session is already free set a lock
        _setLock();
        }
        return(true);
    }
    /**
    * __isLocked() called to check if the session locked returns a boolean
    *
    */
    function _isLocked(){
        clearstatcache();
        if(file_exists($GLOBALS['gszLockFile'])){
            debug_msg("LOCKED");
            return true;
        }
        else
        {
            debug_msg("UNLOCKED");
            return false;
        }
    }

    /**
    * _setLock() creates a lockfile and sets the global 'bLockSession' to true
    */
    function _setLock(){
        debug_msg(" _setLock: ".$_SERVER['PHP_SELF']);
        $GLOBALS['bLockSession'] = true;
        clearstatcache();
        if(!file_exists($GLOBALS['gszLockFile'])){
            $fh = fopen( $GLOBALS['gszLockFile'], "w+" );
            if ($fh !== false){
                fwrite( $fh, "1" );
                fclose($fh);
            }
        }
    }
    /**
    * _checkLock() used on init to see if another process already has a lock on this session.
    */
    function _checkLock(){
        if(_isLocked()){
            $GLOBALS['bLockSession'] = true;
        }
        else
        {
            $GLOBALS['bLockSession'] = false;
        }
    }

    /**
    * _freeLock() deletes the locfile and sets the global 'bLockSession' to false
    */
    function _freeLock(){
        debug_msg(" _freeLock: ".$_SERVER['PHP_SELF']);
        debug_msg("FREE LOCK");
        if(file_exists($GLOBALS['gszLockFile'])){
            unlink($GLOBALS['gszLockFile']);
        }
        $GLOBALS['bLockSession'] = false;
    }

  /**
   * _close Called by PHP session manager when a session is closed,
   * not destroyed. In this case we do nothing.
   */
  function _close(){
      debug_msg(" _close: ".$_SERVER['PHP_SELF']);
      if ($GLOBALS['bLockSession']){
          if ( _isLocked()  ){
              _freeLock();
          }
      }
      return(true);
  }

  /**
   * _read Called by PHP session manager when the session file
   * is read. In this case we just return the file content of
   * session_file file.
   */
  function _read($szId){
      _setLock();
     debug_msg(" _read: ".$_SERVER['PHP_SELF']);
    $GLOBALS["gszSessId"] = $szId;

    $szSessionDir = $GLOBALS['gszSessSavePath']."/sess_".$szId;
    $szSessionFile = $szSessionDir."/session_file";

    clearstatcache();
    if (!file_exists($szSessionDir)){
        mkdir($szSessionDir);
    }

    if ($fp = @fopen($szSessionFile, "r")){
        $szSessionData = fread($fp, filesize($szSessionFile));
        fclose( $fp );
        debug_msg($szSessionData);
        return($szSessionData);
    }
    else
    {
        return(""); // Must return "" here.
    }
}

  /**
   * _write Called by PHP session manager when session should be
   * saved.
   *
   * @param szId String containing the unique identifier of current
   *             session.
   * @param szSessionData String containig the session file content
   *                      to be saved.
   */
  function _write($szId, $szSessionData){
      _setLock();
      debug_msg(" _write: ".$_SERVER['PHP_SELF']);
      $result = false;

      $szSessionFile = $GLOBALS['gszSessSavePath']."/sess_".$szId."/session_file";

      if ($fp = @fopen($szSessionFile, "w")) {
          $result = fwrite($fp, $szSessionData);
          fclose($fp);
          return($result);
      }
      else
      {
        return false;
      }
      
  }

  /**
   * _destroy Called by PHP session manager when it should be explicitly
   * destroyed now.
   */
    function _destroy($szId){
        debug_msg(" _destroy: ".$_SERVER['PHP_SELF']);
        if ( @is_file( $GLOBALS['gszLockFile'] )){
            @unlink( $GLOBALS['gszLockFile'] );
        }
        return true;
    }

  /**
   * _gc Called by PHP session manager when a session is started or
   * register (not all the time) depending og session.gc_probability
   */
  function _gc($nMaxLifeTime){
      debug_msg(" _gc: ".$_SERVER['PHP_SELF']);

      if ($GLOBALS['gszGarbageColectionCallBackFunction'] != ""){
          if (function_exists($GLOBALS['gszGarbageColectionCallBackFunction']))
              eval($GLOBALS['gszGarbageColectionCallBackFunction']);
      }

      $bReturn = true;

      if (!$hDir = @opendir($GLOBALS['gszSessSavePath'])){
          return false;
      }

      while($szFile = readdir($hDir)){
          if (!strstr($szFile,'sess_'))
              continue;

          if (strpos($szFile,'sess_') != 0)
              continue;

          $szSessionDir = $GLOBALS['gszSessSavePath']."/".$szFile;
          $szSessionFile = $szSessionDir."/session_file";

          if (!($mtime = @filemtime($szSessionFile))){
              $bReturn=false;
              continue;
          }

          if (time() > $mtime + $nMaxLifeTime){
              $bReturn = (deleteDirectory($szSessionDir)) ? $bReturn : false;
          }
          closedir($hDir);
          return $bReturn;
      }
  }


  function deleteDirectory($szFile){
      debug_msg("deleteDirectory: called from:".$_SERVER['PHP_SELF']);
      if (is_dir($szFile)){
          debug_msg("DELETE:".$szFile);
          $handle = opendir($szFile);
          while($szFileName = readdir($handle)){
              if ($szFileName != "." && $szFileName != ".."){
                  deleteDirectory($szFile."/".$szFileName);
              }
          }
          closedir($handle);
          rmdir($szFile);
      }
      else
      {
          unlink($szFile);
      }
  }

  function installSessionDirectoryHandler($szGCCallBack=""){
      debug_msg(" installSessionDirectoryHandler: ".$_SERVER['PHP_SELF']);
      if (!isset($GLOBALS['gszSessionDirectoryHandlerInstalled'])) {
          $GLOBALS['gszGarbageColectionCallBackFunction'] = $szGCCallBack;          
          // Set handler functions
          session_set_save_handler("_open",
                                  "_close",
                                  "_read",
                                  "_write",
                                  "_destroy",
                                  "_gc");
          $GLOBALS['gszSessionDirectoryHandlerInstalled'] = true;
      }
  }

function initializeSession( $szSessName="sid", $szSessSavePath="", $szSessionID="" ){
    debug_msg(" initializeSession: ".$_SERVER['PHP_SELF']);
    if($szSessName != ""){
        debug_msg("session.name".$szSessName);
        ini_set("session.name", $szSessName);
    }
    
    if($szSessSavePath != ""){
        debug_msg("session.save_path".$szSessSavePath);
        ini_set("session.save_path", $szSessSavePath);
    }

    clearstatcache();
    // Check if save path is writable
    if (!(file_exists(session_save_path()) &&
        is_writable(session_save_path()))){
            die("FATAL ERROR: Session save path (".session_save_path().") doesn't exist or is not writable");
    }

    $szTmpID = "";

    // check both get and post variables
    debug_msg("Checking GPC for session ...");
    if ( isset($GLOBALS['_COOKIE'][ini_get('session.name')]) ) {
        $szTmpID = $GLOBALS['_COOKIE'][ini_get('session.name')];
        debug_msg("_COOKIE:".$GLOBALS['_COOKIE'][ini_get('session.name')]);
    } elseif (isset($GLOBALS['_GET'][ini_get('session.name')])) {
        $szTmpID = $GLOBALS['_GET'][ini_get('session.name')];
        debug_msg("_GET:".$GLOBALS['_GET'][ini_get('session.name')]);
    } elseif (isset($GLOBALS['_POST'][ini_get('session.name')])) {
        $szTmpID = $GLOBALS['_POST'][ini_get('session.name')];
        debug_msg("_POST:".$GLOBALS['_POST'][ini_get('session.name')]);
    }
    debug_msg("szTmpID:".$szTmpID);

    // create new if necessary
    if ( strlen( $szTmpID ) <= 0 ){

        // create new and set IP flag
        if ( strlen( $szSessionID ) > 0 ){
            $szTmpID = $szSessionID;
        }
        else            
        {
            $szTmpID = uniqid("");
        }
        $bNewSession = true;
    }
    else
    {
        $bNewSession = false;
    }

    // initialize flag variable
    $bSessionOK = true;

    if (!$bNewSession){
        debug_msg("EXISTING Session");
    }
    else
    {
        debug_msg("NEW Session");
    }

    session_name($szSessName);
    $sessionID = session_id();
    if(empty($sessionID)){
        session_id( $szTmpID );
        session_start();
    }
    debug_msg($_SERVER['PHP_SELF']);

    register_shutdown_function( "session_write_close" );

    return $bSessionOK;
}

  function getSessionSavePath(){
      $szReturn  = ini_get("session.save_path")."/sess_".session_id()."/";
      $szReturn = str_replace( "\\", "/", $szReturn );
      return $szReturn;
  }

  function debug_msg( $szMsg ){
        global $gSzSessionDebug;
        if($gSzSessionDebug){
            
            list($usec, $sec) = explode(" ",microtime()); 
            $ts = sprintf( "%s.%4d", date( "H:s", $sec), round( 10000 * $usec )); 
            error_log($ts. " - ".$szMsg);
        }
  }


}
?>