]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUserNew.php
several performance improvements, esp. with peardb
[SourceForge/phpwiki.git] / lib / WikiUserNew.php
1 <?php //-*-php-*-
2 rcs_id('$Id: WikiUserNew.php,v 1.23 2004-02-27 13:21:17 rurban Exp $');
3
4 /**
5  * This is a complete OOP rewrite of the old WikiUser code with various
6  * configurable external authentification methods.
7  *
8  * There's only one entry point, the function WikiUser which returns 
9  * a WikiUser object, which contains the user's preferences.
10  * This object might get upgraded during the login step and later also.
11  * There exist three preferences storage methods: cookie, homepage and db,
12  * and multiple password checking methods.
13  * See index.php for $USER_AUTH_ORDER[] and USER_AUTH_POLICY if 
14  * ALLOW_USER_PASSWORDS is defined.
15  *
16  * Each user object must define the two preferences methods 
17  *  getPreferences(), setPreferences(), 
18  * and the following 1-4 auth methods
19  *  checkPass()  must be defined by all classes,
20  *  userExists() only if USER_AUTH_POLICY'=='strict' 
21  *  mayChangePass()  only if the password is storable.
22  *  storePass()  only if the password is storable.
23  *
24  * WikiUser() given no name, returns an _AnonUser (anonymous user)
25  * object, who may or may not have a cookie. 
26  * However, if the there's a cookie with the userid or a session, 
27  * the user is upgraded to the matching user object.
28  * Given a user name, returns a _BogoUser object, who may or may not 
29  * have a cookie and/or PersonalPage, one of the various _PassUser objects 
30  * or an _AdminUser object.
31  *
32  * Takes care of passwords, all preference loading/storing in the
33  * user's page and any cookies. lib/main.php will query the user object to
34  * verify the password as appropriate.
35  *
36  * Notes by 2004-01-25 03:43:45 rurban
37  * Test it by defining ENABLE_USER_NEW in index.php
38  * 1) Now a ForbiddenUser is returned instead of false.
39  * 2) Previously ALLOW_ANON_USER = false meant that anon users cannot edit, 
40  * but may browse. Now with ALLOW_ANON_USER = false he may not browse, 
41  * which is needed to disable browse PagePermissions. Hmm...
42  * I added now ALLOW_ANON_EDIT = true to makes things clear. 
43  * (which replaces REQUIRE_SIGNIN_BEFORE_EDIT)
44  *
45  *  Authors: Reini Urban (the tricky parts), 
46  *           Carsten Klapp (started rolling the ball)
47  */
48
49 define('WIKIAUTH_FORBIDDEN', -1); // Completely not allowed.
50 define('WIKIAUTH_ANON', 0);       // Not signed in.
51 define('WIKIAUTH_BOGO', 1);       // Any valid WikiWord is enough.
52 define('WIKIAUTH_USER', 2);       // Bogo user with a password.
53 define('WIKIAUTH_ADMIN', 10);     // UserName == ADMIN_USER.
54
55 if (!defined('COOKIE_EXPIRATION_DAYS')) define('COOKIE_EXPIRATION_DAYS', 365);
56 if (!defined('COOKIE_DOMAIN'))          define('COOKIE_DOMAIN', '/');
57
58 if (!defined('EDITWIDTH_MIN_COLS'))     define('EDITWIDTH_MIN_COLS',     30);
59 if (!defined('EDITWIDTH_MAX_COLS'))     define('EDITWIDTH_MAX_COLS',    150);
60 if (!defined('EDITWIDTH_DEFAULT_COLS')) define('EDITWIDTH_DEFAULT_COLS', 80);
61
62 if (!defined('EDITHEIGHT_MIN_ROWS'))     define('EDITHEIGHT_MIN_ROWS',      5);
63 if (!defined('EDITHEIGHT_MAX_ROWS'))     define('EDITHEIGHT_MAX_ROWS',     80);
64 if (!defined('EDITHEIGHT_DEFAULT_ROWS')) define('EDITHEIGHT_DEFAULT_ROWS', 22);
65
66 define('TIMEOFFSET_MIN_HOURS', -26);
67 define('TIMEOFFSET_MAX_HOURS',  26);
68 if (!defined('TIMEOFFSET_DEFAULT_HOURS')) define('TIMEOFFSET_DEFAULT_HOURS', 0);
69
70 /**
71  * There are be the following constants in index.php to 
72  * establish login parameters:
73  *
74  * ALLOW_ANON_USER         default true
75  * ALLOW_ANON_EDIT         default true
76  * ALLOW_BOGO_LOGIN        default true
77  * ALLOW_USER_PASSWORDS    default true
78  * PASSWORD_LENGTH_MINIMUM default 6?
79  *
80  * To require user passwords for editing:
81  * ALLOW_ANON_USER  = true
82  * ALLOW_ANON_EDIT  = false   (before named REQUIRE_SIGNIN_BEFORE_EDIT)
83  * ALLOW_BOGO_LOGIN = false
84  * ALLOW_USER_PASSWORDS = true
85  *
86  * To establish a COMPLETELY private wiki, such as an internal
87  * corporate one:
88  * ALLOW_ANON_USER = false
89  * (and probably require user passwords as described above). In this
90  * case the user will be prompted to login immediately upon accessing
91  * any page.
92  *
93  * There are other possible combinations, but the typical wiki (such
94  * as PhpWiki.sf.net) would usually just leave all four enabled.
95  *
96  */
97
98 // The last object in the row is the bad guy...
99 if (!is_array($USER_AUTH_ORDER))
100     $USER_AUTH_ORDER = array("Forbidden");
101 else
102     $USER_AUTH_ORDER[] = "Forbidden";
103
104 // Local convenience functions.
105 function _isAnonUserAllowed() {
106     return (defined('ALLOW_ANON_USER') && ALLOW_ANON_USER);
107 }
108 function _isBogoUserAllowed() {
109     return (defined('ALLOW_BOGO_LOGIN') && ALLOW_BOGO_LOGIN);
110 }
111 function _isUserPasswordsAllowed() {
112     return (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS);
113 }
114
115
116 // Possibly upgrade userobject functions.
117 function _determineAdminUserOrOtherUser($UserName) {
118     // Sanity check. User name is a condition of the definition of the
119     // _AdminUser, _BogoUser and _passuser.
120     if (!$UserName)
121         return $GLOBALS['ForbiddenUser'];
122
123     if ($UserName == ADMIN_USER)
124         return new _AdminUser($UserName);
125     else
126         return _determineBogoUserOrPassUser($UserName);
127 }
128
129 function _determineBogoUserOrPassUser($UserName) {
130     global $ForbiddenUser;
131
132     // Sanity check. User name is a condition of the definition of
133     // _BogoUser and _PassUser.
134     if (!$UserName)
135         return $ForbiddenUser;
136
137     // Check for password and possibly upgrade user object.
138     // $_BogoUser = new _BogoUser($UserName);
139     if (_isBogoUserAllowed()) {
140         $_BogoUser = new _BogoLoginPassUser($UserName);
141         if ($_BogoUser->userExists())
142             return $_BogoUser;
143     }
144     if (_isUserPasswordsAllowed()) {
145         if (isset($_BogoUser) and $_BogoUser->_prefs->get('passwd'))
146             return new _PassUser($UserName,$_BogoUser->_prefs);
147         else { // PassUsers override BogoUsers if they exist
148             $_PassUser = new _PassUser($UserName,isset($_BogoUser) ? $_BogoUser->_prefs : false);
149             if ($_PassUser->userExists())
150                 return $_PassUser;
151         }
152     }
153     // Passwords are not allowed, and Bogo is disallowed too. (Only
154     // the admin can sign in).
155     return $ForbiddenUser;
156 }
157
158 /**
159  * Primary WikiUser function, called by main.php.
160  * 
161  * This determines the user's type and returns an appropriate user
162  * object. lib/main.php then querys the resultant object for password
163  * validity as necessary.
164  *
165  * If an _AnonUser object is returned, the user may only browse pages
166  * (and save prefs in a cookie).
167  *
168  * To disable access but provide prefs the global $ForbiddenUser class 
169  * is returned. (was previously false)
170  * 
171  */
172 function WikiUser ($UserName = '') {
173     global $ForbiddenUser;
174
175     //Maybe: Check sessionvar for username & save username into
176     //sessionvar (may be more appropriate to do this in lib/main.php).
177     if ($UserName) {
178         $ForbiddenUser = new _ForbiddenUser($UserName);
179         // Found a user name.
180         return _determineAdminUserOrOtherUser($UserName);
181     }
182     elseif (!empty($_SESSION['userid'])) {
183         // Found a user name.
184         $ForbiddenUser = new _ForbiddenUser($_SESSION['userid']);
185         return _determineAdminUserOrOtherUser($_SESSION['userid']);
186     }
187     else {
188         // Check for autologin pref in cookie and possibly upgrade
189         // user object to another type.
190         $_AnonUser = new _AnonUser();
191         if ($UserName = $_AnonUser->_userid && $_AnonUser->_prefs->get('autologin')) {
192             // Found a user name.
193             $ForbiddenUser = new _ForbiddenUser($UserName);
194             return _determineAdminUserOrOtherUser($UserName);
195         }
196         else {
197             $ForbiddenUser = new _ForbiddenUser();
198             if (_isAnonUserAllowed())
199                 return $_AnonUser;
200             return $ForbiddenUser; // User must sign in to browse pages.
201         }
202         return $ForbiddenUser;     // User must sign in with a password.
203     }
204     /*
205     trigger_error("DEBUG: Note: End of function reached in WikiUser." . " "
206                   . "Unexpectedly, an appropriate user class could not be determined.");
207     return $ForbiddenUser; // Failsafe.
208     */
209 }
210
211 function WikiUserClassname() {
212     return '_WikiUser';
213 }
214
215 function UpgradeUser ($olduser, $user) {
216     if (isa($user,'_WikiUser') and isa($olduser,'_WikiUser')) {
217         // populate the upgraded class $olduser with the values from the new user object
218         foreach (get_object_vars($user) as $k => $v) {
219             if (!empty($v)) $olduser->$k = $v;  
220         }
221         $GLOBALS['request']->_user = $olduser;
222         return $olduser;
223     } else {
224         return false;
225     }
226 }
227
228 function UserExists ($UserName) {
229     global $request;
230     if (!($user = $request->getUser()))
231         $user = WikiUser($UserName);
232     if (!$user) 
233         return false;
234     if ($user->userExists($UserName)) {
235         $request->_user = $user;
236         return true;
237     }
238     if (isa($user,'_BogoUser'))
239         $user = new _PassUser($UserName,$user->_prefs);
240     while ($user = $user->nextClass()) {
241         return $user->userExists($UserName);
242         $this = $user; // does this work on all PHP version?
243     }
244     $request->_user = $GLOBALS['ForbiddenUser'];
245     return false;
246 }
247
248 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
249
250 // Base WikiUser class.
251 class _WikiUser
252 {
253     var $_userid = '';
254
255     var $_level = WIKIAUTH_FORBIDDEN;
256     var $_prefs = false;
257     var $_HomePagehandle = false;
258
259     // constructor
260     function _WikiUser($UserName='', $prefs=false) {
261
262         $this->_userid = $UserName;
263         if ($UserName) {
264             $this->hasHomePage();
265         }
266         if (!$this->_prefs) {
267             if ($prefs) $this->_prefs = $prefs;
268             else $this->getPreferences();
269         }
270     }
271
272     function UserName() {
273         if (!empty($this->_userid))
274             return $this->_userid;
275     }
276
277     function getPreferences() {
278         trigger_error("DEBUG: Note: undefined _WikiUser class trying to load prefs." . " "
279                       . "New subclasses of _WikiUser must override this function.");
280         return false;
281     }
282
283     function setPreferences($prefs, $id_only) {
284         trigger_error("DEBUG: Note: undefined _WikiUser class trying to save prefs." . " "
285                       . "New subclasses of _WikiUser must override this function.");
286         return false;
287     }
288
289     function userExists() {
290         return $this->hasHomePage();
291     }
292
293     function checkPass($submitted_password) {
294         // By definition, an undefined user class cannot sign in.
295         trigger_error("DEBUG: Warning: undefined _WikiUser class trying to sign in." . " "
296                       . "New subclasses of _WikiUser must override this function.");
297         return false;
298     }
299
300     // returns page_handle to user's home page or false if none
301     function hasHomePage() {
302         if ($this->_userid) {
303             if ($this->_HomePagehandle) {
304                 return $this->_HomePagehandle->exists();
305             }
306             else {
307                 // check db again (maybe someone else created it since
308                 // we logged in.)
309                 global $request;
310                 $this->_HomePagehandle = $request->getPage($this->_userid);
311                 return $this->_HomePagehandle->exists();
312             }
313         }
314         // nope
315         return false;
316     }
317
318     // innocent helper: case-insensitive position in _auth_methods
319     function array_position ($string, $array) {
320         $string = strtolower($string);
321         for ($found = 0; $found < count($array); $found++) {
322             if (strtolower($array[$found]) == $string)
323                 return $found;
324         }
325         return false;
326     }
327
328     function nextAuthMethodIndex() {
329         if (empty($this->_auth_methods)) 
330             $this->_auth_methods = $GLOBALS['USER_AUTH_ORDER'];
331         if (empty($this->_current_index)) {
332             if (get_class($this) != '_passuser') {
333                 $this->_current_method = substr(get_class($this),1,-8);
334                 $this->_current_index = $this->array_position($this->_current_method,
335                                                               $this->_auth_methods);
336             } else {
337                 $this->_current_index = -1;
338             }
339         }
340         $this->_current_index++;
341         if ($this->_current_index >= count($this->_auth_methods))
342             return false;
343         $this->_current_method = $this->_auth_methods[$this->_current_index];
344         return $this->_current_index;
345     }
346
347     function AuthMethod($index = false) {
348         return $this->_auth_methods[ $index === false ? 0 : $index];
349     }
350
351     // upgrade the user object
352     function nextClass() {
353         if (($next = $this->nextAuthMethodIndex()) !== false) {
354             $method = $this->AuthMethod($next);
355             $class = "_".$method."PassUser";
356             if ($user = new $class($this->_userid)) {
357                 // prevent from endless recursion.
358                 //$user->_current_method = $this->_current_method;
359                 //$user->_current_index = $this->_current_index;
360                 $user = UpgradeUser($user, $this);
361             }
362             return $user;
363         }
364     }
365
366     //Fixme: for _HttpAuthPassUser
367     function PrintLoginForm (&$request, $args, $fail_message = false,
368                              $seperate_page = true) {
369         include_once('lib/Template.php');
370         // Call update_locale in case the system's default language is not 'en'.
371         // (We have no user pref for lang at this point yet, no one is logged in.)
372         update_locale(DEFAULT_LANGUAGE);
373         $userid = $this->_userid;
374         $require_level = 0;
375         extract($args); // fixme
376
377         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
378
379         $pagename = $request->getArg('pagename');
380         $nocache = 1;
381         $login = new Template('login', $request,
382                               compact('pagename', 'userid', 'require_level',
383                                       'fail_message', 'pass_required', 'nocache'));
384         if ($seperate_page) {
385             $top = new Template('html', $request,
386                                 array('TITLE' => _("Sign In")));
387             return $top->printExpansion($login);
388         } else {
389             return $login;
390         }
391     }
392
393     // signed in but probably not password checked
394     function isSignedIn() {
395         return (isa($this,'_BogoUser') or isa($this,'_PassUser'));
396     }
397
398     function isAuthenticated () {
399         //return isa($this,'_PassUser');
400         //return isa($this,'_BogoUser') || isa($this,'_PassUser');
401         return $this->_level >= WIKIAUTH_BOGO; // hmm.
402     }
403
404     function isAdmin () {
405         return $this->_level == WIKIAUTH_ADMIN;
406     }
407
408     function getId () {
409         return ( $this->UserName()
410                  ? $this->UserName()
411                  : $GLOBALS['request']->get('REMOTE_ADDR') ); // FIXME: globals
412     }
413
414     function getAuthenticatedId() {
415         return ( $this->isAuthenticated()
416                  ? $this->_userid
417                  : ''); //$GLOBALS['request']->get('REMOTE_ADDR') ); // FIXME: globals
418     }
419
420     function hasAuthority ($require_level) {
421         return $this->_level >= $require_level;
422     }
423
424     function AuthCheck ($postargs) {
425         // Normalize args, and extract.
426         $keys = array('userid', 'passwd', 'require_level', 'login', 'logout',
427                       'cancel');
428         foreach ($keys as $key)
429             $args[$key] = isset($postargs[$key]) ? $postargs[$key] : false;
430         extract($args);
431         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
432
433         if ($logout) { // Log out
434             $GLOBALS['request']->_user = new _AnonUser();
435             return $GLOBALS['request']->_user; 
436         } elseif ($cancel)
437             return false;        // User hit cancel button.
438         elseif (!$login && !$userid)
439             return false;       // Nothing to do?
440
441         $authlevel = $this->checkPass($passwd === false ? '' : $passwd);
442         if (!$authlevel)
443             return _("Invalid password or userid.");
444         elseif ($authlevel < $require_level)
445             return _("Insufficient permissions.");
446
447         // Successful login.
448         $user = $GLOBALS['request']->_user;
449         $user->_userid = $userid;
450         $user->_level = $authlevel;
451         return $user;
452     }
453
454 }
455
456 class _AnonUser
457 extends _WikiUser
458 {
459     var $_level = WIKIAUTH_ANON;
460
461     // Anon only gets to load and save prefs in a cookie, that's it.
462     function getPreferences() {
463         global $request;
464
465         if (empty($this->_prefs))
466             $this->_prefs = new UserPreferences;
467         $UserName = $this->UserName();
468         if ($cookie = $request->getCookieVar(WIKI_NAME)) {
469             if (! $unboxedcookie = $this->_prefs->retrieve($cookie)) {
470                 trigger_error(_("Format of UserPreferences cookie not recognised.") . " "
471                               . _("Default preferences will be used."),
472                               E_USER_WARNING);
473             }
474             // TODO: try reading userid from old PhpWiki cookie
475             // formats, then delete old cookie from browser!
476             //
477             //else {
478                 // try old cookie format.
479                 //$cookie = $request->getCookieVar('WIKI_ID');
480             //}
481
482             /**
483              * Only keep the cookie if it matches the UserName who is
484              * signing in or if this really is an Anon login (no
485              * username). (Remember, _BogoUser and higher inherit this
486              * function too!).
487              */
488             if (! $UserName || $UserName == $unboxedcookie['userid']) {
489                 $this->_prefs = new UserPreferences($unboxedcookie);
490                 $this->_userid = $unboxedcookie['userid'];
491             }
492         }
493         // initializeTheme() needs at least an empty object
494         if (! $this->_prefs )
495             $this->_prefs = new UserPreferences;
496         return $this->_prefs;
497     }
498
499     function setPreferences($prefs, $id_only=false) {
500         // Allow for multiple wikis in same domain. Encode only the
501         // _prefs array of the UserPreference object. Ideally the
502         // prefs array should just be imploded into a single string or
503         // something so it is completely human readable by the end
504         // user. In that case stricter error checking will be needed
505         // when loading the cookie.
506         if (!is_object($prefs)) {
507             $prefs = new UserPreferences($prefs);
508         }
509         $packed = $prefs->store();
510         $unpacked = $prefs->unpack($packed);
511         if (!empty($unpacked)) { // check how many are different from the default_value
512             setcookie(WIKI_NAME, $packed,
513                       COOKIE_EXPIRATION_DAYS, COOKIE_DOMAIN);
514         }
515         if (count($unpacked)) {
516             global $request;
517             $this->_prefs = $prefs;
518             $request->_prefs =& $this->_prefs; 
519             $request->_user->_prefs =& $this->_prefs; 
520             //$request->setSessionVar('wiki_prefs', $this->_prefs);
521             $request->setSessionVar('wiki_user', $request->_user);
522         }
523         return count($unpacked);
524     }
525
526     function userExists() {
527         return true;
528     }
529
530     function checkPass($submitted_password) {
531         // By definition, the _AnonUser does not HAVE a password
532         // (compared to _BogoUser, who has an EMPTY password).
533         trigger_error("DEBUG: Warning: _AnonUser unexpectedly asked to checkPass()." . " "
534                       . "Check isa(\$user, '_PassUser'), or: isa(\$user, '_AdminUser') etc. first." . " "
535                       . "New subclasses of _WikiUser must override this function.");
536         return false;
537     }
538
539 }
540
541 class _ForbiddenUser
542 extends _AnonUser
543 {
544     var $_level = WIKIAUTH_FORBIDDEN;
545
546     function checkPass($submitted_password) {
547         return WIKIAUTH_FORBIDDEN;
548     }
549
550     function userExists() {
551         if ($this->_HomePagehandle) return true;
552         return false;
553     }
554 }
555 class _ForbiddenPassUser
556 extends _ForbiddenUser
557 {
558     function dummy() {
559         return;
560     }
561 }
562
563 /**
564  * Do NOT extend _BogoUser to other classes, for checkPass()
565  * security. (In case of defects in code logic of the new class!)
566  */
567 class _BogoUser
568 extends _AnonUser
569 {
570     function userExists() {
571         if (isWikiWord($this->_userid)) {
572             $this->_level = WIKIAUTH_BOGO;
573             return true;
574         } else {
575             $this->_level = WIKIAUTH_ANON;
576             return false;
577         }
578     }
579
580     function checkPass($submitted_password) {
581         // By definition, BogoUser has an empty password.
582         $this->userExists();
583         return $this->_level;
584     }
585 }
586
587 class _PassUser
588 extends _AnonUser
589 /**
590  * Called if ALLOW_USER_PASSWORDS and Anon and Bogo failed.
591  *
592  * The classes for all subsequent auth methods extend from
593  * this class. 
594  * This handles the auth method type dispatcher according $USER_AUTH_ORDER, 
595  * the three auth method policies first-only, strict and stacked
596  * and the two methods for prefs: homepage or database, 
597  * if $DBAuthParams['pref_select'] is defined.
598  *
599  * Default is PersonalPage auth and prefs.
600  * 
601  *  TODO: password changing
602  *  TODO: email verification
603  */
604 {
605     var $_auth_dbi, $_prefs;
606     var $_current_method, $_current_index;
607
608     // check and prepare the auth and pref methods only once
609     function _PassUser($UserName='', $prefs=false) {
610         global $DBAuthParams, $DBParams;
611         if ($UserName) {
612             $this->_userid = $UserName;
613             if ($this->hasHomePage())
614                 $this->_HomePagehandle = $GLOBALS['request']->getPage($this->_userid);
615         }
616         $this->_authmethod = substr(get_class($this),1,-8);
617         if (!$this->_prefs) {
618             if ($prefs) $this->_prefs = $prefs;
619             else $this->getPreferences();
620         }
621
622         // Check the configured Prefs methods
623         if ( !isset($this->_prefs->_select) and !empty($DBAuthParams['pref_select']) 
624              and in_array($DBParams['dbtype'],array('SQL','ADODB'))) {
625             $this->_prefs->_method = $DBParams['dbtype'];
626             $this->getAuthDbh();
627             // preparate the SELECT statement
628             $this->_prefs->_select = str_replace('"$userid"','%s',$DBAuthParams['pref_select']);
629         //} else {
630         //    unset($this->_prefs->_select);
631         }
632         if (  !isset($this->_prefs->_update) and !empty($DBAuthParams['pref_update'])
633              and in_array($DBParams['dbtype'],array('SQL','ADODB'))) {
634             $this->_prefs->_method = $DBParams['dbtype'];
635             $this->getAuthDbh();
636             // preparate the SELECT statement
637             $this->_prefs->_update = str_replace(array('"$userid"','"$pref_blob"'),array('%s','%s'),
638                                              $DBAuthParams['pref_update']);
639         }
640         
641         // Upgrade to the next parent _PassUser class. Avoid recursion.
642         if ( strtolower(get_class($this)) === '_passuser' ) {
643             //auth policy: Check the order of the configured auth methods
644             // 1. first-only: Upgrade the class here in the constructor
645             // 2. old:       ignore USER_AUTH_ORDER and try to use all available methods as 
646             ///              in the previous PhpWiki releases (slow)
647             // 3. strict:    upgrade the class after checking the user existance in userExists()
648             // 4. stacked:   upgrade the class after the password verification in checkPass()
649             // Methods: PersonalPage, HttpAuth, DB, Ldap, Imap, File
650             if (!defined('USER_AUTH_POLICY')) define('USER_AUTH_POLICY','old');
651             if (defined('USER_AUTH_POLICY')) {
652                 // policy 1: only pre-define one method for all users
653                 if (USER_AUTH_POLICY === 'first-only') {
654                     if ($user = $this->nextClass())
655                         return $user;
656                     else 
657                         return $GLOBALS['ForbiddenUser'];
658                 }
659                 // use the default behaviour from the previous versions:
660                 elseif (USER_AUTH_POLICY === 'old') {
661                     // default: try to be smart
662                     if (!empty($GLOBALS['PHP_AUTH_USER'])) {
663                         $this = new _HttpAuthPassUser($UserName,$this->_prefs);
664                         return $this;
665                     } elseif (!empty($DBAuthParams['auth_check']) and 
666                               (!empty($DBAuthParams['auth_dsn']) or !empty($GLOBALS ['DBParams']['dsn']))) {
667                         $this = new _DbPassUser($UserName,$this->_prefs);
668                         return $this;
669                     } elseif (defined('LDAP_AUTH_HOST') and defined('LDAP_BASE_DN') and function_exists('ldap_open')) {
670                         $this = new _LDAPPassUser($UserName,$this->_prefs);
671                         return $this;
672                     } elseif (defined('IMAP_AUTH_HOST') and function_exists('imap_open')) {
673                         $this = new _IMAPPassUser($UserName,$this->_prefs);
674                         return $this;
675                     } elseif (defined('AUTH_USER_FILE')) {
676                         $this = new _FilePassUser($UserName,$this->_prefs);
677                         return $this;
678                     } else {
679                         $this = new _PersonalPagePassUser($UserName,$this->_prefs);
680                         return $this;
681                     }
682                 }
683                 else 
684                     // else use the page methods defined in _PassUser.
685                     return $this;
686             }
687         }
688     }
689
690     function getAuthDbh () {
691         global $request, $DBParams, $DBAuthParams;
692
693         // session restauration doesn't re-connect to the database automatically, so dirty it here.
694         if (($DBParams['dbtype'] == 'SQL') and isset($this->_auth_dbi) and empty($this->_auth_dbi->connection))
695             unset($this->_auth_dbi);
696         if (($DBParams['dbtype'] == 'ADODB') and isset($this->_auth_dbi) and empty($this->_auth_dbi->_connectionID))
697             unset($this->_auth_dbi);
698
699         if (empty($this->_auth_dbi)) {
700             if (empty($DBAuthParams['auth_dsn'])) {
701                 if ($DBParams['dbtype'] == 'SQL')
702                     $dbh = $request->getDbh(); // use phpwiki database 
703                 else 
704                     return false;
705             }
706             elseif ($DBAuthParams['auth_dsn'] == $DBParams['dsn'])
707                 $dbh = $request->getDbh(); // same phpwiki database 
708             else // use another external database handle. needs PHP >= 4.1
709                 $dbh = WikiDB::open($DBAuthParams);
710                 
711             $this->_auth_dbi =& $dbh->_backend->_dbh;    
712         }
713         return $this->_auth_dbi;
714     }
715
716     // not used anymore.
717     function prepare ($stmt, $variables) {
718         global $DBParams, $request;
719         // preparate the SELECT statement, for ADODB and PearDB (MDB not)
720         $this->getAuthDbh();
721         $place = ($DBParams['dbtype'] == 'ADODB') ? '%s' : '?';
722         if (is_array($variables)) {
723             $new = array();
724             foreach ($variables as $v) { $new[] = $place; }
725         } else {
726             $new = $place;
727         }
728         // probably prefix table names if in same database
729         if (!empty($DBParams['prefix']) and 
730             $this->_auth_dbi === $request->_dbi->_backend->_dbh) {
731             if (!stristr($DBParams['prefix'],$stmt)) {
732                 //Do it automatically for the lazy admin? Esp. on sf.net it's nice to have
733                 trigger_error("TODO: Need to prefix the DBAuthParam tablename in index.php: $stmt",
734                               E_USER_WARNING);
735                 $stmt = str_replace(array(" user "," pref "," member "),
736                                     array(" ".$prefix."user ",
737                                           " ".$prefix."prefs ",
738                                           " ".$prefix."member "),$stmt);
739             }
740         }
741         return $this->_auth_dbi->prepare(str_replace($variables,$new,$stmt));
742     }
743
744     function getPreferences() {
745         if (!empty($this->_prefs->_method)) {
746             if ($this->_prefs->_method == 'ADODB') {
747                 _AdoDbPassUser::_AdoDbPassUser();
748                 return _AdoDbPassUser::getPreferences();
749             } elseif ($this->_prefs->_method == 'SQL') {
750                 _PearDbPassUser::_PearDbPassUser();
751                 return _PearDbPassUser::getPreferences();
752             }
753         }
754
755         // We don't necessarily have to read the cookie first. Since
756         // the user has a password, the prefs stored in the homepage
757         // cannot be arbitrarily altered by other Bogo users.
758         _AnonUser::getPreferences();
759         // User may have deleted cookie, retrieve from his
760         // PersonalPage if there is one.
761         if (! $this->_prefs && $this->_HomePagehandle) {
762             if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) {
763                 $this->_prefs = new UserPreferences($restored_from_page);
764                 return $this->_prefs;
765             }
766         }
767         return $this->_prefs;
768     }
769
770     function setPreferences($prefs, $id_only=false) {
771         if (!empty($this->_prefs->_method)) {
772             if ($this->_prefs->_method == 'ADODB') {
773                 _AdoDbPassUser::_AdoDbPassUser();
774                 return _AdoDbPassUser::setPreferences($prefs, $id_only);
775             }
776             elseif ($this->_prefs->_method == 'SQL') {
777                 _PearDbPassUser::_PearDbPassUser();
778                 return _PearDbPassUser::setPreferences($prefs, $id_only);
779             }
780         }
781
782         _AnonUser::setPreferences($prefs, $id_only);
783         // Encode only the _prefs array of the UserPreference object
784         if (!is_object($prefs)) {
785             $prefs = new UserPreferences($prefs);
786         }
787         $packed = $prefs->store();
788         $unpacked = $prefs->unpack($packed);
789         if (count($unpacked)) {
790             global $request;
791             $this->_prefs = $prefs;
792             $request->_prefs =& $this->_prefs; 
793             $request->_user->_prefs =& $this->_prefs; 
794             //$request->setSessionVar('wiki_prefs', $this->_prefs);
795             $request->setSessionVar('wiki_user', $request->_user);
796         }
797         if ($this->_HomePagehandle)
798             $this->_HomePagehandle->set('pref', $packed);
799         return count($unpacked);    
800     }
801
802     function mayChangePass() {
803         return true;
804     }
805
806     //The default method is getting the password from prefs. 
807     // child methods obtain $stored_password from external auth.
808     function userExists() {
809         //if ($this->_HomePagehandle) return true;
810         while ($user = $this->nextClass()) {
811               if ($user->userExists()) {
812                   $this = $user;
813                   return true;
814               }
815               $this = $user; // prevent endless loop. does this work on all PHP's?
816               // it just has to set the classname, what it correctly does.
817         }
818         return false;
819     }
820
821     //The default method is getting the password from prefs. 
822     // child methods obtain $stored_password from external auth.
823     function checkPass($submitted_password) {
824         $stored_password = $this->_prefs->get('passwd');
825         if ($this->_checkPass($submitted_password, $stored_password)) {
826             $this->_level = WIKIAUTH_USER;
827             return $this->_level;
828         } else {
829             return $this->_tryNextPass($submitted_password);
830         }
831     }
832
833     //TODO: remove crypt() function check from config.php:396 ??
834     function _checkPass($submitted_password, $stored_password) {
835         if(!empty($submitted_password)) {
836             if (defined('ENCRYPTED_PASSWD') && ENCRYPTED_PASSWD) {
837                 // Verify against encrypted password.
838                 if (function_exists('crypt')) {
839                     if (crypt($submitted_password, $stored_password) == $stored_password )
840                         return true; // matches encrypted password
841                     else
842                         return false;
843                 }
844                 else {
845                     trigger_error(_("The crypt function is not available in this version of PHP.") . " "
846                                   . _("Please set ENCRYPTED_PASSWD to false in index.php and change ADMIN_PASSWD."),
847                                   E_USER_WARNING);
848                     return false;
849                 }
850             }
851             else {
852                 // Verify against cleartext password.
853                 if ($submitted_password == $stored_password)
854                     return true;
855                 else {
856                     // Check whether we forgot to enable ENCRYPTED_PASSWD
857                     if (function_exists('crypt')) {
858                         if (crypt($submitted_password, $stored_password) == $stored_password) {
859                             trigger_error(_("Please set ENCRYPTED_PASSWD to true in index.php."),
860                                           E_USER_WARNING);
861                             return true;
862                         }
863                     }
864                 }
865             }
866         }
867         return false;
868     }
869
870     //The default method is storing the password in prefs. 
871     // Child methods (DB,File) may store in external auth also, but this 
872     // must be explicitly enabled.
873     // This may be called by plugin/UserPreferences or by ->SetPreferences()
874     function changePass($submitted_password) {
875         $stored_password = $this->_prefs->get('passwd');
876         // check if authenticated
877         if ($this->isAuthenticated() and $stored_password != $submitted_password) {
878             $this->_prefs->set('passwd',$submitted_password);
879             //update the storage (session, homepage, ...)
880             $this->SetPreferences($this->_prefs);
881             return true;
882         }
883         //Todo: return an error msg to the caller what failed? 
884         // same password or no privilege
885         return false;
886     }
887
888     function _tryNextPass($submitted_password) {
889         if (USER_AUTH_POLICY === 'strict') {
890             if ($user = $this->nextClass()) {
891                 if ($user->userExists()) {
892                     return $user->checkPass($submitted_password);
893                 }
894             }
895         }
896         if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') {
897             if ($user = $this->nextClass())
898                 return $user->checkPass($submitted_password);
899         }
900         return $this->_level;
901     }
902
903     function _tryNextUser() {
904         if (USER_AUTH_POLICY === 'strict') {
905             while ($user = $this->nextClass()) {
906                 if ($user->userExists()) {
907                     $this = $user;
908                     return true;
909                 }
910                 $this = $user;
911             }
912             /*
913             if ($user = $this->nextClass())
914                 return $user->userExists();
915             */
916         }
917         return false;
918     }
919
920 }
921
922 // without stored password. this is then PersonalPagePassUser
923 class _BogoLoginPassUser
924 extends _PassUser
925 {
926     var $_authmethod = 'BogoLogin';
927     function userExists() {
928         if (isWikiWord($this->_userid)) {
929             $this->_level = WIKIAUTH_BOGO;
930             return true;
931         } else {
932             $this->_level = WIKIAUTH_ANON;
933             return false;
934         }
935     }
936
937     function checkPass($submitted_password) {
938         // A BogoLoginUser requires no password at all
939         // But if there's one stored, we should prefer PersonalPage instead
940         if ($this->_prefs->get('passwd')) {
941             $user = new _PersonalPagePassUser($this->_userid);
942             if ($user->checkPass($submitted_password)) {
943                 $this = $user;
944                 $this->_level = WIKIAUTH_USER;
945                 return $this->_level;
946             } else {
947                 $this->_level = WIKIAUTH_ANON;
948                 return $this->_level;
949             }
950         }
951         $this->userExists();
952         return $this->_level;
953     }
954 }
955
956
957 class _PersonalPagePassUser
958 extends _PassUser
959 /**
960  * This class is only to simplify the auth method dispatcher.
961  * It inherits almost all all methods from _PassUser.
962  */
963 {
964     var $_authmethod = 'PersonalPage';
965
966     function userExists() {
967         return $this->_HomePagehandle and $this->_HomePagehandle->exists();
968     }
969         
970     function checkPass($submitted_password) {
971         if ($this->userExists()) {
972             // A PersonalPagePassUser requires PASSWORD_LENGTH_MINIMUM.
973             // BUT if the user already has a homepage with en empty password 
974             // stored allow login but warn him to change it.
975             $stored_password = $this->_prefs->get('passwd');
976             if (empty($stored_password)) {
977                 trigger_error(sprintf(
978                 _("\nYou stored an empty password in your %s page.\n").
979                 _("Your access permissions are only for a BogoUser.\n").
980                 _("Please set your password in UserPreferences."),
981                                         $this->_userid), E_USER_NOTICE);
982                 $this->_level = WIKIAUTH_BOGO;
983                 return $this->_level;
984             }
985             if ($this->_checkPass($submitted_password, $stored_password))
986                 return ($this->_level = WIKIAUTH_USER);
987             return _PassUser::checkPass($submitted_password);
988         }
989         return WIKIAUTH_ANON;
990     }
991 }
992
993 class _HttpAuthPassUser
994 extends _PassUser
995 {
996     function _HttpAuthPassUser($UserName='',$prefs=false) {
997         if (!$this->_prefs) {
998             if ($prefs) $this->_prefs = $prefs;
999             else _PassUser::_PassUser($UserName);
1000         }
1001         $this->_authmethod = 'HttpAuth';
1002         if ($this->userExists())
1003             return $this;
1004         else 
1005             return $GLOBALS['ForbiddenUser'];
1006     }
1007
1008     //force http auth authorization
1009     function userExists() {
1010         // todo: older php's
1011         if (empty($_SERVER['PHP_AUTH_USER']) or 
1012             $_SERVER['PHP_AUTH_USER'] != $this->_userid) {
1013             header('WWW-Authenticate: Basic realm="'.WIKI_NAME.'"');
1014             header('HTTP/1.0 401 Unauthorized'); 
1015             exit;
1016         }
1017         $this->_userid = $_SERVER['PHP_AUTH_USER'];
1018         $this->_level = WIKIAUTH_USER;
1019         return $this;
1020     }
1021         
1022     function checkPass($submitted_password) {
1023         return $this->userExists() ? WIKIAUTH_USER : WIKIAUTH_ANON;
1024     }
1025
1026     function mayChangePass() {
1027         return false;
1028     }
1029
1030     // hmm... either the server dialog or our own.
1031     function PrintLoginForm (&$request, $args, $fail_message = false,
1032                              $seperate_page = true) {
1033         header('WWW-Authenticate: Basic realm="'.WIKI_NAME.'"');
1034         header('HTTP/1.0 401 Unauthorized'); 
1035         exit;
1036
1037         include_once('lib/Template.php');
1038         // Call update_locale in case the system's default language is not 'en'.
1039         // (We have no user pref for lang at this point yet, no one is logged in.)
1040         update_locale(DEFAULT_LANGUAGE);
1041         $userid = $this->_userid;
1042         $require_level = 0;
1043         extract($args); // fixme
1044
1045         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
1046
1047         $pagename = $request->getArg('pagename');
1048         $nocache = 1;
1049         $login = new Template('login', $request,
1050                               compact('pagename', 'userid', 'require_level',
1051                                       'fail_message', 'pass_required', 'nocache'));
1052         if ($seperate_page) {
1053             $top = new Template('html', $request,
1054                                 array('TITLE' => _("Sign In")));
1055             return $top->printExpansion($login);
1056         } else {
1057             return $login;
1058         }
1059     }
1060
1061 }
1062
1063 class _DbPassUser
1064 extends _PassUser
1065 /**
1066  * Authenticate against a database, to be able to use shared users.
1067  *   internal: no different $DbAuthParams['dsn'] defined, or
1068  *   external: different $DbAuthParams['dsn']
1069  * The magic is done in the symbolic SQL statements in index.php
1070  *
1071  * We support only the SQL and ADODB backends.
1072  * The other WikiDB backends (flat, cvs, dba, ...) should be used for pages, 
1073  * not for auth stuff. If one would like to use e.g. dba for auth, he should 
1074  * use PearDB (SQL) with the right $DBAuthParam['auth_dsn'].
1075  * Flat files for auth use is handled by the auth method "File".
1076  *
1077  * Preferences are handled in the parent class.
1078  */
1079 {
1080     var $_authselect, $_authupdate;
1081
1082     // This can only be called from _PassUser, because the parent class 
1083     // sets the auth_dbi and pref methods, before this class is initialized.
1084     function _DbPassUser($UserName='',$prefs=false) {
1085         if (!$this->_prefs) {
1086             if ($prefs) $this->_prefs = $prefs;
1087             else _PassUser::_PassUser($UserName);
1088         }
1089         $this->_authmethod = 'Db';
1090         //$this->getAuthDbh();
1091         //$this->_auth_crypt_method = @$GLOBALS['DBAuthParams']['auth_crypt_method'];
1092         if ($GLOBALS['DBParams']['dbtype'] == 'ADODB') {
1093             $this = new _AdoDbPassUser($UserName,$this->_prefs);
1094             return $this;
1095         }
1096         else {
1097             $this = new _PearDbPassUser($UserName,$this->_prefs);
1098             return $this;
1099         }
1100     }
1101
1102     function mayChangePass() {
1103         return !isset($this->_authupdate);
1104     }
1105
1106 }
1107
1108 class _PearDbPassUser
1109 extends _DbPassUser
1110 /**
1111  * Pear DB methods
1112  * optimized not to use prepare, ...query(sprintf($sql,quote())) instead.
1113  */
1114 {
1115     function _PearDbPassUser($UserName='',$prefs=false) {
1116         global $DBAuthParams;
1117         if (!$this->_prefs and isa($this,"_PearDbPassUser")) {
1118             if ($prefs) $this->_prefs = $prefs;
1119             else _PassUser::_PassUser($UserName);
1120         }
1121         $this->_userid = $UserName;
1122         // make use of session data. generally we only initialize this every time, 
1123         // but do auth checks only once
1124         $this->_auth_crypt_method = @$GLOBALS['DBAuthParams']['auth_crypt_method'];
1125         $this->getAuthDbh();
1126         return $this;
1127     }
1128
1129     function getPreferences() {
1130         // override the generic slow method here for efficiency and not to 
1131         // clutter the homepage metadata with prefs.
1132         _AnonUser::getPreferences();
1133         $this->getAuthDbh();
1134         $dbh = &$this->_auth_dbi;
1135         if ((! $this->_prefs) and $this->_prefs->_select) {
1136             $db_result = $dbh->query(sprintf($this->_prefs->_select,$dbh->quote($this->_userid)));
1137             list($prefs_blob) = $db_result->fetchRow();
1138             if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) {
1139                 $this->_prefs = new UserPreferences($restored_from_db);
1140                 return $this->_prefs;
1141             }
1142         }
1143         if ((! $this->_prefs) && $this->_HomePagehandle) {
1144             if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) {
1145                 $this->_prefs = new UserPreferences($restored_from_page);
1146                 return $this->_prefs;
1147             }
1148         }
1149         return $this->_prefs;
1150     }
1151
1152     function setPreferences($prefs, $id_only=false) {
1153         // Encode only the _prefs array of the UserPreference object
1154         $this->getAuthDbh();
1155         if (!is_object($prefs)) {
1156             $prefs = new UserPreferences($prefs);
1157         }
1158         $packed = $prefs->store();
1159         $unpacked = $prefs->unpack($packed);
1160         if (count($unpacked)) {
1161             global $request;
1162             $this->_prefs = $prefs;
1163             $request->_prefs =& $this->_prefs; 
1164             $request->_user->_prefs =& $this->_prefs; 
1165             //$request->setSessionVar('wiki_prefs', $this->_prefs);
1166             //todo: we don't have to store the dbi chain
1167             $request->setSessionVar('wiki_user', $request->_user);
1168         }
1169         if ($this->_prefs->_update) {
1170             $dbh = &$this->_auth_dbi;
1171             $dbh->simpleQuery(sprintf($this->_prefs->_update,
1172                                       $dbh->quote($packed),
1173                                       $dbh->quote($this->_userid)));
1174         } else {
1175             _AnonUser::setPreferences($prefs, $id_only);
1176             $this->_HomePagehandle->set('pref', $packed);
1177         }
1178         return count($unpacked);
1179     }
1180
1181     function userExists() {
1182         global $DBAuthParams;
1183         $this->getAuthDbh();
1184         $dbh = &$this->_auth_dbi;
1185         // Prepare the configured auth statements
1186         if (!empty($DBAuthParams['auth_check']) and !$this->_authselect) {
1187             $this->_authselect = str_replace(array('"$userid"','"$password"'),
1188                                              array('%s','%s'),
1189                                              $DBAuthParams['auth_check']);
1190         }
1191         if (!$this->_authselect)
1192             trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'SQL'",
1193                           E_USER_WARNING);
1194         //NOTE: for auth_crypt_method='crypt' no special auth_user_exists is needed
1195         if ($this->_auth_crypt_method == 'crypt') {
1196             $rs = $dbh->query(sprintf($this->_authselect,$dbh->quote($this->_userid)));
1197             if ($rs->numRows())
1198                 return true;
1199         }
1200         else {
1201             if (! $GLOBALS['DBAuthParams']['auth_user_exists'])
1202                 trigger_error("\$DBAuthParams['auth_user_exists'] is missing",
1203                               E_USER_WARNING);
1204             $this->_authcheck = str_replace('"$userid"','%s',
1205                                              $DBAuthParams['auth_user_exists']);
1206             $rs = $dbh->query(sprintf($this->_authcheck,$dbh->quote($this->_userid)));
1207             if ($rs->numRows())
1208                 return true;
1209         }
1210         return $this->_tryNextUser();
1211     }
1212  
1213     function checkPass($submitted_password) {
1214         global $DBAuthParams;
1215         $this->getAuthDbh();
1216         if (!$this->_authselect)
1217             trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'SQL'",
1218                           E_USER_WARNING);
1219
1220         //NOTE: for auth_crypt_method='crypt'  defined('ENCRYPTED_PASSWD',true) must be set
1221         $dbh = &$this->_auth_dbi;
1222         if ($this->_auth_crypt_method == 'crypt') {
1223             $stored_password = $dbh->getOne(sprintf($this->_authselect,$dbh->quote($this->_userid)));
1224             $result = $this->_checkPass($submitted_password, $stored_password);
1225         } else {
1226             $okay = $dbh->getOne(sprintf($this->_authselect,
1227                                          $dbh->quote($submitted_password),
1228                                          $dbh->quote($this->_userid)));
1229             $result = !empty($okay);
1230         }
1231
1232         if ($result) {
1233             $this->_level = WIKIAUTH_USER;
1234             return $this->_level;
1235         } else {
1236             return $this->_tryNextPass($submitted_password);
1237         }
1238     }
1239
1240     function storePass($submitted_password) {
1241         global $DBAuthParams;
1242         $dbh = &$this->_auth_dbi;
1243         if (!empty($DBAuthParams['auth_update']) and !$this->_authupdate) {
1244             $this->_authupdate = str_replace(array('"$userid"','"$password"'),
1245                                              array('%s','%s'),
1246                                              $DBAuthParams['auth_update']);
1247         }
1248         if (!$this->_authupdate) {
1249             //CHECKME
1250             trigger_warning("Either \$DBAuthParams['auth_update'] not defined or \$DBParams['dbtype'] != 'SQL'",
1251                           E_USER_WARNING);
1252             return false;
1253         }
1254
1255         if ($this->_auth_crypt_method == 'crypt') {
1256             if (function_exists('crypt'))
1257                 $submitted_password = crypt($submitted_password);
1258         }
1259         $dbh->simpleQuery(sprintf($this->_authupdate,
1260                                   $dbh->quote($submitted_password),
1261                                   $dbh->quote($this->_userid)
1262                                   ));
1263     }
1264
1265 }
1266
1267 class _AdoDbPassUser
1268 extends _DbPassUser
1269 /**
1270  * ADODB methods
1271  */
1272 {
1273     function _AdoDbPassUser($UserName='',$prefs=false) {
1274         if (!$this->_prefs and isa($this,"_AdoDbPassUser")) {
1275             if ($prefs) $this->_prefs = $prefs;
1276             else _PassUser::_PassUser($UserName);
1277         }
1278         $this->_userid = $UserName;
1279         $this->_auth_crypt_method = $GLOBALS['DBAuthParams']['auth_crypt_method'];
1280         $this->getAuthDbh();
1281         // Don't prepare the configured auth statements anymore
1282         return $this;
1283     }
1284
1285     function getPreferences() {
1286         // override the generic slow method here for efficiency
1287         _AnonUser::getPreferences();
1288         //$this->getAuthDbh();
1289         if ((! $this->_prefs) and isset($this->_prefs->_select)) {
1290             $dbh = & $this->_auth_dbi;
1291             $rs = $dbh->Execute(sprintf($this->_prefs->_select,$dbh->qstr($this->_userid)));
1292             if ($rs->EOF) {
1293                 $rs->Close();
1294             } else {
1295                 $prefs_blob = $rs->fields['pref_blob'];
1296                 $rs->Close();
1297                 if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) {
1298                     $this->_prefs = new UserPreferences($restored_from_db);
1299                     return $this->_prefs;
1300                 }
1301             }
1302         }
1303         if ((! $this->_prefs) && $this->_HomePagehandle) {
1304             if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) {
1305                 $this->_prefs = new UserPreferences($restored_from_page);
1306                 return $this->_prefs;
1307             }
1308         }
1309         return $this->_prefs;
1310     }
1311
1312     function setPreferences($prefs, $id_only=false) {
1313         if (!is_object($prefs)) {
1314             $prefs = new UserPreferences($prefs);
1315         }
1316         //$this->getAuthDbh();
1317         $packed = $prefs->store();
1318         $unpacked = $prefs->unpack($packed);
1319         if (count($unpacked)) {
1320             global $request;
1321             $this->_prefs = $prefs;
1322             $request->_prefs =& $this->_prefs; 
1323             $request->_user->_prefs =& $this->_prefs; 
1324             //$request->setSessionVar('wiki_prefs', $this->_prefs);
1325             $request->setSessionVar('wiki_user', $request->_user);
1326         }
1327         if (isset($this->_prefs->_update)) {
1328             $dbh = & $this->_auth_dbi;
1329             $db_result = $dbh->Execute(sprintf($this->_prefs->_update,
1330                                                $dbh->qstr($packed),$dbh->qstr($this->_userid)));
1331             $db_result->Close();
1332         } else {
1333             _AnonUser::setPreferences($prefs, $id_only);
1334             if ($this->_HomePagehandle)
1335                 $this->_HomePagehandle->set('pref', $serialized);
1336         }
1337         return count($unpacked);
1338     }
1339  
1340     function userExists() {
1341         global $DBAuthParams;
1342         if (!$this->_authselect and !empty($DBAuthParams['auth_check'])) {
1343             $this->_authselect = str_replace(array('"$userid"','"$password"'),array('%s','%s'),
1344                                               $DBAuthParams['auth_check']);
1345         }
1346         if (!$this->_authselect)
1347             trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'ADODB'",
1348                           E_USER_WARNING);
1349         //$this->getAuthDbh();
1350         $dbh = &$this->_auth_dbi;
1351         //NOTE: for auth_crypt_method='crypt' no special auth_user_exists is needed
1352         if ($this->_auth_crypt_method == 'crypt') {
1353             $rs = $dbh->Execute(sprintf($this->_authselect,$dbh->qstr($this->_userid)));
1354             if (!$rs->EOF) {
1355                 $rs->Close();
1356                 return true;
1357             } else {
1358                 $rs->Close();
1359             }
1360         }
1361         else {
1362             if (! $DBAuthParams['auth_user_exists'])
1363                 trigger_error("\$DBAuthParams['auth_user_exists'] is missing",
1364                               E_USER_WARNING);
1365             $this->_authcheck = str_replace('"$userid"','%s',
1366                                              $DBAuthParams['auth_user_exists']);
1367             $rs = $dbh->Execute(sprintf($this->_authcheck,$dbh->qstr($this->_userid)));
1368             if (!$rs->EOF) {
1369                 $rs->Close();
1370                 return true;
1371             } else {
1372                 $rs->Close();
1373             }
1374         }
1375         
1376         return $this->_tryNextUser();
1377     }
1378
1379     function checkPass($submitted_password) {
1380         global $DBAuthParams;
1381         if (!$this->_authselect and !empty($DBAuthParams['auth_check'])) {
1382             $this->_authselect = str_replace(array('"$userid"','"$password"'),array('%s','%s'),
1383                                               $DBAuthParams['auth_check']);
1384         }
1385         if (!isset($this->_authselect))
1386             trigger_error("Either \$DBAuthParams['auth_check'] is missing or \$DBParams['dbtype'] != 'ADODB'",
1387                           E_USER_WARNING);
1388         //$this->getAuthDbh();
1389         $dbh = &$this->_auth_dbi;
1390         //NOTE: for auth_crypt_method='crypt'  defined('ENCRYPTED_PASSWD',true) must be set
1391         if ($this->_auth_crypt_method == 'crypt') {
1392             $rs = $dbh->Execute(sprintf($this->_authselect,$dbh->qstr($this->_userid)));
1393             if (!$rs->EOF) {
1394                 $stored_password = $rs->fields['password'];
1395                 $rs->Close();
1396                 $result = $this->_checkPass($submitted_password, $stored_password);
1397             } else {
1398                 $rs->Close();
1399                 $result = false;
1400             }
1401         }
1402         else {
1403             $rs = $dbh->Execute(sprintf($this->_authselect,
1404                                         $dbh->qstr($submitted_password),
1405                                         $dbh->qstr($this->_userid)));
1406             $okay = $rs->fields['ok'];
1407             $rs->Close();
1408             $result = !empty($okay);
1409         }
1410
1411         if ($result) { 
1412             $this->_level = WIKIAUTH_USER;
1413             return $this->_level;
1414         } else {
1415             return $this->_tryNextPass($submitted_password);
1416         }
1417     }
1418
1419     function storePass($submitted_password) {
1420         global $DBAuthParams;
1421         if (!isset($this->_authupdate) and !empty($DBAuthParams['auth_update'])) {
1422             $this->_authupdate = str_replace(array('"$userid"','"$password"'),array("%s","%s"),
1423                                               $DBAuthParams['auth_update']);
1424         }
1425         if (!isset($this->_authupdate)) {
1426             trigger_error("Either \$DBAuthParams['auth_update'] not defined or \$DBParams['dbtype'] != 'ADODB'",
1427                           E_USER_WARNING);
1428             return false;
1429         }
1430
1431         if ($this->_auth_crypt_method == 'crypt') {
1432             if (function_exists('crypt'))
1433                 $submitted_password = crypt($submitted_password);
1434         }
1435         $this->getAuthDbh();
1436         $dbh = &$this->_auth_dbi;
1437         $rs = $dbh->Execute(sprintf($this->_authupdate,
1438                                     $dbh->qstr($submitted_password),
1439                                     $dbh->qstr($this->_userid)
1440                                     ));
1441         $rs->Close();
1442     }
1443
1444 }
1445
1446 class _LDAPPassUser
1447 extends _PassUser
1448 /**
1449  * Define the vars LDAP_HOST and LDAP_BASE_DN in index.php
1450  *
1451  * Preferences are handled in _PassUser
1452  */
1453 {
1454     function checkPass($submitted_password) {
1455         $this->_authmethod = 'LDAP';
1456         $userid = $this->_userid;
1457         if ($ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server!
1458             if (defined('LDAP_AUTH_USER'))
1459                 if (defined('LDAP_AUTH_PASSWORD'))
1460                     // Windows Active Directory Server is strict
1461                     $r = @ldap_bind($ldap,LDAP_AUTH_USER,LDAP_AUTH_PASSWORD); 
1462                 else
1463                     $r = @ldap_bind($ldap,LDAP_AUTH_USER); 
1464             else
1465                 $r = @ldap_bind($ldap); // this is an anonymous bind
1466             if (!empty($LDAP_SET_OPTION)) {
1467                 foreach ($LDAP_SET_OPTION as $key => $value) {
1468                     ldap_set_option($ldap,$key,$value);
1469                 }
1470             }
1471             // Need to set the right root search information. see ../index.php
1472             $st_search = defined('LDAP_SEARCH_FIELD') 
1473                 ? LDAP_SEARCH_FIELD."=$userid"
1474                 : "uid=$userid";
1475             $sr = ldap_search($ldap, LDAP_BASE_DN, $st_search);
1476             $info = ldap_get_entries($ldap, $sr); 
1477             // there may be more hits with this userid.
1478             // of course it would be better to narrow down the BASE_DN
1479             for ($i = 0; $i < $info["count"]; $i++) {
1480                 $dn = $info[$i]["dn"];
1481                 // The password is still plain text.
1482                 if ($r = @ldap_bind($ldap, $dn, $submitted_password)) {
1483                     // ldap_bind will return TRUE if everything matches
1484                     ldap_close($ldap);
1485                     $this->_level = WIKIAUTH_USER;
1486                     return $this->_level;
1487                 }
1488             }
1489         } else {
1490             trigger_error(fmt("Unable to connect to LDAP server %s", LDAP_AUTH_HOST), 
1491                           E_USER_WARNING);
1492             //return false;
1493         }
1494
1495         return $this->_tryNextPass($submitted_password);
1496     }
1497
1498     function userExists() {
1499         $userid = $this->_userid;
1500
1501         if ($ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server!
1502             if (defined('LDAP_AUTH_USER'))
1503                 if (defined('LDAP_AUTH_PASSWORD'))
1504                     // Windows Active Directory Server is strict
1505                     $r = @ldap_bind($ldap,LDAP_AUTH_USER,LDAP_AUTH_PASSWORD); 
1506                 else
1507                     $r = @ldap_bind($ldap,LDAP_AUTH_USER); 
1508             else
1509                 $r = @ldap_bind($ldap); // this is an anonymous bind
1510             if (!empty($LDAP_SET_OPTION)) {
1511                 foreach ($LDAP_SET_OPTION as $key => $value) {
1512                     ldap_set_option($ldap,$key,$value);
1513                 }
1514             }
1515             // Need to set the right root search information. see ../index.php
1516             $st_search = defined('LDAP_SEARCH_FIELD') 
1517                 ? LDAP_SEARCH_FIELD."=$userid"
1518                 : "uid=$userid";
1519             $sr = ldap_search($ldap, LDAP_BASE_DN, $st_search);
1520             $info = ldap_get_entries($ldap, $sr); 
1521
1522             if ($info["count"] > 0) {
1523                 ldap_close($ldap);
1524                 return true;
1525             }
1526         } else {
1527             trigger_error(_("Unable to connect to LDAP server "). LDAP_AUTH_HOST, E_USER_WARNING);
1528         }
1529
1530         return $this->_tryNextUser();
1531     }
1532
1533     function mayChangePass() {
1534         return false;
1535     }
1536
1537 }
1538
1539 class _IMAPPassUser
1540 extends _PassUser
1541 /**
1542  * Define the var IMAP_HOST in index.php
1543  *
1544  * Preferences are handled in _PassUser
1545  */
1546 {
1547     function checkPass($submitted_password) {
1548         $userid = $this->_userid;
1549         $mbox = @imap_open( "{" . IMAP_AUTH_HOST . "}",
1550                             $userid, $submitted_password, OP_HALFOPEN );
1551         if ($mbox) {
1552             imap_close($mbox);
1553             $this->_authmethod = 'IMAP';
1554             $this->_level = WIKIAUTH_USER;
1555             return $this->_level;
1556         } else {
1557             trigger_error(_("Unable to connect to IMAP server "). IMAP_AUTH_HOST, E_USER_WARNING);
1558         }
1559
1560         return $this->_tryNextPass($submitted_password);
1561     }
1562
1563     //CHECKME: this will not be okay for the auth policy strict
1564     function userExists() {
1565         if (checkPass($this->_prefs->get('passwd')))
1566             return true;
1567             
1568         return $this->_tryNextUser();
1569     }
1570
1571     function mayChangePass() {
1572         return false;
1573     }
1574
1575 }
1576
1577 class _FilePassUser
1578 extends _PassUser
1579 /**
1580  * Check users defined in a .htaccess style file
1581  * username:crypt\n...
1582  *
1583  * Preferences are handled in _PassUser
1584  */
1585 {
1586     var $_file, $_may_change;
1587
1588     // This can only be called from _PassUser, because the parent class 
1589     // sets the pref methods, before this class is initialized.
1590     function _FilePassUser($UserName='',$file='') {
1591         if (!$this->_prefs)
1592             _PassUser::_PassUser($UserName);
1593
1594         // read the .htaccess style file. We use our own copy of the standard pear class.
1595         require 'lib/pear/File_Passwd.php';
1596         // if passwords may be changed we have to lock them:
1597         $this->_may_change = defined('AUTH_USER_FILE_STORABLE') && AUTH_USER_FILE_STORABLE;
1598         if (empty($file) and defined('AUTH_USER_FILE'))
1599             $this->_file = File_Passwd(AUTH_USER_FILE, !empty($this->_may_change));
1600         elseif (!empty($file))
1601             $this->_file = File_Passwd($file, !empty($this->_may_change));
1602         else
1603             return false;
1604         return $this;
1605     }
1606  
1607     function mayChangePass() {
1608         return $this->_may_change;
1609     }
1610
1611     function userExists() {
1612         if (isset($this->_file->users[$this->_userid]))
1613             return true;
1614             
1615         return $this->_tryNextUser();
1616     }
1617
1618     function checkPass($submitted_password) {
1619         if ($this->_file->verifyPassword($this->_userid,$submitted_password)) {
1620             $this->_authmethod = 'File';
1621             $this->_level = WIKIAUTH_USER;
1622             return $this->_level;
1623         }
1624         
1625         return $this->_tryNextPass($submitted_password);
1626     }
1627
1628     function storePass($submitted_password) {
1629         if ($this->_may_change)
1630             return $this->_file->modUser($this->_userid,$submitted_password);
1631         else 
1632             return false;
1633     }
1634
1635 }
1636
1637 /**
1638  * Insert more auth classes here...
1639  * For example a customized db class for another db connection 
1640  * or a socket-based auth server
1641  *
1642  */
1643
1644
1645 /**
1646  * For security, this class should not be extended. Instead, extend
1647  * from _PassUser (think of this as unix "root").
1648  */
1649 class _AdminUser
1650 extends _PassUser
1651 {
1652
1653     function checkPass($submitted_password) {
1654         $stored_password = ADMIN_PASSWD;
1655         if ($this->_checkPass($submitted_password, $stored_password)) {
1656             $this->_level = WIKIAUTH_ADMIN;
1657             return $this->_level;
1658         } else {
1659             $this->_level = WIKIAUTH_ANON;
1660             return $this->_level;
1661         }
1662     }
1663
1664 }
1665
1666 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1667 /**
1668  * Various data classes for the preference types, 
1669  * to support get, set, sanify (range checking, ...)
1670  * update() will do the neccessary side-effects if a 
1671  * setting gets changed (theme, language, ...)
1672 */
1673
1674 class _UserPreference
1675 {
1676     var $default_value;
1677
1678     function _UserPreference ($default_value) {
1679         $this->default_value = $default_value;
1680     }
1681
1682     function sanify ($value) {
1683         return (string)$value;
1684     }
1685
1686     function get ($name) {
1687         if (isset($this->{$name}))
1688             return $this->{$name};
1689         else 
1690             return $this->default_value;
1691     }
1692
1693     function getraw ($name) {
1694         if (!empty($this->{$name}))
1695             return $this->{$name};
1696     }
1697
1698     // stores the value as $this->$name, and not as $this->value (clever?)
1699     function set ($name, $value) {
1700         if ($this->get($name) != $value) {
1701             $this->update($value);
1702         }
1703         if ($value != $this->default_value) {
1704             $this->{$name} = $value;
1705         }
1706         else 
1707             unset($this->{$name});
1708     }
1709
1710     // default: no side-effects 
1711     function update ($value) {
1712         ;
1713     }
1714 }
1715
1716 class _UserPreference_numeric
1717 extends _UserPreference
1718 {
1719     function _UserPreference_numeric ($default, $minval = false,
1720                                       $maxval = false) {
1721         $this->_UserPreference((double)$default);
1722         $this->_minval = (double)$minval;
1723         $this->_maxval = (double)$maxval;
1724     }
1725
1726     function sanify ($value) {
1727         $value = (double)$value;
1728         if ($this->_minval !== false && $value < $this->_minval)
1729             $value = $this->_minval;
1730         if ($this->_maxval !== false && $value > $this->_maxval)
1731             $value = $this->_maxval;
1732         return $value;
1733     }
1734 }
1735
1736 class _UserPreference_int
1737 extends _UserPreference_numeric
1738 {
1739     function _UserPreference_int ($default, $minval = false, $maxval = false) {
1740         $this->_UserPreference_numeric((int)$default, (int)$minval,
1741                                        (int)$maxval);
1742     }
1743
1744     function sanify ($value) {
1745         return (int)parent::sanify((int)$value);
1746     }
1747 }
1748
1749 class _UserPreference_bool
1750 extends _UserPreference
1751 {
1752     function _UserPreference_bool ($default = false) {
1753         $this->_UserPreference((bool)$default);
1754     }
1755
1756     function sanify ($value) {
1757         if (is_array($value)) {
1758             /* This allows for constructs like:
1759              *
1760              *   <input type="hidden" name="pref[boolPref][]" value="0" />
1761              *   <input type="checkbox" name="pref[boolPref][]" value="1" />
1762              *
1763              * (If the checkbox is not checked, only the hidden input
1764              * gets sent. If the checkbox is sent, both inputs get
1765              * sent.)
1766              */
1767             foreach ($value as $val) {
1768                 if ($val)
1769                     return true;
1770             }
1771             return false;
1772         }
1773         return (bool) $value;
1774     }
1775 }
1776
1777 class _UserPreference_language
1778 extends _UserPreference
1779 {
1780     function _UserPreference_language ($default = DEFAULT_LANGUAGE) {
1781         $this->_UserPreference($default);
1782     }
1783
1784     // FIXME: check for valid locale
1785     function sanify ($value) {
1786         // Revert to DEFAULT_LANGUAGE if user does not specify
1787         // language in UserPreferences or chooses <system language>.
1788         if ($value == '' or empty($value))
1789             $value = DEFAULT_LANGUAGE;
1790
1791         return (string) $value;
1792     }
1793 }
1794
1795 class _UserPreference_theme
1796 extends _UserPreference
1797 {
1798     function _UserPreference_theme ($default = THEME) {
1799         $this->_UserPreference($default);
1800     }
1801
1802     function sanify ($value) {
1803         if (FindFile($this->_themefile($value)))
1804             return $value;
1805         return $this->default_value;
1806     }
1807
1808     function update ($newvalue) {
1809         global $Theme;
1810         include_once($this->_themefile($newvalue));
1811         if (empty($Theme))
1812             include_once($this->_themefile(THEME));
1813     }
1814
1815     function _themefile ($theme) {
1816         return "themes/$theme/themeinfo.php";
1817     }
1818 }
1819
1820 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1821
1822 /**
1823  * UserPreferences
1824  * 
1825  * This object holds the $request->_prefs subobjects.
1826  * A simple packed array of non-default values get's stored as cookie,
1827  * homepage, or database, which are converted to the array of 
1828  * ->_prefs objects.
1829  * We don't store the objects, because otherwise we will
1830  * not be able to upgrade any subobject. And it's a waste of space also.
1831  */
1832 class UserPreferences
1833 {
1834     function UserPreferences ($saved_prefs = false) {
1835         // userid stored too, to ensure the prefs are being loaded for
1836         // the correct (currently signing in) userid if stored in a
1837         // cookie.
1838         $this->_prefs
1839             = array(
1840                     'userid'        => new _UserPreference(''),
1841                     'passwd'        => new _UserPreference(''),
1842                     'autologin'     => new _UserPreference_bool(),
1843                     'email'         => new _UserPreference(''),
1844                     'emailVerified' => new _UserPreference_bool(),
1845                     'notifyPages'   => new _UserPreference(''),
1846                     'theme'         => new _UserPreference_theme(THEME),
1847                     'lang'          => new _UserPreference_language(DEFAULT_LANGUAGE),
1848                     'editWidth'     => new _UserPreference_int(EDITWIDTH_DEFAULT_COLS,
1849                                                                EDITWIDTH_MIN_COLS,
1850                                                                EDITWIDTH_MAX_COLS),
1851                     'noLinkIcons'   => new _UserPreference_bool(),
1852                     'editHeight'    => new _UserPreference_int(EDITHEIGHT_DEFAULT_ROWS,
1853                                                                EDITHEIGHT_MIN_ROWS,
1854                                                                EDITHEIGHT_DEFAULT_ROWS),
1855                     'timeOffset'    => new _UserPreference_numeric(TIMEOFFSET_DEFAULT_HOURS,
1856                                                                    TIMEOFFSET_MIN_HOURS,
1857                                                                    TIMEOFFSET_MAX_HOURS),
1858                     'relativeDates' => new _UserPreference_bool()
1859                     );
1860
1861         if (is_array($saved_prefs)) {
1862             foreach ($saved_prefs as $name => $value)
1863                 $this->set($name, $value);
1864         }
1865     }
1866
1867     function _getPref ($name) {
1868         if (!isset($this->_prefs[$name])) {
1869             if ($name == 'passwd2') return false;
1870             trigger_error("$name: unknown preference", E_USER_NOTICE);
1871             return false;
1872         }
1873         return $this->_prefs[$name];
1874     }
1875     
1876     // get the value or default_value of the subobject
1877     function get ($name) {
1878         if ($_pref = $this->_getPref($name))
1879           return $_pref->get($name);
1880         else 
1881           return false;  
1882         /*      
1883         if (is_object($this->_prefs[$name]))
1884             return $this->_prefs[$name]->get($name);
1885         elseif (($value = $this->_getPref($name)) === false)
1886             return false;
1887         elseif (!isset($value))
1888             return $this->_prefs[$name]->default_value;
1889         else return $value;
1890         */
1891     }
1892
1893     // check and set the new value in the subobject
1894     function set ($name, $value) {
1895         $pref = $this->_getPref($name);
1896         if ($pref === false)
1897             return false;
1898
1899         /* do it here or outside? */
1900         if ($name == 'passwd' and 
1901             defined('PASSWORD_LENGTH_MINIMUM') and 
1902             strlen($value) <= PASSWORD_LENGTH_MINIMUM ) {
1903             //TODO: How to notify the user?
1904             return false;
1905         }
1906
1907         $newvalue = $pref->sanify($value);
1908         $pref->set($name,$newvalue);
1909         $this->_prefs[$name] = $pref;
1910         return true;
1911
1912         // don't set default values to save space (in cookies, db and
1913         // sesssion)
1914         /*
1915         if ($value == $pref->default_value)
1916             unset($this->_prefs[$name]);
1917         else
1918             $this->_prefs[$name] = $pref;
1919         */
1920     }
1921
1922     // array of objects => array of values
1923     function store() {
1924         $prefs = array();
1925         foreach ($this->_prefs as $name => $object) {
1926             if ($value = $object->getraw($name))
1927                 $prefs[] = array($name => $value);
1928         }
1929         return $this->pack($prefs);
1930     }
1931
1932     // packed string or array of values => array of values
1933     function retrieve($packed) {
1934         if (is_string($packed) and (substr($packed, 0, 2) == "a:"))
1935             $packed = unserialize($packed);
1936         if (!is_array($packed)) return false;
1937         $prefs = array();
1938         foreach ($packed as $name => $packed_pref) {
1939             if (substr($packed_pref, 0, 2) == "O:") {
1940                 //legacy: check if it's an old array of objects
1941                 // Looks like a serialized object. 
1942                 // This might fail if the object definition does not exist anymore.
1943                 // object with ->$name and ->default_value vars.
1944                 $pref = unserialize($packed_pref);
1945                 $prefs[$name] = $pref->get($name);
1946             } else {
1947                 $prefs[$name] = unserialize($packed_pref);
1948             }
1949         }
1950         return $prefs;
1951     }
1952     
1953     // array of objects
1954     function getAll() {
1955         return $this->_prefs;
1956     }
1957
1958     function pack($nonpacked) {
1959         return serialize($nonpacked);
1960     }
1961
1962     function unpack($packed) {
1963         if (!$packed)
1964             return false;
1965         if (substr($packed, 0, 2) == "O:") {
1966             // Looks like a serialized object
1967             return unserialize($packed);
1968         }
1969         if (substr($packed, 0, 2) == "a:") {
1970             return unserialize($packed);
1971         }
1972         //trigger_error("DEBUG: Can't unpack bad UserPreferences",
1973         //E_USER_WARNING);
1974         return false;
1975     }
1976
1977     function hash () {
1978         return hash($this->_prefs);
1979     }
1980 }
1981
1982 class CookieUserPreferences
1983 extends UserPreferences
1984 {
1985     function CookieUserPreferences ($saved_prefs = false) {
1986         UserPreferences::UserPreferences($saved_prefs);
1987     }
1988 }
1989
1990 class PageUserPreferences
1991 extends UserPreferences
1992 {
1993     function PageUserPreferences ($saved_prefs = false) {
1994         UserPreferences::UserPreferences($saved_prefs);
1995     }
1996 }
1997
1998 class PearDbUserPreferences
1999 extends UserPreferences
2000 {
2001     function PearDbUserPreferences ($saved_prefs = false) {
2002         UserPreferences::UserPreferences($saved_prefs);
2003     }
2004 }
2005
2006 class AdoDbUserPreferences
2007 extends UserPreferences
2008 {
2009     function AdoDbUserPreferences ($saved_prefs = false) {
2010         UserPreferences::UserPreferences($saved_prefs);
2011     }
2012     function getPreferences() {
2013         // override the generic slow method here for efficiency
2014         _AnonUser::getPreferences();
2015         $this->getAuthDbh();
2016         if ((! $this->_prefs) and isset($this->_prefs->_select)) {
2017             $dbh = & $this->_auth_dbi;
2018             $rs = $dbh->Execute(sprintf($this->_prefs->_select,$dbh->qstr($this->_userid)));
2019             if ($rs->EOF) {
2020                 $rs->Close();
2021             } else {
2022                 $prefs_blob = $rs->fields['pref_blob'];
2023                 $rs->Close();
2024                 if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) {
2025                     $this->_prefs = new UserPreferences($restored_from_db);
2026                     return $this->_prefs;
2027                 }
2028             }
2029         }
2030         if ((! $this->_prefs) && $this->_HomePagehandle) {
2031             if ($restored_from_page = $this->_prefs->retrieve($this->_HomePagehandle->get('pref'))) {
2032                 $this->_prefs = new UserPreferences($restored_from_page);
2033                 return $this->_prefs;
2034             }
2035         }
2036         return $this->_prefs;
2037     }
2038 }
2039
2040
2041 // $Log: not supported by cvs2svn $
2042 // Revision 1.22  2004/02/27 05:15:40  rurban
2043 // more stability. detected by Micki
2044 //
2045 // Revision 1.21  2004/02/26 20:43:49  rurban
2046 // new HttpAuthPassUser class (forces http auth if in the auth loop)
2047 // fixed user upgrade: don't return _PassUser in the first hand.
2048 //
2049 // Revision 1.20  2004/02/26 01:29:11  rurban
2050 // important fixes: endless loops in certain cases. minor rewrite
2051 //
2052 // Revision 1.19  2004/02/25 17:15:17  rurban
2053 // improve stability
2054 //
2055 // Revision 1.18  2004/02/24 15:20:05  rurban
2056 // fixed minor warnings: unchecked args, POST => Get urls for sortby e.g.
2057 //
2058 // Revision 1.17  2004/02/17 12:16:42  rurban
2059 // started with changePass support. not yet used.
2060 //
2061 // Revision 1.16  2004/02/15 22:23:45  rurban
2062 // oops, fixed showstopper (endless recursion)
2063 //
2064 // Revision 1.15  2004/02/15 21:34:37  rurban
2065 // PageList enhanced and improved.
2066 // fixed new WikiAdmin... plugins
2067 // editpage, Theme with exp. htmlarea framework
2068 //   (htmlarea yet committed, this is really questionable)
2069 // WikiUser... code with better session handling for prefs
2070 // enhanced UserPreferences (again)
2071 // RecentChanges for show_deleted: how should pages be deleted then?
2072 //
2073 // Revision 1.14  2004/02/15 17:30:13  rurban
2074 // workaround for lost db connnection handle on session restauration (->_auth_dbi)
2075 // fixed getPreferences() (esp. from sessions)
2076 // fixed setPreferences() (update and set),
2077 // fixed AdoDb DB statements,
2078 // update prefs only at UserPreferences POST (for testing)
2079 // unified db prefs methods (but in external pref classes yet)
2080 //
2081 // Revision 1.13  2004/02/09 03:58:12  rurban
2082 // for now default DB_SESSION to false
2083 // PagePerm:
2084 //   * not existing perms will now query the parent, and not
2085 //     return the default perm
2086 //   * added pagePermissions func which returns the object per page
2087 //   * added getAccessDescription
2088 // WikiUserNew:
2089 //   * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
2090 //   * force init of authdbh in the 2 db classes
2091 // main:
2092 //   * fixed session handling (not triple auth request anymore)
2093 //   * don't store cookie prefs with sessions
2094 // stdlib: global obj2hash helper from _AuthInfo, also needed for PagePerm
2095 //
2096 // Revision 1.12  2004/02/07 10:41:25  rurban
2097 // fixed auth from session (still double code but works)
2098 // fixed GroupDB
2099 // fixed DbPassUser upgrade and policy=old
2100 // added GroupLdap
2101 //
2102 // Revision 1.11  2004/02/03 09:45:39  rurban
2103 // LDAP cleanup, start of new Pref classes
2104 //
2105 // Revision 1.10  2004/02/01 09:14:11  rurban
2106 // Started with Group_Ldap (not yet ready)
2107 // added new _AuthInfo plugin to help in auth problems (warning: may display passwords)
2108 // fixed some configurator vars
2109 // renamed LDAP_AUTH_SEARCH to LDAP_BASE_DN
2110 // changed PHPWIKI_VERSION from 1.3.8a to 1.3.8pre
2111 // USE_DB_SESSION defaults to true on SQL
2112 // changed GROUP_METHOD definition to string, not constants
2113 // changed sample user DBAuthParams from UPDATE to REPLACE to be able to
2114 //   create users. (Not to be used with external databases generally, but
2115 //   with the default internal user table)
2116 //
2117 // fixed the IndexAsConfigProblem logic. this was flawed:
2118 //   scripts which are the same virtual path defined their own lib/main call
2119 //   (hmm, have to test this better, phpwiki.sf.net/demo works again)
2120 //
2121 // Revision 1.9  2004/01/30 19:57:58  rurban
2122 // fixed DBAuthParams['pref_select']: wrong _auth_dbi object used.
2123 //
2124 // Revision 1.8  2004/01/30 18:46:15  rurban
2125 // fix "lib/WikiUserNew.php:572: Notice[8]: Undefined variable: DBParams"
2126 //
2127 // Revision 1.7  2004/01/27 23:23:39  rurban
2128 // renamed ->Username => _userid for consistency
2129 // renamed mayCheckPassword => mayCheckPass
2130 // fixed recursion problem in WikiUserNew
2131 // fixed bogo login (but not quite 100% ready yet, password storage)
2132 //
2133 // Revision 1.6  2004/01/26 09:17:49  rurban
2134 // * changed stored pref representation as before.
2135 //   the array of objects is 1) bigger and 2)
2136 //   less portable. If we would import packed pref
2137 //   objects and the object definition was changed, PHP would fail.
2138 //   This doesn't happen with an simple array of non-default values.
2139 // * use $prefs->retrieve and $prefs->store methods, where retrieve
2140 //   understands the interim format of array of objects also.
2141 // * simplified $prefs->get() and fixed $prefs->set()
2142 // * added $user->_userid and class '_WikiUser' portability functions
2143 // * fixed $user object ->_level upgrading, mostly using sessions.
2144 //   this fixes yesterdays problems with loosing authorization level.
2145 // * fixed WikiUserNew::checkPass to return the _level
2146 // * fixed WikiUserNew::isSignedIn
2147 // * added explodePageList to class PageList, support sortby arg
2148 // * fixed UserPreferences for WikiUserNew
2149 // * fixed WikiPlugin for empty defaults array
2150 // * UnfoldSubpages: added pagename arg, renamed pages arg,
2151 //   removed sort arg, support sortby arg
2152 //
2153 // Revision 1.5  2004/01/25 03:05:00  rurban
2154 // First working version, but has some problems with the current main loop.
2155 // Implemented new auth method dispatcher and policies, all the external
2156 // _PassUser classes (also for ADODB and Pear DB).
2157 // The two global funcs UserExists() and CheckPass() are probably not needed,
2158 // since the auth loop is done recursively inside the class code, upgrading
2159 // the user class within itself.
2160 // Note: When a higher user class is returned, this doesn't mean that the user
2161 // is authorized, $user->_level is still low, and only upgraded on successful
2162 // login.
2163 //
2164 // Revision 1.4  2003/12/07 19:29:48  carstenklapp
2165 // Code Housecleaning: fixed syntax errors. (php -l *.php)
2166 //
2167 // Revision 1.3  2003/12/06 19:10:46  carstenklapp
2168 // Finished off logic for determining user class, including
2169 // PassUser. Removed ability of BogoUser to save prefs into a page.
2170 //
2171 // Revision 1.2  2003/12/03 21:45:48  carstenklapp
2172 // Added admin user, password user, and preference classes. Added
2173 // password checking functions for users and the admin. (Now the easy
2174 // parts are nearly done).
2175 //
2176 // Revision 1.1  2003/12/02 05:46:36  carstenklapp
2177 // Complete rewrite of WikiUser.php.
2178 //
2179 // This should make it easier to hook in user permission groups etc. some
2180 // time in the future. Most importantly, to finally get UserPreferences
2181 // fully working properly for all classes of users: AnonUser, BogoUser,
2182 // AdminUser; whether they have a NamesakePage (PersonalHomePage) or not,
2183 // want a cookie or not, and to bring back optional AutoLogin with the
2184 // UserName stored in a cookie--something that was lost after PhpWiki had
2185 // dropped the default http auth login method.
2186 //
2187 // Added WikiUser classes which will (almost) work together with existing
2188 // UserPreferences class. Other parts of PhpWiki need to be updated yet
2189 // before this code can be hooked up.
2190 //
2191
2192 // Local Variables:
2193 // mode: php
2194 // tab-width: 8
2195 // c-basic-offset: 4
2196 // c-hanging-comment-ender-p: nil
2197 // indent-tabs-mode: nil
2198 // End:
2199 ?>