]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/DbSession.php
fix duplicate session id error
[SourceForge/phpwiki.git] / lib / DbSession.php
1 <?php rcs_id('$Id: DbSession.php,v 1.19 2004-06-02 17:13:16 rurban Exp $');
2
3 /**
4  * Store sessions data in Pear DB / ADODB / dba / ....
5  *
6  * History
7  *
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.
12  */
13 class DbSession
14 {
15     var $_backend;
16     /**
17      * Constructor
18      *
19      * @param mixed $dbh
20      * Pear DB handle, or WikiDB object (from which the Pear DB handle will
21      * be extracted.
22      *
23      * @param string $table
24      * Name of SQL table containing session data.
25      */
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;
34             
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')
39                 return false;
40                 
41             if (class_exists($class)) {
42                 $this->_backend = new $class($backend->_dbh, $table);
43                 return $this->_backend;
44             }
45         }
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);
50         return false;
51     }
52     
53     function currentSessions() {
54         return $this->_backend->currentSessions();
55     }
56     function query($sql) {
57         return $this->_backend->query($sql);
58     }
59     function quote($string) {
60         return $this->_backend->quote($string);
61     }
62
63 }
64
65 class DbSession_SQL
66 extends DbSession
67 {
68     var $_backend_type = "SQL";
69
70     function DbSession_SQL (&$dbh, $table) {
71
72         $this->_dbh = $dbh;
73         $this->_table = $table;
74
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'),
82                                  array(&$this, 'gc'));
83         return $this;
84     }
85
86     function _connect() {
87         $dbh = &$this->_dbh;
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());
93             }
94         }
95         return $dbh;
96     }
97     
98     function query($sql) {
99         return $this->_dbh->query($sql);
100     }
101
102     function quote($string) {
103         return $this->_dbh->quote($string);
104     }
105
106     function _disconnect() {
107         if (0 and $this->_connected)
108             $this->_dbh->disconnect();
109     }
110
111     /**
112      * Opens a session.
113      *
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 
118      * is good.
119      * @access private
120      */
121     function open ($save_path, $session_name) {
122         //$this->log("_open($save_path, $session_name)");
123         return true;
124     }
125
126     /**
127      * Closes a session.
128      *
129      * This function is called just after <i>write</i> call.
130      *
131      * @return boolean true just a variable to notify PHP that everything 
132      * is good.
133      * @access private
134      */
135     function close() {
136         //$this->log("_close()");
137         return true;
138     }
139
140     /**
141      * Reads the session data from DB.
142      *
143      * @param  string $id an id of current session
144      * @return string
145      * @access private
146      */
147     function read ($id) {
148         //$this->log("_read($id)");
149         $dbh = &$this->_connect();
150         $table = $this->_table;
151         $qid = $dbh->quote($id);
152     
153         $res = $dbh->getOne("SELECT sess_data FROM $table WHERE sess_id=$qid");
154
155         $this->_disconnect();
156         if (DB::isError($res) || empty($res))
157             return '';
158         if (isa($dbh, 'DB_pgsql'))
159             //if (preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
160             $res = base64_decode($res);
161         return $res;
162     }
163   
164     /**
165      * Saves the session data into DB.
166      *
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.
173      *
174      * @param  string $id
175      * @param  string $sess_data
176      * @return boolean true if data saved successfully  and false
177      * otherwise.
178      * @access private
179      */
180     function write ($id, $sess_data) {
181         
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 = time();
188         if (DEBUG and $sess_data == 'wiki_user|N;') {
189             trigger_error("delete empty session $qid", E_USER_WARNING);
190             /*echo "<pre>";
191             print_r($GLOBALS['request']->_user);
192             echo "</pre>";
193             */
194         }
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);
199         
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)");
207         }
208         $this->_disconnect();
209         return ! DB::isError($res);
210     }
211
212     /**
213      * Destroys a session.
214      *
215      * Removes a session from the table.
216      *
217      * @param  string $id
218      * @return boolean true 
219      * @access private
220      */
221     function destroy ($id) {
222         $dbh = &$this->_connect();
223         $table = $this->_table;
224         $qid = $dbh->quote($id);
225
226         $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
227
228         $this->_disconnect();
229         return true;     
230     }
231
232     /**
233      * Cleans out all expired sessions.
234      *
235      * @param  int $maxlifetime session's time to live.
236      * @return boolean true
237      * @access private
238      */
239     function gc ($maxlifetime) {
240         $dbh = &$this->_connect();
241         $table = $this->_table;
242         $threshold = time() - $maxlifetime;
243
244         $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
245
246         $this->_disconnect();
247         return true;
248     }
249
250     // WhoIsOnline support
251     // TODO: ip-accesstime dynamic blocking API
252     function currentSessions() {
253         $sessions = array();
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))
258             return $sessions;
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             // session_data contains the <variable name> + "|" + <packed string>
266             // we need just the wiki_user object (might be array as well)
267             $user = strstr($data,"wiki_user|");
268             $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
269                                 'date' => $date,
270                                 'ip'   => $ip);
271         }
272         $this->_disconnect();
273         return $sessions;
274     }
275 }
276
277 // self-written adodb-sessions
278 class DbSession_ADODB
279 extends DbSession
280 {
281     var $_backend_type = "ADODB";
282
283     function DbSession_ADODB ($dbh, $table) {
284
285         $this->_dbh = $dbh;
286         $this->_table = $table;
287
288         ini_set('session.save_handler','user');
289         session_module_name('user'); // new style
290         session_set_save_handler(array(&$this, 'open'),
291                                  array(&$this, 'close'),
292                                  array(&$this, 'read'),
293                                  array(&$this, 'write'),
294                                  array(&$this, 'destroy'),
295                                  array(&$this, 'gc'));
296         return $this;
297     }
298
299     function _connect() {
300         global $request;
301         static $parsed = false;
302         $dbh = &$this->_dbh;
303         if (!$dbh or !is_resource($dbh->_connectionID)) {
304             if (!$parsed) $parsed = parseDSN($request->_dbi->getParam('dsn'));
305             $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
306             $this->_dbh->Connect($parsed['hostspec'],$parsed['username'], 
307                                  $parsed['password'], $parsed['database']);
308             $dbh = &$this->_dbh;                             
309         }
310         return $dbh;
311     }
312     
313     function query($sql) {
314         return $this->_dbh->Execute($sql);
315     }
316
317     function quote($string) {
318         return $this->_dbh->qstr($string);
319     }
320
321     function _disconnect() {
322         if (0 and $this->_dbh)
323             $this->_dbh->close();
324     }
325
326     /**
327      * Opens a session.
328      *
329      * Actually this function is a fake for session_set_save_handle.
330      * @param  string $save_path a path to stored files
331      * @param  string $session_name a name of the concrete file
332      * @return boolean true just a variable to notify PHP that everything 
333      * is good.
334      * @access private
335      */
336     function open ($save_path, $session_name) {
337         //$this->log("_open($save_path, $session_name)");
338         return true;
339     }
340
341     /**
342      * Closes a session.
343      *
344      * This function is called just after <i>write</i> call.
345      *
346      * @return boolean true just a variable to notify PHP that everything 
347      * is good.
348      * @access private
349      */
350     function close() {
351         //$this->log("_close()");
352         return true;
353     }
354
355     /**
356      * Reads the session data from DB.
357      *
358      * @param  string $id an id of current session
359      * @return string
360      * @access private
361      */
362     function read ($id) {
363         //$this->log("_read($id)");
364         $dbh = &$this->_connect();
365         $table = $this->_table;
366         $qid = $dbh->qstr($id);
367         $res = '';
368         $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
369         if ($row)
370             $res = $row[0];
371         $this->_disconnect();
372         if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
373             $res = base64_decode($res);
374         return $res;
375     }
376   
377     /**
378      * Saves the session data into DB.
379      *
380      * Just  a  comment:       The  "write"  handler  is  not 
381      * executed until after the output stream is closed. Thus,
382      * output from debugging statements in the "write" handler
383      * will  never be seen in the browser. If debugging output
384      * is  necessary, it is suggested that the debug output be
385      * written to a file instead.
386      *
387      * @param  string $id
388      * @param  string $sess_data
389      * @return boolean true if data saved successfully  and false
390      * otherwise.
391      * @access private
392      */
393     function write ($id, $sess_data) {
394         
395         $dbh = &$this->_connect();
396         $table = $this->_table;
397         $qid = $dbh->qstr($id);
398         $qip = $dbh->qstr($GLOBALS['request']->get('REMOTE_ADDR'));
399         $time = time();
400
401         // postgres can't handle binary data in a TEXT field.
402         if (isa($dbh, 'ADODB_postgres64'))
403             $sess_data = base64_encode($sess_data);
404         $qdata = $dbh->qstr($sess_data);
405         $rs = $dbh->Execute("UPDATE $table"
406                            . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
407                            . " WHERE sess_id=$qid");
408         if ( ! $dbh->Affected_Rows() ) { // false or int > 0
409             $rs = $dbh->Execute("INSERT INTO $table"
410                                . " (sess_id, sess_data, sess_date, sess_ip)"
411                                . " VALUES ($qid, $qdata, $time, $qip)");
412         }
413         $result = ! $rs->EOF;
414         if ($result) $rs->free();                        
415         $this->_disconnect();
416         return $result;
417     }
418
419     /**
420      * Destroys a session.
421      *
422      * Removes a session from the table.
423      *
424      * @param  string $id
425      * @return boolean true 
426      * @access private
427      */
428     function destroy ($id) {
429         $dbh = &$this->_connect();
430         $table = $this->_table;
431         $qid = $dbh->qstr($id);
432
433         $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
434
435         $this->_disconnect();
436         return true;     
437     }
438
439     /**
440      * Cleans out all expired sessions.
441      *
442      * @param  int $maxlifetime session's time to live.
443      * @return boolean true
444      * @access private
445      */
446     function gc ($maxlifetime) {
447         $dbh = &$this->_connect();
448         $table = $this->_table;
449         $threshold = time() - $maxlifetime;
450
451         $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
452
453         $this->_disconnect();
454         return true;
455     }
456
457     // WhoIsOnline support. 
458     // TODO: ip-accesstime dynamic blocking API
459     function currentSessions() {
460         $sessions = array();
461         $dbh = &$this->_connect();
462         $table = $this->_table;
463         $rs = $this->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
464         if ($rs->EOF) {
465             $rs->free();
466             return $sessions;
467         }
468         while (!$rs->EOF) {
469             $row = $rs->fetchRow();
470             $data = $row[0];
471             $date = $row[1];
472             $ip   = $row[2];
473             if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
474                 $data = base64_decode($data);
475             // session_data contains the <variable name> + "|" + <packed string>
476             // we need just the wiki_user object (might be array as well)
477             $user = strstr($data,"wiki_user|");
478             $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
479                                 'date' => $date,
480                                 'ip' => $ip);
481             $rs->MoveNext();
482         }
483         $rs->free();
484         $this->_disconnect();
485         return $sessions;
486     }
487 }
488
489 /** DBA Sessions
490  *  session:
491  *    Index: session_id
492  *   Values: date : IP : data
493  */
494 class DbSession_dba
495 extends DbSession
496 {
497     var $_backend_type = "dba";
498
499     function DbSession_dba (&$dbh, $table) {
500         $this->_dbh = $dbh;
501         ini_set('session.save_handler','user');
502         session_module_name('user'); // new style
503         session_set_save_handler(array(&$this, 'open'),
504                                  array(&$this, 'close'),
505                                  array(&$this, 'read'),
506                                  array(&$this, 'write'),
507                                  array(&$this, 'destroy'),
508                                  array(&$this, 'gc'));
509         return $this;
510     }
511
512     function quote($str) { return $str; }
513     function query($sql) { return false; }
514
515     function _connect() {
516         global $DBParams;
517         $dbh = &$this->_dbh;
518         if (!$dbh) {
519             $directory = '/tmp';
520             $prefix = 'wiki_';
521             $dba_handler = 'gdbm';
522             $timeout = 20;
523             extract($DBParams);
524             $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
525             $dbh = new DbaDatabase($dbfile, false, $dba_handler);
526             $dbh->set_timeout($timeout);
527             if (!$dbh->open('c')) {
528                 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
529                 global $request;
530                 $request->finish(fmt("%s: Can't open dba database", $dbfile));
531             }
532             $this->_dbh = &$dbh;
533         }
534         return $dbh;
535     }
536
537     function _disconnect() {
538         if (0 and isset($this->_dbh))
539             $this->_dbh->close();
540     }
541
542     function open ($save_path, $session_name) {
543         $dbh = &$this->_connect();
544         $dbh->open();
545     }
546
547     function close() {
548         if ($this->_dbh)
549             $this->_dbh->close();
550     }
551
552     function read ($id) {
553         $dbh = &$this->_connect();
554         $result = $dbh->get($id);
555         if (!$result) {
556             return false;
557         }
558         list(,,$packed) = explode(':', $result, 3);
559         $this->_disconnect();
560         return $packed;
561     }
562   
563     function write ($id, $sess_data) {
564         $dbh = &$this->_connect();
565         $time = time();
566         $ip = $GLOBALS['request']->get('REMOTE_ADDR');
567         $dbh->set($id,$time.':'.$ip.':'.$sess_data);
568         $this->_disconnect();
569         return true;
570     }
571
572     function destroy ($id) {
573         $dbh = &$this->_connect();
574         $dbh->delete($id);
575         $this->_disconnect();
576         return true;
577     }
578
579     function gc ($maxlifetime) {
580         $dbh = &$this->_connect();
581         $threshold = time() - $maxlifetime;
582         for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
583             $result = $dbh->get($id);
584             list($date,,) = explode(':', $result, 3);
585             //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
586             if ($date < $threshold)
587                 $dbh->delete($id);
588         }
589         $this->_disconnect();
590         return true;
591     }
592
593     // WhoIsOnline support. 
594     // TODO: ip-accesstime dynamic blocking API
595     function currentSessions() {
596         $sessions = array();
597         $dbh = &$this->_connect();
598         for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
599             $result = $dbh->get($id);
600             list($date,$ip,$packed) = explode(':', $result, 3);
601             $data = unserialize($packed);
602             // session_data contains the <variable name> + "|" + <packed string>
603             // we need just the wiki_user object (might be array as well)
604             $user = strstr($data,"wiki_user|");
605             $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
606                                 'date' => $date,
607                                 'ip' => $ip);
608         }
609         $this->_disconnect();
610         return $sessions;
611     }
612 }
613
614
615 // Local Variables:
616 // mode: php
617 // tab-width: 8
618 // c-basic-offset: 4
619 // c-hanging-comment-ender-p: nil
620 // indent-tabs-mode: nil
621 // End:
622 ?>