]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/DbSession.php
fixed another DbSession crash: wrong WikiGroup vars
[SourceForge/phpwiki.git] / lib / DbSession.php
1 <?php rcs_id('$Id: DbSession.php,v 1.21 2004-07-01 09:29:40 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         if (strlen($res) > 4000) {
162             trigger_error("Overlarge session data!", E_USER_WARNING);
163             $res = '';
164             //$res = preg_replace('/s:6:"_cache";O:12:"WikiDB_cache".+}$/',"",$res);
165         }    
166         return $res;
167     }
168   
169     /**
170      * Saves the session data into DB.
171      *
172      * Just  a  comment:       The  "write"  handler  is  not 
173      * executed until after the output stream is closed. Thus,
174      * output from debugging statements in the "write" handler
175      * will  never be seen in the browser. If debugging output
176      * is  necessary, it is suggested that the debug output be
177      * written to a file instead.
178      *
179      * @param  string $id
180      * @param  string $sess_data
181      * @return boolean true if data saved successfully  and false
182      * otherwise.
183      * @access private
184      */
185     function write ($id, $sess_data) {
186         
187         $dbh = &$this->_connect();
188         //$dbh->unlock(false,1);
189         $table = $this->_table;
190         $qid = $dbh->quote($id);
191         $qip = $dbh->quote($GLOBALS['request']->get('REMOTE_ADDR'));
192         $time = $dbh->quote(time());
193         if (DEBUG and $sess_data == 'wiki_user|N;') {
194             trigger_error("delete empty session $qid", E_USER_WARNING);
195             /*echo "<pre>";
196             print_r($GLOBALS['request']->_user);
197             echo "</pre>";
198             */
199         }
200         // postgres can't handle binary data in a TEXT field.
201         if (isa($dbh, 'DB_pgsql'))
202             $sess_data = base64_encode($sess_data);
203         $qdata = $dbh->quote($sess_data);
204         
205         $res = $dbh->query("UPDATE $table"
206                            . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
207                            . " WHERE sess_id=$qid");
208         if ( ! $dbh->affectedRows() ) { // 0 (none) or -1 (failure) on mysql
209             $res = $dbh->query("INSERT INTO $table"
210                                . " (sess_id, sess_data, sess_date, sess_ip)"
211                                . " VALUES ($qid, $qdata, $time, $qip)");
212         }
213         $this->_disconnect();
214         return ! DB::isError($res);
215     }
216
217     /**
218      * Destroys a session.
219      *
220      * Removes a session from the table.
221      *
222      * @param  string $id
223      * @return boolean true 
224      * @access private
225      */
226     function destroy ($id) {
227         $dbh = &$this->_connect();
228         $table = $this->_table;
229         $qid = $dbh->quote($id);
230
231         $dbh->query("DELETE FROM $table WHERE sess_id=$qid");
232
233         $this->_disconnect();
234         return true;     
235     }
236
237     /**
238      * Cleans out all expired sessions.
239      *
240      * @param  int $maxlifetime session's time to live.
241      * @return boolean true
242      * @access private
243      */
244     function gc ($maxlifetime) {
245         $dbh = &$this->_connect();
246         $table = $this->_table;
247         $threshold = time() - $maxlifetime;
248
249         $dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
250
251         $this->_disconnect();
252         return true;
253     }
254
255     // WhoIsOnline support
256     // TODO: ip-accesstime dynamic blocking API
257     function currentSessions() {
258         $sessions = array();
259         $dbh = &$this->_connect();
260         $table = $this->_table;
261         $res = $dbh->query("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
262         if (DB::isError($res) || empty($res))
263             return $sessions;
264         while ($row = $res->fetchRow()) {
265             $data = $row['sess_data'];
266             $date = $row['sess_date'];
267             $ip   = $row['sess_ip'];
268             if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
269                 $data = base64_decode($data);
270             if ($date < 908437560 or $date > 1588437560)
271                 $date = 0;
272             // session_data contains the <variable name> + "|" + <packed string>
273             // we need just the wiki_user object (might be array as well)
274             $user = strstr($data,"wiki_user|");
275             $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
276                                 'date' => $date,
277                                 'ip'   => $ip);
278         }
279         $this->_disconnect();
280         return $sessions;
281     }
282 }
283
284 // self-written adodb-sessions
285 class DbSession_ADODB
286 extends DbSession
287 {
288     var $_backend_type = "ADODB";
289
290     function DbSession_ADODB ($dbh, $table) {
291
292         $this->_dbh = $dbh;
293         $this->_table = $table;
294
295         ini_set('session.save_handler','user');
296         session_module_name('user'); // new style
297         session_set_save_handler(array(&$this, 'open'),
298                                  array(&$this, 'close'),
299                                  array(&$this, 'read'),
300                                  array(&$this, 'write'),
301                                  array(&$this, 'destroy'),
302                                  array(&$this, 'gc'));
303         return $this;
304     }
305
306     function _connect() {
307         global $request;
308         static $parsed = false;
309         $dbh = &$this->_dbh;
310         if (!$dbh or !is_resource($dbh->_connectionID)) {
311             if (!$parsed) $parsed = parseDSN($request->_dbi->getParam('dsn'));
312             $this->_dbh = &ADONewConnection($parsed['phptype']); // Probably only MySql works just now
313             $this->_dbh->Connect($parsed['hostspec'],$parsed['username'], 
314                                  $parsed['password'], $parsed['database']);
315             $dbh = &$this->_dbh;                             
316         }
317         return $dbh;
318     }
319     
320     function query($sql) {
321         return $this->_dbh->Execute($sql);
322     }
323
324     function quote($string) {
325         return $this->_dbh->qstr($string);
326     }
327
328     function _disconnect() {
329         if (0 and $this->_dbh)
330             $this->_dbh->close();
331     }
332
333     /**
334      * Opens a session.
335      *
336      * Actually this function is a fake for session_set_save_handle.
337      * @param  string $save_path a path to stored files
338      * @param  string $session_name a name of the concrete file
339      * @return boolean true just a variable to notify PHP that everything 
340      * is good.
341      * @access private
342      */
343     function open ($save_path, $session_name) {
344         //$this->log("_open($save_path, $session_name)");
345         return true;
346     }
347
348     /**
349      * Closes a session.
350      *
351      * This function is called just after <i>write</i> call.
352      *
353      * @return boolean true just a variable to notify PHP that everything 
354      * is good.
355      * @access private
356      */
357     function close() {
358         //$this->log("_close()");
359         return true;
360     }
361
362     /**
363      * Reads the session data from DB.
364      *
365      * @param  string $id an id of current session
366      * @return string
367      * @access private
368      */
369     function read ($id) {
370         //$this->log("_read($id)");
371         $dbh = &$this->_connect();
372         $table = $this->_table;
373         $qid = $dbh->qstr($id);
374         $res = '';
375         $row = $dbh->GetRow("SELECT sess_data FROM $table WHERE sess_id=$qid");
376         if ($row)
377             $res = $row[0];
378         $this->_disconnect();
379         if (!empty($res) and preg_match('|^[a-zA-Z0-9/+=]+$|', $res))
380             $res = base64_decode($res);
381         if (strlen($res) > 4000) {
382             trigger_error("Overlarge session data!", E_USER_WARNING);
383             $res = '';
384         }
385         return $res;
386     }
387   
388     /**
389      * Saves the session data into DB.
390      *
391      * Just  a  comment:       The  "write"  handler  is  not 
392      * executed until after the output stream is closed. Thus,
393      * output from debugging statements in the "write" handler
394      * will  never be seen in the browser. If debugging output
395      * is  necessary, it is suggested that the debug output be
396      * written to a file instead.
397      *
398      * @param  string $id
399      * @param  string $sess_data
400      * @return boolean true if data saved successfully  and false
401      * otherwise.
402      * @access private
403      */
404     function write ($id, $sess_data) {
405         
406         $dbh = &$this->_connect();
407         $table = $this->_table;
408         $qid = $dbh->qstr($id);
409         $qip = $dbh->qstr($GLOBALS['request']->get('REMOTE_ADDR'));
410         $time = $dbh->qstr(time());
411
412         // postgres can't handle binary data in a TEXT field.
413         if (isa($dbh, 'ADODB_postgres64'))
414             $sess_data = base64_encode($sess_data);
415         $qdata = $dbh->qstr($sess_data);
416         $rs = $dbh->Execute("UPDATE $table"
417                            . " SET sess_data=$qdata, sess_date=$time, sess_ip=$qip"
418                            . " WHERE sess_id=$qid");
419         if ( ! $dbh->Affected_Rows() ) { // false or int > 0
420             $rs = $dbh->Execute("INSERT INTO $table"
421                                . " (sess_id, sess_data, sess_date, sess_ip)"
422                                . " VALUES ($qid, $qdata, $time, $qip)");
423         }
424         $result = ! $rs->EOF;
425         if ($result) $rs->free();                        
426         $this->_disconnect();
427         return $result;
428     }
429
430     /**
431      * Destroys a session.
432      *
433      * Removes a session from the table.
434      *
435      * @param  string $id
436      * @return boolean true 
437      * @access private
438      */
439     function destroy ($id) {
440         $dbh = &$this->_connect();
441         $table = $this->_table;
442         $qid = $dbh->qstr($id);
443
444         $dbh->Execute("DELETE FROM $table WHERE sess_id=$qid");
445
446         $this->_disconnect();
447         return true;     
448     }
449
450     /**
451      * Cleans out all expired sessions.
452      *
453      * @param  int $maxlifetime session's time to live.
454      * @return boolean true
455      * @access private
456      */
457     function gc ($maxlifetime) {
458         $dbh = &$this->_connect();
459         $table = $this->_table;
460         $threshold = time() - $maxlifetime;
461
462         $dbh->Execute("DELETE FROM $table WHERE sess_date < $threshold");
463
464         $this->_disconnect();
465         return true;
466     }
467
468     // WhoIsOnline support. 
469     // TODO: ip-accesstime dynamic blocking API
470     function currentSessions() {
471         $sessions = array();
472         $dbh = &$this->_connect();
473         $table = $this->_table;
474         $rs = $dbh->Execute("SELECT sess_data,sess_date,sess_ip FROM $table ORDER BY sess_date DESC");
475         if ($rs->EOF) {
476             $rs->free();
477             return $sessions;
478         }
479         while (!$rs->EOF) {
480             $row = $rs->fetchRow();
481             $data = $row[0];
482             $date = $row[1];
483             $ip   = $row[2];
484             if (preg_match('|^[a-zA-Z0-9/+=]+$|', $data))
485                 $data = base64_decode($data);
486             if ($date < 908437560 or $date > 1588437560)
487                 $date = 0;
488             // session_data contains the <variable name> + "|" + <packed string>
489             // we need just the wiki_user object (might be array as well)
490             $user = strstr($data,"wiki_user|");
491             $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
492                                 'date' => $date,
493                                 'ip' => $ip);
494             $rs->MoveNext();
495         }
496         $rs->free();
497         $this->_disconnect();
498         return $sessions;
499     }
500 }
501
502 /** DBA Sessions
503  *  session:
504  *    Index: session_id
505  *   Values: date : IP : data
506  */
507 class DbSession_dba
508 extends DbSession
509 {
510     var $_backend_type = "dba";
511
512     function DbSession_dba (&$dbh, $table) {
513         $this->_dbh = $dbh;
514         ini_set('session.save_handler','user');
515         session_module_name('user'); // new style
516         session_set_save_handler(array(&$this, 'open'),
517                                  array(&$this, 'close'),
518                                  array(&$this, 'read'),
519                                  array(&$this, 'write'),
520                                  array(&$this, 'destroy'),
521                                  array(&$this, 'gc'));
522         return $this;
523     }
524
525     function quote($str) { return $str; }
526     function query($sql) { return false; }
527
528     function _connect() {
529         global $DBParams;
530         $dbh = &$this->_dbh;
531         if (!$dbh) {
532             $directory = '/tmp';
533             $prefix = 'wiki_';
534             $dba_handler = 'gdbm';
535             $timeout = 20;
536             extract($DBParams);
537             $dbfile = "$directory/$prefix" . 'session' . '.' . $dba_handler;
538             $dbh = new DbaDatabase($dbfile, false, $dba_handler);
539             $dbh->set_timeout($timeout);
540             if (!$dbh->open('c')) {
541                 trigger_error(sprintf(_("%s: Can't open dba database"), $dbfile), E_USER_ERROR);
542                 global $request;
543                 $request->finish(fmt("%s: Can't open dba database", $dbfile));
544             }
545             $this->_dbh = &$dbh;
546         }
547         return $dbh;
548     }
549
550     function _disconnect() {
551         if (0 and isset($this->_dbh))
552             $this->_dbh->close();
553     }
554
555     function open ($save_path, $session_name) {
556         $dbh = &$this->_connect();
557         $dbh->open();
558     }
559
560     function close() {
561         if ($this->_dbh)
562             $this->_dbh->close();
563     }
564
565     function read ($id) {
566         $dbh = &$this->_connect();
567         $result = $dbh->get($id);
568         if (!$result) {
569             return false;
570         }
571         list(,,$packed) = explode(':', $result, 3);
572         $this->_disconnect();
573         return $packed;
574     }
575   
576     function write ($id, $sess_data) {
577         $dbh = &$this->_connect();
578         $time = time();
579         $ip = $GLOBALS['request']->get('REMOTE_ADDR');
580         $dbh->set($id,$time.':'.$ip.':'.$sess_data);
581         $this->_disconnect();
582         return true;
583     }
584
585     function destroy ($id) {
586         $dbh = &$this->_connect();
587         $dbh->delete($id);
588         $this->_disconnect();
589         return true;
590     }
591
592     function gc ($maxlifetime) {
593         $dbh = &$this->_connect();
594         $threshold = time() - $maxlifetime;
595         for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
596             $result = $dbh->get($id);
597             list($date,,) = explode(':', $result, 3);
598             //$dbh->query("DELETE FROM $table WHERE sess_date < $threshold");
599             if ($date < $threshold)
600                 $dbh->delete($id);
601         }
602         $this->_disconnect();
603         return true;
604     }
605
606     // WhoIsOnline support. 
607     // TODO: ip-accesstime dynamic blocking API
608     function currentSessions() {
609         $sessions = array();
610         $dbh = &$this->_connect();
611         for ($id = $dbh->firstkey(); $id !== false; $id = $dbh->nextkey()) {
612             $result = $dbh->get($id);
613             list($date,$ip,$packed) = explode(':', $result, 3);
614             $data = unserialize($packed);
615             // session_data contains the <variable name> + "|" + <packed string>
616             // we need just the wiki_user object (might be array as well)
617             $user = strstr($data,"wiki_user|");
618             $sessions[] = array('wiki_user' => substr($user,10), // from "O:" onwards
619                                 'date' => $date,
620                                 'ip' => $ip);
621         }
622         $this->_disconnect();
623         return $sessions;
624     }
625 }
626
627
628 // Local Variables:
629 // mode: php
630 // tab-width: 8
631 // c-basic-offset: 4
632 // c-hanging-comment-ender-p: nil
633 // indent-tabs-mode: nil
634 // End:
635 ?>