]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUserNew.php
protect against empty username
[SourceForge/phpwiki.git] / lib / WikiUserNew.php
1 <?php //-*-php-*-
2 rcs_id('$Id: WikiUserNew.php,v 1.145 2007-06-07 16:56:27 rurban Exp $');
3 /* Copyright (C) 2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
4  *
5  * This file is part of PhpWiki.
6  * 
7  * PhpWiki is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  * 
12  * PhpWiki is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with PhpWiki; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 /**
22  * This is a complete OOP rewrite of the old WikiUser code with various
23  * configurable external authentication methods.
24  *
25  * There's only one entry point, the function WikiUser which returns 
26  * a WikiUser object, which contains the name, authlevel and user's preferences.
27  * This object might get upgraded during the login step and later also.
28  * There exist three preferences storage methods: cookie, homepage and db,
29  * and multiple password checking methods.
30  * See index.php for $USER_AUTH_ORDER[] and USER_AUTH_POLICY if 
31  * ALLOW_USER_PASSWORDS is defined.
32  *
33  * Each user object must define the two preferences methods 
34  *  getPreferences(), setPreferences(), 
35  * and the following 1-4 auth methods
36  *  checkPass()  must be defined by all classes,
37  *  userExists() only if USER_AUTH_POLICY'=='strict' 
38  *  mayChangePass()  only if the password is storable.
39  *  storePass()  only if the password is storable.
40  *
41  * WikiUser() given no name, returns an _AnonUser (anonymous user)
42  * object, who may or may not have a cookie. 
43  * However, if the there's a cookie with the userid or a session, 
44  * the user is upgraded to the matching user object.
45  * Given a user name, returns a _BogoUser object, who may or may not 
46  * have a cookie and/or PersonalPage, one of the various _PassUser objects 
47  * or an _AdminUser object.
48  * BTW: A BogoUser is a userid (loginname) as valid WikiWord, who might 
49  * have stored a password or not. If so, his account is secure, if not
50  * anybody can use it, because the username is visible e.g. in RecentChanges.
51  *
52  * Takes care of passwords, all preference loading/storing in the
53  * user's page and any cookies. lib/main.php will query the user object to
54  * verify the password as appropriate.
55  *
56  * @author: Reini Urban (the tricky parts), 
57  *          Carsten Klapp (started rolling the ball)
58  *
59  * Random architectural notes, sorted by date:
60  * 2004-01-25 rurban
61  * Test it by defining ENABLE_USER_NEW in config/config.ini
62  * 1) Now a ForbiddenUser is returned instead of false.
63  * 2) Previously ALLOW_ANON_USER = false meant that anon users cannot edit, 
64  *    but may browse. Now with ALLOW_ANON_USER = false he may not browse, 
65  *    which is needed to disable browse PagePermissions.
66  *    I added now ALLOW_ANON_EDIT = true to makes things clear. 
67  *    (which replaces REQUIRE_SIGNIN_BEFORE_EDIT)
68  * 2004-02-27 rurban:
69  * 3) Removed pear prepare. Performance hog, and using integers as 
70  *    handler doesn't help. Do simple sprintf as with adodb. And a prepare
71  *    in the object init is no advantage, because in the init loop a lot of 
72  *    objects are tried, but not used.
73  * 4) Already gotten prefs are passed to the next object to avoid 
74  *    duplicate getPreferences() calls.
75  * 2004-03-18 rurban
76  * 5) Major php-5 problem: $this re-assignment is disallowed by the parser
77  *    So we cannot just discrimate with 
78  *      if (!check_php_version(5))
79  *          $this = $user;
80  *    A /php5-patch.php is provided, which patches the src automatically 
81  *    for php4 and php5. Default is php4.
82  *    Update: not needed anymore. we use eval to fool the load-time syntax checker.
83  * 2004-03-24 rurban
84  * 6) enforced new cookie policy: prefs don't get stored in cookies
85  *    anymore, only in homepage and/or database, but always in the 
86  *    current session. old pref cookies will get deleted.
87  * 2004-04-04 rurban
88  * 7) Certain themes should be able to extend the predefined list 
89  *    of preferences. Display/editing is done in the theme specific userprefs.tmpl,
90  *    but storage must be extended to the Get/SetPreferences methods.
91  *    <theme>/themeinfo.php must provide CustomUserPreferences:
92  *      A list of name => _UserPreference class pairs.
93  */
94
95 define('WIKIAUTH_FORBIDDEN', -1); // Completely not allowed.
96 define('WIKIAUTH_ANON', 0);       // Not signed in.
97 define('WIKIAUTH_BOGO', 1);       // Any valid WikiWord is enough.
98 define('WIKIAUTH_USER', 2);       // Bogo user with a password.
99 define('WIKIAUTH_ADMIN', 10);     // UserName == ADMIN_USER.
100 define('WIKIAUTH_UNOBTAINABLE', 100);  // Permissions that no user can achieve
101
102 //if (!defined('COOKIE_EXPIRATION_DAYS')) define('COOKIE_EXPIRATION_DAYS', 365);
103 //if (!defined('COOKIE_DOMAIN'))          define('COOKIE_DOMAIN', '/');
104 if (!defined('EDITWIDTH_MIN_COLS'))     define('EDITWIDTH_MIN_COLS',     30);
105 if (!defined('EDITWIDTH_MAX_COLS'))     define('EDITWIDTH_MAX_COLS',    150);
106 if (!defined('EDITWIDTH_DEFAULT_COLS')) define('EDITWIDTH_DEFAULT_COLS', 80);
107
108 if (!defined('EDITHEIGHT_MIN_ROWS'))     define('EDITHEIGHT_MIN_ROWS',      5);
109 if (!defined('EDITHEIGHT_MAX_ROWS'))     define('EDITHEIGHT_MAX_ROWS',     80);
110 if (!defined('EDITHEIGHT_DEFAULT_ROWS')) define('EDITHEIGHT_DEFAULT_ROWS', 22);
111
112 define('TIMEOFFSET_MIN_HOURS', -26);
113 define('TIMEOFFSET_MAX_HOURS',  26);
114 if (!defined('TIMEOFFSET_DEFAULT_HOURS')) define('TIMEOFFSET_DEFAULT_HOURS', 0);
115
116 /* EMAIL VERIFICATION
117  * On certain nets or hosts the email domain cannot be determined automatically from the DNS.
118  * Provide some overrides here.
119  *    ( username @ ) domain => mail-domain
120  */
121 $EMailHosts = array('avl.com' => 'mail.avl.com');
122
123 /**
124  * There are be the following constants in config/config.ini to 
125  * establish login parameters:
126  *
127  * ALLOW_ANON_USER         default true
128  * ALLOW_ANON_EDIT         default true
129  * ALLOW_BOGO_LOGIN        default true
130  * ALLOW_USER_PASSWORDS    default true
131  * PASSWORD_LENGTH_MINIMUM default 0
132  *
133  * To require user passwords for editing:
134  * ALLOW_ANON_USER  = true
135  * ALLOW_ANON_EDIT  = false   (before named REQUIRE_SIGNIN_BEFORE_EDIT)
136  * ALLOW_BOGO_LOGIN = false
137  * ALLOW_USER_PASSWORDS = true
138  *
139  * To establish a COMPLETELY private wiki, such as an internal
140  * corporate one:
141  * ALLOW_ANON_USER = false
142  * (and probably require user passwords as described above). In this
143  * case the user will be prompted to login immediately upon accessing
144  * any page.
145  *
146  * There are other possible combinations, but the typical wiki (such
147  * as http://PhpWiki.sf.net/phpwiki) would usually just leave all four 
148  * enabled.
149  *
150  */
151
152 // The last object in the row is the bad guy...
153 if (!is_array($USER_AUTH_ORDER))
154     $USER_AUTH_ORDER = array("Forbidden");
155 else
156     $USER_AUTH_ORDER[] = "Forbidden";
157
158 // Local convenience functions.
159 function _isAnonUserAllowed() {
160     return (defined('ALLOW_ANON_USER') && ALLOW_ANON_USER);
161 }
162 function _isBogoUserAllowed() {
163     return (defined('ALLOW_BOGO_LOGIN') && ALLOW_BOGO_LOGIN);
164 }
165 function _isUserPasswordsAllowed() {
166     return (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS);
167 }
168
169 // Possibly upgrade userobject functions.
170 function _determineAdminUserOrOtherUser($UserName) {
171     // Sanity check. User name is a condition of the definition of the
172     // _AdminUser, _BogoUser and _passuser.
173     if (!$UserName)
174         return $GLOBALS['ForbiddenUser'];
175
176     //FIXME: check admin membership later at checkPass. Now we cannot raise the level.
177     //$group = &WikiGroup::getGroup($GLOBALS['request']);
178     if ($UserName == ADMIN_USER)
179         return new _AdminUser($UserName);
180     /* elseif ($group->isMember(GROUP_ADMIN)) { // unneeded code
181         return _determineBogoUserOrPassUser($UserName);
182     }
183     */
184     else
185         return _determineBogoUserOrPassUser($UserName);
186 }
187
188 function _determineBogoUserOrPassUser($UserName) {
189     global $ForbiddenUser;
190
191     // Sanity check. User name is a condition of the definition of
192     // _BogoUser and _PassUser.
193     if (!$UserName)
194         return $ForbiddenUser;
195
196     // Check for password and possibly upgrade user object.
197     // $_BogoUser = new _BogoUser($UserName);
198     if (_isBogoUserAllowed() and isWikiWord($UserName)) {
199         include_once("lib/WikiUser/BogoLogin.php");
200         $_BogoUser = new _BogoLoginPassUser($UserName);
201         if ($_BogoUser->userExists() or $GLOBALS['request']->getArg('auth'))
202             return $_BogoUser;
203     }
204     if (_isUserPasswordsAllowed()) {
205         // PassUsers override BogoUsers if a password is stored
206         if (isset($_BogoUser) and isset($_BogoUser->_prefs) 
207             and $_BogoUser->_prefs->get('passwd'))
208             return new _PassUser($UserName, $_BogoUser->_prefs);
209         else { 
210             $_PassUser = new _PassUser($UserName,
211                                        isset($_BogoUser) ? $_BogoUser->_prefs : false);
212             if ($_PassUser->userExists() or $GLOBALS['request']->getArg('auth')) {
213                 if (isset($GLOBALS['request']->_user_class))
214                     $class = $GLOBALS['request']->_user_class;
215                 elseif (strtolower(get_class($_PassUser)) == "_passuser")
216                     $class = $_PassUser->nextClass();
217                 else
218                     $class = get_class($_PassUser);
219                 if ($user = new $class($UserName, $_PassUser->_prefs)) {
220                     return $user;
221                 } else {
222                     return $_PassUser;
223                 }
224             }
225         }
226     }
227     // No Bogo- or PassUser exists, or
228     // passwords are not allowed, and bogo is disallowed too.
229     // (Only the admin can sign in).
230     return $ForbiddenUser;
231 }
232
233 /**
234  * Primary WikiUser function, called by lib/main.php.
235  * 
236  * This determines the user's type and returns an appropriate user
237  * object. lib/main.php then querys the resultant object for password
238  * validity as necessary.
239  *
240  * If an _AnonUser object is returned, the user may only browse pages
241  * (and save prefs in a cookie).
242  *
243  * To disable access but provide prefs the global $ForbiddenUser class 
244  * is returned. (was previously false)
245  * 
246  */
247 function WikiUser ($UserName = '') {
248     global $ForbiddenUser, $HTTP_SESSION_VARS;
249
250     //Maybe: Check sessionvar for username & save username into
251     //sessionvar (may be more appropriate to do this in lib/main.php).
252     if ($UserName) {
253         $ForbiddenUser = new _ForbiddenUser($UserName);
254         // Found a user name.
255         return _determineAdminUserOrOtherUser($UserName);
256     }
257     elseif (!empty($HTTP_SESSION_VARS['userid'])) {
258         // Found a user name.
259         $ForbiddenUser = new _ForbiddenUser($_SESSION['userid']);
260         return _determineAdminUserOrOtherUser($_SESSION['userid']);
261     }
262     else {
263         // Check for autologin pref in cookie and possibly upgrade
264         // user object to another type.
265         $_AnonUser = new _AnonUser();
266         if ($UserName = $_AnonUser->_userid && $_AnonUser->_prefs->get('autologin')) {
267             // Found a user name.
268             $ForbiddenUser = new _ForbiddenUser($UserName);
269             return _determineAdminUserOrOtherUser($UserName);
270         }
271         else {
272             $ForbiddenUser = new _ForbiddenUser();
273             if (_isAnonUserAllowed())
274                 return $_AnonUser;
275             return $ForbiddenUser; // User must sign in to browse pages.
276         }
277         return $ForbiddenUser;     // User must sign in with a password.
278     }
279     /*
280     trigger_error("DEBUG: Note: End of function reached in WikiUser." . " "
281                   . "Unexpectedly, an appropriate user class could not be determined.");
282     return $ForbiddenUser; // Failsafe.
283     */
284 }
285
286 /**
287  * WikiUser.php use the name 'WikiUser'
288  */
289 function WikiUserClassname() {
290     return '_WikiUser';
291 }
292
293
294 /**
295  * Upgrade olduser by copying properties from user to olduser.
296  * We are not sure yet, for which php's a simple $this = $user works reliably,
297  * (on php4 it works ok, on php5 it's currently disallowed on the parser level)
298  * that's why try it the hard way.
299  */
300 function UpgradeUser ($user, $newuser) {
301     if (isa($user,'_WikiUser') and isa($newuser,'_WikiUser')) {
302         // populate the upgraded class $newuser with the values from the current user object
303         //only _auth_level, _current_method, _current_index,
304         if (!empty($user->_level) and 
305             $user->_level > $newuser->_level)
306             $newuser->_level = $user->_level;
307         if (!empty($user->_current_index) and
308             $user->_current_index > $newuser->_current_index) {
309             $newuser->_current_index = $user->_current_index;
310             $newuser->_current_method = $user->_current_method;
311         }
312         if (!empty($user->_authmethod))
313             $newuser->_authmethod = $user->_authmethod;
314         $GLOBALS['request']->_user_class = get_class($newuser);
315         /*
316         foreach (get_object_vars($user) as $k => $v) {
317             if (!empty($v)) $olduser->$k = $v;  
318         }
319         */
320         $newuser->hasHomePage(); // revive db handle, because these don't survive sessions
321         //$GLOBALS['request']->_user = $olduser;
322         return $newuser;
323     } else {
324         return false;
325     }
326 }
327
328 /**
329  * Probably not needed, since we use the various user objects methods so far.
330  * Anyway, here it is, looping through all available objects.
331  */
332 function UserExists ($UserName) {
333     global $request;
334     if (!($user = $request->getUser()))
335         $user = WikiUser($UserName);
336     if (!$user) 
337         return false;
338     if ($user->userExists($UserName)) {
339         $request->_user = $user;
340         return true;
341     }
342     if (isa($user,'_BogoUser'))
343         $user = new _PassUser($UserName,$user->_prefs);
344     $class = $user->nextClass();
345     if ($user = new $class($UserName, $user->_prefs)) {
346         return $user->userExists($UserName);
347     }
348     $request->_user = $GLOBALS['ForbiddenUser'];
349     return false;
350 }
351
352 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
353
354 /** 
355  * Base WikiUser class.
356  */
357 class _WikiUser
358 {
359      var $_userid = '';
360      var $_level = WIKIAUTH_ANON;
361      var $_prefs = false;
362      var $_HomePagehandle = false;
363
364     // constructor
365     function _WikiUser($UserName='', $prefs=false) {
366
367         $this->_userid = $UserName;
368         $this->_HomePagehandle = false;
369         if ($UserName) {
370             $this->hasHomePage();
371         }
372         if (empty($this->_prefs)) {
373             if ($prefs) $this->_prefs = $prefs;
374             else $this->getPreferences();
375         }
376     }
377
378     function UserName() {
379         if (!empty($this->_userid))
380             return $this->_userid;
381     }
382
383     function getPreferences() {
384         trigger_error("DEBUG: Note: undefined _WikiUser class trying to load prefs." . " "
385                       . "New subclasses of _WikiUser must override this function.");
386         return false;
387     }
388
389     function setPreferences($prefs, $id_only) {
390         trigger_error("DEBUG: Note: undefined _WikiUser class trying to save prefs." 
391                       . " "
392                       . "New subclasses of _WikiUser must override this function.");
393         return false;
394     }
395
396     function userExists() {
397         return $this->hasHomePage();
398     }
399
400     function checkPass($submitted_password) {
401         // By definition, an undefined user class cannot sign in.
402         trigger_error("DEBUG: Warning: undefined _WikiUser class trying to sign in." 
403                       . " "
404                       . "New subclasses of _WikiUser must override this function.");
405         return false;
406     }
407
408     // returns page_handle to user's home page or false if none
409     function hasHomePage() {
410         if ($this->_userid) {
411             if (!empty($this->_HomePagehandle) and is_object($this->_HomePagehandle)) {
412                 return $this->_HomePagehandle->exists();
413             }
414             else {
415                 // check db again (maybe someone else created it since
416                 // we logged in.)
417                 global $request;
418                 $this->_HomePagehandle = $request->getPage($this->_userid);
419                 return $this->_HomePagehandle->exists();
420             }
421         }
422         // nope
423         return false;
424     }
425
426     // 
427     function createHomePage() {
428         global $request;
429         $versiondata = array('author' => _("The PhpWiki programming team"));
430         $request->_dbi->save(_("Automatically created user homepage to be able to store UserPreferences.").
431                              "\n{{Template/UserPage}}",
432                              1, $versiondata);
433         $request->_dbi->touch();                             
434         $this->_HomePagehandle = $request->getPage($this->_userid);
435     }
436     
437     // innocent helper: case-insensitive position in _auth_methods
438     function array_position ($string, $array) {
439         $string = strtolower($string);
440         for ($found = 0; $found < count($array); $found++) {
441             if (strtolower($array[$found]) == $string)
442                 return $found;
443         }
444         return false;
445     }
446
447     function nextAuthMethodIndex() {
448         if (empty($this->_auth_methods)) 
449             $this->_auth_methods = $GLOBALS['USER_AUTH_ORDER'];
450         if (empty($this->_current_index)) {
451             if (strtolower(get_class($this)) != '_passuser') {
452                 $this->_current_method = substr(get_class($this),1,-8);
453                 $this->_current_index = $this->array_position($this->_current_method,
454                                                               $this->_auth_methods);
455             } else {
456                 $this->_current_index = -1;
457             }
458         }
459         $this->_current_index++;
460         if ($this->_current_index >= count($this->_auth_methods))
461             return false;
462         $this->_current_method = $this->_auth_methods[$this->_current_index];
463         return $this->_current_index;
464     }
465
466     function AuthMethod($index = false) {
467         return $this->_auth_methods[ $index === false 
468                                      ? count($this->_auth_methods)-1 
469                                      : $index];
470     }
471
472     // upgrade the user object
473     function nextClass() {
474         $method = $this->AuthMethod($this->nextAuthMethodIndex());
475         include_once("lib/WikiUser/$method.php");
476         return "_".$method."PassUser";
477     }
478
479     //Fixme: for _HttpAuthPassUser
480     function PrintLoginForm (&$request, $args, $fail_message = false,
481                              $seperate_page = false) {
482         include_once('lib/Template.php');
483         // Call update_locale in case the system's default language is not 'en'.
484         // (We have no user pref for lang at this point yet, no one is logged in.)
485         if ($GLOBALS['LANG'] != DEFAULT_LANGUAGE)
486             update_locale(DEFAULT_LANGUAGE);
487         $userid = $this->_userid;
488         $require_level = 0;
489         extract($args); // fixme
490
491         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
492
493         $pagename = $request->getArg('pagename');
494         $nocache = 1;
495         $login = Template('login',
496                           compact('pagename', 'userid', 'require_level',
497                                   'fail_message', 'pass_required', 'nocache'));
498         // check if the html template was already processed
499         $seperate_page = $seperate_page ? true : !alreadyTemplateProcessed('html');
500         if ($seperate_page) {
501             $page = $request->getPage($pagename);
502             $revision = $page->getCurrentRevision();
503             return GeneratePage($login,_("Sign In"), $revision);
504         } else {
505             return $login->printExpansion();
506         }
507     }
508
509     /** Signed in but not password checked or empty password.
510      */
511     function isSignedIn() {
512         return (isa($this,'_BogoUser') or isa($this,'_PassUser'));
513     }
514
515     /** This is password checked for sure.
516      */
517     function isAuthenticated() {
518         //return isa($this,'_PassUser');
519         //return isa($this,'_BogoUser') || isa($this,'_PassUser');
520         return $this->_level >= WIKIAUTH_BOGO;
521     }
522
523     function isAdmin () {
524         static $group; 
525         if ($this->_level == WIKIAUTH_ADMIN) return true;
526         if (!$this->isSignedIn()) return false;
527         if (!$this->isAuthenticated()) return false;
528
529         if (!$group) $group = &$GLOBALS['request']->getGroup();
530         return ($this->_level > WIKIAUTH_BOGO and $group->isMember(GROUP_ADMIN));
531     }
532
533     /** Name or IP for a signed user. UserName could come from a cookie e.g.
534      */
535     function getId () {
536         return ( $this->UserName()
537                  ? $this->UserName()
538                  : $GLOBALS['request']->get('REMOTE_ADDR') );
539     }
540
541     /** Name for an authenticated user. No IP here.
542      */
543     function getAuthenticatedId() {
544         return ( $this->isAuthenticated()
545                  ? $this->_userid
546                  : ''); //$GLOBALS['request']->get('REMOTE_ADDR') );
547     }
548
549     function hasAuthority ($require_level) {
550         return $this->_level >= $require_level;
551     }
552
553     /* This is quite restrictive and not according the login description online. 
554        Any word char (A-Za-z0-9_), " ", ".", "@" and "-"
555        The backends may loosen or tighten this.
556     */
557     function isValidName ($userid = false) {
558         if (!$userid) $userid = $this->_userid;
559         if (!$userid) return false;
560         return preg_match("/^[\-\w\.@ ]+$/u", $userid) and strlen($userid) < 32;
561     }
562
563     /**
564      * Called on an auth_args POST request, such as login, logout or signin.
565      * TODO: Check BogoLogin users with empty password. (self-signed users)
566      */
567     function AuthCheck ($postargs) {
568         // Normalize args, and extract.
569         $keys = array('userid', 'passwd', 'require_level', 'login', 'logout',
570                       'cancel');
571         foreach ($keys as $key)
572             $args[$key] = isset($postargs[$key]) ? $postargs[$key] : false;
573         extract($args);
574         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
575
576         if ($logout) { // Log out
577             if (method_exists($GLOBALS['request']->_user, "logout")) { //_HttpAuthPassUser
578                 $GLOBALS['request']->_user->logout();
579             }
580             $user = new _AnonUser();
581             $user->_userid = '';
582             $user->_level = WIKIAUTH_ANON;
583             return $user; 
584         } elseif ($cancel)
585             return false;        // User hit cancel button.
586         elseif (!$login && !$userid)
587             return false;       // Nothing to do?
588
589         if (!$this->isValidName($userid))
590             return _("Invalid username.");;
591
592         $authlevel = $this->checkPass($passwd === false ? '' : $passwd);
593         if ($authlevel <= 0) { // anon or forbidden
594             if ($passwd)
595                 return _("Invalid password.");
596             else
597                 return _("Invalid password or userid.");
598         } elseif ($authlevel < $require_level) { // auth ok, but not enough 
599             if (!empty($this->_current_method) and strtolower(get_class($this)) == '_passuser') 
600             {
601                 // upgrade class
602                 $class = "_" . $this->_current_method . "PassUser";
603                 include_once("lib/WikiUser/".$this->_current_method.".php");
604                 $user = new $class($userid,$this->_prefs);
605                 if (!check_php_version(5))
606                     eval("\$this = \$user;");
607                 // /*PHP5 patch*/$this = $user;
608                 $this->_level = $authlevel;
609                 return $user;
610             }
611             $this->_userid = $userid;
612             $this->_level = $authlevel;
613             return _("Insufficient permissions.");
614         }
615
616         // Successful login.
617         //$user = $GLOBALS['request']->_user;
618         if (!empty($this->_current_method) and 
619             strtolower(get_class($this)) == '_passuser') 
620         {
621             // upgrade class
622             $class = "_" . $this->_current_method . "PassUser";
623             include_once("lib/WikiUser/".$this->_current_method.".php");
624             $user = new $class($userid, $this->_prefs);
625             if (!check_php_version(5))
626                 eval("\$this = \$user;");
627             // /*PHP5 patch*/$this = $user;
628             $user->_level = $authlevel;
629             return $user;
630         }
631         $this->_userid = $userid;
632         $this->_level = $authlevel;
633         return $this;
634     }
635
636 }
637
638 /**
639  * Not authenticated in user, but he may be signed in. Basicly with view access only.
640  * prefs are stored in cookies, but only the userid.
641  */
642 class _AnonUser
643 extends _WikiUser
644 {
645     var $_level = WIKIAUTH_ANON;        // var in php-5.0.0RC1 deprecated
646
647     /** Anon only gets to load and save prefs in a cookie, that's it.
648      */
649     function getPreferences() {
650         global $request;
651
652         if (empty($this->_prefs))
653             $this->_prefs = new UserPreferences;
654         $UserName = $this->UserName();
655
656         // Try to read deprecated 1.3.x style cookies
657         if ($cookie = $request->cookies->get_old(WIKI_NAME)) {
658             if (! $unboxedcookie = $this->_prefs->retrieve($cookie)) {
659                 trigger_error(_("Empty Preferences or format of UserPreferences cookie not recognised.") 
660                               . "\n"
661                               . sprintf("%s='%s'", WIKI_NAME, $cookie)
662                               . "\n"
663                               . _("Default preferences will be used."),
664                               E_USER_NOTICE);
665             }
666             /**
667              * Only set if it matches the UserName who is
668              * signing in or if this really is an Anon login (no
669              * username). (Remember, _BogoUser and higher inherit this
670              * function too!).
671              */
672             if (! $UserName || $UserName == @$unboxedcookie['userid']) {
673                 $updated = $this->_prefs->updatePrefs($unboxedcookie);
674                 //$this->_prefs = new UserPreferences($unboxedcookie);
675                 $UserName = @$unboxedcookie['userid'];
676                 if (is_string($UserName) and (substr($UserName,0,2) != 's:'))
677                     $this->_userid = $UserName;
678                 else 
679                     $UserName = false;    
680             }
681             // v1.3.8 policy: don't set PhpWiki cookies, only plaintext WIKI_ID cookies
682             if (!headers_sent())
683                 $request->deleteCookieVar(WIKI_NAME);
684         }
685         // Try to read deprecated 1.3.4 style cookies
686         if (! $UserName and ($cookie = $request->cookies->get_old("WIKI_PREF2"))) {
687             if (! $unboxedcookie = $this->_prefs->retrieve($cookie)) {
688                 if (! $UserName || $UserName == $unboxedcookie['userid']) {
689                     $updated = $this->_prefs->updatePrefs($unboxedcookie);
690                     //$this->_prefs = new UserPreferences($unboxedcookie);
691                     $UserName = $unboxedcookie['userid'];
692                     if (is_string($UserName) and (substr($UserName,0,2) != 's:'))
693                         $this->_userid = $UserName;
694                     else 
695                         $UserName = false;    
696                 }
697                 if (!headers_sent())
698                     $request->deleteCookieVar("WIKI_PREF2");
699             }
700         }
701         if (! $UserName ) {
702             // Try reading userid from old PhpWiki cookie formats:
703             if ($cookie = $request->cookies->get_old(getCookieName())) {
704                 if (is_string($cookie) and (substr($cookie,0,2) != 's:'))
705                     $UserName = $cookie;
706                 elseif (is_array($cookie) and !empty($cookie['userid']))
707                     $UserName = $cookie['userid'];
708             }
709             if (! $UserName and !headers_sent())
710                 $request->deleteCookieVar(getCookieName());
711             else
712                 $this->_userid = $UserName;
713         }
714
715         // initializeTheme() needs at least an empty object
716         /*
717          if (empty($this->_prefs))
718             $this->_prefs = new UserPreferences;
719         */
720         return $this->_prefs;
721     }
722
723     /** _AnonUser::setPreferences(): Save prefs in a cookie and session and update all global vars
724      *
725      * Allow for multiple wikis in same domain. Encode only the
726      * _prefs array of the UserPreference object. Ideally the
727      * prefs array should just be imploded into a single string or
728      * something so it is completely human readable by the end
729      * user. In that case stricter error checking will be needed
730      * when loading the cookie.
731      */
732     function setPreferences($prefs, $id_only=false) {
733         if (!is_object($prefs)) {
734             if (is_object($this->_prefs)) {
735                 $updated = $this->_prefs->updatePrefs($prefs);
736                 $prefs =& $this->_prefs;
737             } else {
738                 // update the prefs values from scratch. This could leed to unnecessary
739                 // side-effects: duplicate emailVerified, ...
740                 $this->_prefs = new UserPreferences($prefs);
741                 $updated = true;
742             }
743         } else {
744             if (!isset($this->_prefs))
745                 $this->_prefs =& $prefs;
746             else
747                 $updated = $this->_prefs->isChanged($prefs);
748         }
749         if ($updated) {
750             if ($id_only and !headers_sent()) {
751                 global $request;
752                 // new 1.3.8 policy: no array cookies, only plain userid string as in 
753                 // the pre 1.3.x versions.
754                 // prefs should be stored besides the session in the homepagehandle or in a db.
755                 $request->setCookieVar(getCookieName(), $this->_userid,
756                                        COOKIE_EXPIRATION_DAYS, COOKIE_DOMAIN);
757                 //$request->setCookieVar(WIKI_NAME, array('userid' => $prefs->get('userid')),
758                 //                       COOKIE_EXPIRATION_DAYS, COOKIE_DOMAIN);
759             }
760         }
761         $packed = $prefs->store();
762         $unpacked = $prefs->unpack($packed);
763         if (count($unpacked)) {
764             foreach (array('_method','_select','_update','_insert') as $param) {
765                 if (!empty($this->_prefs->{$param}))
766                     $prefs->{$param} = $this->_prefs->{$param};
767             }
768             $this->_prefs = $prefs;
769         }
770         return $updated;
771     }
772
773     function userExists() {
774         return true;
775     }
776
777     function checkPass($submitted_password) {
778         return false;
779         // this might happen on a old-style signin button.
780
781         // By definition, the _AnonUser does not HAVE a password
782         // (compared to _BogoUser, who has an EMPTY password).
783         trigger_error("DEBUG: Warning: _AnonUser unexpectedly asked to checkPass()." . " "
784                       . "Check isa(\$user, '_PassUser'), or: isa(\$user, '_AdminUser') etc. first." . " "
785                       . "New subclasses of _WikiUser must override this function.");
786         return false;
787     }
788
789 }
790
791 /** 
792  * Helper class to finish the PassUser auth loop. 
793  * This is added automatically to USER_AUTH_ORDER.
794  */
795 class _ForbiddenUser
796 extends _AnonUser
797 {
798     var $_level = WIKIAUTH_FORBIDDEN;
799
800     function checkPass($submitted_password) {
801         return WIKIAUTH_FORBIDDEN;
802     }
803
804     function userExists() {
805         if ($this->_HomePagehandle) return true;
806         return false;
807     }
808 }
809
810 /**
811  * Do NOT extend _BogoUser to other classes, for checkPass()
812  * security. (In case of defects in code logic of the new class!)
813  * The intermediate step between anon and passuser.
814  * We also have the _BogoLoginPassUser class with stricter 
815  * password checking, which fits into the auth loop.
816  * Note: This class is not called anymore by WikiUser()
817  */
818 class _BogoUser
819 extends _AnonUser
820 {
821     function userExists() {
822         if (isWikiWord($this->_userid)) {
823             $this->_level = WIKIAUTH_BOGO;
824             return true;
825         } else {
826             $this->_level = WIKIAUTH_ANON;
827             return false;
828         }
829     }
830
831     function checkPass($submitted_password) {
832         // By definition, BogoUser has an empty password.
833         $this->userExists();
834         return $this->_level;
835     }
836 }
837
838 class _PassUser
839 extends _AnonUser
840 /**
841  * Called if ALLOW_USER_PASSWORDS and Anon and Bogo failed.
842  *
843  * The classes for all subsequent auth methods extend from this class. 
844  * This handles the auth method type dispatcher according $USER_AUTH_ORDER, 
845  * the three auth method policies first-only, strict and stacked
846  * and the two methods for prefs: homepage or database, 
847  * if $DBAuthParams['pref_select'] is defined.
848  *
849  * Default is PersonalPage auth and prefs.
850  * 
851  * @author: Reini Urban
852  * @tables: pref
853  */
854 {
855     var $_auth_dbi, $_prefs;
856     var $_current_method, $_current_index;
857
858     // check and prepare the auth and pref methods only once
859     function _PassUser($UserName='', $prefs=false) {
860         //global $DBAuthParams, $DBParams;
861         if ($UserName) {
862             /*if (!$this->isValidName($UserName))
863                 return false;*/
864             $this->_userid = $UserName;
865             if ($this->hasHomePage())
866                 $this->_HomePagehandle = $GLOBALS['request']->getPage($this->_userid);
867         }
868         $this->_authmethod = substr(get_class($this),1,-8);
869         if ($this->_authmethod == 'a') $this->_authmethod = 'admin';
870
871         // Check the configured Prefs methods
872         $dbi = $this->getAuthDbh();
873         $dbh = $GLOBALS['request']->getDbh();
874         if ( $dbi and !isset($this->_prefs->_select) and $dbh->getAuthParam('pref_select')) {
875             if (!$this->_prefs) {
876                 $this->_prefs = new UserPreferences();
877                 $need_pref = true;
878             }
879             $this->_prefs->_method = $dbh->getParam('dbtype');
880             $this->_prefs->_select = $this->prepare($dbh->getAuthParam('pref_select'), "userid");
881             // read-only prefs?
882             if ( !isset($this->_prefs->_update) and $dbh->getAuthParam('pref_update')) {
883                 $this->_prefs->_update = $this->prepare($dbh->getAuthParam('pref_update'), 
884                                                         array("userid", "pref_blob"));
885             }
886         } else {
887             if (!$this->_prefs) {
888                 $this->_prefs = new UserPreferences();
889                 $need_pref = true;
890             }
891             $this->_prefs->_method = 'HomePage';
892         }
893         
894         if (! $this->_prefs or isset($need_pref) ) {
895             if ($prefs) $this->_prefs = $prefs;
896             else $this->getPreferences();
897         }
898         
899         // Upgrade to the next parent _PassUser class. Avoid recursion.
900         if ( strtolower(get_class($this)) === '_passuser' ) {
901             //auth policy: Check the order of the configured auth methods
902             // 1. first-only: Upgrade the class here in the constructor
903             // 2. old:       ignore USER_AUTH_ORDER and try to use all available methods as 
904             ///              in the previous PhpWiki releases (slow)
905             // 3. strict:    upgrade the class after checking the user existance in userExists()
906             // 4. stacked:   upgrade the class after the password verification in checkPass()
907             // Methods: PersonalPage, HttpAuth, DB, Ldap, Imap, File
908             //if (!defined('USER_AUTH_POLICY')) define('USER_AUTH_POLICY','old');
909             if (defined('USER_AUTH_POLICY')) {
910                 // policy 1: only pre-define one method for all users
911                 if (USER_AUTH_POLICY === 'first-only') {
912                     $class = $this->nextClass();
913                     return new $class($UserName,$this->_prefs);
914                 }
915                 // Use the default behaviour from the previous versions:
916                 elseif (USER_AUTH_POLICY === 'old') {
917                     // Default: try to be smart
918                     // On php5 we can directly return and upgrade the Object,
919                     // before we have to upgrade it manually.
920                     if (!empty($GLOBALS['PHP_AUTH_USER']) or !empty($_SERVER['REMOTE_USER'])) {
921                         include_once("lib/WikiUser/HttpAuth.php");
922                         if (check_php_version(5))
923                             return new _HttpAuthPassUser($UserName,$this->_prefs);
924                         else {
925                             $user = new _HttpAuthPassUser($UserName,$this->_prefs);
926                             eval("\$this = \$user;");
927                             // /*PHP5 patch*/$this = $user;
928                             return $user;
929                         }
930                     } elseif (in_array('Db', $dbh->getAuthParam('USER_AUTH_ORDER')) and
931                               $dbh->getAuthParam('auth_check') and
932                               ($dbh->getAuthParam('auth_dsn') or $dbh->getParam('dsn'))) {
933                         if (check_php_version(5))
934                             return new _DbPassUser($UserName,$this->_prefs);
935                         else {
936                             $user = new _DbPassUser($UserName,$this->_prefs);
937                             eval("\$this = \$user;");
938                             // /*PHP5 patch*/$this = $user;
939                             return $user;
940                         }
941                     } elseif (in_array('LDAP', $dbh->getAuthParam('USER_AUTH_ORDER')) and
942                               defined('LDAP_AUTH_HOST') and defined('LDAP_BASE_DN') and 
943                               function_exists('ldap_connect')) {
944                         include_once("lib/WikiUser/LDAP.php");
945                         if (check_php_version(5))
946                             return new _LDAPPassUser($UserName,$this->_prefs);
947                         else {
948                             $user = new _LDAPPassUser($UserName,$this->_prefs);
949                             eval("\$this = \$user;");
950                             // /*PHP5 patch*/$this = $user;
951                             return $user;
952                         }
953                     } elseif (in_array('IMAP', $dbh->getAuthParam('USER_AUTH_ORDER')) and
954                               defined('IMAP_AUTH_HOST') and function_exists('imap_open')) {
955                         include_once("lib/WikiUser/IMAP.php");
956                         if (check_php_version(5))
957                             return new _IMAPPassUser($UserName,$this->_prefs);
958                         else {
959                             $user = new _IMAPPassUser($UserName,$this->_prefs);
960                             eval("\$this = \$user;");
961                             // /*PHP5 patch*/$this = $user;
962                             return $user;
963                         }
964                     } elseif (in_array('File', $dbh->getAuthParam('USER_AUTH_ORDER')) and
965                               defined('AUTH_USER_FILE') and file_exists(AUTH_USER_FILE)) {
966                         include_once("lib/WikiUser/File.php");
967                         if (check_php_version(5))
968                             return new _FilePassUser($UserName, $this->_prefs);
969                         else {
970                             $user = new _FilePassUser($UserName, $this->_prefs);
971                             eval("\$this = \$user;");
972                             // /*PHP5 patch*/$this = $user;
973                             return $user;
974                         }
975                     } else {
976                         include_once("lib/WikiUser/PersonalPage.php");
977                         if (check_php_version(5))
978                             return new _PersonalPagePassUser($UserName,$this->_prefs);
979                         else {
980                             $user = new _PersonalPagePassUser($UserName,$this->_prefs);
981                             eval("\$this = \$user;");
982                             // /*PHP5 patch*/$this = $user;
983                             return $user;
984                         }
985                     }
986                 }
987                 else 
988                     // else use the page methods defined in _PassUser.
989                     return $this;
990             }
991         }
992     }
993
994     function getAuthDbh () {
995         global $request; //, $DBParams, $DBAuthParams;
996
997         $dbh = $request->getDbh();
998         // session restauration doesn't re-connect to the database automatically, 
999         // so dirty it here, to force a reconnect.
1000         if (isset($this->_auth_dbi)) {
1001             if (($dbh->getParam('dbtype') == 'SQL') and empty($this->_auth_dbi->connection))
1002                 unset($this->_auth_dbi);
1003             if (($dbh->getParam('dbtype') == 'ADODB') and empty($this->_auth_dbi->_connectionID))
1004                 unset($this->_auth_dbi);
1005         }
1006         if (empty($this->_auth_dbi)) {
1007             if ($dbh->getParam('dbtype') != 'SQL' 
1008                 and $dbh->getParam('dbtype') != 'ADODB'
1009                 and $dbh->getParam('dbtype') != 'PDO')
1010                 return false;
1011             if (empty($GLOBALS['DBAuthParams']))
1012                 return false;
1013             if (!$dbh->getAuthParam('auth_dsn')) {
1014                 $dbh = $request->getDbh(); // use phpwiki database 
1015             } elseif ($dbh->getAuthParam('auth_dsn') == $dbh->getParam('dsn')) {
1016                 $dbh = $request->getDbh(); // same phpwiki database 
1017             } else { // use another external database handle. needs PHP >= 4.1
1018                 $local_params = array_merge($GLOBALS['DBParams'],$GLOBALS['DBAuthParams']);
1019                 $local_params['dsn'] = $local_params['auth_dsn'];
1020                 $dbh = WikiDB::open($local_params);
1021             }       
1022             $this->_auth_dbi =& $dbh->_backend->_dbh;    
1023         }
1024         return $this->_auth_dbi;
1025     }
1026
1027     function _normalize_stmt_var($var, $oldstyle = false) {
1028         static $valid_variables = array('userid','password','pref_blob','groupname');
1029         // old-style: "'$userid'"
1030         // new-style: '"\$userid"' or just "userid"
1031         $new = str_replace(array("'",'"','\$','$'),'',$var);
1032         if (!in_array($new, $valid_variables)) {
1033             trigger_error("Unknown DBAuthParam statement variable: ". $new, E_USER_ERROR);
1034             return false;
1035         }
1036         return !$oldstyle ? "'$".$new."'" : '\$'.$new;
1037     }
1038
1039     // TODO: use it again for the auth and member tables
1040     // sprintfstyle vs prepare style: %s or ?
1041     //   multiple vars should be executed via prepare(?,?)+execute, 
1042     //   single vars with execute(sprintf(quote(var)))
1043     // help with position independency
1044     function prepare ($stmt, $variables, $oldstyle = false, $sprintfstyle = true) {
1045         global $request;
1046         $dbi = $request->getDbh();
1047         $this->getAuthDbh();
1048         // "'\$userid"' => %s
1049         // variables can be old-style: '"\$userid"' or new-style: "'$userid'" or just "userid"
1050         // old-style strings don't survive pear/Config/IniConfig treatment, that's why we changed it.
1051         $new = array();
1052         if (is_array($variables)) {
1053             //$sprintfstyle = false;
1054             for ($i=0; $i < count($variables); $i++) { 
1055                 $var = $this->_normalize_stmt_var($variables[$i], $oldstyle);
1056                 if (!$var)
1057                     trigger_error(sprintf("DbAuthParams: Undefined or empty statement variable %s in %s",
1058                                           $variables[$i], $stmt), E_USER_WARNING);
1059                 $variables[$i] = $var;
1060                 if (!$var) $new[] = '';
1061                 else {
1062                     $s = "%" . ($i+1) . "s";    
1063                     $new[] = $sprintfstyle ? $s : "?";
1064                 }
1065             }
1066         } else {
1067             $var = $this->_normalize_stmt_var($variables, $oldstyle);
1068             if (!$var)
1069                 trigger_error(sprintf("DbAuthParams: Undefined or empty statement variable %s in %s",
1070                                       $variables, $stmt), E_USER_WARNING);
1071             $variables = $var;
1072             if (!$var) $new = ''; 
1073             else $new = $sprintfstyle ? '%s' : "?"; 
1074         }
1075         $prefix = $dbi->getParam('prefix');
1076         // probably prefix table names if in same database
1077         if ($prefix and isset($this->_auth_dbi) and isset($dbi->_backend->_dbh) and 
1078             ($dbi->getAuthParam('auth_dsn') and $dbi->getParam('dsn') == $dbi->getAuthParam('auth_dsn')))
1079         {
1080             if (!stristr($stmt, $prefix)) {
1081                 $oldstmt = $stmt;
1082                 $stmt = str_replace(array(" user "," pref "," member "),
1083                                     array(" ".$prefix."user ",
1084                                           " ".$prefix."pref ",
1085                                           " ".$prefix."member "), $stmt);
1086                 //Do it automatically for the lazy admin? Esp. on sf.net it's nice to have
1087                 trigger_error("Need to prefix the DBAUTH tablename in config/config.ini:\n  $oldstmt \n=> $stmt",
1088                               E_USER_WARNING);
1089             }
1090         }
1091         // Preparate the SELECT statement, for ADODB and PearDB (MDB not).
1092         // Simple sprintf-style.
1093         $new_stmt = str_replace($variables, $new, $stmt);
1094         if ($new_stmt == $stmt) {
1095             if ($oldstyle) {
1096                 trigger_error(sprintf("DbAuthParams: Invalid statement in %s",
1097                                   $stmt), E_USER_WARNING);
1098             } else {
1099                 trigger_error(sprintf("DbAuthParams: Old statement quoting style in %s",
1100                                   $stmt), E_USER_WARNING);
1101                 $new_stmt = $this->prepare($stmt, $variables, 'oldstyle');
1102             }
1103         }
1104         return $new_stmt;
1105     }
1106
1107     function getPreferences() {
1108         if (!empty($this->_prefs->_method)) {
1109             if ($this->_prefs->_method == 'ADODB') {
1110                 // FIXME: strange why this should be needed...
1111                 include_once("lib/WikiUser/Db.php");
1112                 include_once("lib/WikiUser/AdoDb.php");
1113                 _AdoDbPassUser::_AdoDbPassUser($this->_userid, $this->_prefs);
1114                 return _AdoDbPassUser::getPreferences();
1115             } elseif ($this->_prefs->_method == 'SQL') {
1116                 include_once("lib/WikiUser/Db.php");
1117                 include_once("lib/WikiUser/PearDb.php");
1118                 _PearDbPassUser::_PearDbPassUser($this->_userid, $this->_prefs);
1119                 return _PearDbPassUser::getPreferences();
1120             } elseif ($this->_prefs->_method == 'PDO') {
1121                 include_once("lib/WikiUser/Db.php");
1122                 include_once("lib/WikiUser/PdoDb.php");
1123                 _PdoDbPassUser::_PdoDbPassUser($this->_userid, $this->_prefs);
1124                 return _PdoDbPassUser::getPreferences();
1125             }
1126         }
1127
1128         // We don't necessarily have to read the cookie first. Since
1129         // the user has a password, the prefs stored in the homepage
1130         // cannot be arbitrarily altered by other Bogo users.
1131         _AnonUser::getPreferences();
1132         // User may have deleted cookie, retrieve from his
1133         // PersonalPage if there is one.
1134         if (!empty($this->_HomePagehandle)) {
1135             if ($restored_from_page = $this->_prefs->retrieve
1136                 ($this->_HomePagehandle->get('pref'))) {
1137                 $updated = $this->_prefs->updatePrefs($restored_from_page,'init');
1138                 //$this->_prefs = new UserPreferences($restored_from_page);
1139                 return $this->_prefs;
1140             }
1141         }
1142         return $this->_prefs;
1143     }
1144
1145     function setPreferences($prefs, $id_only=false) {
1146         if (!empty($this->_prefs->_method)) {
1147             if ($this->_prefs->_method == 'ADODB') {
1148                 // FIXME: strange why this should be needed...
1149                 include_once("lib/WikiUser/Db.php");
1150                 include_once("lib/WikiUser/AdoDb.php");
1151                 _AdoDbPassUser::_AdoDbPassUser($this->_userid, $prefs);
1152                 return _AdoDbPassUser::setPreferences($prefs, $id_only);
1153             }
1154             elseif ($this->_prefs->_method == 'SQL') {
1155                 include_once("lib/WikiUser/Db.php");
1156                 include_once("lib/WikiUser/PearDb.php");
1157                 _PearDbPassUser::_PearDbPassUser($this->_userid, $prefs);
1158                 return _PearDbPassUser::setPreferences($prefs, $id_only);
1159             }
1160             elseif ($this->_prefs->_method == 'PDO') {
1161                 include_once("lib/WikiUser/Db.php");
1162                 include_once("lib/WikiUser/PdoDb.php");
1163                 _PdoDbPassUser::_PdoDbPassUser($this->_userid, $prefs);
1164                 return _PdoDbPassUser::setPreferences($prefs, $id_only);
1165             }
1166         }
1167         if ($updated = _AnonUser::setPreferences($prefs, $id_only)) {
1168             // Encode only the _prefs array of the UserPreference object
1169             // If no DB method exists to store the prefs we must store it in the page, not in the cookies.
1170             if (empty($this->_HomePagehandle)) {
1171                 $this->_HomePagehandle = $GLOBALS['request']->getPage($this->_userid);
1172             }
1173             if (! $this->_HomePagehandle->exists() ) {
1174                 $this->createHomePage();
1175             }
1176             if (!empty($this->_HomePagehandle) and !$id_only) {
1177                 $this->_HomePagehandle->set('pref', $this->_prefs->store());
1178             }
1179         }
1180         return $updated;
1181     }
1182
1183     function mayChangePass() {
1184         return true;
1185     }
1186
1187     //The default method is getting the password from prefs. 
1188     // child methods obtain $stored_password from external auth.
1189     function userExists() {
1190         //if ($this->_HomePagehandle) return true;
1191         if (strtolower(get_class($this)) == "_passuser") {
1192             $class = $this->nextClass();
1193             $user = new $class($this->_userid, $this->_prefs);
1194         } else {
1195             $user = $this;
1196         }
1197         while ($user) {
1198             if (!check_php_version(5))
1199                 eval("\$this = \$user;");
1200             $user = UpgradeUser($this, $user);
1201             if ($user->userExists()) {
1202                 $user = UpgradeUser($this, $user);
1203                 return true;
1204             }
1205             // prevent endless loop. does this work on all PHP's?
1206             // it just has to set the classname, what it correctly does.
1207             $class = $user->nextClass();
1208             if ($class == "_ForbiddenPassUser")
1209                 return false;
1210         }
1211         return false;
1212     }
1213
1214     //The default method is getting the password from prefs. 
1215     // child methods obtain $stored_password from external auth.
1216     function checkPass($submitted_password) {
1217         $stored_password = $this->_prefs->get('passwd');
1218         if ($this->_checkPass($submitted_password, $stored_password)) {
1219             $this->_level = WIKIAUTH_USER;
1220             return $this->_level;
1221         } else {
1222             if ((USER_AUTH_POLICY === 'strict') and $this->userExists()) {
1223                 $this->_level = WIKIAUTH_FORBIDDEN;
1224                 return $this->_level;
1225             }
1226             return $this->_tryNextPass($submitted_password);
1227         }
1228     }
1229
1230
1231     function _checkPassLength($submitted_password) {
1232         if (strlen($submitted_password) < PASSWORD_LENGTH_MINIMUM) {
1233             trigger_error(_("The length of the password is shorter than the system policy allows."));
1234             return false;
1235         }
1236         return true;
1237     }
1238
1239     /**
1240      * The basic password checker for all PassUser objects.
1241      * Uses global ENCRYPTED_PASSWD and PASSWORD_LENGTH_MINIMUM.
1242      * Empty passwords are always false!
1243      * PASSWORD_LENGTH_MINIMUM is enforced here and in the preference set method.
1244      * @see UserPreferences::set
1245      *
1246      * DBPassUser password's have their own crypt definition.
1247      * That's why DBPassUser::checkPass() doesn't call this method, if 
1248      * the db password method is 'plain', which means that the DB SQL 
1249      * statement just returns 1 or 0. To use CRYPT() or PASSWORD() and 
1250      * don't store plain passwords in the DB.
1251      * 
1252      * TODO: remove crypt() function check from config.php:396 ??
1253      */
1254     function _checkPass($submitted_password, $stored_password) {
1255         if (!empty($submitted_password)) {
1256             // This works only on plaintext passwords.
1257             if (!ENCRYPTED_PASSWD and (strlen($stored_password) < PASSWORD_LENGTH_MINIMUM)) {
1258                 // With the EditMetaData plugin
1259                 trigger_error(_("The length of the stored password is shorter than the system policy allows. Sorry, you cannot login.\n You have to ask the System Administrator to reset your password."));
1260                 return false;
1261             }
1262             if (!$this->_checkPassLength($submitted_password)) {
1263                 return false;
1264             }
1265             if (ENCRYPTED_PASSWD) {
1266                 // Verify against encrypted password.
1267                 if (function_exists('crypt')) {
1268                     if (crypt($submitted_password, $stored_password) == $stored_password )
1269                         return true; // matches encrypted password
1270                     else
1271                         return false;
1272                 }
1273                 else {
1274                     trigger_error(_("The crypt function is not available in this version of PHP.") . " "
1275                                   . _("Please set ENCRYPTED_PASSWD to false in config/config.ini and probably change ADMIN_PASSWD."),
1276                                   E_USER_WARNING);
1277                     return false;
1278                 }
1279             }
1280             else {
1281                 // Verify against cleartext password.
1282                 if ($submitted_password == $stored_password)
1283                     return true;
1284                 else {
1285                     // Check whether we forgot to enable ENCRYPTED_PASSWD
1286                     if (function_exists('crypt')) {
1287                         if (crypt($submitted_password, $stored_password) == $stored_password) {
1288                             trigger_error(_("Please set ENCRYPTED_PASSWD to true in config/config.ini."),
1289                                           E_USER_WARNING);
1290                             return true;
1291                         }
1292                     }
1293                 }
1294             }
1295         }
1296         return false;
1297     }
1298
1299     /** The default method is storing the password in prefs. 
1300      *  Child methods (DB, File) may store in external auth also, but this 
1301      *  must be explicitly enabled.
1302      *  This may be called by plugin/UserPreferences or by ->SetPreferences()
1303      */
1304     function changePass($submitted_password) {
1305         $stored_password = $this->_prefs->get('passwd');
1306         // check if authenticated
1307         if (!$this->isAuthenticated()) return false;
1308         if (ENCRYPTED_PASSWD) {
1309             $submitted_password = crypt($submitted_password);
1310         }
1311         // check other restrictions, with side-effects only.
1312         $result = $this->_checkPass($submitted_password, $stored_password);
1313         if ($stored_password != $submitted_password) {
1314             $this->_prefs->set('passwd', $submitted_password);
1315             //update the storage (session, homepage, ...)
1316             $this->SetPreferences($this->_prefs);
1317             return true;
1318         }
1319         //Todo: return an error msg to the caller what failed? 
1320         // same password or no privilege
1321         return ENCRYPTED_PASSWD ? true : false;
1322     }
1323
1324     function _tryNextPass($submitted_password) {
1325         if (DEBUG & _DEBUG_LOGIN) {
1326             $class = strtolower(get_class($this));
1327             if (substr($class,-10) == "dbpassuser") $class = "_dbpassuser";
1328             $GLOBALS['USER_AUTH_ERROR'][$class] = 'wrongpass';
1329         }
1330         if (USER_AUTH_POLICY === 'strict') {
1331             $class = $this->nextClass();
1332             if ($user = new $class($this->_userid,$this->_prefs)) {
1333                 if ($user->userExists()) {
1334                     return $user->checkPass($submitted_password);
1335                 }
1336             }
1337         }
1338         if (USER_AUTH_POLICY === 'stacked' or USER_AUTH_POLICY === 'old') {
1339             $class = $this->nextClass();
1340             if ($user = new $class($this->_userid,$this->_prefs))
1341                 return $user->checkPass($submitted_password);
1342         }
1343         return $this->_level;
1344     }
1345
1346     function _tryNextUser() {
1347         if (DEBUG & _DEBUG_LOGIN) {
1348             $class = strtolower(get_class($this));
1349             if (substr($class,-10) == "dbpassuser") $class = "_dbpassuser";
1350             $GLOBALS['USER_AUTH_ERROR'][$class] = 'nosuchuser';
1351         }
1352         if (USER_AUTH_POLICY === 'strict'
1353             or USER_AUTH_POLICY === 'stacked') {
1354             $class = $this->nextClass();
1355             while ($user = new $class($this->_userid, $this->_prefs)) {
1356                 if (!check_php_version(5))
1357                     eval("\$this = \$user;");
1358                 $user = UpgradeUser($this, $user);
1359                 if ($user->userExists()) {
1360                     $user = UpgradeUser($this, $user);
1361                     return true;
1362                 }
1363                 $class = $this->nextClass();
1364             }
1365         }
1366         return false;
1367     }
1368
1369 }
1370
1371 /**
1372  * Insert more auth classes here...
1373  * For example a customized db class for another db connection 
1374  * or a socket-based auth server.
1375  *
1376  */
1377
1378
1379 /**
1380  * For security, this class should not be extended. Instead, extend
1381  * from _PassUser (think of this as unix "root").
1382  *
1383  * FIXME: This should be a singleton class. Only ADMIN_USER may be of class AdminUser!
1384  * Other members of the Administrators group must raise their level otherwise somehow.
1385  * Currently every member is a AdminUser, which will not work for the various 
1386  * storage methods.
1387  */
1388 class _AdminUser
1389 extends _PassUser
1390 {
1391     function mayChangePass() {
1392         return false;
1393     }
1394     function checkPass($submitted_password) {
1395         if ($this->_userid == ADMIN_USER)
1396             $stored_password = ADMIN_PASSWD;
1397         else {
1398             // Should not happen! Only ADMIN_USER should use this class.
1399             // return $this->_tryNextPass($submitted_password); // ???
1400             // TODO: safety check if really member of the ADMIN group?
1401             $stored_password = $this->_pref->get('passwd');
1402         }
1403         if ($this->_checkPass($submitted_password, $stored_password)) {
1404             $this->_level = WIKIAUTH_ADMIN;
1405             if (!empty($GLOBALS['HTTP_SERVER_VARS']['PHP_AUTH_USER']) and class_exists("_HttpAuthPassUser")) {
1406                 // fake http auth
1407                 _HttpAuthPassUser::_fake_auth($this->_userid, $submitted_password);
1408             }
1409             return $this->_level;
1410         } else {
1411             return $this->_tryNextPass($submitted_password);
1412             //$this->_level = WIKIAUTH_ANON;
1413             //return $this->_level;
1414         }
1415     }
1416
1417     function storePass($submitted_password) {
1418         if ($this->_userid == ADMIN_USER)
1419             return false;
1420         else {
1421             // should not happen! only ADMIN_USER should use this class.
1422             return parent::storePass($submitted_password);
1423         }
1424     }
1425 }
1426
1427 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1428 /**
1429  * Various data classes for the preference types, 
1430  * to support get, set, sanify (range checking, ...)
1431  * update() will do the neccessary side-effects if a 
1432  * setting gets changed (theme, language, ...)
1433 */
1434
1435 class _UserPreference
1436 {
1437     var $default_value;
1438
1439     function _UserPreference ($default_value) {
1440         $this->default_value = $default_value;
1441     }
1442
1443     function sanify ($value) {
1444         return (string)$value;
1445     }
1446
1447     function get ($name) {
1448         if (isset($this->{$name}))
1449             return $this->{$name};
1450         else 
1451             return $this->default_value;
1452     }
1453
1454     function getraw ($name) {
1455         if (!empty($this->{$name}))
1456             return $this->{$name};
1457     }
1458
1459     // stores the value as $this->$name, and not as $this->value (clever?)
1460     function set ($name, $value) {
1461         $return = 0;
1462         $value = $this->sanify($value);
1463         if ($this->get($name) != $value) {
1464             $this->update($value);
1465             $return = 1;
1466         }
1467         if ($value != $this->default_value) {
1468             $this->{$name} = $value;
1469         } else {
1470             unset($this->{$name});
1471         }
1472         return $return;
1473     }
1474
1475     // default: no side-effects 
1476     function update ($value) {
1477         ;
1478     }
1479 }
1480
1481 class _UserPreference_numeric
1482 extends _UserPreference
1483 {
1484     function _UserPreference_numeric ($default, $minval = false,
1485                                       $maxval = false) {
1486         $this->_UserPreference((double)$default);
1487         $this->_minval = (double)$minval;
1488         $this->_maxval = (double)$maxval;
1489     }
1490
1491     function sanify ($value) {
1492         $value = (double)$value;
1493         if ($this->_minval !== false && $value < $this->_minval)
1494             $value = $this->_minval;
1495         if ($this->_maxval !== false && $value > $this->_maxval)
1496             $value = $this->_maxval;
1497         return $value;
1498     }
1499 }
1500
1501 class _UserPreference_int
1502 extends _UserPreference_numeric
1503 {
1504     function _UserPreference_int ($default, $minval = false, $maxval = false) {
1505         $this->_UserPreference_numeric((int)$default, (int)$minval, (int)$maxval);
1506     }
1507
1508     function sanify ($value) {
1509         return (int)parent::sanify((int)$value);
1510     }
1511 }
1512
1513 class _UserPreference_bool
1514 extends _UserPreference
1515 {
1516     function _UserPreference_bool ($default = false) {
1517         $this->_UserPreference((bool)$default);
1518     }
1519
1520     function sanify ($value) {
1521         if (is_array($value)) {
1522             /* This allows for constructs like:
1523              *
1524              *   <input type="hidden" name="pref[boolPref][]" value="0" />
1525              *   <input type="checkbox" name="pref[boolPref][]" value="1" />
1526              *
1527              * (If the checkbox is not checked, only the hidden input
1528              * gets sent. If the checkbox is sent, both inputs get
1529              * sent.)
1530              */
1531             foreach ($value as $val) {
1532                 if ($val)
1533                     return true;
1534             }
1535             return false;
1536         }
1537         return (bool) $value;
1538     }
1539 }
1540
1541 class _UserPreference_language
1542 extends _UserPreference
1543 {
1544     function _UserPreference_language ($default = DEFAULT_LANGUAGE) {
1545         $this->_UserPreference($default);
1546     }
1547
1548     // FIXME: check for valid locale
1549     function sanify ($value) {
1550         // Revert to DEFAULT_LANGUAGE if user does not specify
1551         // language in UserPreferences or chooses <system language>.
1552         if ($value == '' or empty($value))
1553             $value = DEFAULT_LANGUAGE;
1554
1555         return (string) $value;
1556     }
1557     
1558     function update ($newvalue) {
1559         if (! $this->_init ) {
1560             // invalidate etag to force fresh output
1561             $GLOBALS['request']->setValidators(array('%mtime' => false));
1562             update_locale($newvalue ? $newvalue : $GLOBALS['LANG']);
1563         }
1564     }
1565 }
1566
1567 class _UserPreference_theme
1568 extends _UserPreference
1569 {
1570     function _UserPreference_theme ($default = THEME) {
1571         $this->_UserPreference($default);
1572     }
1573
1574     function sanify ($value) {
1575         if (!empty($value) and FindFile($this->_themefile($value)))
1576             return $value;
1577         return $this->default_value;
1578     }
1579
1580     function update ($newvalue) {
1581         global $WikiTheme;
1582         // invalidate etag to force fresh output
1583         if (! $this->_init )
1584             $GLOBALS['request']->setValidators(array('%mtime' => false));
1585         if ($newvalue)
1586             include_once($this->_themefile($newvalue));
1587         if (empty($WikiTheme))
1588             include_once($this->_themefile(THEME));
1589     }
1590
1591     function _themefile ($theme) {
1592         return "themes/$theme/themeinfo.php";
1593     }
1594 }
1595
1596 class _UserPreference_notify
1597 extends _UserPreference
1598 {
1599     function sanify ($value) {
1600         if (!empty($value))
1601             return $value;
1602         else
1603             return $this->default_value;
1604     }
1605
1606     /** update to global user prefs: side-effect on set notify changes
1607      * use a global_data notify hash:
1608      * notify = array('pagematch' => array(userid => ('email' => mail, 
1609      *                                                'verified' => 0|1),
1610      *                                     ...),
1611      *                ...);
1612      */
1613     function update ($value) {
1614         if (!empty($this->_init)) return;
1615         $dbh = $GLOBALS['request']->getDbh();
1616         $notify = $dbh->get('notify');
1617         if (empty($notify))
1618             $data = array();
1619         else 
1620             $data =& $notify;
1621         // expand to existing pages only or store matches?
1622         // for now we store (glob-style) matches which is easier for the user
1623         $pages = $this->_page_split($value);
1624         // Limitation: only current user.
1625         $user = $GLOBALS['request']->getUser();
1626         if (!$user or !method_exists($user,'UserName')) return;
1627         // This fails with php5 and a WIKI_ID cookie:
1628         $userid = $user->UserName();
1629         $email  = $user->_prefs->get('email');
1630         $verified = $user->_prefs->_prefs['email']->getraw('emailVerified');
1631         // check existing notify hash and possibly delete pages for email
1632         if (!empty($data)) {
1633             foreach ($data as $page => $users) {
1634                 if (isset($data[$page][$userid]) and !in_array($page, $pages)) {
1635                     unset($data[$page][$userid]);
1636                 }
1637                 if (count($data[$page]) == 0)
1638                     unset($data[$page]);
1639             }
1640         }
1641         // add the new pages
1642         if (!empty($pages)) {
1643             foreach ($pages as $page) {
1644                 if (!isset($data[$page]))
1645                     $data[$page] = array();
1646                 if (!isset($data[$page][$userid])) {
1647                     // should we really store the verification notice here or 
1648                     // check it dynamically at every page->save?
1649                     if ($verified) {
1650                         $data[$page][$userid] = array('email' => $email,
1651                                                       'verified' => $verified);
1652                     } else {
1653                         $data[$page][$userid] = array('email' => $email);
1654                     }
1655                 }
1656             }
1657         }
1658         // store users changes
1659         $dbh->set('notify',$data);
1660     }
1661
1662     /** split the user-given comma or whitespace delimited pagenames
1663      *  to array
1664      */
1665     function _page_split($value) {
1666         return preg_split('/[\s,]+/',$value,-1,PREG_SPLIT_NO_EMPTY);
1667     }
1668 }
1669
1670 class _UserPreference_email
1671 extends _UserPreference
1672 {
1673     function sanify($value) {
1674         // check for valid email address
1675         if ($this->get('email') == $value and $this->getraw('emailVerified'))
1676             return $value;
1677         // hack!
1678         if ($value == 1 or $value === true)
1679             return $value;
1680         list($ok,$msg) = ValidateMail($value,'noconnect');
1681         if ($ok) {
1682             return $value;
1683         } else {
1684             trigger_error("E-Mail Validation Error: ".$msg, E_USER_WARNING);
1685             return $this->default_value;
1686         }
1687     }
1688     
1689     /** Side-effect on email changes:
1690      * Send a verification mail or for now just a notification email.
1691      * For true verification (value = 2), we'd need a mailserver hook.
1692      */
1693     function update($value) {
1694         if (!empty($this->_init)) return;
1695         $verified = $this->getraw('emailVerified');
1696         // hack!
1697         if (($value == 1 or $value === true) and $verified)
1698             return;
1699         if (!empty($value) and !$verified) {
1700             list($ok,$msg) = ValidateMail($value);
1701             if ($ok and mail($value,"[".WIKI_NAME ."] "._("Email Verification"),
1702                      sprintf(_("Welcome to %s!\nYour email account is verified and\nwill be used to send page change notifications.\nSee %s"),
1703                              WIKI_NAME, WikiURL($GLOBALS['request']->getArg('pagename'),'',true)))) {
1704                 $this->set('emailVerified',1);
1705             } else {
1706                 trigger_error($msg, E_USER_WARNING);
1707             }
1708         }
1709     }
1710 }
1711
1712 /** Check for valid email address
1713     fixed version from http://www.zend.com/zend/spotlight/ev12apr.php
1714     Note: too strict, Bug #1053681
1715  */
1716 function ValidateMail($email, $noconnect=false) {
1717     global $EMailHosts;
1718     $HTTP_HOST = $GLOBALS['request']->get('HTTP_HOST');
1719
1720     // if this check is too strict (like invalid mail addresses in a local network only)
1721     // uncomment the following line:
1722     //return array(true,"not validated");
1723     // see http://sourceforge.net/tracker/index.php?func=detail&aid=1053681&group_id=6121&atid=106121
1724
1725     $result = array();
1726
1727     // This is Paul Warren's (pdw@ex-parrot.com) monster regex for RFC822
1728     // addresses, from the Perl module Mail::RFC822::Address, reduced to
1729     // accept single RFC822 addresses without comments only. (The original
1730     // accepts groups and properly commented addresses also.)
1731     $lwsp = "(?:(?:\\r\\n)?[ \\t])";
1732
1733     $specials = '()<>@,;:\\\\".\\[\\]';
1734     $controls = '\\000-\\031';
1735
1736     $dtext = "[^\\[\\]\\r\\\\]";
1737     $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$lwsp*";
1738
1739     $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$lwsp)*\"$lwsp*";
1740
1741     $atom = "[^$specials $controls]+(?:$lwsp+|\\Z|(?=[\\[\"$specials]))";
1742     $word = "(?:$atom|$quoted_string)";
1743     $localpart = "$word(?:\\.$lwsp*$word)*";
1744
1745     $sub_domain = "(?:$atom|$domain_literal)";
1746     $domain = "$sub_domain(?:\\.$lwsp*$sub_domain)*";
1747
1748     $addr_spec = "$localpart\@$lwsp*$domain";
1749
1750     $phrase = "$word*";
1751     $route = "(?:\@$domain(?:,\@$lwsp*$domain)*:$lwsp*)";
1752     $route_addr = "\\<$lwsp*$route?$addr_spec\\>$lwsp*";
1753     $mailbox = "(?:$addr_spec|$phrase$route_addr)";
1754
1755     $rfc822re = "/$lwsp*$mailbox/";
1756     unset($domain, $route_addr, $route, $phrase, $addr_spec, $sub_domain, $localpart, 
1757           $atom, $word, $quoted_string);
1758     unset($dtext, $controls, $specials, $lwsp, $domain_literal);
1759
1760     if (!preg_match($rfc822re, $email)) {
1761         $result[0] = false;
1762         $result[1] = sprintf(_("E-Mail address '%s' is not properly formatted"), $email);
1763         return $result;
1764     }
1765     if ($noconnect)
1766       return array(true, sprintf(_("E-Mail address '%s' is properly formatted"), $email));
1767
1768     list ( $Username, $Domain ) = split ("@", $email);
1769     //Todo: getmxrr workaround on windows or manual input field to verify it manually
1770     if (!isWindows() and getmxrr($Domain, $MXHost)) { // avoid warning on Windows. 
1771         $ConnectAddress = $MXHost[0];
1772     } else {
1773         $ConnectAddress = $Domain;
1774         if (isset($EMailHosts[ $Domain ])) {
1775             $ConnectAddress = $EMailHosts[ $Domain ];
1776         }
1777     }
1778     $Connect = @fsockopen ( $ConnectAddress, 25 );
1779     if ($Connect) {
1780         if (ereg("^220", $Out = fgets($Connect, 1024))) {
1781             fputs ($Connect, "HELO $HTTP_HOST\r\n");
1782             $Out = fgets ( $Connect, 1024 );
1783             fputs ($Connect, "MAIL FROM: <".$email.">\r\n");
1784             $From = fgets ( $Connect, 1024 );
1785             fputs ($Connect, "RCPT TO: <".$email.">\r\n");
1786             $To = fgets ($Connect, 1024);
1787             fputs ($Connect, "QUIT\r\n");
1788             fclose($Connect);
1789             if (!ereg ("^250", $From)) {
1790                 $result[0]=false;
1791                 $result[1]="Server rejected address: ". $From;
1792                 return $result;
1793             }
1794             if (!ereg ( "^250", $To )) {
1795                 $result[0]=false;
1796                 $result[1]="Server rejected address: ". $To;
1797                 return $result;
1798             }
1799         } else {
1800             $result[0] = false;
1801             $result[1] = "No response from server";
1802             return $result;
1803           }
1804     }  else {
1805         $result[0]=false;
1806         $result[1]="Can not connect E-Mail server.";
1807         return $result;
1808     }
1809     $result[0]=true;
1810     $result[1]="E-Mail address '$email' appears to be valid.";
1811     return $result;
1812 } // end of function 
1813
1814 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1815
1816 /**
1817  * UserPreferences
1818  * 
1819  * This object holds the $request->_prefs subobjects.
1820  * A simple packed array of non-default values get's stored as cookie,
1821  * homepage, or database, which are converted to the array of 
1822  * ->_prefs objects.
1823  * We don't store the objects, because otherwise we will
1824  * not be able to upgrade any subobject. And it's a waste of space also.
1825  *
1826  */
1827 class UserPreferences
1828 {
1829     function UserPreferences($saved_prefs = false) {
1830         // userid stored too, to ensure the prefs are being loaded for
1831         // the correct (currently signing in) userid if stored in a
1832         // cookie.
1833         // Update: for db prefs we disallow passwd. 
1834         // userid is needed for pref reflexion. current pref must know its username, 
1835         // if some app needs prefs from different users, different from current user.
1836         $this->_prefs
1837             = array(
1838                     'userid'        => new _UserPreference(''),
1839                     'passwd'        => new _UserPreference(''),
1840                     'autologin'     => new _UserPreference_bool(),
1841                     //'emailVerified' => new _UserPreference_emailVerified(), 
1842                     //fixed: store emailVerified as email parameter, 1.3.8
1843                     'email'         => new _UserPreference_email(''),
1844                     'notifyPages'   => new _UserPreference_notify(''), // 1.3.8
1845                     'theme'         => new _UserPreference_theme(THEME),
1846                     'lang'          => new _UserPreference_language(DEFAULT_LANGUAGE),
1847                     'editWidth'     => new _UserPreference_int(EDITWIDTH_DEFAULT_COLS,
1848                                                                EDITWIDTH_MIN_COLS,
1849                                                                EDITWIDTH_MAX_COLS),
1850                     'noLinkIcons'   => new _UserPreference_bool(),    // 1.3.8 
1851                     'editHeight'    => new _UserPreference_int(EDITHEIGHT_DEFAULT_ROWS,
1852                                                                EDITHEIGHT_MIN_ROWS,
1853                                                                EDITHEIGHT_MAX_ROWS),
1854                     'timeOffset'    => new _UserPreference_numeric(TIMEOFFSET_DEFAULT_HOURS,
1855                                                                    TIMEOFFSET_MIN_HOURS,
1856                                                                    TIMEOFFSET_MAX_HOURS),
1857                     'relativeDates' => new _UserPreference_bool(),
1858                     'googleLink'    => new _UserPreference_bool(), // 1.3.10
1859                     'doubleClickEdit' => new _UserPreference_bool(), // 1.3.11
1860                     );
1861         // add custom theme-specific pref types:
1862         // FIXME: on theme changes the wiki_user session pref object will fail. 
1863         // We will silently ignore this.
1864         if (!empty($customUserPreferenceColumns))
1865             $this->_prefs = array_merge($this->_prefs, $customUserPreferenceColumns);
1866 /*
1867         if (isset($this->_method) and $this->_method == 'SQL') {
1868             //unset($this->_prefs['userid']);
1869             unset($this->_prefs['passwd']);
1870         }
1871 */
1872         if (is_array($saved_prefs)) {
1873             foreach ($saved_prefs as $name => $value)
1874                 $this->set($name, $value);
1875         }
1876     }
1877
1878     function _getPref($name) {
1879         if ($name == 'emailVerified')
1880             $name = 'email';
1881         if (!isset($this->_prefs[$name])) {
1882             if ($name == 'passwd2') return false;
1883             if ($name == 'passwd') return false;
1884             trigger_error("$name: unknown preference", E_USER_NOTICE);
1885             return false;
1886         }
1887         return $this->_prefs[$name];
1888     }
1889     
1890     // get the value or default_value of the subobject
1891     function get($name) {
1892         if ($_pref = $this->_getPref($name))
1893             if ($name == 'emailVerified')
1894                 return $_pref->getraw($name);
1895             else
1896                 return $_pref->get($name);
1897         else
1898             return false;  
1899     }
1900
1901     // check and set the new value in the subobject
1902     function set($name, $value) {
1903         $pref = $this->_getPref($name);
1904         if ($pref === false)
1905             return false;
1906
1907         /* do it here or outside? */
1908         if ($name == 'passwd' and 
1909             defined('PASSWORD_LENGTH_MINIMUM') and 
1910             strlen($value) <= PASSWORD_LENGTH_MINIMUM ) {
1911             //TODO: How to notify the user?
1912             return false;
1913         }
1914         /*
1915         if ($name == 'theme' and $value == '')
1916            return true;
1917         */
1918         // Fix Fatal error for undefined value. Thanks to Jim Ford and Joel Schaubert
1919         if ((!$value and $pref->default_value)
1920             or ($value and !isset($pref->{$name})) // bug #1355533
1921             or ($value and ($pref->{$name} != $pref->default_value)))
1922         {
1923             if ($name == 'emailVerified') $newvalue = $value;
1924             else $newvalue = $pref->sanify($value);
1925             $pref->set($name, $newvalue);
1926         }
1927         $this->_prefs[$name] =& $pref;
1928         return true;
1929     }
1930     /**
1931      * use init to avoid update on set
1932      */
1933     function updatePrefs($prefs, $init = false) {
1934         $count = 0;
1935         if ($init) $this->_init = $init;
1936         if (is_object($prefs)) {
1937             $type = 'emailVerified'; $obj =& $this->_prefs['email'];
1938             $obj->_init = $init;
1939             if ($obj->get($type) !== $prefs->get($type)) {
1940                 if ($obj->set($type, $prefs->get($type)))
1941                     $count++;
1942             }
1943             foreach (array_keys($this->_prefs) as $type) {
1944                 $obj =& $this->_prefs[$type];
1945                 $obj->_init = $init;
1946                 if ($prefs->get($type) !== $obj->get($type)) {
1947                     // special systemdefault prefs: (probably not needed)
1948                     if ($type == 'theme' and $prefs->get($type) == '' and 
1949                         $obj->get($type) == THEME) continue;
1950                     if ($type == 'lang' and $prefs->get($type) == '' and 
1951                         $obj->get($type) == DEFAULT_LANGUAGE) continue;
1952                     if ($this->_prefs[$type]->set($type, $prefs->get($type)))
1953                         $count++;
1954                 }
1955             }
1956         } elseif (is_array($prefs)) {
1957             //unset($this->_prefs['userid']);
1958             /*
1959             if (isset($this->_method) and 
1960                  ($this->_method == 'SQL' or $this->_method == 'ADODB')) {
1961                 unset($this->_prefs['passwd']);
1962             }
1963             */
1964             // emailVerified at first, the rest later
1965             $type = 'emailVerified'; $obj =& $this->_prefs['email'];
1966             $obj->_init = $init;
1967             if (isset($prefs[$type]) and $obj->get($type) !== $prefs[$type]) {
1968                 if ($obj->set($type,$prefs[$type]))
1969                     $count++;
1970             }
1971             foreach (array_keys($this->_prefs) as $type) {
1972                 $obj =& $this->_prefs[$type];
1973                 $obj->_init = $init;
1974                 if (!isset($prefs[$type]) and isa($obj,"_UserPreference_bool")) 
1975                     $prefs[$type] = false;
1976                 if (isset($prefs[$type]) and isa($obj,"_UserPreference_int"))
1977                     $prefs[$type] = (int) $prefs[$type];
1978                 if (isset($prefs[$type]) and $obj->get($type) != $prefs[$type]) {
1979                     // special systemdefault prefs:
1980                     if ($type == 'theme' and $prefs[$type] == '' and 
1981                         $obj->get($type) == THEME) continue;
1982                     if ($type == 'lang' and $prefs[$type] == '' and 
1983                         $obj->get($type) == DEFAULT_LANGUAGE) continue;
1984                     if ($obj->set($type,$prefs[$type]))
1985                         $count++;
1986                 }
1987             }
1988         }
1989         return $count;
1990     }
1991
1992     // For now convert just array of objects => array of values
1993     // Todo: the specialized subobjects must override this.
1994     function store() {
1995         $prefs = array();
1996         foreach ($this->_prefs as $name => $object) {
1997             if ($value = $object->getraw($name))
1998                 $prefs[$name] = $value;
1999             if ($name == 'email' and ($value = $object->getraw('emailVerified')))
2000                 $prefs['emailVerified'] = $value;
2001             if ($name == 'passwd' and $value and ENCRYPTED_PASSWD) {
2002                 if (strlen($value) != strlen(crypt('test')))
2003                     $prefs['passwd'] = crypt($value);
2004                 else // already crypted
2005                     $prefs['passwd'] = $value;
2006             }
2007         }
2008         return $this->pack($prefs);
2009     }
2010
2011     // packed string or array of values => array of values
2012     // Todo: the specialized subobjects must override this.
2013     function retrieve($packed) {
2014         if (is_string($packed) and (substr($packed, 0, 2) == "a:"))
2015             $packed = unserialize($packed);
2016         if (!is_array($packed)) return false;
2017         $prefs = array();
2018         foreach ($packed as $name => $packed_pref) {
2019             if (is_string($packed_pref) and substr($packed_pref, 0, 2) == "O:") {
2020                 //legacy: check if it's an old array of objects
2021                 // Looks like a serialized object. 
2022                 // This might fail if the object definition does not exist anymore.
2023                 // object with ->$name and ->default_value vars.
2024                 $pref =  @unserialize($packed_pref);
2025                 if (empty($pref))
2026                     $pref = @unserialize(base64_decode($packed_pref));
2027                 $prefs[$name] = $pref->get($name);
2028             // fix old-style prefs
2029             } elseif (is_numeric($name) and is_array($packed_pref)) {
2030                 if (count($packed_pref) == 1) {
2031                     list($name,$value) = each($packed_pref);
2032                     $prefs[$name] = $value;
2033                 }
2034             } else {
2035                 $prefs[$name] = @unserialize($packed_pref);
2036                 if (empty($prefs[$name]))
2037                     $prefs[$name] = @unserialize(base64_decode($packed_pref));
2038                 // patched by frederik@pandora.be
2039                 if (empty($prefs[$name]))
2040                     $prefs[$name] = $packed_pref;
2041             }
2042         }
2043         return $prefs;
2044     }
2045
2046     /**
2047      * Check if the given prefs object is different from the current prefs object
2048      */
2049     function isChanged($other) {
2050         foreach ($this->_prefs as $type => $obj) {
2051             if ($obj->get($type) !== $other->get($type))
2052                 return true;
2053         }
2054         return false;
2055     }
2056
2057     function defaultPreferences() {
2058         $prefs = array();
2059         foreach ($this->_prefs as $key => $obj) {
2060             $prefs[$key] = $obj->default_value;
2061         }
2062         return $prefs;
2063     }
2064     
2065     // array of objects
2066     function getAll() {
2067         return $this->_prefs;
2068     }
2069
2070     function pack($nonpacked) {
2071         return serialize($nonpacked);
2072     }
2073
2074     function unpack($packed) {
2075         if (!$packed)
2076             return false;
2077         //$packed = base64_decode($packed);
2078         if (substr($packed, 0, 2) == "O:") {
2079             // Looks like a serialized object
2080             return unserialize($packed);
2081         }
2082         if (substr($packed, 0, 2) == "a:") {
2083             return unserialize($packed);
2084         }
2085         //trigger_error("DEBUG: Can't unpack bad UserPreferences",
2086         //E_USER_WARNING);
2087         return false;
2088     }
2089
2090     function hash () {
2091         return wikihash($this->_prefs);
2092     }
2093 }
2094
2095 /** TODO: new pref storage classes
2096  *  These are currently user specific and should be rewritten to be pref specific.
2097  *  i.e. $this == $user->_prefs
2098  */
2099 /*
2100 class CookieUserPreferences
2101 extends UserPreferences
2102 {
2103     function CookieUserPreferences ($saved_prefs = false) {
2104         //_AnonUser::_AnonUser('',$saved_prefs);
2105         UserPreferences::UserPreferences($saved_prefs);
2106     }
2107 }
2108
2109 class PageUserPreferences
2110 extends UserPreferences
2111 {
2112     function PageUserPreferences ($saved_prefs = false) {
2113         UserPreferences::UserPreferences($saved_prefs);
2114     }
2115 }
2116
2117 class PearDbUserPreferences
2118 extends UserPreferences
2119 {
2120     function PearDbUserPreferences ($saved_prefs = false) {
2121         UserPreferences::UserPreferences($saved_prefs);
2122     }
2123 }
2124
2125 class AdoDbUserPreferences
2126 extends UserPreferences
2127 {
2128     function AdoDbUserPreferences ($saved_prefs = false) {
2129         UserPreferences::UserPreferences($saved_prefs);
2130     }
2131     function getPreferences() {
2132         // override the generic slow method here for efficiency
2133         _AnonUser::getPreferences();
2134         $this->getAuthDbh();
2135         if (isset($this->_select)) {
2136             $dbh = & $this->_auth_dbi;
2137             $rs = $dbh->Execute(sprintf($this->_select,$dbh->qstr($this->_userid)));
2138             if ($rs->EOF) {
2139                 $rs->Close();
2140             } else {
2141                 $prefs_blob = $rs->fields['pref_blob'];
2142                 $rs->Close();
2143                 if ($restored_from_db = $this->_prefs->retrieve($prefs_blob)) {
2144                     $updated = $this->_prefs->updatePrefs($restored_from_db);
2145                     //$this->_prefs = new UserPreferences($restored_from_db);
2146                     return $this->_prefs;
2147                 }
2148             }
2149         }
2150         if (empty($this->_prefs->_prefs) and $this->_HomePagehandle) {
2151             if ($restored_from_page = $this->_prefs->retrieve
2152                 ($this->_HomePagehandle->get('pref'))) {
2153                 $updated = $this->_prefs->updatePrefs($restored_from_page);
2154                 //$this->_prefs = new UserPreferences($restored_from_page);
2155                 return $this->_prefs;
2156             }
2157         }
2158         return $this->_prefs;
2159     }
2160 }
2161 */
2162
2163 // $Log: not supported by cvs2svn $
2164 // Revision 1.144  2007/06/01 06:36:57  rurban
2165 // allow space in user names. backends should tighten it
2166 //
2167 // Revision 1.143  2007/05/24 18:37:53  rurban
2168 // silence AdminUser HomePagehandle warning
2169 //
2170 // Revision 1.142  2007/05/15 16:32:34  rurban
2171 // Refactor class upgrading at ->UserExists
2172 //
2173 // Revision 1.141  2007/05/13 18:31:24  rurban
2174 // Refactor UpgradeUser. Added EMailHosts
2175 //
2176 // Revision 1.140  2006/12/22 01:20:14  rurban
2177 // Automatically create a Users homepage, when no SQL method exists
2178 // not to rely on cookies.
2179 //
2180 // Revision 1.139  2006/09/03 09:55:37  rurban
2181 // Remove too early and too strict isValidName check in _PassUser. This really should be done in
2182 // the method, when we know it. This fixes NTLM auth. (userid=domain\user)
2183 //
2184 // Revision 1.138  2006/06/18 11:02:55  rurban
2185 // pref->value > -name, fix bug #1355533
2186 //
2187 // Revision 1.137  2006/05/03 06:05:37  rurban
2188 // Fix default preferences for editheight maxrows, by Manuel Vacelet.
2189 //
2190 // Revision 1.136  2006/04/16 11:07:48  rurban
2191 // Dont crypt the passwd twice on storing prefs. Patch by Thomas Harding.
2192 // Fixes bug #1327470
2193 //
2194 // Revision 1.135  2006/03/19 16:26:39  rurban
2195 // fix DBAUTH arguments to be position independent, fixes bug #1358973
2196 //
2197 // Revision 1.134  2006/03/19 15:01:00  rurban
2198 // sf.net patch #1333957 by Matt Brown: Authentication cookie identical across all wikis on a host
2199 //
2200 // Revision 1.133  2006/03/07 18:39:21  rurban
2201 // add PdoDb, rename hash to wikihash (php-5.1), fix output of Homepage prefs update
2202 //
2203 // Revision 1.132  2006/03/04 13:19:12  rurban
2204 // fix for fatal error on empty pref value (sign out). Thanks to Jim Ford and Joel Schaubert. rename hash for php-5.1
2205 //
2206 // Revision 1.131  2005/10/12 06:16:48  rurban
2207 // add new _insert statement
2208 //
2209 // Revision 1.129  2005/06/10 06:10:35  rurban
2210 // ensure Update Preferences gets through
2211 //
2212 // Revision 1.128  2005/06/05 05:38:02  rurban
2213 // Default ENABLE_DOUBLECLICKEDIT = false. Moved to UserPreferences
2214 //
2215 // Revision 1.127  2005/04/02 18:01:41  uckelman
2216 // Fixed regex for RFC822 addresses.
2217 //
2218 // Revision 1.126  2005/02/28 20:30:46  rurban
2219 // some stupid code for _AdminUser (probably not needed)
2220 //
2221 // Revision 1.125  2005/02/08 13:25:50  rurban
2222 // encrypt password. fix strict logic.
2223 // both bugs reported by Mikhail Vladimirov
2224 //
2225 // Revision 1.124  2005/01/30 23:11:00  rurban
2226 // allow self-creating passuser on login
2227 //
2228 // Revision 1.123  2005/01/25 06:58:21  rurban
2229 // reformatting
2230 //
2231 // Revision 1.122  2005/01/08 22:51:56  rurban
2232 // remove deprecated workaround
2233 //
2234 // Revision 1.121  2004/12/19 00:58:01  rurban
2235 // Enforce PASSWORD_LENGTH_MINIMUM in almost all PassUser checks,
2236 // Provide an errormessage if so. Just PersonalPage and BogoLogin not.
2237 // Simplify httpauth logout handling and set sessions for all methods.
2238 // fix main.php unknown index "x" getLevelDescription() warning.
2239 //
2240 // Revision 1.120  2004/12/17 12:31:57  rurban
2241 // better logout, fake httpauth not yet
2242 //
2243 // Revision 1.119  2004/11/21 11:59:17  rurban
2244 // remove final \n to be ob_cache independent
2245 //
2246 // Revision 1.118  2004/11/19 19:22:03  rurban
2247 // ModeratePage part1: change status
2248 //
2249 // Revision 1.117  2004/11/10 15:29:21  rurban
2250 // * requires newer Pear_DB (as the internal one): quote() uses now escapeSimple for strings
2251 // * ACCESS_LOG_SQL: fix cause request not yet initialized
2252 // * WikiDB: moved SQL specific methods upwards
2253 // * new Pear_DB quoting: same as ADODB and as newer Pear_DB.
2254 //   fixes all around: WikiGroup, WikiUserNew SQL methods, SQL logging
2255 //
2256 // Revision 1.116  2004/11/05 21:03:27  rurban
2257 // new DEBUG flag: _DEBUG_LOGIN (64)
2258 //   verbose login debug-msg (settings and reason for failure)
2259 //
2260 // Revision 1.115  2004/11/05 20:53:35  rurban
2261 // login cleanup: better debug msg on failing login,
2262 // checked password less immediate login (bogo or anon),
2263 // checked olduser pref session error,
2264 // better PersonalPage without password warning on minimal password length=0
2265 //   (which is default now)
2266 //
2267 // Revision 1.114  2004/11/05 16:15:57  rurban
2268 // forgot the BogoLogin inclusion with the latest rewrite
2269 //
2270 // Revision 1.113  2004/11/03 17:13:49  rurban
2271 // make it easier to disable EmailVerification
2272 //   Bug #1053681
2273 //
2274 // Revision 1.112  2004/11/01 10:43:57  rurban
2275 // seperate PassUser methods into seperate dir (memory usage)
2276 // fix WikiUser (old) overlarge data session
2277 // remove wikidb arg from various page class methods, use global ->_dbi instead
2278 // ...
2279 //
2280 // Revision 1.111  2004/10/21 21:03:50  rurban
2281 // isAdmin must be signed and authenticated
2282 // comment out unused sections (memory)
2283 //
2284 // Revision 1.110  2004/10/14 19:19:33  rurban
2285 // loadsave: check if the dumped file will be accessible from outside.
2286 // and some other minor fixes. (cvsclient native not yet ready)
2287 //
2288 // Revision 1.109  2004/10/07 16:08:58  rurban
2289 // fixed broken FileUser session handling.
2290 //   thanks to Arnaud Fontaine for detecting this.
2291 // enable file user Administrator membership.
2292 //
2293 // Revision 1.108  2004/10/05 17:00:04  rurban
2294 // support paging for simple lists
2295 // fix RatingDb sql backend.
2296 // remove pages from AllPages (this is ListPages then)
2297 //
2298 // Revision 1.107  2004/10/04 23:42:15  rurban
2299 // HttpAuth admin group logic. removed old logs
2300 //
2301 // Revision 1.106  2004/07/01 08:49:38  rurban
2302 // obsolete php5-patch.php: minor php5 login problem though
2303 //
2304 // Revision 1.105  2004/06/29 06:48:03  rurban
2305 // Improve LDAP auth and GROUP_LDAP membership:
2306 //   no error message on false password,
2307 //   added two new config vars: LDAP_OU_USERS and LDAP_OU_GROUP with GROUP_METHOD=LDAP
2308 //   fixed two group queries (this -> user)
2309 // stdlib: ConvertOldMarkup still flawed
2310 //
2311 // Revision 1.104  2004/06/28 15:39:37  rurban
2312 // fixed endless recursion in WikiGroup: isAdmin()
2313 //
2314 // Revision 1.103  2004/06/28 15:01:07  rurban
2315 // fixed LDAP_SET_OPTION handling, LDAP error on connection problem
2316 //
2317 // Revision 1.102  2004/06/27 10:23:48  rurban
2318 // typo detected by Philippe Vanhaesendonck
2319 //
2320 // Revision 1.101  2004/06/25 14:29:19  rurban
2321 // WikiGroup refactoring:
2322 //   global group attached to user, code for not_current user.
2323 //   improved helpers for special groups (avoid double invocations)
2324 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
2325 // fixed a XHTML validation error on userprefs.tmpl
2326 //
2327 // Revision 1.100  2004/06/21 06:29:35  rurban
2328 // formatting: linewrap only
2329 //
2330 // Revision 1.99  2004/06/20 15:30:05  rurban
2331 // get_class case-sensitivity issues
2332 //
2333 // Revision 1.98  2004/06/16 21:24:31  rurban
2334 // do not display no-connect warning: #2662
2335 //
2336 // Revision 1.97  2004/06/16 13:21:16  rurban
2337 // stabilize on failing ldap queries or bind
2338 //
2339 // Revision 1.96  2004/06/16 12:42:06  rurban
2340 // fix homepage prefs
2341 //
2342 // Revision 1.95  2004/06/16 10:38:58  rurban
2343 // Disallow refernces in calls if the declaration is a reference
2344 // ("allow_call_time_pass_reference clean").
2345 //   PhpWiki is now allow_call_time_pass_reference = Off clean,
2346 //   but several external libraries may not.
2347 //   In detail these libs look to be affected (not tested):
2348 //   * Pear_DB odbc
2349 //   * adodb oracle
2350 //
2351 // Revision 1.94  2004/06/15 10:40:35  rurban
2352 // minor WikiGroup cleanup: no request param, start of current user independency
2353 //
2354 // Revision 1.93  2004/06/15 09:15:52  rurban
2355 // IMPORTANT: fixed passwd handling for passwords stored in prefs:
2356 //   fix encrypted usage, actually store and retrieve them from db
2357 //   fix bogologin with passwd set.
2358 // fix php crashes with call-time pass-by-reference (references wrongly used
2359 //   in declaration AND call). This affected mainly Apache2 and IIS.
2360 //   (Thanks to John Cole to detect this!)
2361 //
2362 // Revision 1.92  2004/06/14 11:31:36  rurban
2363 // renamed global $Theme to $WikiTheme (gforge nameclash)
2364 // inherit PageList default options from PageList
2365 //   default sortby=pagename
2366 // use options in PageList_Selectable (limit, sortby, ...)
2367 // added action revert, with button at action=diff
2368 // added option regex to WikiAdminSearchReplace
2369 //
2370 // Revision 1.91  2004/06/08 14:57:43  rurban
2371 // stupid ldap bug detected by John Cole
2372 //
2373 // Revision 1.90  2004/06/08 09:31:15  rurban
2374 // fixed typo detected by lucidcarbon (line 1663 assertion)
2375 //
2376 // Revision 1.89  2004/06/06 16:58:51  rurban
2377 // added more required ActionPages for foreign languages
2378 // install now english ActionPages if no localized are found. (again)
2379 // fixed default anon user level to be 0, instead of -1
2380 //   (wrong "required administrator to view this page"...)
2381 //
2382 // Revision 1.88  2004/06/04 20:32:53  rurban
2383 // Several locale related improvements suggested by Pierrick Meignen
2384 // LDAP fix by John Cole
2385 // reanable admin check without ENABLE_PAGEPERM in the admin plugins
2386 //
2387 // Revision 1.87  2004/06/04 12:40:21  rurban
2388 // Restrict valid usernames to prevent from attacks against external auth or compromise
2389 // possible holes.
2390 // Fix various WikiUser old issues with default IMAP,LDAP,POP3 configs. Removed these.
2391 // Fxied more warnings
2392 //
2393 // Revision 1.86  2004/06/03 18:06:29  rurban
2394 // fix file locking issues (only needed on write)
2395 // fixed immediate LANG and THEME in-session updates if not stored in prefs
2396 // advanced editpage toolbars (search & replace broken)
2397 //
2398 // Revision 1.85  2004/06/03 12:46:03  rurban
2399 // fix signout, level must be 0 not -1
2400 //
2401 // Revision 1.84  2004/06/03 12:36:03  rurban
2402 // fix eval warning on signin
2403 //
2404 // Revision 1.83  2004/06/03 10:18:19  rurban
2405 // fix User locking issues, new config ENABLE_PAGEPERM
2406 //
2407 // Revision 1.82  2004/06/03 09:39:51  rurban
2408 // fix LDAP injection (wildcard in username) detected by Steve Christey, MITRE
2409 //
2410 // Revision 1.81  2004/06/02 18:01:45  rurban
2411 // init global FileFinder to add proper include paths at startup
2412 //   adds PHPWIKI_DIR if started from another dir, lib/pear also
2413 // fix slashify for Windows
2414 // fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
2415 //
2416 // Revision 1.80  2004/06/02 14:20:27  rurban
2417 // fix adodb DbPassUser login
2418 //
2419 // Revision 1.79  2004/06/01 15:27:59  rurban
2420 // AdminUser only ADMIN_USER not member of Administrators
2421 // some RateIt improvements by dfrankow
2422 // edit_toolbar buttons
2423 //
2424 // Revision 1.78  2004/05/27 17:49:06  rurban
2425 // renamed DB_Session to DbSession (in CVS also)
2426 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
2427 // remove leading slash in error message
2428 // added force_unlock parameter to File_Passwd (no return on stale locks)
2429 // fixed adodb session AffectedRows
2430 // added FileFinder helpers to unify local filenames and DATA_PATH names
2431 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
2432 //
2433 // Revision 1.77  2004/05/18 14:49:51  rurban
2434 // Simplified strings for easier translation
2435 //
2436 // Revision 1.76  2004/05/18 13:30:04  rurban
2437 // prevent from endless loop with oldstyle warnings
2438 //
2439 // Revision 1.75  2004/05/16 22:07:35  rurban
2440 // check more config-default and predefined constants
2441 // various PagePerm fixes:
2442 //   fix default PagePerms, esp. edit and view for Bogo and Password users
2443 //   implemented Creator and Owner
2444 //   BOGOUSERS renamed to BOGOUSER
2445 // fixed syntax errors in signin.tmpl
2446 //
2447 // Revision 1.74  2004/05/15 19:48:33  rurban
2448 // fix some too loose PagePerms for signed, but not authenticated users
2449 //  (admin, owner, creator)
2450 // no double login page header, better login msg.
2451 // moved action_pdf to lib/pdf.php
2452 //
2453 // Revision 1.73  2004/05/15 18:31:01  rurban
2454 // some action=pdf Request fixes: With MSIE it works now. Now the work with the page formatting begins.
2455 //
2456 // Revision 1.72  2004/05/12 10:49:55  rurban
2457 // require_once fix for those libs which are loaded before FileFinder and
2458 //   its automatic include_path fix, and where require_once doesn't grok
2459 //   dirname(__FILE__) != './lib'
2460 // upgrade fix with PearDB
2461 // navbar.tmpl: remove spaces for IE &nbsp; button alignment
2462 //
2463 // Revision 1.71  2004/05/10 12:34:47  rurban
2464 // stabilize DbAuthParam statement pre-prozessor:
2465 //   try old-style and new-style (double-)quoting
2466 //   reject unknown $variables
2467 //   use ->prepare() for all calls (again)
2468 //
2469 // Revision 1.70  2004/05/06 19:26:16  rurban
2470 // improve stability, trying to find the InlineParser endless loop on sf.net
2471 //
2472 // remove end-of-zip comments to fix sf.net bug #777278 and probably #859628
2473 //
2474 // Revision 1.69  2004/05/06 13:56:40  rurban
2475 // Enable the Administrators group, and add the WIKIPAGE group default root page.
2476 //
2477 // Revision 1.68  2004/05/05 13:37:54  rurban
2478 // Support to remove all UserPreferences
2479 //
2480 // Revision 1.66  2004/05/03 21:44:24  rurban
2481 // fixed sf,net bug #947264: LDAP options are constants, not strings!
2482 //
2483 // Revision 1.65  2004/05/03 13:16:47  rurban
2484 // fixed UserPreferences update, esp for boolean and int
2485 //
2486 // Revision 1.64  2004/05/02 15:10:06  rurban
2487 // new finally reliable way to detect if /index.php is called directly
2488 //   and if to include lib/main.php
2489 // new global AllActionPages
2490 // SetupWiki now loads all mandatory pages: HOME_PAGE, action pages, and warns if not.
2491 // WikiTranslation what=buttons for Carsten to create the missing MacOSX buttons
2492 // PageGroupTestOne => subpages
2493 // renamed PhpWikiRss to PhpWikiRecentChanges
2494 // more docs, default configs, ...
2495 //
2496 // Revision 1.63  2004/05/01 15:59:29  rurban
2497 // more php-4.0.6 compatibility: superglobals
2498 //
2499 // Revision 1.62  2004/04/29 18:31:24  rurban
2500 // Prevent from warning where no db pref was previously stored.
2501 //
2502 // Revision 1.61  2004/04/29 17:18:19  zorloc
2503 // Fixes permission failure issues.  With PagePermissions and Disabled Actions when user did not have permission WIKIAUTH_FORBIDDEN was returned.  In WikiUser this was ok because WIKIAUTH_FORBIDDEN had a value of 11 -- thus no user could perform that action.  But WikiUserNew has a WIKIAUTH_FORBIDDEN value of -1 -- thus a user without sufficent permission to do anything.  The solution is a new high value permission level (WIKIAUTH_UNOBTAINABLE) to be the default level for access failure.
2504 //
2505 // Revision 1.60  2004/04/27 18:20:54  rurban
2506 // sf.net patch #940359 by rassie
2507 //
2508 // Revision 1.59  2004/04/26 12:35:21  rurban
2509 // POP3_AUTH_PORT deprecated, use "host:port" similar to IMAP
2510 // File_Passwd is already loaded
2511 //
2512 // Revision 1.58  2004/04/20 17:08:28  rurban
2513 // Some IniConfig fixes: prepend our private lib/pear dir
2514 //   switch from " to ' in the auth statements
2515 //   use error handling.
2516 // WikiUserNew changes for the new "'$variable'" syntax
2517 //   in the statements
2518 // TODO: optimization to put config vars into the session.
2519 //
2520 // Revision 1.57  2004/04/19 18:27:45  rurban
2521 // Prevent from some PHP5 warnings (ref args, no :: object init)
2522 //   php5 runs now through, just one wrong XmlElement object init missing
2523 // Removed unneccesary UpgradeUser lines
2524 // Changed WikiLink to omit version if current (RecentChanges)
2525 //
2526 // Revision 1.56  2004/04/19 09:13:24  rurban
2527 // new pref: googleLink
2528 //
2529 // Revision 1.54  2004/04/18 00:24:45  rurban
2530 // re-use our simple prepare: just for table prefix warnings
2531 //
2532 // Revision 1.53  2004/04/12 18:29:15  rurban
2533 // exp. Session auth for already authenticated users from another app
2534 //
2535 // Revision 1.52  2004/04/12 13:04:50  rurban
2536 // added auth_create: self-registering Db users
2537 // fixed IMAP auth
2538 // removed rating recommendations
2539 // ziplib reformatting
2540 //
2541 // Revision 1.51  2004/04/11 10:42:02  rurban
2542 // pgsrc/CreatePagePlugin
2543 //
2544 // Revision 1.50  2004/04/10 05:34:35  rurban
2545 // sf bug#830912
2546 //
2547 // Revision 1.49  2004/04/07 23:13:18  rurban
2548 // fixed pear/File_Passwd for Windows
2549 // fixed FilePassUser sessions (filehandle revive) and password update
2550 //
2551 // Revision 1.48  2004/04/06 20:00:10  rurban
2552 // Cleanup of special PageList column types
2553 // Added support of plugin and theme specific Pagelist Types
2554 // Added support for theme specific UserPreferences
2555 // Added session support for ip-based throttling
2556 //   sql table schema change: ALTER TABLE session ADD sess_ip CHAR(15);
2557 // Enhanced postgres schema
2558 // Added DB_Session_dba support
2559 //
2560 // Revision 1.47  2004/04/02 15:06:55  rurban
2561 // fixed a nasty ADODB_mysql session update bug
2562 // improved UserPreferences layout (tabled hints)
2563 // fixed UserPreferences auth handling
2564 // improved auth stability
2565 // improved old cookie handling: fixed deletion of old cookies with paths
2566 //
2567 // Revision 1.46  2004/04/01 06:29:51  rurban
2568 // better wording
2569 // RateIt also for ADODB
2570 //
2571
2572 // Local Variables:
2573 // mode: php
2574 // tab-width: 8
2575 // c-basic-offset: 4
2576 // c-hanging-comment-ender-p: nil
2577 // indent-tabs-mode: nil
2578 // End:
2579 ?>