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