1 <?php rcs_id('$Id: DbSession.php,v 1.24 2004-11-21 11:59:14 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);
161 if (strlen($res) > 4000) {
162 trigger_error("Overlarge session data! ".strlen($res).
163 " gt. 4000", E_USER_WARNING);
165 $res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
166 $res = preg_replace('/s:12:"_cached_html";s:.+",s:4:"hits"/','s:4:"hits"',$res);
172 * Saves the session data into DB.
174 * Just a comment: The "write" handler is not
175 * executed until after the output stream is closed. Thus,
176 * output from debugging statements in the "write" handler
177 * will never be seen in the browser. If debugging output
178 * is necessary, it is suggested that the debug output be
179 * written to a file instead.
182 * @param string $sess_data
183 * @return boolean true if data saved successfully and false
187 function write ($id, $sess_data) {
189 $dbh = &$this->_connect();
190 //$dbh->unlock(false,1);
191 $table = $this->_table;
192 $qid = $dbh->quote($id);
193 $qip = $dbh->quote($GLOBALS['request']->get('REMOTE_ADDR'));
194 $time = $dbh->quote(time());
195 if (DEBUG and $sess_data == 'wiki_user|N;') {
196 trigger_error("delete empty session $qid", E_USER_WARNING);
198 print_r($GLOBALS['request']->_user);
202 // postgres can't handle binary data in a TEXT field.
203 if (isa($dbh, 'DB_pgsql'))
204 $sess_data = base64_encode($sess_data);
205 $qdata = $dbh->quote($sess_data);
207 $res = $dbh->query("UPDATE $table"
208 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
209 . " WHERE sess_id=$qid");
210 if ( ! $dbh->affectedRows() ) { // 0 (none) or -1 (failure) on mysql
211 $res = $dbh->query("INSERT INTO $table"
212 . " (sess_id, sess_data, sess_date, sess_ip)"
213 . " VALUES ($qid, $qdata, $time, $qip)");
215 $this->_disconnect();
216 return ! DB::isError($res);
220 * Destroys a session.
222 * Removes a session from the table.
225 * @return boolean true
228 function destroy ($id) {
229 $dbh = &$this->_connect();
230 $table = $this->_table;
231 $qid = $dbh->quote($id);
233 $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
235 $this->_disconnect();
240 * Cleans out all expired sessions.
242 * @param int $maxlifetime session's time to live.
243 * @return boolean true
246 function gc ($maxlifetime) {
247 $dbh = &$this->_connect();
248 $table = $this->_table;
249 $threshold = time() - $maxlifetime;
251 $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
253 $this->_disconnect();
257 // WhoIsOnline support
258 // TODO: ip-accesstime dynamic blocking API
259 function currentSessions() {
261 $dbh = &$this->_connect();
262 $table = $this->_table;
263 $res = $dbh->query("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
264 if (DB::isError($res) || empty($res))
266 while ($row = $res->fetchRow()) {
267 $data = $row['sess_data'];
268 $date = $row['sess_date'];
269 $ip = $row['sess_ip'];
270 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
271 $data = base64_decode($data);
272 if ($date < 908437560 or $date > 1588437560)
274 // session_data contains the <variable name> + "|" + <packed string>
275 // we need just the wiki_user object (might be array as well)
276 $user = strstr($data,"wiki_user|");
277 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
281 $this->_disconnect();
286 // self-written adodb-sessions
287 class DbSession_ADODB
290 var $_backend_type = "ADODB";
292 function DbSession_ADODB ($dbh, $table) {
295 $this->_table = $table;
297 ini_set('session.save_handler','user');
298 session_module_name('user'); // new style
299 session_set_save_handler(array(&$this, 'open'),
300 array(&$this, 'close'),
301 array(&$this, 'read'),
302 array(&$this, 'write'),
303 array(&$this, 'destroy'),
304 array(&$this, 'gc'));
308 function _connect() {
310 static $parsed = false;
312 if (!$dbh or !is_resource($dbh->_connectionID)) {
313 if (!$parsed) $parsed = parseDSN($request->_dbi->getParam('dsn'));
314 $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
315 $this->_dbh->Connect($parsed['hostspec'],$parsed['username'],
316 $parsed['password'], $parsed['database']);
322 function query($sql) {
323 return $this->_dbh->Execute($sql);
326 function quote($string) {
327 return $this->_dbh->qstr($string);
330 function _disconnect() {
331 if (0 and $this->_dbh)
332 $this->_dbh->close();
338 * Actually this function is a fake for session_set_save_handle.
339 * @param string $save_path a path to stored files
340 * @param string $session_name a name of the concrete file
341 * @return boolean true just a variable to notify PHP that everything
345 function open ($save_path, $session_name) {
346 //$this->log("_open($save_path, $session_name)");
353 * This function is called just after <i>write</i> call.
355 * @return boolean true just a variable to notify PHP that everything
360 //$this->log("_close()");
365 * Reads the session data from DB.
367 * @param string $id an id of current session
371 function read ($id) {
372 //$this->log("_read($id)");
373 $dbh = &$this->_connect();
374 $table = $this->_table;
375 $qid = $dbh->qstr($id);
377 $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
380 $this->_disconnect();
381 if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
382 $res = base64_decode($res);
383 if (strlen($res) > 4000) {
384 trigger_error("Overlarge session data! ".strlen($res).
385 " gt. 4000", E_USER_WARNING);
387 $res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
388 $res = preg_replace('/s:12:"_cached_html";s:.+",s:4:"hits"/','s:4:"hits"',$res);
394 * Saves the session data into DB.
396 * Just a comment: The "write" handler is not
397 * executed until after the output stream is closed. Thus,
398 * output from debugging statements in the "write" handler
399 * will never be seen in the browser. If debugging output
400 * is necessary, it is suggested that the debug output be
401 * written to a file instead.
404 * @param string $sess_data
405 * @return boolean true if data saved successfully and false
409 function write ($id, $sess_data) {
411 $dbh = &$this->_connect();
412 $table = $this->_table;
413 $qid = $dbh->qstr($id);
414 $qip = $dbh->qstr($GLOBALS['request']->get('REMOTE_ADDR'));
415 $time = $dbh->qstr(time());
417 // postgres can't handle binary data in a TEXT field.
418 if (isa($dbh, 'ADODB_postgres64'))
419 $sess_data = base64_encode($sess_data);
420 $qdata = $dbh->qstr($sess_data);
421 $rs = $dbh->Execute("UPDATE $table"
422 . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
423 . " WHERE sess_id=$qid");
424 if ( ! $dbh->Affected_Rows() ) { // false or int > 0
425 $rs = $dbh->Execute("INSERT INTO $table"
426 . " (sess_id, sess_data, sess_date, sess_ip)"
427 . " VALUES ($qid, $qdata, $time, $qip)");
429 $result = ! $rs->EOF;
430 if ($result) $rs->free();
431 $this->_disconnect();
436 * Destroys a session.
438 * Removes a session from the table.
441 * @return boolean true
444 function destroy ($id) {
445 $dbh = &$this->_connect();
446 $table = $this->_table;
447 $qid = $dbh->qstr($id);
449 $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
451 $this->_disconnect();
456 * Cleans out all expired sessions.
458 * @param int $maxlifetime session's time to live.
459 * @return boolean true
462 function gc ($maxlifetime) {
463 $dbh = &$this->_connect();
464 $table = $this->_table;
465 $threshold = time() - $maxlifetime;
467 $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
469 $this->_disconnect();
473 // WhoIsOnline support.
474 // TODO: ip-accesstime dynamic blocking API
475 function currentSessions() {
477 $dbh = &$this->_connect();
478 $table = $this->_table;
479 $rs = $dbh->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
485 $row = $rs->fetchRow();
489 if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
490 $data = base64_decode($data);
491 if ($date < 908437560 or $date > 1588437560)
493 // session_data contains the <variable name> + "|" + <packed string>
494 // we need just the wiki_user object (might be array as well)
495 $user = strstr($data,"wiki_user|");
496 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
502 $this->_disconnect();
510 * Values: date : IP : data
515 var $_backend_type = "dba";
517 function DbSession_dba (&$dbh, $table) {
519 ini_set('session.save_handler','user');
520 session_module_name('user'); // new style
521 session_set_save_handler(array(&$this, 'open'),
522 array(&$this, 'close'),
523 array(&$this, 'read'),
524 array(&$this, 'write'),
525 array(&$this, 'destroy'),
526 array(&$this, 'gc'));
530 function quote($str) { return $str; }
531 function query($sql) { return false; }
533 function _connect() {
539 $dba_handler = 'gdbm';
542 $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
543 $dbh = new DbaDatabase($dbfile, false, $dba_handler);
544 $dbh->set_timeout($timeout);
545 if (!$dbh->open('c')) {
546 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
548 $request->finish(fmt("%s: Can't open dba database", $dbfile));
555 function _disconnect() {
556 if (0 and isset($this->_dbh))
557 $this->_dbh->close();
560 function open ($save_path, $session_name) {
561 $dbh = &$this->_connect();
567 $this->_dbh->close();
570 function read ($id) {
571 $dbh = &$this->_connect();
572 $result = $dbh->get($id);
576 list(,,$packed) = explode(':', $result, 3);
577 $this->_disconnect();
578 if (strlen($packed) > 4000) {
579 trigger_error("Overlarge session data!", E_USER_WARNING);
581 //$res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
586 function write ($id, $sess_data) {
587 $dbh = &$this->_connect();
589 $ip = $GLOBALS['request']->get('REMOTE_ADDR');
590 $dbh->set($id,$time.':'.$ip.':'.$sess_data);
591 $this->_disconnect();
595 function destroy ($id) {
596 $dbh = &$this->_connect();
598 $this->_disconnect();
602 function gc ($maxlifetime) {
603 $dbh = &$this->_connect();
604 $threshold = time() - $maxlifetime;
605 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
606 $result = $dbh->get($id);
607 list($date,,) = explode(':', $result, 3);
608 //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
609 if ($date < $threshold)
612 $this->_disconnect();
616 // WhoIsOnline support.
617 // TODO: ip-accesstime dynamic blocking API
618 function currentSessions() {
620 $dbh = &$this->_connect();
621 for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
622 $result = $dbh->get($id);
623 list($date,$ip,$packed) = explode(':', $result, 3);
624 $data = unserialize($packed);
625 // session_data contains the <variable name> + "|" + <packed string>
626 // we need just the wiki_user object (might be array as well)
627 $user = strstr($data,"wiki_user|");
628 $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
632 $this->_disconnect();
642 // c-hanging-comment-ender-p: nil
643 // indent-tabs-mode: nil