1 <?php rcs_id('$Id: DbSession.php,v 1.29 2005-01-30 23:08:22 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) { return $string; }
65 var $_backend_type = "SQL";
67 function DbSession_SQL (&$dbh, $table) {
70 $this->_table = $table;
72 ini_set('session.save_handler','user');
73 session_module_name('user'); // new style
74 session_set_save_handler(array(&$this, 'open'),
75 array(&$this, 'close'),
76 array(&$this, 'read'),
77 array(&$this, 'write'),
78 array(&$this, 'destroy'),
85 $this->_connected = is_resource($dbh->connection);
86 if (!$this->_connected) {
87 $res = $dbh->connect($dbh->dsn);
88 if (DB::isError($res)) {
89 error_log("PhpWiki::DbSession::_connect: " . $res->getMessage());
95 function query($sql) {
96 return $this->_dbh->query($sql);
98 // adds surrounding quotes
99 function quote($string) {
100 return $this->_dbh->quote($string);
103 function _disconnect() {
104 if (0 and $this->_connected)
105 $this->_dbh->disconnect();
111 * Actually this function is a fake for session_set_save_handle.
112 * @param string $save_path a path to stored files
113 * @param string $session_name a name of the concrete file
114 * @return boolean true just a variable to notify PHP that everything
118 function open ($save_path, $session_name) {
119 //$this->log("_open($save_path, $session_name)");
126 * This function is called just after <i>write</i> call.
128 * @return boolean true just a variable to notify PHP that everything
133 //$this->log("_close()");
138 * Reads the session data from DB.
140 * @param string $id an id of current session
144 function read ($id) {
145 //$this->log("_read($id)");
146 $dbh = &$this->_connect();
147 $table = $this->_table;
148 $qid = $dbh->quote($id);
150 $res = $dbh->getOne("SELECT sess_data FROM $table WHERE sess_id=$qid");
152 $this->_disconnect();
153 if (DB::isError($res) || empty($res))
155 if (isa($dbh, 'DB_pgsql'))
156 //if (preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
157 $res = base64_decode($res);
158 if (strlen($res) > 4000) {
159 trigger_error("Overlarge session data! ".strlen($res).
160 " gt. 4000", E_USER_WARNING);
162 $res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
163 $res = preg_replace('/s:12:"_cached_html";s:.+",s:4:"hits"/','s:4:"hits"',$res);
169 * Saves the session data into DB.
171 * Just a comment: The "write" handler is not
172 * executed until after the output stream is closed. Thus,
173 * output from debugging statements in the "write" handler
174 * will never be seen in the browser. If debugging output
175 * is necessary, it is suggested that the debug output be
176 * written to a file instead.
179 * @param string $sess_data
180 * @return boolean true if data saved successfully and false
184 function write ($id, $sess_data) {
186 $dbh = &$this->_connect();
187 //$dbh->unlock(false,1);
188 $table = $this->_table;
189 $qid = $dbh->quote($id);
190 $qip = $dbh->quote($GLOBALS['request']->get('REMOTE_ADDR'));
191 $time = $dbh->quote(time());
192 if (DEBUG and $sess_data == 'wiki_user|N;') {
193 trigger_error("delete empty session $qid", E_USER_WARNING);
195 print_r($GLOBALS['request']->_user);
199 // postgres can't handle binary data in a TEXT field.
200 if (isa($dbh, 'DB_pgsql'))
201 $sess_data = base64_encode($sess_data);
202 $qdata = $dbh->quote($sess_data);
204 $res = $dbh->query("UPDATE $table"
205 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
206 . " WHERE sess_id=$qid");
207 $result = $dbh->Affected_Rows();
208 if ( $result === false or $result < 1 ) { // 0 cannot happen, -1 (failure) on mysql
209 $res = $dbh->query("INSERT INTO $table"
210 . " (sess_id, sess_data, sess_date, sess_ip)"
211 . " VALUES ($qid, $qdata, $time, $qip)");
213 $this->_disconnect();
214 return ! DB::isError($res);
218 * Destroys a session.
220 * Removes a session from the table.
223 * @return boolean true
226 function destroy ($id) {
227 $dbh = &$this->_connect();
228 $table = $this->_table;
229 $qid = $dbh->quote($id);
231 $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
233 $this->_disconnect();
238 * Cleans out all expired sessions.
240 * @param int $maxlifetime session's time to live.
241 * @return boolean true
244 function gc ($maxlifetime) {
245 $dbh = &$this->_connect();
246 $table = $this->_table;
247 $threshold = time() - $maxlifetime;
249 $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
251 $this->_disconnect();
255 // WhoIsOnline support
256 // TODO: ip-accesstime dynamic blocking API
257 function currentSessions() {
259 $dbh = &$this->_connect();
260 $table = $this->_table;
261 $res = $dbh->query("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
262 if (DB::isError($res) || empty($res))
264 while ($row = $res->fetchRow()) {
265 $data = $row['sess_data'];
266 $date = $row['sess_date'];
267 $ip = $row['sess_ip'];
268 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
269 $data = base64_decode($data);
270 if ($date < 908437560 or $date > 1588437560)
272 // session_data contains the <variable name> + "|" + <packed string>
273 // we need just the wiki_user object (might be array as well)
274 $user = strstr($data,"wiki_user|");
275 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
279 $this->_disconnect();
284 // self-written adodb-sessions
285 class DbSession_ADODB
288 var $_backend_type = "ADODB";
290 function DbSession_ADODB ($dbh, $table) {
293 $this->_table = $table;
295 ini_set('session.save_handler','user');
296 session_module_name('user'); // new style
297 session_set_save_handler(array(&$this, 'open'),
298 array(&$this, 'close'),
299 array(&$this, 'read'),
300 array(&$this, 'write'),
301 array(&$this, 'destroy'),
302 array(&$this, 'gc'));
306 function _connect() {
308 static $parsed = false;
310 if (!$dbh or !is_resource($dbh->_connectionID)) {
311 if (!$parsed) $parsed = parseDSN($request->_dbi->getParam('dsn'));
312 $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
313 $this->_dbh->Connect($parsed['hostspec'],$parsed['username'],
314 $parsed['password'], $parsed['database']);
320 function query($sql) {
321 return $this->_dbh->Execute($sql);
324 function quote($string) {
325 return $this->_dbh->qstr($string);
328 function _disconnect() {
329 if (0 and $this->_dbh)
330 $this->_dbh->close();
336 * Actually this function is a fake for session_set_save_handle.
337 * @param string $save_path a path to stored files
338 * @param string $session_name a name of the concrete file
339 * @return boolean true just a variable to notify PHP that everything
343 function open ($save_path, $session_name) {
344 //$this->log("_open($save_path, $session_name)");
351 * This function is called just after <i>write</i> call.
353 * @return boolean true just a variable to notify PHP that everything
358 //$this->log("_close()");
363 * Reads the session data from DB.
365 * @param string $id an id of current session
369 function read ($id) {
370 //$this->log("_read($id)");
371 $dbh = &$this->_connect();
372 $table = $this->_table;
373 $qid = $dbh->qstr($id);
375 $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
378 $this->_disconnect();
379 if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
380 $res = base64_decode($res);
381 if (strlen($res) > 4000) {
382 trigger_error("Overlarge session data! ".strlen($res).
383 " gt. 4000", E_USER_WARNING);
385 $res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
386 $res = preg_replace('/s:12:"_cached_html";s:.+",s:4:"hits"/','s:4:"hits"',$res);
392 * Saves the session data into DB.
394 * Just a comment: The "write" handler is not
395 * executed until after the output stream is closed. Thus,
396 * output from debugging statements in the "write" handler
397 * will never be seen in the browser. If debugging output
398 * is necessary, it is suggested that the debug output be
399 * written to a file instead.
402 * @param string $sess_data
403 * @return boolean true if data saved successfully and false
407 function write ($id, $sess_data) {
409 $dbh = &$this->_connect();
410 $table = $this->_table;
411 $qid = $dbh->qstr($id);
412 $qip = $dbh->qstr($GLOBALS['request']->get('REMOTE_ADDR'));
413 $time = $dbh->qstr(time());
415 // postgres can't handle binary data in a TEXT field.
416 if (isa($dbh, 'ADODB_postgres64'))
417 $sess_data = base64_encode($sess_data);
418 $qdata = $dbh->qstr($sess_data);
419 $rs = $dbh->Execute("UPDATE $table"
420 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
421 . " WHERE sess_id=$qid");
422 $result = $dbh->Affected_Rows();
423 if ( $result === false or $result < 1 ) { // false or int > 0
424 $rs = $dbh->Execute("INSERT INTO $table"
425 . " (sess_id, sess_data, sess_date, sess_ip)"
426 . " VALUES ($qid, $qdata, $time, $qip)");
428 $result = ! $rs->EOF;
429 if ($result) $rs->free();
430 $this->_disconnect();
435 * Destroys a session.
437 * Removes a session from the table.
440 * @return boolean true
443 function destroy ($id) {
444 $dbh = &$this->_connect();
445 $table = $this->_table;
446 $qid = $dbh->qstr($id);
448 $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
450 $this->_disconnect();
455 * Cleans out all expired sessions.
457 * @param int $maxlifetime session's time to live.
458 * @return boolean true
461 function gc ($maxlifetime) {
462 $dbh = &$this->_connect();
463 $table = $this->_table;
464 $threshold = time() - $maxlifetime;
466 $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
468 $this->_disconnect();
472 // WhoIsOnline support.
473 // TODO: ip-accesstime dynamic blocking API
474 function currentSessions() {
476 $dbh = &$this->_connect();
477 $table = $this->_table;
478 $rs = $dbh->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
484 $row = $rs->fetchRow();
488 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
489 $data = base64_decode($data);
490 if ($date < 908437560 or $date > 1588437560)
492 // session_data contains the <variable name> + "|" + <packed string>
493 // we need just the wiki_user object (might be array as well)
494 $user = strstr($data,"wiki_user|");
495 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
501 $this->_disconnect();
509 * Values: date : IP : data
514 var $_backend_type = "dba";
516 function DbSession_dba (&$dbh, $table) {
518 ini_set('session.save_handler','user');
519 session_module_name('user'); // new style
520 session_set_save_handler(array(&$this, 'open'),
521 array(&$this, 'close'),
522 array(&$this, 'read'),
523 array(&$this, 'write'),
524 array(&$this, 'destroy'),
525 array(&$this, 'gc'));
529 function quote($str) { return $str; }
530 function query($sql) { return false; }
532 function _connect() {
538 $dba_handler = 'gdbm';
541 $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
542 $dbh = new DbaDatabase($dbfile, false, $dba_handler);
543 $dbh->set_timeout($timeout);
544 if (!$dbh->open('c')) {
545 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
547 $request->finish(fmt("%s: Can't open dba database", $dbfile));
554 function _disconnect() {
555 if (0 and isset($this->_dbh))
556 $this->_dbh->close();
559 function open ($save_path, $session_name) {
560 $dbh = &$this->_connect();
566 $this->_dbh->close();
569 function read ($id) {
570 $dbh = &$this->_connect();
571 $result = $dbh->get($id);
575 list(,,$packed) = explode(':', $result, 3);
576 $this->_disconnect();
577 if (strlen($packed) > 4000) {
578 trigger_error("Overlarge session data!", E_USER_WARNING);
580 //$res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
585 function write ($id, $sess_data) {
586 $dbh = &$this->_connect();
588 $ip = $GLOBALS['request']->get('REMOTE_ADDR');
589 if (strlen($sess_data) > 4000) {
590 trigger_error("Overlarge session data!", E_USER_WARNING);
593 $dbh->set($id,$time.':'.$ip.':'.$sess_data);
594 $this->_disconnect();
598 function destroy ($id) {
599 $dbh = &$this->_connect();
601 $this->_disconnect();
605 function gc ($maxlifetime) {
606 $dbh = &$this->_connect();
607 $threshold = time() - $maxlifetime;
608 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
609 $result = $dbh->get($id);
610 list($date,,) = explode(':', $result, 3);
611 //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
612 if ($date < $threshold)
615 $this->_disconnect();
619 // WhoIsOnline support.
620 // TODO: ip-accesstime dynamic blocking API
621 function currentSessions() {
623 $dbh = &$this->_connect();
624 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
625 $result = $dbh->get($id);
626 list($date,$ip,$packed) = explode(':', $result, 3);
627 if (!$packed) continue;
628 //$data = @unserialize($packed);
629 // session_data contains the <variable name> + "|" + <packed string>
630 // we need just the wiki_user object (might be array as well)
631 if ($date < 908437560 or $date > 1588437560)
633 $user = strstr($packed, "wiki_user|");
634 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
638 $this->_disconnect();
648 // c-hanging-comment-ender-p: nil
649 // indent-tabs-mode: nil