]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/DbSession.php
whoisonline fix
[SourceForge/phpwiki.git] / lib / DbSession.php
1 <?php rcs_id('$Id: DbSession.php,v 1.20 2004-06-28 16:34:30 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 = $dbh->quote(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             if ($date < 908437560 or $date > 1588437560)
266                 $date = 0;
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
271                                 'date' => $date,
272                                 'ip'   => $ip);
273         }
274         $this->_disconnect();
275         return $sessions;
276     }
277 }
278
279 // self-written adodb-sessions
280 class DbSession_ADODB
281 extends DbSession
282 {
283     var $_backend_type = "ADODB";
284
285     function DbSession_ADODB ($dbh, $table) {
286
287         $this->_dbh = $dbh;
288         $this->_table = $table;
289
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'));
298         return $this;
299     }
300
301     function _connect() {
302         global $request;
303         static $parsed = false;
304         $dbh = &$this->_dbh;
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']);
310             $dbh = &$this->_dbh;                             
311         }
312         return $dbh;
313     }
314     
315     function query($sql) {
316         return $this->_dbh->Execute($sql);
317     }
318
319     function quote($string) {
320         return $this->_dbh->qstr($string);
321     }
322
323     function _disconnect() {
324         if (0 and $this->_dbh)
325             $this->_dbh->close();
326     }
327
328     /**
329      * Opens a session.
330      *
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 
335      * is good.
336      * @access private
337      */
338     function open ($save_path, $session_name) {
339         //$this->log("_open($save_path, $session_name)");
340         return true;
341     }
342
343     /**
344      * Closes a session.
345      *
346      * This function is called just after <i>write</i> call.
347      *
348      * @return boolean true just a variable to notify PHP that everything 
349      * is good.
350      * @access private
351      */
352     function close() {
353         //$this->log("_close()");
354         return true;
355     }
356
357     /**
358      * Reads the session data from DB.
359      *
360      * @param  string $id an id of current session
361      * @return string
362      * @access private
363      */
364     function read ($id) {
365         //$this->log("_read($id)");
366         $dbh = &$this->_connect();
367         $table = $this->_table;
368         $qid = $dbh->qstr($id);
369         $res = '';
370         $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
371         if ($row)
372             $res = $row[0];
373         $this->_disconnect();
374         if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
375             $res = base64_decode($res);
376         return $res;
377     }
378   
379     /**
380      * Saves the session data into DB.
381      *
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.
388      *
389      * @param  string $id
390      * @param  string $sess_data
391      * @return boolean true if data saved successfully  and false
392      * otherwise.
393      * @access private
394      */
395     function write ($id, $sess_data) {
396         
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());
402
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)");
414         }
415         $result = ! $rs->EOF;
416         if ($result) $rs->free();                        
417         $this->_disconnect();
418         return $result;
419     }
420
421     /**
422      * Destroys a session.
423      *
424      * Removes a session from the table.
425      *
426      * @param  string $id
427      * @return boolean true 
428      * @access private
429      */
430     function destroy ($id) {
431         $dbh = &$this->_connect();
432         $table = $this->_table;
433         $qid = $dbh->qstr($id);
434
435         $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
436
437         $this->_disconnect();
438         return true;     
439     }
440
441     /**
442      * Cleans out all expired sessions.
443      *
444      * @param  int $maxlifetime session's time to live.
445      * @return boolean true
446      * @access private
447      */
448     function gc ($maxlifetime) {
449         $dbh = &$this->_connect();
450         $table = $this->_table;
451         $threshold = time() - $maxlifetime;
452
453         $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
454
455         $this->_disconnect();
456         return true;
457     }
458
459     // WhoIsOnline support. 
460     // TODO: ip-accesstime dynamic blocking API
461     function currentSessions() {
462         $sessions = array();
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");
466         if ($rs->EOF) {
467             $rs->free();
468             return $sessions;
469         }
470         while (!$rs->EOF) {
471             $row = $rs->fetchRow();
472             $data = $row[0];
473             $date = $row[1];
474             $ip   = $row[2];
475             if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
476                 $data = base64_decode($data);
477             if ($date < 908437560 or $date > 1588437560)
478                 $date = 0;
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
483                                 'date' => $date,
484                                 'ip' => $ip);
485             $rs->MoveNext();
486         }
487         $rs->free();
488         $this->_disconnect();
489         return $sessions;
490     }
491 }
492
493 /** DBA Sessions
494  *  session:
495  *    Index: session_id
496  *   Values: date : IP : data
497  */
498 class DbSession_dba
499 extends DbSession
500 {
501     var $_backend_type = "dba";
502
503     function DbSession_dba (&$dbh, $table) {
504         $this->_dbh = $dbh;
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'));
513         return $this;
514     }
515
516     function quote($str) { return $str; }
517     function query($sql) { return false; }
518
519     function _connect() {
520         global $DBParams;
521         $dbh = &$this->_dbh;
522         if (!$dbh) {
523             $directory = '/tmp';
524             $prefix = 'wiki_';
525             $dba_handler = 'gdbm';
526             $timeout = 20;
527             extract($DBParams);
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);
533                 global $request;
534                 $request->finish(fmt("%s: Can't open dba database", $dbfile));
535             }
536             $this->_dbh = &$dbh;
537         }
538         return $dbh;
539     }
540
541     function _disconnect() {
542         if (0 and isset($this->_dbh))
543             $this->_dbh->close();
544     }
545
546     function open ($save_path, $session_name) {
547         $dbh = &$this->_connect();
548         $dbh->open();
549     }
550
551     function close() {
552         if ($this->_dbh)
553             $this->_dbh->close();
554     }
555
556     function read ($id) {
557         $dbh = &$this->_connect();
558         $result = $dbh->get($id);
559         if (!$result) {
560             return false;
561         }
562         list(,,$packed) = explode(':', $result, 3);
563         $this->_disconnect();
564         return $packed;
565     }
566   
567     function write ($id, $sess_data) {
568         $dbh = &$this->_connect();
569         $time = time();
570         $ip = $GLOBALS['request']->get('REMOTE_ADDR');
571         $dbh->set($id,$time.':'.$ip.':'.$sess_data);
572         $this->_disconnect();
573         return true;
574     }
575
576     function destroy ($id) {
577         $dbh = &$this->_connect();
578         $dbh->delete($id);
579         $this->_disconnect();
580         return true;
581     }
582
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)
591                 $dbh->delete($id);
592         }
593         $this->_disconnect();
594         return true;
595     }
596
597     // WhoIsOnline support. 
598     // TODO: ip-accesstime dynamic blocking API
599     function currentSessions() {
600         $sessions = array();
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
610                                 'date' => $date,
611                                 'ip' => $ip);
612         }
613         $this->_disconnect();
614         return $sessions;
615     }
616 }
617
618
619 // Local Variables:
620 // mode: php
621 // tab-width: 8
622 // c-basic-offset: 4
623 // c-hanging-comment-ender-p: nil
624 // indent-tabs-mode: nil
625 // End:
626 ?>