1 <?php rcs_id('$Id: DbSession.php,v 1.26 2004-12-06 19:25:50 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 if ( ! $dbh->affectedRows() ) { // 0 (none) or -1 (failure) on mysql
208 $res = $dbh->query("INSERT INTO $table"
209 . " (sess_id, sess_data, sess_date, sess_ip)"
210 . " VALUES ($qid, $qdata, $time, $qip)");
212 $this->_disconnect();
213 return ! DB::isError($res);
217 * Destroys a session.
219 * Removes a session from the table.
222 * @return boolean true
225 function destroy ($id) {
226 $dbh = &$this->_connect();
227 $table = $this->_table;
228 $qid = $dbh->quote($id);
230 $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
232 $this->_disconnect();
237 * Cleans out all expired sessions.
239 * @param int $maxlifetime session's time to live.
240 * @return boolean true
243 function gc ($maxlifetime) {
244 $dbh = &$this->_connect();
245 $table = $this->_table;
246 $threshold = time() - $maxlifetime;
248 $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
250 $this->_disconnect();
254 // WhoIsOnline support
255 // TODO: ip-accesstime dynamic blocking API
256 function currentSessions() {
258 $dbh = &$this->_connect();
259 $table = $this->_table;
260 $res = $dbh->query("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
261 if (DB::isError($res) || empty($res))
263 while ($row = $res->fetchRow()) {
264 $data = $row['sess_data'];
265 $date = $row['sess_date'];
266 $ip = $row['sess_ip'];
267 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
268 $data = base64_decode($data);
269 if ($date < 908437560 or $date > 1588437560)
271 // session_data contains the <variable name> + "|" + <packed string>
272 // we need just the wiki_user object (might be array as well)
273 $user = strstr($data,"wiki_user|");
274 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
278 $this->_disconnect();
283 // self-written adodb-sessions
284 class DbSession_ADODB
287 var $_backend_type = "ADODB";
289 function DbSession_ADODB ($dbh, $table) {
292 $this->_table = $table;
294 ini_set('session.save_handler','user');
295 session_module_name('user'); // new style
296 session_set_save_handler(array(&$this, 'open'),
297 array(&$this, 'close'),
298 array(&$this, 'read'),
299 array(&$this, 'write'),
300 array(&$this, 'destroy'),
301 array(&$this, 'gc'));
305 function _connect() {
307 static $parsed = false;
309 if (!$dbh or !is_resource($dbh->_connectionID)) {
310 if (!$parsed) $parsed = parseDSN($request->_dbi->getParam('dsn'));
311 $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
312 $this->_dbh->Connect($parsed['hostspec'],$parsed['username'],
313 $parsed['password'], $parsed['database']);
319 function query($sql) {
320 return $this->_dbh->Execute($sql);
323 function quote($string) {
324 return $this->_dbh->qstr($string);
327 function _disconnect() {
328 if (0 and $this->_dbh)
329 $this->_dbh->close();
335 * Actually this function is a fake for session_set_save_handle.
336 * @param string $save_path a path to stored files
337 * @param string $session_name a name of the concrete file
338 * @return boolean true just a variable to notify PHP that everything
342 function open ($save_path, $session_name) {
343 //$this->log("_open($save_path, $session_name)");
350 * This function is called just after <i>write</i> call.
352 * @return boolean true just a variable to notify PHP that everything
357 //$this->log("_close()");
362 * Reads the session data from DB.
364 * @param string $id an id of current session
368 function read ($id) {
369 //$this->log("_read($id)");
370 $dbh = &$this->_connect();
371 $table = $this->_table;
372 $qid = $dbh->qstr($id);
374 $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
377 $this->_disconnect();
378 if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
379 $res = base64_decode($res);
380 if (strlen($res) > 4000) {
381 trigger_error("Overlarge session data! ".strlen($res).
382 " gt. 4000", E_USER_WARNING);
384 $res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
385 $res = preg_replace('/s:12:"_cached_html";s:.+",s:4:"hits"/','s:4:"hits"',$res);
391 * Saves the session data into DB.
393 * Just a comment: The "write" handler is not
394 * executed until after the output stream is closed. Thus,
395 * output from debugging statements in the "write" handler
396 * will never be seen in the browser. If debugging output
397 * is necessary, it is suggested that the debug output be
398 * written to a file instead.
401 * @param string $sess_data
402 * @return boolean true if data saved successfully and false
406 function write ($id, $sess_data) {
408 $dbh = &$this->_connect();
409 $table = $this->_table;
410 $qid = $dbh->qstr($id);
411 $qip = $dbh->qstr($GLOBALS['request']->get('REMOTE_ADDR'));
412 $time = $dbh->qstr(time());
414 // postgres can't handle binary data in a TEXT field.
415 if (isa($dbh, 'ADODB_postgres64'))
416 $sess_data = base64_encode($sess_data);
417 $qdata = $dbh->qstr($sess_data);
418 $rs = $dbh->Execute("UPDATE $table"
419 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
420 . " WHERE sess_id=$qid");
421 if ( ! $dbh->Affected_Rows() ) { // false or int > 0
422 $rs = $dbh->Execute("INSERT INTO $table"
423 . " (sess_id, sess_data, sess_date, sess_ip)"
424 . " VALUES ($qid, $qdata, $time, $qip)");
426 $result = ! $rs->EOF;
427 if ($result) $rs->free();
428 $this->_disconnect();
433 * Destroys a session.
435 * Removes a session from the table.
438 * @return boolean true
441 function destroy ($id) {
442 $dbh = &$this->_connect();
443 $table = $this->_table;
444 $qid = $dbh->qstr($id);
446 $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
448 $this->_disconnect();
453 * Cleans out all expired sessions.
455 * @param int $maxlifetime session's time to live.
456 * @return boolean true
459 function gc ($maxlifetime) {
460 $dbh = &$this->_connect();
461 $table = $this->_table;
462 $threshold = time() - $maxlifetime;
464 $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
466 $this->_disconnect();
470 // WhoIsOnline support.
471 // TODO: ip-accesstime dynamic blocking API
472 function currentSessions() {
474 $dbh = &$this->_connect();
475 $table = $this->_table;
476 $rs = $dbh->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
482 $row = $rs->fetchRow();
486 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
487 $data = base64_decode($data);
488 if ($date < 908437560 or $date > 1588437560)
490 // session_data contains the <variable name> + "|" + <packed string>
491 // we need just the wiki_user object (might be array as well)
492 $user = strstr($data,"wiki_user|");
493 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
499 $this->_disconnect();
507 * Values: date : IP : data
512 var $_backend_type = "dba";
514 function DbSession_dba (&$dbh, $table) {
516 ini_set('session.save_handler','user');
517 session_module_name('user'); // new style
518 session_set_save_handler(array(&$this, 'open'),
519 array(&$this, 'close'),
520 array(&$this, 'read'),
521 array(&$this, 'write'),
522 array(&$this, 'destroy'),
523 array(&$this, 'gc'));
527 function quote($str) { return $str; }
528 function query($sql) { return false; }
530 function _connect() {
536 $dba_handler = 'gdbm';
539 $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
540 $dbh = new DbaDatabase($dbfile, false, $dba_handler);
541 $dbh->set_timeout($timeout);
542 if (!$dbh->open('c')) {
543 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
545 $request->finish(fmt("%s: Can't open dba database", $dbfile));
552 function _disconnect() {
553 if (0 and isset($this->_dbh))
554 $this->_dbh->close();
557 function open ($save_path, $session_name) {
558 $dbh = &$this->_connect();
564 $this->_dbh->close();
567 function read ($id) {
568 $dbh = &$this->_connect();
569 $result = $dbh->get($id);
573 list(,,$packed) = explode(':', $result, 3);
574 $this->_disconnect();
575 if (strlen($packed) > 4000) {
576 trigger_error("Overlarge session data!", E_USER_WARNING);
578 //$res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
583 function write ($id, $sess_data) {
584 $dbh = &$this->_connect();
586 $ip = $GLOBALS['request']->get('REMOTE_ADDR');
587 $dbh->set($id,$time.':'.$ip.':'.$sess_data);
588 $this->_disconnect();
592 function destroy ($id) {
593 $dbh = &$this->_connect();
595 $this->_disconnect();
599 function gc ($maxlifetime) {
600 $dbh = &$this->_connect();
601 $threshold = time() - $maxlifetime;
602 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
603 $result = $dbh->get($id);
604 list($date,,) = explode(':', $result, 3);
605 //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
606 if ($date < $threshold)
609 $this->_disconnect();
613 // WhoIsOnline support.
614 // TODO: ip-accesstime dynamic blocking API
615 function currentSessions() {
617 $dbh = &$this->_connect();
618 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
619 $result = $dbh->get($id);
620 list($date,$ip,$packed) = explode(':', $result, 3);
621 if (!$packed) continue;
622 $data = unserialize($packed);
623 // session_data contains the <variable name> + "|" + <packed string>
624 // we need just the wiki_user object (might be array as well)
625 $user = strstr($data,"wiki_user|");
626 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
630 $this->_disconnect();
640 // c-hanging-comment-ender-p: nil
641 // indent-tabs-mode: nil