58fb9db37ef77a32110e6c27dd5aa7e83e8459fd
[lhc/web/wiklou.git] / includes / User.php
1 <?php
2 /**
3 * See user.doc
4 *
5 * @package MediaWiki
6 */
7
8 /**
9 *
10 */
11 require_once( 'WatchedItem.php' );
12 require_once( 'Group.php' );
13
14 # Number of characters in user_token field
15 define( 'USER_TOKEN_LENGTH', 32 );
16
17 /**
18 *
19 * @package MediaWiki
20 */
21 class User {
22 /**#@+
23 * @access private
24 */
25 var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
26 var $mEmailAuthenticationtimestamp;
27 var $mRights, $mOptions;
28 var $mDataLoaded, $mNewpassword;
29 var $mSkin;
30 var $mBlockedby, $mBlockreason;
31 var $mTouched;
32 var $mToken;
33 var $mRealName;
34 var $mHash;
35 /** Array of group id the user belong to */
36 var $mGroups;
37 /**#@-*/
38
39 /** Construct using User:loadDefaults() */
40 function User() {
41 $this->loadDefaults();
42 }
43
44 /**
45 * Static factory method
46 * @static
47 * @param string $name Username, validated by Title:newFromText()
48 */
49 function newFromName( $name ) {
50 $u = new User();
51
52 # Clean up name according to title rules
53
54 $t = Title::newFromText( $name );
55 if( is_null( $t ) ) {
56 return NULL;
57 } else {
58 $u->setName( $t->getText() );
59 $u->setId( $u->idFromName( $t->getText() ) );
60 return $u;
61 }
62 }
63
64 /**
65 * Get username given an id.
66 * @param integer $id Database user id
67 * @return string Nickname of a user
68 * @static
69 */
70 function whoIs( $id ) {
71 $dbr =& wfGetDB( DB_SLAVE );
72 return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ) );
73 }
74
75 /**
76 * Get real username given an id.
77 * @param integer $id Database user id
78 * @return string Realname of a user
79 * @static
80 */
81 function whoIsReal( $id ) {
82 $dbr =& wfGetDB( DB_SLAVE );
83 return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ) );
84 }
85
86 /**
87 * Get database id given a user name
88 * @param string $name Nickname of a user
89 * @return integer|null Database user id (null: if non existent
90 * @static
91 */
92 function idFromName( $name ) {
93 $fname = "User::idFromName";
94
95 $nt = Title::newFromText( $name );
96 if( is_null( $nt ) ) {
97 # Illegal name
98 return null;
99 }
100 $dbr =& wfGetDB( DB_SLAVE );
101 $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
102
103 if ( $s === false ) {
104 return 0;
105 } else {
106 return $s->user_id;
107 }
108 }
109
110 /**
111 * does the string match an anonymous user IP address?
112 * @param string $name Nickname of a user
113 * @static
114 */
115 function isIP( $name ) {
116 return preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$name);
117 }
118
119 /**
120 * does the string match roughly an email address ?
121 * @param string $addr email address
122 * @static
123 */
124 function isValidEmailAddr ( $addr ) {
125 return preg_match( '/^([a-z0-9_.-]+([a-z0-9_.-]+)*\@[a-z0-9_-]+([a-z0-9_.-]+)*([a-z.]{2,})+)$/', strtolower($addr));
126 }
127
128 /**
129 * probably return a random password
130 * @return string probably a random password
131 * @static
132 * @todo Check what is doing really [AV]
133 */
134 function randomPassword() {
135 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
136 $l = strlen( $pwchars ) - 1;
137
138 $np = $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
139 $pwchars{mt_rand( 0, $l )} . chr( mt_rand(48, 57) ) .
140 $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
141 $pwchars{mt_rand( 0, $l )};
142 return $np;
143 }
144
145 /**
146 * Set properties to default
147 * Used at construction. It will load per language default settings only
148 * if we have an available language object.
149 */
150 function loadDefaults() {
151 static $n=0;
152 $n++;
153 $fname = 'User::loadDefaults' . $n;
154 wfProfileIn( $fname );
155
156 global $wgContLang, $wgIP;
157 global $wgNamespacesToBeSearchedDefault;
158
159 $this->mId = 0;
160 $this->mNewtalk = -1;
161 $this->mName = $wgIP;
162 $this->mRealName = $this->mEmail = '';
163 $this->mEmailAuthenticationtimestamp = 0;
164 $this->mPassword = $this->mNewpassword = '';
165 $this->mRights = array();
166 $this->mGroups = array();
167 // Getting user defaults only if we have an available language
168 if( isset( $wgContLang ) ) {
169 $this->loadDefaultFromLanguage();
170 }
171
172 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
173 $this->mOptions['searchNs'.$nsnum] = $val;
174 }
175 unset( $this->mSkin );
176 $this->mDataLoaded = false;
177 $this->mBlockedby = -1; # Unset
178 $this->mTouched = '0'; # Allow any pages to be cached
179 $this->setToken(); # Random
180 $this->mHash = false;
181 wfProfileOut( $fname );
182 }
183
184 /**
185 * Used to load user options from a language.
186 * This is not in loadDefault() cause we sometime create user before having
187 * a language object.
188 */
189 function loadDefaultFromLanguage(){
190 $this->mOptions = User::getDefaultOptions();
191 }
192
193 /**
194 * Combine the language default options with any site-specific options
195 * and add the default language variants.
196 *
197 * @return array
198 * @static
199 * @access private
200 */
201 function getDefaultOptions() {
202 /**
203 * Site defaults will override the global/language defaults
204 */
205 global $wgContLang, $wgDefaultUserOptions;
206 $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptions();
207
208 /**
209 * default language setting
210 */
211 $variant = $wgContLang->getPreferredVariant();
212 $defOpt['variant'] = $variant;
213 $defOpt['language'] = $variant;
214
215 return $defOpt;
216 }
217
218 /**
219 * Get a given default option value.
220 *
221 * @param string $opt
222 * @return string
223 * @static
224 * @access public
225 */
226 function getDefaultOption( $opt ) {
227 $defOpts = User::getDefaultOptions();
228 if( isset( $defOpts[$opt] ) ) {
229 return $defOpts[$opt];
230 } else {
231 return '';
232 }
233 }
234
235 /**
236 * Get blocking information
237 * @access private
238 */
239 function getBlockedStatus() {
240 global $wgIP, $wgBlockCache, $wgProxyList;
241
242 if ( -1 != $this->mBlockedby ) { return; }
243
244 $this->mBlockedby = 0;
245
246 # User blocking
247 if ( $this->mId ) {
248 $block = new Block();
249 if ( $block->load( $wgIP , $this->mId ) ) {
250 $this->mBlockedby = $block->mBy;
251 $this->mBlockreason = $block->mReason;
252 }
253 }
254
255 # IP/range blocking
256 if ( !$this->mBlockedby ) {
257 $block = $wgBlockCache->get( $wgIP );
258 if ( $block !== false ) {
259 $this->mBlockedby = $block->mBy;
260 $this->mBlockreason = $block->mReason;
261 }
262 }
263
264 # Proxy blocking
265 if ( !$this->mBlockedby ) {
266 if ( array_key_exists( $wgIP, $wgProxyList ) ) {
267 $this->mBlockreason = wfMsg( 'proxyblockreason' );
268 $this->mBlockedby = "Proxy blocker";
269 }
270 }
271 }
272
273 /**
274 * Check if user is blocked
275 * @return bool True if blocked, false otherwise
276 */
277 function isBlocked() {
278 $this->getBlockedStatus();
279 if ( 0 === $this->mBlockedby ) { return false; }
280 return true;
281 }
282
283 /**
284 * Get name of blocker
285 * @return string name of blocker
286 */
287 function blockedBy() {
288 $this->getBlockedStatus();
289 return $this->mBlockedby;
290 }
291
292 /**
293 * Get blocking reason
294 * @return string Blocking reason
295 */
296 function blockedFor() {
297 $this->getBlockedStatus();
298 return $this->mBlockreason;
299 }
300
301 /**
302 * Initialise php session
303 */
304 function SetupSession() {
305 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
306 if( $wgSessionsInMemcached ) {
307 require_once( 'MemcachedSessions.php' );
308 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
309 # If it's left on 'user' or another setting from another
310 # application, it will end up failing. Try to recover.
311 ini_set ( 'session.save_handler', 'files' );
312 }
313 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
314 session_cache_limiter( 'private, must-revalidate' );
315 @session_start();
316 }
317
318 /**
319 * Read datas from session
320 * @static
321 */
322 function loadFromSession() {
323 global $wgMemc, $wgDBname;
324
325 if ( isset( $_SESSION['wsUserID'] ) ) {
326 if ( 0 != $_SESSION['wsUserID'] ) {
327 $sId = $_SESSION['wsUserID'];
328 } else {
329 return new User();
330 }
331 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
332 $sId = IntVal( $_COOKIE["{$wgDBname}UserID"] );
333 $_SESSION['wsUserID'] = $sId;
334 } else {
335 return new User();
336 }
337 if ( isset( $_SESSION['wsUserName'] ) ) {
338 $sName = $_SESSION['wsUserName'];
339 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
340 $sName = $_COOKIE["{$wgDBname}UserName"];
341 $_SESSION['wsUserName'] = $sName;
342 } else {
343 return new User();
344 }
345
346 $passwordCorrect = FALSE;
347 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
348 if($makenew = !$user) {
349 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
350 $user = new User();
351 $user->mId = $sId;
352 $user->loadFromDatabase();
353 } else {
354 wfDebug( "User::loadFromSession() got from cache!\n" );
355 }
356
357 if ( isset( $_SESSION['wsToken'] ) ) {
358 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken;
359 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
360 $passwordCorrect = $user->mToken == $_COOKIE["{$wgDBname}Token"];
361 } else {
362 return new User(); # Can't log in from session
363 }
364
365 if ( ( $sName == $user->mName ) && $passwordCorrect ) {
366 if($makenew) {
367 if($wgMemc->set( $key, $user ))
368 wfDebug( "User::loadFromSession() successfully saved user\n" );
369 else
370 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
371 }
372 $user->spreadBlock();
373 return $user;
374 }
375 return new User(); # Can't log in from session
376 }
377
378 /**
379 * Load a user from the database
380 */
381 function loadFromDatabase() {
382 global $wgCommandLineMode, $wgAnonGroupId, $wgLoggedInGroupId;
383 $fname = "User::loadFromDatabase";
384 if ( $this->mDataLoaded || $wgCommandLineMode ) {
385 return;
386 }
387
388 # Paranoia
389 $this->mId = IntVal( $this->mId );
390
391 /** Anonymous user */
392 if(!$this->mId) {
393 /** Get rights */
394 $anong = Group::newFromId($wgAnonGroupId);
395 if (!$anong)
396 wfDebugDieBacktrace("Please update your database schema "
397 ."and populate initial group data from "
398 ."maintenance/archives patches");
399 $anong->loadFromDatabase();
400 $this->mRights = explode(',', $anong->getRights());
401 $this->mDataLoaded = true;
402 return;
403 } # the following stuff is for non-anonymous users only
404
405 $dbr =& wfGetDB( DB_SLAVE );
406 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
407 'user_emailauthenticationtimestamp',
408 'user_real_name','user_options','user_touched', 'user_token' ),
409 array( 'user_id' => $this->mId ), $fname );
410
411 if ( $s !== false ) {
412 $this->mName = $s->user_name;
413 $this->mEmail = $s->user_email;
414 $this->mEmailAuthenticationtimestamp = $s->user_emailauthenticationtimestamp;
415 $this->mRealName = $s->user_real_name;
416 $this->mPassword = $s->user_password;
417 $this->mNewpassword = $s->user_newpassword;
418 $this->decodeOptions( $s->user_options );
419 $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
420 $this->mToken = $s->user_token;
421
422 // Get groups id
423 $res = $dbr->select( 'user_groups', array( 'ug_group' ), array( 'ug_user' => $this->mId ) );
424
425 while($group = $dbr->fetchRow($res)) {
426 $this->mGroups[] = $group[0];
427 }
428
429 // add the default group for logged in user
430 $this->mGroups[] = $wgLoggedInGroupId;
431
432 $this->mRights = array();
433 // now we merge groups rights to get this user rights
434 foreach($this->mGroups as $aGroupId) {
435 $g = Group::newFromId($aGroupId);
436 $g->loadFromDatabase();
437 $this->mRights = array_merge($this->mRights, explode(',', $g->getRights()));
438 }
439
440 // array merge duplicate rights which are part of several groups
441 $this->mRights = array_unique($this->mRights);
442
443 $dbr->freeResult($res);
444 }
445
446 $this->mDataLoaded = true;
447 }
448
449 function getID() { return $this->mId; }
450 function setID( $v ) {
451 $this->mId = $v;
452 $this->mDataLoaded = false;
453 }
454
455 function getName() {
456 $this->loadFromDatabase();
457 return $this->mName;
458 }
459
460 function setName( $str ) {
461 $this->loadFromDatabase();
462 $this->mName = $str;
463 }
464
465 function getNewtalk() {
466 $fname = 'User::getNewtalk';
467 $this->loadFromDatabase();
468
469 # Load the newtalk status if it is unloaded (mNewtalk=-1)
470 if ( $this->mNewtalk == -1 ) {
471 $this->mNewtalk=0; # reset talk page status
472 $dbr =& wfGetDB( DB_SLAVE );
473 extract( $dbr->tableNames( 'watchlist' ) );
474 if($this->mId) {
475 $sql = "SELECT wl_user FROM $watchlist
476 WHERE wl_title='" . $dbr->strencode( str_replace( ' ', '_', $this->mName) )."' AND wl_namespace = " . NS_USER_TALK .
477 " AND wl_user=" . $this->mId . " AND wl_notificationtimestamp != 0";
478 $res = $dbr->query( $sql,'User::get:Newtalk');
479
480 if ( $dbr->numRows($res)>0 ) {
481 $this->mNewtalk= 1;
482 }
483 $dbr->freeResult( $res );
484 } else {
485 global $wgDBname, $wgMemc;
486 $key = "$wgDBname:newtalk:ip:{$this->mName}";
487 $newtalk = $wgMemc->get( $key );
488 if( ! is_integer( $newtalk ) ){
489 extract( $dbr->tableNames( 'watchlist' ) );
490 $sql = "SELECT wl_user FROM $watchlist
491 WHERE wl_title='" . $dbr->strencode( str_replace( ' ', '_', $this->mName) )."' AND wl_namespace = " . NS_USER_TALK .
492 " AND wl_user =" . 0 . " AND wl_notificationtimestamp != 0";
493 $res = $dbr->query( $sql,'User::getNewtalk');
494
495 $this->mNewtalk = $dbr->numRows( $res ) > 0 ? 1 : 0;
496 $dbr->freeResult( $res );
497
498 $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 );
499 } else {
500 $this->mNewtalk = $newtalk ? 1 : 0;
501 }
502 }
503 }
504
505 return ( 0 != $this->mNewtalk );
506 }
507
508 function setNewtalk( $val ) {
509 $this->loadFromDatabase();
510 $this->mNewtalk = $val;
511 $this->invalidateCache();
512 }
513
514 function invalidateCache() {
515 $this->loadFromDatabase();
516 $this->mTouched = wfTimestampNow();
517 # Don't forget to save the options after this or
518 # it won't take effect!
519 }
520
521 function validateCache( $timestamp ) {
522 $this->loadFromDatabase();
523 return ($timestamp >= $this->mTouched);
524 }
525
526 /**
527 * Salt a password.
528 * Will only be salted if $wgPasswordSalt is true
529 * @param string Password.
530 * @return string Salted password or clear password.
531 */
532 function addSalt( $p ) {
533 global $wgPasswordSalt;
534 if($wgPasswordSalt)
535 return md5( "{$this->mId}-{$p}" );
536 else
537 return $p;
538 }
539
540 /**
541 * Encrypt a password.
542 * It can eventuall salt a password @see User::addSalt()
543 * @param string $p clear Password.
544 * @param string Encrypted password.
545 */
546 function encryptPassword( $p ) {
547 return $this->addSalt( md5( $p ) );
548 }
549
550 # Set the password and reset the random token
551 function setPassword( $str ) {
552 $this->loadFromDatabase();
553 $this->setToken();
554 $this->mPassword = $this->encryptPassword( $str );
555 $this->mNewpassword = '';
556 }
557
558 # Set the random token (used for persistent authentication)
559 function setToken( $token = false ) {
560 if ( !$token ) {
561 $this->mToken = '';
562 # Take random data from PRNG
563 # This is reasonably secure if the PRNG has been seeded correctly
564 for ($i = 0; $i<USER_TOKEN_LENGTH / 4; $i++) {
565 $this->mToken .= sprintf( "%04X", mt_rand( 0, 65535 ) );
566 }
567 } else {
568 $this->mToken = $token;
569 }
570 }
571
572
573 function setCookiePassword( $str ) {
574 $this->loadFromDatabase();
575 $this->mCookiePassword = md5( $str );
576 }
577
578 function setNewpassword( $str ) {
579 $this->loadFromDatabase();
580 $this->mNewpassword = $this->encryptPassword( $str );
581 }
582
583 function getEmail() {
584 $this->loadFromDatabase();
585 return $this->mEmail;
586 }
587
588 function getEmailAuthenticationtimestamp() {
589 $this->loadFromDatabase();
590 return $this->mEmailAuthenticationtimestamp;
591 }
592
593 function setEmail( $str ) {
594 $this->loadFromDatabase();
595 $this->mEmail = $str;
596 }
597
598 function getRealName() {
599 $this->loadFromDatabase();
600 return $this->mRealName;
601 }
602
603 function setRealName( $str ) {
604 $this->loadFromDatabase();
605 $this->mRealName = $str;
606 }
607
608 function getOption( $oname ) {
609 $this->loadFromDatabase();
610 if ( array_key_exists( $oname, $this->mOptions ) ) {
611 return $this->mOptions[$oname];
612 } else {
613 return '';
614 }
615 }
616
617 function setOption( $oname, $val ) {
618 $this->loadFromDatabase();
619 if ( $oname == 'skin' ) {
620 # Clear cached skin, so the new one displays immediately in Special:Preferences
621 unset( $this->mSkin );
622 }
623 $this->mOptions[$oname] = $val;
624 $this->invalidateCache();
625 }
626
627 function getRights() {
628 $this->loadFromDatabase();
629 return $this->mRights;
630 }
631
632 function addRight( $rname ) {
633 $this->loadFromDatabase();
634 array_push( $this->mRights, $rname );
635 $this->invalidateCache();
636 }
637
638 function getGroups() {
639 $this->loadFromDatabase();
640 return $this->mGroups;
641 }
642
643 function setGroups($groups) {
644 $this->loadFromDatabase();
645 $this->mGroups = $groups;
646 $this->invalidateCache();
647 }
648
649 /**
650 * Check if a user is sysop
651 * Die with backtrace. Use User:isAllowed() instead.
652 * @deprecated
653 */
654 function isSysop() {
655 /**
656 $this->loadFromDatabase();
657 if ( 0 == $this->mId ) { return false; }
658
659 return in_array( 'sysop', $this->mRights );
660 */
661 wfDebugDieBacktrace("User::isSysop() is deprecated. Use User::isAllowed() instead");
662 }
663
664 /** @deprecated */
665 function isDeveloper() {
666 /**
667 $this->loadFromDatabase();
668 if ( 0 == $this->mId ) { return false; }
669
670 return in_array( 'developer', $this->mRights );
671 */
672 wfDebugDieBacktrace("User::isDeveloper() is deprecated. Use User::isAllowed() instead");
673 }
674
675 /** @deprecated */
676 function isBureaucrat() {
677 /**
678 $this->loadFromDatabase();
679 if ( 0 == $this->mId ) { return false; }
680
681 return in_array( 'bureaucrat', $this->mRights );
682 */
683 wfDebugDieBacktrace("User::isBureaucrat() is deprecated. Use User::isAllowed() instead");
684 }
685
686 /**
687 * Whether the user is a bot
688 * @todo need to be migrated to the new user level management sytem
689 */
690 function isBot() {
691 $this->loadFromDatabase();
692
693 # Why was this here? I need a UID=0 conversion script [TS]
694 # if ( 0 == $this->mId ) { return false; }
695
696 return in_array( 'bot', $this->mRights );
697 }
698
699 /**
700 * Check if user is allowed to access a feature / make an action
701 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
702 * @return boolean True: action is allowed, False: action should not be allowed
703 */
704 function isAllowed($action='') {
705 $this->loadFromDatabase();
706 return in_array( $action , $this->mRights );
707 }
708
709 /**
710 * Load a skin if it doesn't exist or return it
711 * @todo FIXME : need to check the old failback system [AV]
712 */
713 function &getSkin() {
714 global $IP;
715 if ( ! isset( $this->mSkin ) ) {
716 $fname = 'User::getSkin';
717 wfProfileIn( $fname );
718
719 # get all skin names available
720 $skinNames = Skin::getSkinNames();
721
722 # get the user skin
723 $userSkin = $this->getOption( 'skin' );
724 if ( $userSkin == '' ) { $userSkin = 'standard'; }
725
726 if ( !isset( $skinNames[$userSkin] ) ) {
727 # in case the user skin could not be found find a replacement
728 $fallback = array(
729 0 => 'Standard',
730 1 => 'Nostalgia',
731 2 => 'CologneBlue');
732 # if phptal is enabled we should have monobook skin that
733 # superseed the good old SkinStandard.
734 if ( isset( $skinNames['monobook'] ) ) {
735 $fallback[0] = 'MonoBook';
736 }
737
738 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
739 $sn = $fallback[$userSkin];
740 } else {
741 $sn = 'Standard';
742 }
743 } else {
744 # The user skin is available
745 $sn = $skinNames[$userSkin];
746 }
747
748 # Grab the skin class and initialise it. Each skin checks for PHPTal
749 # and will not load if it's not enabled.
750 require_once( $IP.'/skins/'.$sn.'.php' );
751
752 # Check if we got if not failback to default skin
753 $className = 'Skin'.$sn;
754 if( !class_exists( $className ) ) {
755 # DO NOT die if the class isn't found. This breaks maintenance
756 # scripts and can cause a user account to be unrecoverable
757 # except by SQL manipulation if a previously valid skin name
758 # is no longer valid.
759 $className = 'SkinStandard';
760 require_once( $IP.'/skins/Standard.php' );
761 }
762 $this->mSkin =& new $className;
763 wfProfileOut( $fname );
764 }
765 return $this->mSkin;
766 }
767
768 /**#@+
769 * @param string $title Article title to look at
770 */
771
772 /**
773 * Check watched status of an article
774 * @return bool True if article is watched
775 */
776 function isWatched( $title ) {
777 $wl = WatchedItem::fromUserTitle( $this, $title );
778 return $wl->isWatched();
779 }
780
781 /**
782 * Watch an article
783 */
784 function addWatch( $title ) {
785 $wl = WatchedItem::fromUserTitle( $this, $title );
786 $wl->addWatch();
787 $this->invalidateCache();
788 }
789
790 /**
791 * Stop watching an article
792 */
793 function removeWatch( $title ) {
794 $wl = WatchedItem::fromUserTitle( $this, $title );
795 $wl->removeWatch();
796 $this->invalidateCache();
797 }
798 /**#@-*/
799
800 /**
801 * @access private
802 * @return string Encoding options
803 */
804 function encodeOptions() {
805 $a = array();
806 foreach ( $this->mOptions as $oname => $oval ) {
807 array_push( $a, $oname.'='.$oval );
808 }
809 $s = implode( "\n", $a );
810 return $s;
811 }
812
813 /**
814 * @access private
815 */
816 function decodeOptions( $str ) {
817 $a = explode( "\n", $str );
818 foreach ( $a as $s ) {
819 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
820 $this->mOptions[$m[1]] = $m[2];
821 }
822 }
823 }
824
825 function setCookies() {
826 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
827 if ( 0 == $this->mId ) return;
828 $this->loadFromDatabase();
829 $exp = time() + $wgCookieExpiration;
830
831 $_SESSION['wsUserID'] = $this->mId;
832 setcookie( $wgDBname.'UserID', $this->mId, $exp, $wgCookiePath, $wgCookieDomain );
833
834 $_SESSION['wsUserName'] = $this->mName;
835 setcookie( $wgDBname.'UserName', $this->mName, $exp, $wgCookiePath, $wgCookieDomain );
836
837 $_SESSION['wsToken'] = $this->mToken;
838 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
839 setcookie( $wgDBname.'Token', $this->mToken, $exp, $wgCookiePath, $wgCookieDomain );
840 } else {
841 setcookie( $wgDBname.'Token', '', time() - 3600 );
842 }
843 }
844
845 /**
846 * Logout user
847 * It will clean the session cookie
848 */
849 function logout() {
850 global $wgCookiePath, $wgCookieDomain, $wgDBname, $wgIP;
851 $this->loadDefaults();
852 $this->setLoaded( true );
853
854 $_SESSION['wsUserID'] = 0;
855
856 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
857 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
858 }
859
860 /**
861 * Save object settings into database
862 */
863 function saveSettings() {
864 global $wgMemc, $wgDBname;
865 $fname = 'User::saveSettings';
866
867 $dbw =& wfGetDB( DB_MASTER );
868 if ( ! $this->getNewtalk() ) {
869 # Delete the watchlist entry for user_talk page X watched by user X
870 if( $this->mId ) {
871 $dbw->delete( 'watchlist', array( 'wl_user' => $this->mId, 'wl_title' => str_replace('_', ' ', $this->mName) ,'wl_namespace' => NS_USER_TALK ), $fname );
872 } else {
873 $dbw->delete( 'watchlist', array( 'wl_user' => 0, 'wl_title' => $this->mName, 'wl_namespace' => NS_USER_TALK ), $fname );
874 $wgMemc->delete( "$wgDBname:newtalk:ip:{$this->mName}" );
875 }
876 }
877
878 if ( 0 == $this->mId ) { return; }
879
880 $dbw->update( 'user',
881 array( /* SET */
882 'user_name' => $this->mName,
883 'user_password' => $this->mPassword,
884 'user_newpassword' => $this->mNewpassword,
885 'user_real_name' => $this->mRealName,
886 'user_email' => $this->mEmail,
887 'user_emailauthenticationtimestamp' => $this->mEmailAuthenticationtimestamp,
888 'user_options' => $this->encodeOptions(),
889 'user_touched' => $dbw->timestamp($this->mTouched),
890 'user_token' => $this->mToken
891 ), array( /* WHERE */
892 'user_id' => $this->mId
893 ), $fname
894 );
895 $dbw->set( 'user_rights', 'ur_rights', implode( ',', $this->mRights ),
896 'ur_user='. $this->mId, $fname );
897 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
898
899 // delete old groups
900 $dbw->delete( 'user_groups', array( 'ug_user' => $this->mId), $fname);
901
902 // save new ones
903 foreach ($this->mGroups as $group) {
904 $dbw->replace( 'user_groups',
905 array(array('ug_user','ug_group')),
906 array(
907 'ug_user' => $this->mId,
908 'ug_group' => $group
909 ), $fname
910 );
911 }
912 }
913
914
915 /**
916 * Checks if a user with the given name exists, returns the ID
917 */
918 function idForName() {
919 $fname = 'User::idForName';
920
921 $gotid = 0;
922 $s = trim( $this->mName );
923 if ( 0 == strcmp( '', $s ) ) return 0;
924
925 $dbr =& wfGetDB( DB_SLAVE );
926 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
927 if ( $id === false ) {
928 $id = 0;
929 }
930 return $id;
931 }
932
933 /**
934 * Add user object to the database
935 */
936 function addToDatabase() {
937 $fname = 'User::addToDatabase';
938 $dbw =& wfGetDB( DB_MASTER );
939 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
940 $dbw->insert( 'user',
941 array(
942 'user_id' => $seqVal,
943 'user_name' => $this->mName,
944 'user_password' => $this->mPassword,
945 'user_newpassword' => $this->mNewpassword,
946 'user_email' => $this->mEmail,
947 'user_emailauthenticationtimestamp' => $this->mEmailAuthenticationtimestamp,
948 'user_real_name' => $this->mRealName,
949 'user_options' => $this->encodeOptions(),
950 'user_token' => $this->mToken
951 ), $fname
952 );
953 $this->mId = $dbw->insertId();
954 $dbw->insert( 'user_rights',
955 array(
956 'ur_user' => $this->mId,
957 'ur_rights' => implode( ',', $this->mRights )
958 ), $fname
959 );
960
961 foreach ($this->mGroups as $group) {
962 $dbw->insert( 'user_groups',
963 array(
964 'ug_user' => $this->mId,
965 'ug_group' => $group
966 ), $fname
967 );
968 }
969 }
970
971 function spreadBlock() {
972 global $wgIP;
973 # If the (non-anonymous) user is blocked, this function will block any IP address
974 # that they successfully log on from.
975 $fname = 'User::spreadBlock';
976
977 wfDebug( "User:spreadBlock()\n" );
978 if ( $this->mId == 0 ) {
979 return;
980 }
981
982 $userblock = Block::newFromDB( '', $this->mId );
983 if ( !$userblock->isValid() ) {
984 return;
985 }
986
987 # Check if this IP address is already blocked
988 $ipblock = Block::newFromDB( $wgIP );
989 if ( $ipblock->isValid() ) {
990 # Just update the timestamp
991 $ipblock->updateTimestamp();
992 return;
993 }
994
995 # Make a new block object with the desired properties
996 wfDebug( "Autoblocking {$this->mName}@{$wgIP}\n" );
997 $ipblock->mAddress = $wgIP;
998 $ipblock->mUser = 0;
999 $ipblock->mBy = $userblock->mBy;
1000 $ipblock->mReason = wfMsg( 'autoblocker', $this->getName(), $userblock->mReason );
1001 $ipblock->mTimestamp = wfTimestampNow();
1002 $ipblock->mAuto = 1;
1003 # If the user is already blocked with an expiry date, we don't
1004 # want to pile on top of that!
1005 if($userblock->mExpiry) {
1006 $ipblock->mExpiry = min ( $userblock->mExpiry, Block::getAutoblockExpiry( $ipblock->mTimestamp ));
1007 } else {
1008 $ipblock->mExpiry = Block::getAutoblockExpiry( $ipblock->mTimestamp );
1009 }
1010
1011 # Insert it
1012 $ipblock->insert();
1013
1014 }
1015
1016 function getPageRenderingHash() {
1017 global $wgContLang;
1018 if( $this->mHash ){
1019 return $this->mHash;
1020 }
1021
1022 // stubthreshold is only included below for completeness,
1023 // it will always be 0 when this function is called by parsercache.
1024
1025 $confstr = $this->getOption( 'math' );
1026 $confstr .= '!' . $this->getOption( 'highlightbroken' );
1027 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1028 $confstr .= '!' . $this->getOption( 'editsection' );
1029 $confstr .= '!' . $this->getOption( 'editsectiononrightclick' );
1030 $confstr .= '!' . $this->getOption( 'showtoc' );
1031 $confstr .= '!' . $this->getOption( 'date' );
1032 $confstr .= '!' . $this->getOption( 'numberheadings' );
1033 $confstr .= '!' . $this->getOption( 'language' );
1034 // add in language specific options, if any
1035 $extra = $wgContLang->getExtraHashOptions();
1036 foreach( $extra as $e ) {
1037 $confstr .= '!' . $this->getOption( $e );
1038 }
1039
1040 $this->mHash = $confstr;
1041 return $confstr ;
1042 }
1043
1044 function isAllowedToCreateAccount() {
1045 global $wgWhitelistAccount;
1046 $allowed = false;
1047
1048 if (!$wgWhitelistAccount) { return 1; }; // default behaviour
1049 foreach ($wgWhitelistAccount as $right => $ok) {
1050 $userHasRight = (!strcmp($right, 'user') || in_array($right, $this->getRights()));
1051 $allowed |= ($ok && $userHasRight);
1052 }
1053 return $allowed;
1054 }
1055
1056 /**
1057 * Set mDataLoaded, return previous value
1058 * Use this to prevent DB access in command-line scripts or similar situations
1059 */
1060 function setLoaded( $loaded ) {
1061 return wfSetVar( $this->mDataLoaded, $loaded );
1062 }
1063
1064 function getUserPage() {
1065 return Title::makeTitle( NS_USER, $this->mName );
1066 }
1067
1068 /**
1069 * @static
1070 */
1071 function getMaxID() {
1072 $dbr =& wfGetDB( DB_SLAVE );
1073 return $dbr->selectField( 'user', 'max(user_id)', false );
1074 }
1075
1076 /**
1077 * Determine whether the user is a newbie. Newbies are either
1078 * anonymous IPs, or the 1% most recently created accounts.
1079 * Bots and sysops are excluded.
1080 * @return bool True if it is a newbie.
1081 */
1082 function isNewbie() {
1083 return $this->mId > User::getMaxID() * 0.99 && !$this->isSysop() && !$this->isBot() || $this->getID() == 0;
1084 }
1085
1086 /**
1087 * Check to see if the given clear-text password is one of the accepted passwords
1088 * @param string $password User password.
1089 * @return bool True if the given password is correct otherwise False.
1090 */
1091 function checkPassword( $password ) {
1092 global $wgAuth;
1093 $this->loadFromDatabase();
1094
1095 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1096 return true;
1097 } elseif( $wgAuth->strict() ) {
1098 /* Auth plugin doesn't allow local authentication */
1099 return false;
1100 }
1101 $ep = $this->encryptPassword( $password );
1102 if ( 0 == strcmp( $ep, $this->mPassword ) ) {
1103 return true;
1104 } elseif ( ($this->mNewpassword != '') && (0 == strcmp( $ep, $this->mNewpassword )) ) {
1105 $this->mEmailAuthenticationtimestamp = wfTimestampNow();
1106 $this->mNewpassword = ''; # use the temporary one-time password only once: clear it now !
1107 $this->saveSettings();
1108 return true;
1109 } elseif ( function_exists( 'iconv' ) ) {
1110 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1111 # Check for this with iconv
1112 /* $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1113 if ( 0 == strcmp( $cp1252hash, $this->mPassword ) ) {
1114 return true;
1115 }*/
1116 }
1117 return false;
1118 }
1119 }
1120
1121 ?>