1 <?php rcs_id('$Id: DbSession.php,v 1.16 2004-05-07 15:11:28 rurban Exp $');
4 * Store sessions data in Pear DB / ADODB ....
8 * Originally by Stanislav Shramko <stanis@movingmail.com>
9 * Minor rewrite by Reini Urban <rurban@x-ray.at> for Phpwiki.
10 * Quasi-major rewrite/decruft/fix by Jeff Dairiki <dairiki@dairiki.org>.
19 * Pear DB handle, or WikiDB object (from which the Pear DB handle will
22 * @param string $table
23 * Name of SQL table containing session data.
25 function DB_Session(&$dbh, $table = 'session') {
26 // Coerce WikiDB to PearDB or ADODB.
27 // Todo: adodb/dba handlers
28 $db_type = $GLOBALS['DBParams']['dbtype'];
29 if (isa($dbh, 'WikiDB')) {
30 $backend = &$dbh->_backend;
31 $db_type = substr(get_class($dbh),7);
32 $class = "DB_Session_".$db_type;
34 // < 4.1.2 crash on dba sessions at session_write_close().
35 // (Tested with 4.1.1 and 4.1.2)
36 // Didn't try postgres sessions.
37 if (!check_php_version(4,1,2) and $db_type=='dba')
40 if (class_exists($class)) {
41 $this->_backend = new $class($backend->_dbh, $table);
42 return $this->_backend;
45 //Fixme: E_USER_WARNING ignored!
46 trigger_error(sprintf(
47 _("Your WikiDB DB backend '%s' cannot be used for DB_Session. Set USE_DB_SESSION to false."),
48 $db_type), E_USER_WARNING);
52 function currentSessions() {
53 return $this->_backend->currentSessions();
55 function query($sql) {
56 return $this->_backend->query($sql);
58 function quote($string) {
59 return $this->_backend->quote($string);
67 var $_backend_type = "SQL";
69 function DB_Session_SQL (&$dbh, $table) {
72 $this->_table = $table;
74 ini_set('session.save_handler','user');
75 session_module_name('user'); // new style
76 session_set_save_handler(array(&$this, 'open'),
77 array(&$this, 'close'),
78 array(&$this, 'read'),
79 array(&$this, 'write'),
80 array(&$this, 'destroy'),
87 $this->_connected = is_resource($dbh->connection);
88 if (!$this->_connected) {
89 $res = $dbh->connect($dbh->dsn);
90 if (DB::isError($res)) {
91 error_log("PhpWiki::DB_Session::_connect: " . $res->getMessage());
97 function query($sql) {
98 return $this->_dbh->query($sql);
101 function quote($string) {
102 return $this->_dbh->quote($string);
105 function _disconnect() {
106 if (0 and $this->_connected)
107 $this->_dbh->disconnect();
113 * Actually this function is a fake for session_set_save_handle.
114 * @param string $save_path a path to stored files
115 * @param string $session_name a name of the concrete file
116 * @return boolean true just a variable to notify PHP that everything
120 function open ($save_path, $session_name) {
121 //$this->log("_open($save_path, $session_name)");
128 * This function is called just after <i>write</i> call.
130 * @return boolean true just a variable to notify PHP that everything
135 //$this->log("_close()");
140 * Reads the session data from DB.
142 * @param string $id an id of current session
146 function read ($id) {
147 //$this->log("_read($id)");
148 $dbh = &$this->_connect();
149 $table = $this->_table;
150 $qid = $dbh->quote($id);
152 $res = $dbh->getOne("SELECT sess_data FROM $table WHERE sess_id=$qid");
154 $this->_disconnect();
155 if (DB::isError($res) || empty($res))
157 if (isa($dbh, 'DB_pgsql'))
158 //if (preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
159 $res = base64_decode($res);
164 * Saves the session data into DB.
166 * Just a comment: The "write" handler is not
167 * executed until after the output stream is closed. Thus,
168 * output from debugging statements in the "write" handler
169 * will never be seen in the browser. If debugging output
170 * is necessary, it is suggested that the debug output be
171 * written to a file instead.
174 * @param string $sess_data
175 * @return boolean true if data saved successfully and false
179 function write ($id, $sess_data) {
181 $dbh = &$this->_connect();
182 //$dbh->unlock(false,1);
183 $table = $this->_table;
184 $qid = $dbh->quote($id);
185 $qip = $dbh->quote($GLOBALS['HTTP_SERVER_VARS']['REMOTE_ADDR']);
187 if (DEBUG and $sess_data == 'wiki_user|N;') {
188 trigger_error("delete session $qid",E_USER_WARNING);
190 print_r($GLOBALS['request']->_user);
194 // postgres can't handle binary data in a TEXT field.
195 if (isa($dbh, 'DB_pgsql'))
196 $sess_data = base64_encode($sess_data);
197 $qdata = $dbh->quote($sess_data);
199 $res = $dbh->query("UPDATE $table"
200 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
201 . " WHERE sess_id=$qid");
202 if ( $dbh->affectedRows() < 1 ) // 0 (none) or -1 (failure) on mysql
203 $res = $dbh->query("INSERT INTO $table"
204 . " (sess_id, sess_data, sess_date, sess_ip)"
205 . " VALUES ($qid, $qdata, $time, $qip)");
207 $this->_disconnect();
208 return ! DB::isError($res);
212 * Destroys a session.
214 * Removes a session from the table.
217 * @return boolean true
220 function destroy ($id) {
221 $dbh = &$this->_connect();
222 $table = $this->_table;
223 $qid = $dbh->quote($id);
225 $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
227 $this->_disconnect();
232 * Cleans out all expired sessions.
234 * @param int $maxlifetime session's time to live.
235 * @return boolean true
238 function gc ($maxlifetime) {
239 $dbh = &$this->_connect();
240 $table = $this->_table;
241 $threshold = time() - $maxlifetime;
243 $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
245 $this->_disconnect();
249 // WhoIsOnline support
250 // TODO: ip-accesstime dynamic blocking API
251 function currentSessions() {
253 $dbh = &$this->_connect();
254 $table = $this->_table;
255 $res = $dbh->query("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
256 if (DB::isError($res) || empty($res))
258 while ($row = $res->fetchRow()) {
259 $data = $row['sess_data'];
260 $date = $row['sess_date'];
261 $ip = $row['sess_ip'];
262 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
263 $data = base64_decode($data);
264 // session_data contains the <variable name> + "|" + <packed string>
265 // we need just the wiki_user object (might be array as well)
266 $user = strstr($data,"wiki_user|");
267 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
271 $this->_disconnect();
276 // self-written adodb-sessions
277 class DB_Session_ADODB
280 var $_backend_type = "ADODB";
282 function DB_Session_ADODB ($dbh, $table) {
285 $this->_table = $table;
287 ini_set('session.save_handler','user');
288 session_module_name('user'); // new style
289 session_set_save_handler(array(&$this, 'open'),
290 array(&$this, 'close'),
291 array(&$this, 'read'),
292 array(&$this, 'write'),
293 array(&$this, 'destroy'),
294 array(&$this, 'gc'));
298 function _connect() {
300 static $parsed = false;
302 if (!$dbh or !is_resource($dbh->_connectionID)) {
303 if (!$parsed) $parsed = parseDSN($DBParams['dsn']);
304 $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
305 $this->_dbh->Connect($parsed['hostspec'],$parsed['username'],
306 $parsed['password'], $parsed['database']);
312 function query($sql) {
313 return $this->_dbh->Execute($sql);
316 function quote($string) {
317 return $this->_dbh->qstr($string);
320 function _disconnect() {
321 if (0 and $this->_dbh)
322 $this->_dbh->close();
328 * Actually this function is a fake for session_set_save_handle.
329 * @param string $save_path a path to stored files
330 * @param string $session_name a name of the concrete file
331 * @return boolean true just a variable to notify PHP that everything
335 function open ($save_path, $session_name) {
336 //$this->log("_open($save_path, $session_name)");
343 * This function is called just after <i>write</i> call.
345 * @return boolean true just a variable to notify PHP that everything
350 //$this->log("_close()");
355 * Reads the session data from DB.
357 * @param string $id an id of current session
361 function read ($id) {
362 //$this->log("_read($id)");
363 $dbh = &$this->_connect();
364 $table = $this->_table;
365 $qid = $dbh->qstr($id);
367 $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
370 $this->_disconnect();
371 if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
372 $res = base64_decode($res);
377 * Saves the session data into DB.
379 * Just a comment: The "write" handler is not
380 * executed until after the output stream is closed. Thus,
381 * output from debugging statements in the "write" handler
382 * will never be seen in the browser. If debugging output
383 * is necessary, it is suggested that the debug output be
384 * written to a file instead.
387 * @param string $sess_data
388 * @return boolean true if data saved successfully and false
392 function write ($id, $sess_data) {
394 $dbh = &$this->_connect();
395 $table = $this->_table;
396 $qid = $dbh->qstr($id);
397 $qip = $dbh->qstr($GLOBALS['HTTP_SERVER_VARS']['REMOTE_ADDR']);
400 // postgres can't handle binary data in a TEXT field.
401 if (isa($dbh, 'ADODB_postgres64'))
402 $sess_data = base64_encode($sess_data);
403 $qdata = $dbh->qstr($sess_data);
404 $rs = $dbh->Execute("UPDATE $table"
405 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
406 . " WHERE sess_id=$qid");
407 if ( $dbh->Affected_Rows() < 1 ) // 0 (none) or -1 (failure) on mysql
408 $rs = $dbh->Execute("INSERT INTO $table"
409 . " (sess_id, sess_data, sess_date, sess_ip)"
410 . " VALUES ($qid, $qdata, $time, $qip)");
411 $result = ! $rs->EOF;
412 if ($result) $rs->free();
413 $this->_disconnect();
418 * Destroys a session.
420 * Removes a session from the table.
423 * @return boolean true
426 function destroy ($id) {
427 $dbh = &$this->_connect();
428 $table = $this->_table;
429 $qid = $dbh->qstr($id);
431 $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
433 $this->_disconnect();
438 * Cleans out all expired sessions.
440 * @param int $maxlifetime session's time to live.
441 * @return boolean true
444 function gc ($maxlifetime) {
445 $dbh = &$this->_connect();
446 $table = $this->_table;
447 $threshold = time() - $maxlifetime;
449 $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
451 $this->_disconnect();
455 // WhoIsOnline support.
456 // TODO: ip-accesstime dynamic blocking API
457 function currentSessions() {
459 $dbh = &$this->_connect();
460 $table = $this->_table;
461 $rs = $this->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
467 $row = $rs->fetchRow();
471 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
472 $data = base64_decode($data);
473 // session_data contains the <variable name> + "|" + <packed string>
474 // we need just the wiki_user object (might be array as well)
475 $user = strstr($data,"wiki_user|");
476 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
482 $this->_disconnect();
490 * Values: date : IP : data
495 var $_backend_type = "dba";
497 function DB_Session_dba (&$dbh, $table) {
499 ini_set('session.save_handler','user');
500 session_module_name('user'); // new style
501 session_set_save_handler(array(&$this, 'open'),
502 array(&$this, 'close'),
503 array(&$this, 'read'),
504 array(&$this, 'write'),
505 array(&$this, 'destroy'),
506 array(&$this, 'gc'));
510 function quote($str) { return $str; }
511 function query($sql) { return false; }
513 function _connect() {
519 $dba_handler = 'gdbm';
522 $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
523 $dbh = new DbaDatabase($dbfile, false, $dba_handler);
524 $dbh->set_timeout($timeout);
525 if (!$dbh->open('c')) {
526 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
528 $request->finish(fmt("%s: Can't open dba database", $dbfile));
535 function _disconnect() {
536 if (0 and isset($this->_dbh))
537 $this->_dbh->close();
540 function open ($save_path, $session_name) {
541 $dbh = &$this->_connect();
547 $this->_dbh->close();
550 function read ($id) {
551 $dbh = &$this->_connect();
552 $result = $dbh->get($id);
556 list(,,$packed) = explode(':', $result, 3);
557 $this->_disconnect();
561 function write ($id, $sess_data) {
562 $dbh = &$this->_connect();
564 $ip = $GLOBALS['HTTP_SERVER_VARS']['REMOTE_ADDR'];
565 $dbh->set($id,$time.':'.$ip.':'.$sess_data);
566 $this->_disconnect();
570 function destroy ($id) {
571 $dbh = &$this->_connect();
573 $this->_disconnect();
577 function gc ($maxlifetime) {
578 $dbh = &$this->_connect();
579 $threshold = time() - $maxlifetime;
580 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
581 $result = $dbh->get($id);
582 list($date,,) = explode(':', $result, 3);
583 //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
584 if ($date < $threshold)
587 $this->_disconnect();
591 // WhoIsOnline support.
592 // TODO: ip-accesstime dynamic blocking API
593 function currentSessions() {
595 $dbh = &$this->_connect();
596 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
597 $result = $dbh->get($id);
598 list($date,$ip,$packed) = explode(':', $result, 3);
599 $data = unserialize($packed);
600 // session_data contains the <variable name> + "|" + <packed string>
601 // we need just the wiki_user object (might be array as well)
602 $user = strstr($data,"wiki_user|");
603 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
607 $this->_disconnect();
617 // c-hanging-comment-ender-p: nil
618 // indent-tabs-mode: nil