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