71b96ead6ae5afb3d16bac6f3709a9527d0135c4
[lhc/web/wiklou.git] / includes / User.php
1 <?php
2 # See user.doc
3
4 include_once( "WatchedItem.php" );
5
6 class User {
7 /* private */ var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
8 /* private */ var $mRights, $mOptions;
9 /* private */ var $mDataLoaded, $mNewpassword;
10 /* private */ var $mSkin;
11 /* private */ var $mBlockedby, $mBlockreason;
12 /* private */ var $mTouched;
13 /* private */ var $mCookiePassword;
14 /* private */ var $mRealName;
15
16 function User()
17 {
18 $this->loadDefaults();
19 }
20
21 # Static factory method
22 #
23 function newFromName( $name )
24 {
25 $u = new User();
26
27 # Clean up name according to title rules
28
29 $t = Title::newFromText( $name );
30 $u->setName( $t->getText() );
31 return $u;
32 }
33
34 /* static */ function whoIs( $id )
35 {
36 return wfGetSQL( "user", "user_name", "user_id=$id" );
37 }
38
39 /* static */ function idFromName( $name )
40 {
41 $nt = Title::newFromText( $name );
42 $sql = "SELECT user_id FROM user WHERE user_name='" .
43 wfStrencode( $nt->getText() ) . "'";
44 $res = wfQuery( $sql, DB_READ, "User::idFromName" );
45
46 if ( 0 == wfNumRows( $res ) ) { return 0; }
47 else {
48 $s = wfFetchObject( $res );
49 return $s->user_id;
50 }
51 }
52
53 # does the string match an anonymous user IP address?
54 /* static */ function isIP( $name ) {
55 return preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$name);
56
57 }
58
59 /* static */ function randomPassword()
60 {
61 $pwchars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz";
62 $l = strlen( $pwchars ) - 1;
63
64 wfSeedRandom();
65 $np = $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
66 $pwchars{mt_rand( 0, $l )} . chr( mt_rand(48, 57) ) .
67 $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
68 $pwchars{mt_rand( 0, $l )};
69 return $np;
70 }
71
72 function loadDefaults()
73 {
74 global $wgLang, $wgIP;
75 global $wgNamespacesToBeSearchedDefault;
76
77 $this->mId = $this->mNewtalk = 0;
78 $this->mName = $wgIP;
79 $this->mEmail = "";
80 $this->mPassword = $this->mNewpassword = "";
81 $this->mRights = array();
82 $defOpt = $wgLang->getDefaultUserOptions() ;
83 foreach ( $defOpt as $oname => $val ) {
84 $this->mOptions[$oname] = $val;
85 }
86 foreach ($wgNamespacesToBeSearchedDefault as $nsnum => $val) {
87 $this->mOptions["searchNs".$nsnum] = $val;
88 }
89 unset( $this->mSkin );
90 $this->mDataLoaded = false;
91 $this->mBlockedby = -1; # Unset
92 $this->mTouched = '0'; # Allow any pages to be cached
93 $this->cookiePassword = "";
94 }
95
96 /* private */ function getBlockedStatus()
97 {
98 global $wgIP, $wgBlockCache;
99
100 if ( -1 != $this->mBlockedby ) { return; }
101
102 $this->mBlockedby = 0;
103
104 # User blocking
105 if ( $this->mId ) {
106 $block = new Block();
107 if ( $block->load( $wgIP , $this->mId ) ) {
108 $this->mBlockedby = $block->mBy;
109 $this->mBlockreason = $block->mReason;
110 }
111 }
112
113 # IP/range blocking
114 if ( !$this->mBlockedby ) {
115 $block = $wgBlockCache->get( $wgIP );
116 if ( $block !== false ) {
117 $this->mBlockedby = $block->mBy;
118 $this->mBlockreason = $block->mReason;
119 }
120 }
121 }
122
123 function isBlocked()
124 {
125 $this->getBlockedStatus();
126 if ( 0 == $this->mBlockedby ) { return false; }
127 return true;
128 }
129
130 function blockedBy() {
131 $this->getBlockedStatus();
132 return $this->mBlockedby;
133 }
134
135 function blockedFor() {
136 $this->getBlockedStatus();
137 return $this->mBlockreason;
138 }
139
140 function SetupSession() {
141 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
142 if( $wgSessionsInMemcached ) {
143 include_once( "MemcachedSessions.php" );
144 }
145 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
146 session_cache_limiter( "private, must-revalidate" );
147 session_start();
148 }
149
150 /* static */ function loadFromSession()
151 {
152 global $wgMemc, $wgDBname;
153
154 if ( isset( $_SESSION['wsUserID'] ) ) {
155 if ( 0 != $_SESSION['wsUserID'] ) {
156 $sId = $_SESSION['wsUserID'];
157 } else {
158 return new User();
159 }
160 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
161 $sId = IntVal( $_COOKIE["{$wgDBname}UserID"] );
162 $_SESSION['wsUserID'] = $sId;
163 } else {
164 return new User();
165 }
166 if ( isset( $_SESSION['wsUserName'] ) ) {
167 $sName = $_SESSION['wsUserName'];
168 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
169 $sName = $_COOKIE["{$wgDBname}UserName"];
170 $_SESSION['wsUserName'] = $sName;
171 } else {
172 return new User();
173 }
174
175 $passwordCorrect = FALSE;
176 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
177 if($makenew = !$user) {
178 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
179 $user = new User();
180 $user->mId = $sId;
181 $user->loadFromDatabase();
182 } else {
183 wfDebug( "User::loadFromSession() got from cache!\n" );
184 }
185
186 if ( isset( $_SESSION['wsUserPassword'] ) ) {
187 $passwordCorrect = $_SESSION['wsUserPassword'] == $user->mPassword;
188 } else if ( isset( $_COOKIE["{$wgDBname}Password"] ) ) {
189 $user->mCookiePassword = $_COOKIE["{$wgDBname}Password"];
190 $_SESSION['wsUserPassword'] = $user->addSalt( $user->mCookiePassword );
191 $passwordCorrect = $_SESSION['wsUserPassword'] == $user->mPassword;
192 } else {
193 return new User(); # Can't log in from session
194 }
195
196 if ( ( $sName == $user->mName ) && $passwordCorrect ) {
197 if($makenew) {
198 if($wgMemc->set( $key, $user ))
199 wfDebug( "User::loadFromSession() successfully saved user\n" );
200 else
201 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
202 }
203 $user->spreadBlock();
204 return $user;
205 }
206 return new User(); # Can't log in from session
207 }
208
209 function loadFromDatabase()
210 {
211 global $wgCommandLineMode;
212 if ( $this->mDataLoaded || $wgCommandLineMode ) {
213 return;
214 }
215
216 # Paranoia
217 $this->mId = IntVal( $this->mId );
218
219 # check in separate table if there are changes to the talk page
220 $this->mNewtalk=0; # reset talk page status
221 if($this->mId) {
222 $sql = "SELECT 1 FROM user_newtalk WHERE user_id={$this->mId}";
223 $res = wfQuery ($sql, DB_READ, "User::loadFromDatabase" );
224
225 if (wfNumRows($res)>0) {
226 $this->mNewtalk= 1;
227 }
228 wfFreeResult( $res );
229 } else {
230 global $wgDBname, $wgMemc;
231 $key = "$wgDBname:newtalk:ip:{$this->mName}";
232 $newtalk = $wgMemc->get( $key );
233 if( ! is_integer( $newtalk ) ){
234 $sql = "SELECT 1 FROM user_newtalk WHERE user_ip='{$this->mName}'";
235 $res = wfQuery ($sql, DB_READ, "User::loadFromDatabase" );
236
237 $this->mNewtalk = (wfNumRows($res)>0) ? 1 : 0;
238 wfFreeResult( $res );
239
240 $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 );
241 } else {
242 $this->mNewtalk = $newtalk ? 1 : 0;
243 }
244 }
245 if(!$this->mId) {
246 $this->mDataLoaded = true;
247 return;
248 } # the following stuff is for non-anonymous users only
249
250 $sql = "SELECT user_name,user_password,user_newpassword,user_email," .
251 "user_real_name,user_options,user_rights,user_touched " .
252 " FROM user WHERE user_id=" . $this->mId;
253 $res = wfQuery( $sql, DB_READ, "User::loadFromDatabase" );
254
255 if ( wfNumRows( $res ) > 0 ) {
256 $s = wfFetchObject( $res );
257 $this->mName = $s->user_name;
258 $this->mEmail = $s->user_email;
259 $this->mRealName = $s->user_real_name;
260 $this->mPassword = $s->user_password;
261 $this->mNewpassword = $s->user_newpassword;
262 $this->decodeOptions( $s->user_options );
263 $this->mRights = explode( ",", strtolower( $s->user_rights ) );
264 $this->mTouched = $s->user_touched;
265 }
266
267 wfFreeResult( $res );
268 $this->mDataLoaded = true;
269 }
270
271 function getID() { return $this->mId; }
272 function setID( $v ) {
273 $this->mId = $v;
274 $this->mDataLoaded = false;
275 }
276
277 function getName() {
278 $this->loadFromDatabase();
279 return $this->mName;
280 }
281
282 function setName( $str )
283 {
284 $this->loadFromDatabase();
285 $this->mName = $str;
286 }
287
288 function getNewtalk()
289 {
290 $this->loadFromDatabase();
291 return ( 0 != $this->mNewtalk );
292 }
293
294 function setNewtalk( $val )
295 {
296 $this->loadFromDatabase();
297 $this->mNewtalk = $val;
298 $this->invalidateCache();
299 }
300
301 function invalidateCache() {
302 $this->loadFromDatabase();
303 $this->mTouched = wfTimestampNow();
304 # Don't forget to save the options after this or
305 # it won't take effect!
306 }
307
308 function validateCache( $timestamp ) {
309 $this->loadFromDatabase();
310 return ($timestamp >= $this->mTouched);
311 }
312
313 function getPassword()
314 {
315 $this->loadFromDatabase();
316 return $this->mPassword;
317 }
318
319 function getNewpassword()
320 {
321 $this->loadFromDatabase();
322 return $this->mNewpassword;
323 }
324
325 function addSalt( $p )
326 {
327 global $wgPasswordSalt;
328 if($wgPasswordSalt)
329 return md5( "{$this->mId}-{$p}" );
330 else
331 return $p;
332 }
333
334 function encryptPassword( $p )
335 {
336 return $this->addSalt( md5( $p ) );
337 }
338
339 function setPassword( $str )
340 {
341 $this->loadFromDatabase();
342 $this->setCookiePassword( $str );
343 $this->mPassword = $this->encryptPassword( $str );
344 $this->mNewpassword = "";
345 }
346
347 function setCookiePassword( $str )
348 {
349 $this->loadFromDatabase();
350 $this->mCookiePassword = md5( $str );
351 }
352
353 function setNewpassword( $str )
354 {
355 $this->loadFromDatabase();
356 $this->mNewpassword = $this->encryptPassword( $str );
357 }
358
359 function getEmail()
360 {
361 $this->loadFromDatabase();
362 return $this->mEmail;
363 }
364
365 function setEmail( $str )
366 {
367 $this->loadFromDatabase();
368 $this->mEmail = $str;
369 }
370
371 function getRealName()
372 {
373 $this->loadFromDatabase();
374 return $this->mRealName;
375 }
376
377 function setRealName( $str )
378 {
379 $this->loadFromDatabase();
380 $this->mRealName = $str;
381 }
382
383 function getOption( $oname )
384 {
385 $this->loadFromDatabase();
386 if ( array_key_exists( $oname, $this->mOptions ) ) {
387 return $this->mOptions[$oname];
388 } else {
389 return "";
390 }
391 }
392
393 function setOption( $oname, $val )
394 {
395 $this->loadFromDatabase();
396 if ( $oname == 'skin' ) {
397 # Clear cached skin, so the new one displays immediately in Special:Preferences
398 unset( $this->mSkin );
399 }
400 $this->mOptions[$oname] = $val;
401 $this->invalidateCache();
402 }
403
404 function getRights()
405 {
406 $this->loadFromDatabase();
407 return $this->mRights;
408 }
409
410 function addRight( $rname )
411 {
412 $this->loadFromDatabase();
413 array_push( $this->mRights, $rname );
414 $this->invalidateCache();
415 }
416
417 function isSysop()
418 {
419 $this->loadFromDatabase();
420 if ( 0 == $this->mId ) { return false; }
421
422 return in_array( "sysop", $this->mRights );
423 }
424
425 function isDeveloper()
426 {
427 $this->loadFromDatabase();
428 if ( 0 == $this->mId ) { return false; }
429
430 return in_array( "developer", $this->mRights );
431 }
432
433 function isBureaucrat()
434 {
435 $this->loadFromDatabase();
436 if ( 0 == $this->mId ) { return false; }
437
438 return in_array( "bureaucrat", $this->mRights );
439 }
440
441 function isBot()
442 {
443 $this->loadFromDatabase();
444
445 # Why was this here? I need a UID=0 conversion script [TS]
446 # if ( 0 == $this->mId ) { return false; }
447
448 return in_array( "bot", $this->mRights );
449 }
450
451 function &getSkin()
452 {
453 if ( ! isset( $this->mSkin ) ) {
454 $skinNames = Skin::getSkinNames();
455 $s = $this->getOption( "skin" );
456 if ( "" == $s ) { $s = 'standard'; }
457
458 if ( !isset( $skinNames[$s] ) ) {
459 $fallback = array(
460 'standard' => "Standard",
461 'nostalgia' => "Nostalgia",
462 'cologneblue' => "Cologne Blue");
463 if(is_int($s) && isset( $fallback[$s]) ){
464 $sn = $fallback[$s];
465 } else {
466 $sn = "SkinStandard";
467 }
468 } else {
469 $sn = "Skin" . $skinNames[$s];
470 }
471 $this->mSkin = new $sn;
472 }
473 return $this->mSkin;
474 }
475
476 function isWatched( $title ) {
477 $wl = WatchedItem::fromUserTitle( $this, $title );
478 return $wl->isWatched();
479 }
480
481 function addWatch( $title ) {
482 $wl = WatchedItem::fromUserTitle( $this, $title );
483 $wl->addWatch();
484 $this->invalidateCache();
485 }
486
487 function removeWatch( $title ) {
488 $wl = WatchedItem::fromUserTitle( $this, $title );
489 $wl->removeWatch();
490 $this->invalidateCache();
491 }
492
493
494 /* private */ function encodeOptions()
495 {
496 $a = array();
497 foreach ( $this->mOptions as $oname => $oval ) {
498 array_push( $a, "{$oname}={$oval}" );
499 }
500 $s = implode( "\n", $a );
501 return wfStrencode( $s );
502 }
503
504 /* private */ function decodeOptions( $str )
505 {
506 $a = explode( "\n", $str );
507 foreach ( $a as $s ) {
508 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
509 $this->mOptions[$m[1]] = $m[2];
510 }
511 }
512 }
513
514 function setCookies()
515 {
516 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
517 if ( 0 == $this->mId ) return;
518 $this->loadFromDatabase();
519 $exp = time() + $wgCookieExpiration;
520
521 $_SESSION['wsUserID'] = $this->mId;
522 setcookie( "{$wgDBname}UserID", $this->mId, $exp, $wgCookiePath, $wgCookieDomain );
523
524 $_SESSION['wsUserName'] = $this->mName;
525 setcookie( "{$wgDBname}UserName", $this->mName, $exp, $wgCookiePath, $wgCookieDomain );
526
527 $_SESSION['wsUserPassword'] = $this->mPassword;
528 if ( 1 == $this->getOption( "rememberpassword" ) ) {
529 setcookie( "{$wgDBname}Password", $this->mCookiePassword, $exp, $wgCookiePath, $wgCookieDomain );
530 } else {
531 setcookie( "{$wgDBname}Password", "", time() - 3600 );
532 }
533 }
534
535 function logout()
536 {
537 global $wgCookiePath, $wgCookieDomain, $wgDBname;
538 $this->mId = 0;
539
540 $_SESSION['wsUserID'] = 0;
541
542 setcookie( "{$wgDBname}UserID", "", time() - 3600, $wgCookiePath, $wgCookieDomain );
543 setcookie( "{$wgDBname}Password", "", time() - 3600, $wgCookiePath, $wgCookieDomain );
544 }
545
546 function saveSettings()
547 {
548 global $wgMemc, $wgDBname;
549
550 if ( ! $this->mNewtalk ) {
551 if( $this->mId ) {
552 $sql="DELETE FROM user_newtalk WHERE user_id={$this->mId}";
553 wfQuery ($sql, DB_WRITE, "User::saveSettings");
554 } else {
555 $sql="DELETE FROM user_newtalk WHERE user_ip='{$this->mName}'";
556 wfQuery ($sql, DB_WRITE, "User::saveSettings");
557 $wgMemc->delete( "$wgDBname:newtalk:ip:{$this->mName}" );
558 }
559 }
560 if ( 0 == $this->mId ) { return; }
561
562 $sql = "UPDATE user SET " .
563 "user_name= '" . wfStrencode( $this->mName ) . "', " .
564 "user_password= '" . wfStrencode( $this->mPassword ) . "', " .
565 "user_newpassword= '" . wfStrencode( $this->mNewpassword ) . "', " .
566 "user_real_name= '" . wfStrencode( $this->mRealName ) . "', " .
567 "user_email= '" . wfStrencode( $this->mEmail ) . "', " .
568 "user_options= '" . $this->encodeOptions() . "', " .
569 "user_rights= '" . wfStrencode( implode( ",", $this->mRights ) ) . "', " .
570 "user_touched= '" . wfStrencode( $this->mTouched ) .
571 "' WHERE user_id={$this->mId}";
572 wfQuery( $sql, DB_WRITE, "User::saveSettings" );
573 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
574 }
575
576 # Checks if a user with the given name exists
577 #
578 function idForName()
579 {
580 $gotid = 0;
581 $s = trim( $this->mName );
582 if ( 0 == strcmp( "", $s ) ) return 0;
583
584 $sql = "SELECT user_id FROM user WHERE user_name='" .
585 wfStrencode( $s ) . "'";
586 $res = wfQuery( $sql, DB_READ, "User::idForName" );
587 if ( 0 == wfNumRows( $res ) ) { return 0; }
588
589 $s = wfFetchObject( $res );
590 if ( "" == $s ) return 0;
591
592 $gotid = $s->user_id;
593 wfFreeResult( $res );
594 return $gotid;
595 }
596
597 function addToDatabase()
598 {
599 $sql = "INSERT INTO user (user_name,user_password,user_newpassword," .
600 "user_email, user_real_name, user_rights, user_options) " .
601 " VALUES ('" . wfStrencode( $this->mName ) . "', '" .
602 wfStrencode( $this->mPassword ) . "', '" .
603 wfStrencode( $this->mNewpassword ) . "', '" .
604 wfStrencode( $this->mEmail ) . "', '" .
605 wfStrencode( $this->mRealName ) . "', '" .
606 wfStrencode( implode( ",", $this->mRights ) ) . "', '" .
607 $this->encodeOptions() . "')";
608 wfQuery( $sql, DB_WRITE, "User::addToDatabase" );
609 $this->mId = $this->idForName();
610 }
611
612 function spreadBlock()
613 {
614 global $wgIP;
615 # If the (non-anonymous) user is blocked, this function will block any IP address
616 # that they successfully log on from.
617 $fname = "User::spreadBlock";
618
619 wfDebug( "User:spreadBlock()\n" );
620 if ( $this->mId == 0 ) {
621 return;
622 }
623
624 $userblock = Block::newFromDB( "", $this->mId );
625 if ( !$userblock->isValid() ) {
626 return;
627 }
628
629 # Check if this IP address is already blocked
630 $ipblock = Block::newFromDB( $wgIP );
631 if ( $ipblock->isValid() ) {
632 # Just update the timestamp
633 $ipblock->updateTimestamp();
634 return;
635 }
636
637 # Make a new block object with the desired properties
638 wfDebug( "Autoblocking {$this->mUserName}@{$wgIP}\n" );
639 $ipblock->mAddress = $wgIP;
640 $ipblock->mUser = 0;
641 $ipblock->mBy = $userblock->mBy;
642 $ipblock->mReason = wfMsg( "autoblocker", $this->getName(), $userblock->mReason );
643 $ipblock->mTimestamp = wfTimestampNow();
644 $ipblock->mAuto = 1;
645 # If the user is already blocked with an expiry date, we don't
646 # want to pile on top of that!
647 if($userblock->mExpiry) {
648 $ipblock->mExpiry = min ( $userblock->mExpiry, Block::getAutoblockExpiry( $ipblock->mTimestamp ));
649 } else {
650 $ipblock->mExpiry = Block::getAutoblockExpiry( $ipblock->mTimestamp );
651 }
652
653 # Insert it
654 $ipblock->insert();
655
656 }
657
658 function getPageRenderingHash(){
659 static $hash = false;
660 if( $hash ){
661 return $hash;
662 }
663
664 // stubthreshold is only included below for completeness,
665 // it will always be 0 when this function is called by parsercache.
666
667 $confstr = $this->getOption( "quickbar" );
668 $confstr .= "!" . $this->getOption( "underline" );
669 $confstr .= "!" . $this->getOption( "hover" );
670 $confstr .= "!" . $this->getOption( "skin" );
671 $confstr .= "!" . $this->getOption( "math" );
672 $confstr .= "!" . $this->getOption( "highlightbroken" );
673 $confstr .= "!" . $this->getOption( "stubthreshold" );
674 $confstr .= "!" . $this->getOption( "editsection" );
675 $confstr .= "!" . $this->getOption( "editsectiononrightclick" );
676 $confstr .= "!" . $this->getOption( "showtoc" );
677 $confstr .= "!" . $this->getOption( "date" );
678
679 if(strlen($confstr) > 32)
680 $hash = md5($confstr);
681 else
682 $hash = $confstr;
683 return $hash;
684 }
685
686 function isAllowedToCreateAccount()
687 {
688 global $wgWhitelistAccount;
689 $allowed = false;
690
691 if (!$wgWhitelistAccount) { return 1; }; // default behaviour
692 foreach ($wgWhitelistAccount as $right => $ok) {
693 $userHasRight = (!strcmp($right, "user") || in_array($right, $this->getRights()));
694 $allowed |= ($ok && $userHasRight);
695 }
696 return $allowed;
697 }
698
699 # Set mDataLoaded, return previous value
700 # Use this to prevent DB access in command-line scripts or similar situations
701 function setLoaded( $loaded )
702 {
703 wfSetVar( $this->mDataLoaded, $loaded );
704 }
705
706 function getUserPage() {
707 return Title::makeTitle( NS_USER, $this->mName );
708 }
709 }
710
711 ?>