1 <?php rcs_id('$Id: DbSession.php,v 1.20 2004-06-28 16:34:30 rurban Exp $');
4 * Store sessions data in Pear DB / ADODB / dba / ....
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>.
11 * ADODB and dba classes by Reini Urban.
20 * Pear DB handle, or WikiDB object (from which the Pear DB handle will
23 * @param string $table
24 * Name of SQL table containing session data.
26 function DbSession(&$dbh, $table = 'session') {
27 // Coerce WikiDB to PearDB or ADODB.
28 // Todo: adodb/dba handlers
29 $db_type = $dbh->getParam('dbtype');
30 if (isa($dbh, 'WikiDB')) {
31 $backend = &$dbh->_backend;
32 $db_type = substr(get_class($dbh),7);
33 $class = "DbSession_".$db_type;
35 // < 4.1.2 crash on dba sessions at session_write_close().
36 // (Tested with 4.1.1 and 4.1.2)
37 // Didn't try postgres sessions.
38 if (!check_php_version(4,1,2) and $db_type == 'dba')
41 if (class_exists($class)) {
42 $this->_backend = new $class($backend->_dbh, $table);
43 return $this->_backend;
46 //Fixme: E_USER_WARNING ignored!
47 trigger_error(sprintf(_("Your WikiDB DB backend '%s' cannot be used for DbSession.")." ".
48 _("Set USE_DB_SESSION to false."),
49 $db_type), E_USER_WARNING);
53 function currentSessions() {
54 return $this->_backend->currentSessions();
56 function query($sql) {
57 return $this->_backend->query($sql);
59 function quote($string) {
60 return $this->_backend->quote($string);
68 var $_backend_type = "SQL";
70 function DbSession_SQL (&$dbh, $table) {
73 $this->_table = $table;
75 ini_set('session.save_handler','user');
76 session_module_name('user'); // new style
77 session_set_save_handler(array(&$this, 'open'),
78 array(&$this, 'close'),
79 array(&$this, 'read'),
80 array(&$this, 'write'),
81 array(&$this, 'destroy'),
88 $this->_connected = is_resource($dbh->connection);
89 if (!$this->_connected) {
90 $res = $dbh->connect($dbh->dsn);
91 if (DB::isError($res)) {
92 error_log("PhpWiki::DbSession::_connect: " . $res->getMessage());
98 function query($sql) {
99 return $this->_dbh->query($sql);
102 function quote($string) {
103 return $this->_dbh->quote($string);
106 function _disconnect() {
107 if (0 and $this->_connected)
108 $this->_dbh->disconnect();
114 * Actually this function is a fake for session_set_save_handle.
115 * @param string $save_path a path to stored files
116 * @param string $session_name a name of the concrete file
117 * @return boolean true just a variable to notify PHP that everything
121 function open ($save_path, $session_name) {
122 //$this->log("_open($save_path, $session_name)");
129 * This function is called just after <i>write</i> call.
131 * @return boolean true just a variable to notify PHP that everything
136 //$this->log("_close()");
141 * Reads the session data from DB.
143 * @param string $id an id of current session
147 function read ($id) {
148 //$this->log("_read($id)");
149 $dbh = &$this->_connect();
150 $table = $this->_table;
151 $qid = $dbh->quote($id);
153 $res = $dbh->getOne("SELECT sess_data FROM $table WHERE sess_id=$qid");
155 $this->_disconnect();
156 if (DB::isError($res) || empty($res))
158 if (isa($dbh, 'DB_pgsql'))
159 //if (preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
160 $res = base64_decode($res);
165 * Saves the session data into DB.
167 * Just a comment: The "write" handler is not
168 * executed until after the output stream is closed. Thus,
169 * output from debugging statements in the "write" handler
170 * will never be seen in the browser. If debugging output
171 * is necessary, it is suggested that the debug output be
172 * written to a file instead.
175 * @param string $sess_data
176 * @return boolean true if data saved successfully and false
180 function write ($id, $sess_data) {
182 $dbh = &$this->_connect();
183 //$dbh->unlock(false,1);
184 $table = $this->_table;
185 $qid = $dbh->quote($id);
186 $qip = $dbh->quote($GLOBALS['request']->get('REMOTE_ADDR'));
187 $time = $dbh->quote(time());
188 if (DEBUG and $sess_data == 'wiki_user|N;') {
189 trigger_error("delete empty session $qid", E_USER_WARNING);
191 print_r($GLOBALS['request']->_user);
195 // postgres can't handle binary data in a TEXT field.
196 if (isa($dbh, 'DB_pgsql'))
197 $sess_data = base64_encode($sess_data);
198 $qdata = $dbh->quote($sess_data);
200 $res = $dbh->query("UPDATE $table"
201 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
202 . " WHERE sess_id=$qid");
203 if ( ! $dbh->affectedRows() ) { // 0 (none) or -1 (failure) on mysql
204 $res = $dbh->query("INSERT INTO $table"
205 . " (sess_id, sess_data, sess_date, sess_ip)"
206 . " VALUES ($qid, $qdata, $time, $qip)");
208 $this->_disconnect();
209 return ! DB::isError($res);
213 * Destroys a session.
215 * Removes a session from the table.
218 * @return boolean true
221 function destroy ($id) {
222 $dbh = &$this->_connect();
223 $table = $this->_table;
224 $qid = $dbh->quote($id);
226 $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
228 $this->_disconnect();
233 * Cleans out all expired sessions.
235 * @param int $maxlifetime session's time to live.
236 * @return boolean true
239 function gc ($maxlifetime) {
240 $dbh = &$this->_connect();
241 $table = $this->_table;
242 $threshold = time() - $maxlifetime;
244 $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
246 $this->_disconnect();
250 // WhoIsOnline support
251 // TODO: ip-accesstime dynamic blocking API
252 function currentSessions() {
254 $dbh = &$this->_connect();
255 $table = $this->_table;
256 $res = $dbh->query("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
257 if (DB::isError($res) || empty($res))
259 while ($row = $res->fetchRow()) {
260 $data = $row['sess_data'];
261 $date = $row['sess_date'];
262 $ip = $row['sess_ip'];
263 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
264 $data = base64_decode($data);
265 if ($date < 908437560 or $date > 1588437560)
267 // session_data contains the <variable name> + "|" + <packed string>
268 // we need just the wiki_user object (might be array as well)
269 $user = strstr($data,"wiki_user|");
270 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
274 $this->_disconnect();
279 // self-written adodb-sessions
280 class DbSession_ADODB
283 var $_backend_type = "ADODB";
285 function DbSession_ADODB ($dbh, $table) {
288 $this->_table = $table;
290 ini_set('session.save_handler','user');
291 session_module_name('user'); // new style
292 session_set_save_handler(array(&$this, 'open'),
293 array(&$this, 'close'),
294 array(&$this, 'read'),
295 array(&$this, 'write'),
296 array(&$this, 'destroy'),
297 array(&$this, 'gc'));
301 function _connect() {
303 static $parsed = false;
305 if (!$dbh or !is_resource($dbh->_connectionID)) {
306 if (!$parsed) $parsed = parseDSN($request->_dbi->getParam('dsn'));
307 $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
308 $this->_dbh->Connect($parsed['hostspec'],$parsed['username'],
309 $parsed['password'], $parsed['database']);
315 function query($sql) {
316 return $this->_dbh->Execute($sql);
319 function quote($string) {
320 return $this->_dbh->qstr($string);
323 function _disconnect() {
324 if (0 and $this->_dbh)
325 $this->_dbh->close();
331 * Actually this function is a fake for session_set_save_handle.
332 * @param string $save_path a path to stored files
333 * @param string $session_name a name of the concrete file
334 * @return boolean true just a variable to notify PHP that everything
338 function open ($save_path, $session_name) {
339 //$this->log("_open($save_path, $session_name)");
346 * This function is called just after <i>write</i> call.
348 * @return boolean true just a variable to notify PHP that everything
353 //$this->log("_close()");
358 * Reads the session data from DB.
360 * @param string $id an id of current session
364 function read ($id) {
365 //$this->log("_read($id)");
366 $dbh = &$this->_connect();
367 $table = $this->_table;
368 $qid = $dbh->qstr($id);
370 $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
373 $this->_disconnect();
374 if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
375 $res = base64_decode($res);
380 * Saves the session data into DB.
382 * Just a comment: The "write" handler is not
383 * executed until after the output stream is closed. Thus,
384 * output from debugging statements in the "write" handler
385 * will never be seen in the browser. If debugging output
386 * is necessary, it is suggested that the debug output be
387 * written to a file instead.
390 * @param string $sess_data
391 * @return boolean true if data saved successfully and false
395 function write ($id, $sess_data) {
397 $dbh = &$this->_connect();
398 $table = $this->_table;
399 $qid = $dbh->qstr($id);
400 $qip = $dbh->qstr($GLOBALS['request']->get('REMOTE_ADDR'));
401 $time = $dbh->qstr(time());
403 // postgres can't handle binary data in a TEXT field.
404 if (isa($dbh, 'ADODB_postgres64'))
405 $sess_data = base64_encode($sess_data);
406 $qdata = $dbh->qstr($sess_data);
407 $rs = $dbh->Execute("UPDATE $table"
408 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
409 . " WHERE sess_id=$qid");
410 if ( ! $dbh->Affected_Rows() ) { // false or int > 0
411 $rs = $dbh->Execute("INSERT INTO $table"
412 . " (sess_id, sess_data, sess_date, sess_ip)"
413 . " VALUES ($qid, $qdata, $time, $qip)");
415 $result = ! $rs->EOF;
416 if ($result) $rs->free();
417 $this->_disconnect();
422 * Destroys a session.
424 * Removes a session from the table.
427 * @return boolean true
430 function destroy ($id) {
431 $dbh = &$this->_connect();
432 $table = $this->_table;
433 $qid = $dbh->qstr($id);
435 $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
437 $this->_disconnect();
442 * Cleans out all expired sessions.
444 * @param int $maxlifetime session's time to live.
445 * @return boolean true
448 function gc ($maxlifetime) {
449 $dbh = &$this->_connect();
450 $table = $this->_table;
451 $threshold = time() - $maxlifetime;
453 $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
455 $this->_disconnect();
459 // WhoIsOnline support.
460 // TODO: ip-accesstime dynamic blocking API
461 function currentSessions() {
463 $dbh = &$this->_connect();
464 $table = $this->_table;
465 $rs = $dbh->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
471 $row = $rs->fetchRow();
475 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
476 $data = base64_decode($data);
477 if ($date < 908437560 or $date > 1588437560)
479 // session_data contains the <variable name> + "|" + <packed string>
480 // we need just the wiki_user object (might be array as well)
481 $user = strstr($data,"wiki_user|");
482 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
488 $this->_disconnect();
496 * Values: date : IP : data
501 var $_backend_type = "dba";
503 function DbSession_dba (&$dbh, $table) {
505 ini_set('session.save_handler','user');
506 session_module_name('user'); // new style
507 session_set_save_handler(array(&$this, 'open'),
508 array(&$this, 'close'),
509 array(&$this, 'read'),
510 array(&$this, 'write'),
511 array(&$this, 'destroy'),
512 array(&$this, 'gc'));
516 function quote($str) { return $str; }
517 function query($sql) { return false; }
519 function _connect() {
525 $dba_handler = 'gdbm';
528 $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
529 $dbh = new DbaDatabase($dbfile, false, $dba_handler);
530 $dbh->set_timeout($timeout);
531 if (!$dbh->open('c')) {
532 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
534 $request->finish(fmt("%s: Can't open dba database", $dbfile));
541 function _disconnect() {
542 if (0 and isset($this->_dbh))
543 $this->_dbh->close();
546 function open ($save_path, $session_name) {
547 $dbh = &$this->_connect();
553 $this->_dbh->close();
556 function read ($id) {
557 $dbh = &$this->_connect();
558 $result = $dbh->get($id);
562 list(,,$packed) = explode(':', $result, 3);
563 $this->_disconnect();
567 function write ($id, $sess_data) {
568 $dbh = &$this->_connect();
570 $ip = $GLOBALS['request']->get('REMOTE_ADDR');
571 $dbh->set($id,$time.':'.$ip.':'.$sess_data);
572 $this->_disconnect();
576 function destroy ($id) {
577 $dbh = &$this->_connect();
579 $this->_disconnect();
583 function gc ($maxlifetime) {
584 $dbh = &$this->_connect();
585 $threshold = time() - $maxlifetime;
586 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
587 $result = $dbh->get($id);
588 list($date,,) = explode(':', $result, 3);
589 //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
590 if ($date < $threshold)
593 $this->_disconnect();
597 // WhoIsOnline support.
598 // TODO: ip-accesstime dynamic blocking API
599 function currentSessions() {
601 $dbh = &$this->_connect();
602 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
603 $result = $dbh->get($id);
604 list($date,$ip,$packed) = explode(':', $result, 3);
605 $data = unserialize($packed);
606 // session_data contains the <variable name> + "|" + <packed string>
607 // we need just the wiki_user object (might be array as well)
608 $user = strstr($data,"wiki_user|");
609 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
613 $this->_disconnect();
623 // c-hanging-comment-ender-p: nil
624 // indent-tabs-mode: nil