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