We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 14721
    • 16 Posts
    Ok. Filling up the hard drive is not good.

    So, I wrote some PHP code that adds DB-based session management. And you can (optionally) set the timeout in code, so eventually it could be in the manager -- no need for PHP.INI changes on hosted system.

    I’ve done some testing on my MODX development site, but any feedback would be appreciated.

    Instructions:

    1. Add this code to the \manager\includes\config.inc.php (near the bottom) just before existing comment: //start cms session

    	// SAVE SESSIONS IN DB TABLE: modx_sessoin
    	@include_once "session_db.inc.php";
    


    2. Then create a new file: \manager\includes\session_db.inc.php
    This file will automatically create the new modx_session table AND install the custom session handlers.
    <?php
    // FILE: session_db.inc.php
    
    // This is the name for the session table.
    $g_sessTable = "modx_sessions"; // for backward compatibility with etomite .6
    
    // Retrieve the session maximum lifetime (found in php.ini)
    // Or you can just set your desired session time here.
    // For example, setting to 300 will give you 5 minutes (60*5).
    $g_lifetime = ini_get("session.gc_maxlifetime");
    
    // Hold reference to instance of class defined below.
    $g_modxMysqlSession = null;
    
    
    // Need access to DBAPI, but that can't be instantiated without an entire DocumentParser
    //	object because of bug FS#393. For now just make $modx global (which will be re-created later)
    //	during startup.
    include_once $base_path.'/manager/includes/document.parser.class.inc.php';
    // Use for DB access
    $modx = new DocumentParser;
    
    // Override MODX version of session starter with this better one.
    // See bottom of config.inc.php. If this function (below) exists, it overrides that one.
    function startCMSSession(){
    	$g_modxMysqlSession = new ModxMysqlSession();
    }
    
    
    // This class implements the PHP API for Mysql sessions.
    // Currently won't work with any other DB.
    class ModxMysqlSession {
    
    
    
    	// Constructor
    	function ModxMysqlSession()
    	{
    		global $g_sessTable; // Name of session table, from this file.
    		global $site_sessionname; // From config.inc.php
    
    		// Create the session table, if we don't have one.
    		if(!$this->tableExists($g_sessTable))
    			{
    		       $query = "CREATE TABLE $g_sessTable ( SID char(255) NOT NULL, expiration INT NOT NULL, value LONGTEXT NOT NULL, PRIMARY KEY(SID), INDEX ( expiration) );";
    		       $result = $this->query($query);
    		       if( !$result)
    		       {
    			   	die( "ERROR: Failed to create session table: $g_sessTable");
    			   }
    			}
    
    		// Set the session name.
    		session_name($site_sessionname);
    
    		// Setup Mysql API for handling sessions.
    		// THIS MUST BE DONE AFTER SESSION_NAME, OR YOU WILL GET FATAL ERROR ON LOGOUT.
    	    ini_set('session.save_handler', 'user');
    		session_set_save_handler(
    			array(&$this, "open"),
    			array(&$this, "close"),
    		  	array(&$this, "read"),
    		  	array(&$this, "write"),
    		  	array(&$this, "destroy"),
    		  	array(&$this, "garbage_collect") );
    
    		// Works around bug in some newer PHP's.
    		// See: http://fr.php.net/manual/en/function.session-set-save-handler.php#61223
    		register_shutdown_function("session_write_close");
    
    		// Start the session.
    		session_start();
    	}
    
       	// My special version of dbapi query function. Actually returns errors.
    	// You can use the regular DBAPI function when FS#358 is fixed.
    	function query($sql) {
    
    		global $modx;
    		if(empty($modx->db->conn)||!is_resource($modx->db->conn)) {
    			$modx->db->connect();
    		}
    		$tstart = $modx->getMicroTime();
    		if(!$result = @mysql_query($sql, $modx->db->conn)) {
    			// Original printed a message here that caused problems.
    			return $result;
    		} else {
    			$tend = $modx->getMicroTime();
    			$totaltime = $tend-$tstart;
    			$modx->queryTime = $modx->queryTime+$totaltime;
    			if($modx->dumpSQL) {
    				$modx->queryCode .= "<fieldset style='text-align:left'><legend>Query ".($this->executedQueries+1)." - ".sprintf("%2.4f s", $totaltime)."</legend>".$sql."</fieldset><br />";
    			}
    			$modx->executedQueries = $modx->executedQueries+1;
    			return $result;
    		}
    
    	}
    
    	// Check to see if Table exists in current DB.
    	function tableExists($tableName)
    	{
    
    	   global $dbase;
    	   $tables = array();
    	   $dbase_without_ticks = str_replace( "`", "", $dbase);
    	   $tablesResult = $this->query("SHOW TABLES in $dbase_without_ticks;");
    	   while ($row = mysql_fetch_row($tablesResult))
    	   {
    	   	$tables[] = $row[0];
    	   }
    	   return(in_array($tableName, $tables));
    	}
    
    
    	// Cleanup any out of data sessions at startup.
    	function open($session_path, $session_name) {
    	  	global $g_lifetime;
     		global $site_sessionname; // From config.inc.php
    
    
    	  // Cleanup any old sessions.
    	  if( $session_name != $site_sessionname ) die( "Unexpected session name: $session_name");
    	  return $this->garbage_collect( $g_lifetime);
    	}
    
    
    	// Doesn't actually do anything since the server connection is
    	//    persistent. Keep in mind that although this function
    	//    doesn't do anything in my particular implementation, I
    	//    still must define it.
    	function close() {
    
    	  return( true);
    
    	}
    
    
    	// Reads the session data from the database
    	function read($SID) {
    	  global $g_sessTable;
    
    	  $SID = mysql_escape_string($SID);
    
    	  $query = "SELECT value FROM $g_sessTable WHERE SID = '$SID' AND expiration > ". time();
    
    	  $result = $this->query($query);
    	  if($result && (mysql_num_rows($result) == 1) )
    	  {
    	    $row = mysql_fetch_array($result);
    	    return $row['value'];
    	  }
    	  else
    	  {
    	  	return( ''); // Must return "" here.
    	  }
    
    	}
    
    	// This function writes the session data to the database. If that SID // already exists, then the existing data will be updated.
    	function write($SID, $value) {
    	  global $g_sessTable;
    	  GLOBAL $g_lifetime;
    
    	  $SID = mysql_escape_string($SID);
    	  $value = mysql_escape_string( $value);
    
    	  $expiration = time() + $g_lifetime;
    
    	  $query = "INSERT INTO $g_sessTable VALUES('$SID', '$expiration', '$value')";
    
    	  $result = $this->query($query);
    
    	  if (!$result)
    	  {
    
    	   $query = "UPDATE $g_sessTable SET expiration = '$expiration', value = '$value' WHERE SID = '$SID' AND expiration >". time();
    
    	   $result = $this->query($query);
    	   if( !$result)
    	   {
    	   	return( false);
    	   }
    	  }
    	  return strlen(value); /* Should be bytes written, whatever that means in a Mysql */
    
    
    	}
    
    
    	// deletes all session information having input SID (only one row)
    	function destroy($SID) {
    	  global $g_sessTable;
    
    	  $SID = mysql_escape_string($SID);
    
    	  $query = "DELETE FROM $g_sessTable WHERE SID = '$SID'";
    	  $result = $this->query($query);
    	  if( $result && ($result != -1))
    	  {
    	    return mysql_affected_rows($result);
    	  }
    	  else
    	  {
    	  	return 0;
    	  }
    	}
    
    
    	// deletes all sessions that have expired.
    	function garbage_collect($maxlifetime) {
    
    	  global $g_sessTable;
    	  $maxlifetime = mysql_escape_string($maxlifetime);
    	
    	  $query = "DELETE FROM $g_sessTable WHERE expiration < " . (time() - $maxlifetime);
    	  $result = $this->query($query);
    	  return( true);
    	}
    
    }
    
    ?>
    


    If you run into problems, let me know and I will fix it up.

    -- Jorge.
      • 14721
      • 16 Posts
      Did the above script work for anyone? It’s cookie-based (like before) and stores the sessions in the DB...
        • 15826
        • 160 Posts
        After losing half an hour’s worth of writing, I’ve installed this on both a client’s site and one of mine. I’ll let you know how it goes. Thank you for your efforts on this, Jorge!
          "I’d love to change the world but I can’t find the source code . . ."

          Custom ModX Templates
          • 15826
          • 160 Posts
          Jorge, I’m sorry to report that this didn’t seem to do the trick. I just tried it, creating a new page without saving, then walking away to do other stuff for around an hour and a half. When I hit "save" it brought me to the sign in page and the document was not saved.
            "I’d love to change the world but I can’t find the source code . . ."

            Custom ModX Templates
          • There two possible solutions to this -- MODx must introduce cookie-based authentication that automatically recreates the session if it timesout on the server within the specified amount of time the user specifies when logging in. Or, we must introduce some sort of AJAX request to keep the session alive while the user has the manager open in their browser, though this is less elegant in my opinion and would not allow users to automatically be logged in when returning to the site after a few hours absence.

            The former solution is what I will be implementing for MODx 1.0, so logging into MODx will be no different than returning to the SMF forums.
              • 15826
              • 160 Posts
              I really don’t care HOW it’s done, as long as it works. This is a HUGE drawback. I can only imagine what a client will be screaming at me if they lose a bunch of work due to this (and I know how they feel!) The sooner a solution is posted here the better.
                "I’d love to change the world but I can’t find the source code . . ."

                Custom ModX Templates
              • Quote from: kickass at May 21, 2006, 06:14 PM

                I really don’t care HOW it’s done, as long as it works. This is a HUGE drawback. I can only imagine what a client will be screaming at me if they lose a bunch of work due to this (and I know how they feel!) The sooner a solution is posted here the better.

                Feel free to implement a solution and contribute it to MODx 0.9.x -- just FYI, I have ceased most of my efforts in regards to the 0.9.x codebase in an effort to speed completion and delivery of the 1.0 solution (as well as allow myself to get some paying client work done); so don’t expect that I myself am working on such a solution for 0.9.x -- unfortunately, the user systems are so different between the two, there is no real benefit to 1.0 to spend time working on this for 0.9.x. Anyone want to volunteer to pick this work up so we can solve this for existing users?
                  • 15826
                  • 160 Posts
                  Unfortunately I’m front end. I can’t really help with this.
                    "I’d love to change the world but I can’t find the source code . . ."

                    Custom ModX Templates
                  • SMF implemented a javascript keepalive feature that runs every 20 minutes. However, certain FF 1.5 versions were buggy, causing a sudden flurry of repeated keepalive requests, bringing some shared hosting servers to their knees. SMF patched the js file making the calls to work around the FF bug, and FF has since fixed the bug. But my hosting provider still bans the RC versions of SMF.

                    I would sugggest at this time that a custom session_save_path be set in the config.inc.php file, as suggested elsewhere in the formums, and a module to be run occasionally to clear out session files older than an admin-specified limit. I will see about making up the module in the next few days.

                    You would first need to create a new folder somewhere to store session files. If your php configuration allows it, put it outside of the web server space. Most shared hosting plans, where this is a problem (since if you have access to your own php.ini files it’s not a problem) won’t allow scripts to operate outside the web space. The directory has to be set with world-writable permissions (777) if your scripts run as the Apache user. Then add a line similar to this around line 32 of you config.inc.php file, just before the startcmssession section:
                    session_save_path("home/mysite/www/assets/sessions/");

                    Or you can use the .htaccess file:
                    php_value session.save_path /home/mysite/www/assets/sessions/

                    ,
                      Studying MODX in the desert - http://sottwell.com
                      Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
                      Join the Slack Community - http://modx.org
                      • 22815
                      • 1,097 Posts
                      There is another solution:
                      If the user posts a form, and it turns out the user is no longer logged in, you represent the form but with the username/password log-in at the top.

                      ie - you give the user a chance to log back in and keep all his data. (Well, if they’re uploading a file they’ll have to select it again, but that isn’t a big deal).

                      To be fair, I’ve not actually looked at the backend code to see how fiddly this would be to bung in to every page, but it might be worthwhile at least putting this sort of thing on the pages where clients are likely to spend lots of time writing stuff that they don’t want to lose.
                        No, I don&#39;t know what OpenGeek&#39;s saying half the time either.
                        MODx Documentation: The Wiki | My Wiki contributions | Main MODx Documentation
                        Forum: Where to post threads about add-ons | Forum Rules
                        Like MODx? donate (and/or share your resources)
                        Like me? See my Amazon wishlist
                        MODx "Most Promising CMS" - so appropriate!