11 require_once( 'WatchedItem.php' );
13 # Number of characters in user_token field
14 define( 'USER_TOKEN_LENGTH', 32 );
24 var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
25 var $mEmailAuthenticated;
26 var $mRights, $mOptions;
27 var $mDataLoaded, $mNewpassword;
29 var $mBlockedby, $mBlockreason;
36 /** Construct using User:loadDefaults() */
38 $this->loadDefaults();
42 * Static factory method
43 * @param string $name Username, validated by Title:newFromText()
47 function newFromName( $name ) {
50 # Clean up name according to title rules
52 $t = Title
::newFromText( $name );
56 $u->setName( $t->getText() );
57 $u->setId( $u->idFromName( $t->getText() ) );
63 * Factory method to fetch whichever use has a given email confirmation code.
64 * This code is generated when an account is created or its e-mail address
67 * If the code is invalid or has expired, returns NULL.
73 function newFromConfirmationCode( $code ) {
74 $dbr =& wfGetDB( DB_SLAVE
);
75 $name = $dbr->selectField( 'user', 'user_name', array(
76 'user_email_token' => md5( $code ),
77 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
79 if( is_string( $name ) ) {
80 return User
::newFromName( $name );
87 * Serialze sleep function, for better cache efficiency and avoidance of
88 * silly "incomplete type" errors when skins are cached
91 return array( 'mId', 'mName', 'mPassword', 'mEmail', 'mNewtalk',
92 'mEmailAuthenticated', 'mRights', 'mOptions', 'mDataLoaded',
93 'mNewpassword', 'mBlockedby', 'mBlockreason', 'mTouched',
94 'mToken', 'mRealName', 'mHash', 'mGroups' );
98 * Get username given an id.
99 * @param integer $id Database user id
100 * @return string Nickname of a user
103 function whoIs( $id ) {
104 $dbr =& wfGetDB( DB_SLAVE
);
105 return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ) );
109 * Get real username given an id.
110 * @param integer $id Database user id
111 * @return string Realname of a user
114 function whoIsReal( $id ) {
115 $dbr =& wfGetDB( DB_SLAVE
);
116 return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ) );
120 * Get database id given a user name
121 * @param string $name Nickname of a user
122 * @return integer|null Database user id (null: if non existent
125 function idFromName( $name ) {
126 $fname = "User::idFromName";
128 $nt = Title
::newFromText( $name );
129 if( is_null( $nt ) ) {
133 $dbr =& wfGetDB( DB_SLAVE
);
134 $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
136 if ( $s === false ) {
144 * does the string match an anonymous IPv4 address?
147 * @param string $name Nickname of a user
150 function isIP( $name ) {
151 return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/",$name);
152 /*return preg_match("/^
153 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
154 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
155 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
156 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))
161 * does the string match roughly an email address ?
165 * @param string $addr email address
169 function isValidEmailAddr ( $addr ) {
170 # There used to be a regular expression here, it got removed because it
171 # rejected valid addresses.
172 return ( trim( $addr ) != '' ) &&
173 (false !== strpos( $addr, '@' ) );
177 * probably return a random password
178 * @return string probably a random password
180 * @todo Check what is doing really [AV]
182 function randomPassword() {
183 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
184 $l = strlen( $pwchars ) - 1;
186 $np = $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
187 $pwchars{mt_rand( 0, $l )} . chr( mt_rand(48, 57) ) .
188 $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
189 $pwchars{mt_rand( 0, $l )};
194 * Set properties to default
195 * Used at construction. It will load per language default settings only
196 * if we have an available language object.
198 function loadDefaults() {
201 $fname = 'User::loadDefaults' . $n;
202 wfProfileIn( $fname );
204 global $wgContLang, $wgIP, $wgDBname;
205 global $wgNamespacesToBeSearchedDefault;
208 $this->mNewtalk
= -1;
209 $this->mName
= $wgIP;
210 $this->mRealName
= $this->mEmail
= '';
211 $this->mEmailAuthenticated
= null;
212 $this->mPassword
= $this->mNewpassword
= '';
213 $this->mRights
= array();
214 $this->mGroups
= array();
215 $this->mOptions
= User
::getDefaultOptions();
217 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
218 $this->mOptions
['searchNs'.$nsnum] = $val;
220 unset( $this->mSkin
);
221 $this->mDataLoaded
= false;
222 $this->mBlockedby
= -1; # Unset
223 $this->setToken(); # Random
224 $this->mHash
= false;
226 if ( isset( $_COOKIE[$wgDBname.'LoggedOut'] ) ) {
227 $this->mTouched
= wfTimestamp( TS_MW
, $_COOKIE[$wgDBname.'LoggedOut'] );
230 $this->mTouched
= '0'; # Allow any pages to be cached
233 wfProfileOut( $fname );
237 * Combine the language default options with any site-specific options
238 * and add the default language variants.
244 function getDefaultOptions() {
246 * Site defaults will override the global/language defaults
248 global $wgContLang, $wgDefaultUserOptions;
249 $defOpt = $wgDefaultUserOptions +
$wgContLang->getDefaultUserOptions();
252 * default language setting
254 $variant = $wgContLang->getPreferredVariant();
255 $defOpt['variant'] = $variant;
256 $defOpt['language'] = $variant;
262 * Get a given default option value.
269 function getDefaultOption( $opt ) {
270 $defOpts = User
::getDefaultOptions();
271 if( isset( $defOpts[$opt] ) ) {
272 return $defOpts[$opt];
279 * Get blocking information
281 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
282 * non-critical checks are done against slaves. Check when actually saving should be done against
285 * Note that even if $bFromSlave is false, the check is done first against slave, then master.
286 * The logic is that if blocked on slave, we'll assume it's either blocked on master or
287 * just slightly outta sync and soon corrected - safer to block slightly more that less.
288 * And it's cheaper to check slave first, then master if needed, than master always.
290 function getBlockedStatus( $bFromSlave = true ) {
291 global $wgIP, $wgBlockCache, $wgProxyList, $wgEnableSorbs, $wgProxyWhitelist;
293 if ( -1 != $this->mBlockedby
) { return; }
295 $this->mBlockedby
= 0;
299 $block = new Block();
300 $block->forUpdate( $bFromSlave );
301 if ( $block->load( $wgIP , $this->mId
) ) {
302 $this->mBlockedby
= $block->mBy
;
303 $this->mBlockreason
= $block->mReason
;
304 $this->spreadBlock();
309 if ( !$this->mBlockedby
) {
310 # Check first against slave, and optionally from master.
311 $block = $wgBlockCache->get( $wgIP, true );
312 if ( !$block && !$bFromSlave )
314 # Not blocked: check against master, to make sure.
315 $wgBlockCache->clearLocal( );
316 $block = $wgBlockCache->get( $wgIP, false );
318 if ( $block !== false ) {
319 $this->mBlockedby
= $block->mBy
;
320 $this->mBlockreason
= $block->mReason
;
325 if ( !$this->isSysop() && !in_array( $wgIP, $wgProxyWhitelist ) ) {
328 if ( array_key_exists( $wgIP, $wgProxyList ) ) {
329 $this->mBlockedby
= wfMsg( 'proxyblocker' );
330 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
334 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
335 if ( $this->inSorbsBlacklist( $wgIP ) ) {
336 $this->mBlockedby
= wfMsg( 'sorbs' );
337 $this->mBlockreason
= wfMsg( 'sorbsreason' );
343 function inSorbsBlacklist( $ip ) {
344 global $wgEnableSorbs;
345 return $wgEnableSorbs &&
346 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
349 function inOpmBlacklist( $ip ) {
351 return $wgEnableOpm &&
352 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
355 function inDnsBlacklist( $ip, $base ) {
356 $fname = 'User::inDnsBlacklist';
357 wfProfileIn( $fname );
362 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
364 for ( $i=4; $i>=1; $i-- ) {
365 $host .= $m[$i] . '.';
370 $ipList = gethostbynamel( $host );
373 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
376 wfDebug( "Requested $host, not found in $base.\n" );
380 wfProfileOut( $fname );
385 * Primitive rate limits: enforce maximum actions per time period
386 * to put a brake on flooding.
388 * Note: when using a shared cache like memcached, IP-address
389 * last-hit counters will be shared across wikis.
391 * @return bool true if a rate limiter was tripped
394 function pingLimiter( $action='edit' ) {
395 global $wgRateLimits;
396 if( !isset( $wgRateLimits[$action] ) ) {
399 if( $this->isAllowed( 'delete' ) ) {
404 global $wgMemc, $wgIP, $wgDBname, $wgRateLimitLog;
405 $fname = 'User::pingLimiter';
406 $limits = $wgRateLimits[$action];
408 $id = $this->getId();
410 if( isset( $limits['anon'] ) && $id == 0 ) {
411 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
414 if( isset( $limits['user'] ) && $id != 0 ) {
415 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
417 if( $this->isNewbie() ) {
418 if( isset( $limits['newbie'] ) && $id != 0 ) {
419 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
421 if( isset( $limits['ip'] ) ) {
422 $keys["mediawiki:limiter:$action:ip:$wgIP"] = $limits['ip'];
424 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $wgIP, $matches ) ) {
425 $subnet = $matches[1];
426 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
431 foreach( $keys as $key => $limit ) {
432 list( $max, $period ) = $limit;
433 $summary = "(limit $max in {$period}s)";
434 $count = $wgMemc->get( $key );
436 if( $count > $max ) {
437 wfDebug( "$fname: tripped! $key at $count $summary\n" );
438 if( $wgRateLimitLog ) {
439 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
443 wfDebug( "$fname: ok. $key at $count $summary\n" );
446 wfDebug( "$fname: adding record for $key $summary\n" );
447 $wgMemc->add( $key, 1, IntVal( $period ) );
449 $wgMemc->incr( $key );
456 * Check if user is blocked
457 * @return bool True if blocked, false otherwise
459 function isBlocked( $bFromSlave = false ) {
460 $this->getBlockedStatus( $bFromSlave );
461 return $this->mBlockedby
!== 0;
465 * Get name of blocker
466 * @return string name of blocker
468 function blockedBy() {
469 $this->getBlockedStatus();
470 return $this->mBlockedby
;
474 * Get blocking reason
475 * @return string Blocking reason
477 function blockedFor() {
478 $this->getBlockedStatus();
479 return $this->mBlockreason
;
483 * Initialise php session
485 function SetupSession() {
486 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
487 if( $wgSessionsInMemcached ) {
488 require_once( 'MemcachedSessions.php' );
489 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
490 # If it's left on 'user' or another setting from another
491 # application, it will end up failing. Try to recover.
492 ini_set ( 'session.save_handler', 'files' );
494 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
495 session_cache_limiter( 'private, must-revalidate' );
500 * Read datas from session
503 function loadFromSession() {
504 global $wgMemc, $wgDBname;
506 if ( isset( $_SESSION['wsUserID'] ) ) {
507 if ( 0 != $_SESSION['wsUserID'] ) {
508 $sId = $_SESSION['wsUserID'];
512 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
513 $sId = IntVal( $_COOKIE["{$wgDBname}UserID"] );
514 $_SESSION['wsUserID'] = $sId;
518 if ( isset( $_SESSION['wsUserName'] ) ) {
519 $sName = $_SESSION['wsUserName'];
520 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
521 $sName = $_COOKIE["{$wgDBname}UserName"];
522 $_SESSION['wsUserName'] = $sName;
527 $passwordCorrect = FALSE;
528 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
529 if($makenew = !$user) {
530 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
533 $user->loadFromDatabase();
535 wfDebug( "User::loadFromSession() got from cache!\n" );
538 if ( isset( $_SESSION['wsToken'] ) ) {
539 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
540 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
541 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
543 return new User(); # Can't log in from session
546 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
548 if($wgMemc->set( $key, $user ))
549 wfDebug( "User::loadFromSession() successfully saved user\n" );
551 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
555 return new User(); # Can't log in from session
559 * Load a user from the database
561 function loadFromDatabase() {
562 global $wgCommandLineMode;
563 $fname = "User::loadFromDatabase";
565 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
566 # loading in a command line script, don't assume all command line scripts need it like this
567 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
568 if ( $this->mDataLoaded
) {
573 $this->mId
= IntVal( $this->mId
);
575 /** Anonymous user */
578 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
579 $this->mDataLoaded
= true;
581 } # the following stuff is for non-anonymous users only
583 $dbr =& wfGetDB( DB_SLAVE
);
584 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
585 'user_email_authenticated',
586 'user_real_name','user_options','user_touched', 'user_token' ),
587 array( 'user_id' => $this->mId
), $fname );
589 if ( $s !== false ) {
590 $this->mName
= $s->user_name
;
591 $this->mEmail
= $s->user_email
;
592 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
593 $this->mRealName
= $s->user_real_name
;
594 $this->mPassword
= $s->user_password
;
595 $this->mNewpassword
= $s->user_newpassword
;
596 $this->decodeOptions( $s->user_options
);
597 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
598 $this->mToken
= $s->user_token
;
600 $res = $dbr->select( 'user_groups',
602 array( 'ug_user' => $this->mId
),
604 $this->mGroups
= array();
605 while( $row = $dbr->fetchObject( $res ) ) {
606 $this->mGroups
[] = $row->ug_group
;
608 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
609 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
612 $this->mDataLoaded
= true;
615 function getID() { return $this->mId
; }
616 function setID( $v ) {
618 $this->mDataLoaded
= false;
622 $this->loadFromDatabase();
626 function setName( $str ) {
627 $this->loadFromDatabase();
633 * Return the title dbkey form of the name, for eg user pages.
637 function getTitleKey() {
638 return str_replace( ' ', '_', $this->getName() );
641 function getNewtalk() {
643 $fname = 'User::getNewtalk';
644 $this->loadFromDatabase();
646 # Load the newtalk status if it is unloaded (mNewtalk=-1)
647 if( $this->mNewtalk
== -1 ) {
648 $this->mNewtalk
= 0; # reset talk page status
650 # Check memcached separately for anons, who have no
651 # entire User object stored in there.
653 global $wgDBname, $wgMemc;
654 $key = "$wgDBname:newtalk:ip:{$this->mName}";
655 $newtalk = $wgMemc->get( $key );
656 if( is_integer( $newtalk ) ) {
657 $this->mNewtalk
= $newtalk ?
1 : 0;
658 return (bool)$this->mNewtalk
;
662 $dbr =& wfGetDB( DB_SLAVE
);
663 if ( $wgUseEnotif ) {
664 $res = $dbr->select( 'watchlist',
666 array( 'wl_title' => $this->getTitleKey(),
667 'wl_namespace' => NS_USER_TALK
,
668 'wl_user' => $this->mId
,
669 'wl_notificationtimestamp != 0' ),
670 'User::getNewtalk' );
671 if( $dbr->numRows($res) > 0 ) {
674 $dbr->freeResult( $res );
675 } elseif ( $this->mId
) {
676 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
678 if ( $dbr->numRows($res)>0 ) {
681 $dbr->freeResult( $res );
683 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->mName
), $fname );
684 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
685 $dbr->freeResult( $res );
689 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
693 return ( 0 != $this->mNewtalk
);
696 function setNewtalk( $val ) {
697 $this->loadFromDatabase();
698 $this->mNewtalk
= $val;
699 $this->invalidateCache();
702 function invalidateCache() {
703 global $wgClockSkewFudge;
704 $this->loadFromDatabase();
705 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
706 # Don't forget to save the options after this or
707 # it won't take effect!
710 function validateCache( $timestamp ) {
711 $this->loadFromDatabase();
712 return ($timestamp >= $this->mTouched
);
717 * Will only be salted if $wgPasswordSalt is true
718 * @param string Password.
719 * @return string Salted password or clear password.
721 function addSalt( $p ) {
722 global $wgPasswordSalt;
724 return md5( "{$this->mId}-{$p}" );
730 * Encrypt a password.
731 * It can eventuall salt a password @see User::addSalt()
732 * @param string $p clear Password.
733 * @param string Encrypted password.
735 function encryptPassword( $p ) {
736 return $this->addSalt( md5( $p ) );
739 # Set the password and reset the random token
740 function setPassword( $str ) {
741 $this->loadFromDatabase();
743 $this->mPassword
= $this->encryptPassword( $str );
744 $this->mNewpassword
= '';
747 # Set the random token (used for persistent authentication)
748 function setToken( $token = false ) {
749 global $wgSecretKey, $wgProxyKey, $wgDBname;
751 if ( $wgSecretKey ) {
753 } elseif ( $wgProxyKey ) {
758 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
760 $this->mToken
= $token;
765 function setCookiePassword( $str ) {
766 $this->loadFromDatabase();
767 $this->mCookiePassword
= md5( $str );
770 function setNewpassword( $str ) {
771 $this->loadFromDatabase();
772 $this->mNewpassword
= $this->encryptPassword( $str );
775 function getEmail() {
776 $this->loadFromDatabase();
777 return $this->mEmail
;
780 function getEmailAuthenticationTimestamp() {
781 $this->loadFromDatabase();
782 return $this->mEmailAuthenticated
;
785 function setEmail( $str ) {
786 $this->loadFromDatabase();
787 $this->mEmail
= $str;
790 function getRealName() {
791 $this->loadFromDatabase();
792 return $this->mRealName
;
795 function setRealName( $str ) {
796 $this->loadFromDatabase();
797 $this->mRealName
= $str;
800 function getOption( $oname ) {
801 $this->loadFromDatabase();
802 if ( array_key_exists( $oname, $this->mOptions
) ) {
803 return trim( $this->mOptions
[$oname] );
809 function setOption( $oname, $val ) {
810 $this->loadFromDatabase();
811 if ( $oname == 'skin' ) {
812 # Clear cached skin, so the new one displays immediately in Special:Preferences
813 unset( $this->mSkin
);
815 $this->mOptions
[$oname] = $val;
816 $this->invalidateCache();
819 function getRights() {
820 $this->loadFromDatabase();
821 return $this->mRights
;
825 * Get the list of explicit group memberships this user has.
826 * The implicit * and user groups are not included.
827 * @return array of strings
829 function getGroups() {
830 $this->loadFromDatabase();
831 return $this->mGroups
;
835 * Get the list of implicit group memberships this user has.
836 * This includes all explicit groups, plus 'user' if logged in
837 * and '*' for all accounts.
838 * @return array of strings
840 function getEffectiveGroups() {
841 $base = array( '*' );
842 if( $this->isLoggedIn() ) {
845 return array_merge( $base, $this->getGroups() );
849 * Remove the user from the given group.
850 * This takes immediate effect.
853 function addGroup( $group ) {
854 $dbw =& wfGetDB( DB_MASTER
);
855 $dbw->insert( 'user_groups',
857 'ug_user' => $this->getID(),
858 'ug_group' => $group,
863 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
864 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
866 $this->invalidateCache();
867 $this->saveSettings();
871 * Remove the user from the given group.
872 * This takes immediate effect.
875 function removeGroup( $group ) {
876 $dbw =& wfGetDB( DB_MASTER
);
877 $dbw->delete( 'user_groups',
879 'ug_user' => $this->getID(),
880 'ug_group' => $group,
882 'User::removeGroup' );
884 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
885 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
887 $this->invalidateCache();
888 $this->saveSettings();
893 * A more legible check for non-anonymousness.
894 * Returns true if the user is not an anonymous visitor.
898 function isLoggedIn() {
899 return( $this->getID() != 0 );
903 * A more legible check for anonymousness.
904 * Returns true if the user is an anonymous visitor.
909 return !$this->isLoggedIn();
913 * Check if a user is sysop
914 * Die with backtrace. Use User:isAllowed() instead.
918 return $this->isAllowed( 'protect' );
922 function isDeveloper() {
923 return $this->isAllowed( 'siteadmin' );
927 function isBureaucrat() {
928 return $this->isAllowed( 'makesysop' );
932 * Whether the user is a bot
933 * @todo need to be migrated to the new user level management sytem
936 $this->loadFromDatabase();
937 return in_array( 'bot', $this->mRights
);
941 * Check if user is allowed to access a feature / make an action
942 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
943 * @return boolean True: action is allowed, False: action should not be allowed
945 function isAllowed($action='') {
946 $this->loadFromDatabase();
947 return in_array( $action , $this->mRights
);
951 * Load a skin if it doesn't exist or return it
952 * @todo FIXME : need to check the old failback system [AV]
954 function &getSkin() {
956 if ( ! isset( $this->mSkin
) ) {
957 $fname = 'User::getSkin';
958 wfProfileIn( $fname );
960 # get all skin names available
961 $skinNames = Skin
::getSkinNames();
964 $userSkin = $this->getOption( 'skin' );
965 if ( $userSkin == '' ) { $userSkin = 'standard'; }
967 if ( !isset( $skinNames[$userSkin] ) ) {
968 # in case the user skin could not be found find a replacement
973 # if phptal is enabled we should have monobook skin that
974 # superseed the good old SkinStandard.
975 if ( isset( $skinNames['monobook'] ) ) {
976 $fallback[0] = 'MonoBook';
979 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
980 $sn = $fallback[$userSkin];
985 # The user skin is available
986 $sn = $skinNames[$userSkin];
989 # Grab the skin class and initialise it. Each skin checks for PHPTal
990 # and will not load if it's not enabled.
991 require_once( $IP.'/skins/'.$sn.'.php' );
993 # Check if we got if not failback to default skin
994 $className = 'Skin'.$sn;
995 if( !class_exists( $className ) ) {
996 # DO NOT die if the class isn't found. This breaks maintenance
997 # scripts and can cause a user account to be unrecoverable
998 # except by SQL manipulation if a previously valid skin name
999 # is no longer valid.
1000 $className = 'SkinStandard';
1001 require_once( $IP.'/skins/Standard.php' );
1003 $this->mSkin
=& new $className;
1004 wfProfileOut( $fname );
1006 return $this->mSkin
;
1010 * @param string $title Article title to look at
1014 * Check watched status of an article
1015 * @return bool True if article is watched
1017 function isWatched( $title ) {
1018 $wl = WatchedItem
::fromUserTitle( $this, $title );
1019 return $wl->isWatched();
1025 function addWatch( $title ) {
1026 $wl = WatchedItem
::fromUserTitle( $this, $title );
1028 $this->invalidateCache();
1032 * Stop watching an article
1034 function removeWatch( $title ) {
1035 $wl = WatchedItem
::fromUserTitle( $this, $title );
1037 $this->invalidateCache();
1041 * Clear the user's notification timestamp for the given title.
1042 * If e-notif e-mails are on, they will receive notification mails on
1043 * the next change of the page if it's watched etc.
1045 function clearNotification( &$title ) {
1046 global $wgUser, $wgUseEnotif;
1048 if ( !$wgUseEnotif ) {
1052 $userid = $this->getID();
1057 // Only update the timestamp if the page is being watched.
1058 // The query to find out if it is watched is cached both in memcached and per-invocation,
1059 // and when it does have to be executed, it can be on a slave
1060 // If this is the user's newtalk page, we always update the timestamp
1061 if ($title->getNamespace() == NS_USER_TALK
&&
1062 $title->getText() == $wgUser->getName())
1065 } elseif ( $this->getID() == $wgUser->getID() ) {
1066 $watched = $title->userIsWatching();
1071 // If the page is watched by the user (or may be watched), update the timestamp on any
1072 // any matching rows
1074 $dbw =& wfGetDB( DB_MASTER
);
1075 $success = $dbw->update( 'watchlist',
1077 'wl_notificationtimestamp' => 0
1078 ), array( /* WHERE */
1079 'wl_title' => $title->getDBkey(),
1080 'wl_namespace' => $title->getNamespace(),
1081 'wl_user' => $this->getID()
1082 ), 'User::clearLastVisited'
1090 * Resets all of the given user's page-change notification timestamps.
1091 * If e-notif e-mails are on, they will receive notification mails on
1092 * the next change of any watched page.
1094 * @param int $currentUser user ID number
1097 function clearAllNotifications( $currentUser ) {
1098 global $wgUseEnotif;
1099 if ( !$wgUseEnotif ) {
1102 if( $currentUser != 0 ) {
1104 $dbw =& wfGetDB( DB_MASTER
);
1105 $success = $dbw->update( 'watchlist',
1107 'wl_notificationtimestamp' => 0
1108 ), array( /* WHERE */
1109 'wl_user' => $currentUser
1110 ), 'UserMailer::clearAll'
1113 # we also need to clear here the "you have new message" notification for the own user_talk page
1114 # This is cleared one page view later in Article::viewUpdates();
1120 * @return string Encoding options
1122 function encodeOptions() {
1124 foreach ( $this->mOptions
as $oname => $oval ) {
1125 array_push( $a, $oname.'='.$oval );
1127 $s = implode( "\n", $a );
1134 function decodeOptions( $str ) {
1135 $a = explode( "\n", $str );
1136 foreach ( $a as $s ) {
1137 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1138 $this->mOptions
[$m[1]] = $m[2];
1143 function setCookies() {
1144 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
1145 if ( 0 == $this->mId
) return;
1146 $this->loadFromDatabase();
1147 $exp = time() +
$wgCookieExpiration;
1149 $_SESSION['wsUserID'] = $this->mId
;
1150 setcookie( $wgDBname.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain );
1152 $_SESSION['wsUserName'] = $this->mName
;
1153 setcookie( $wgDBname.'UserName', $this->mName
, $exp, $wgCookiePath, $wgCookieDomain );
1155 $_SESSION['wsToken'] = $this->mToken
;
1156 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1157 setcookie( $wgDBname.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain );
1159 setcookie( $wgDBname.'Token', '', time() - 3600 );
1165 * It will clean the session cookie
1168 global $wgCookiePath, $wgCookieDomain, $wgDBname, $wgIP;
1169 $this->loadDefaults();
1170 $this->setLoaded( true );
1172 $_SESSION['wsUserID'] = 0;
1174 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1175 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1177 # Remember when user logged out, to prevent seeing cached pages
1178 setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain );
1182 * Save object settings into database
1184 function saveSettings() {
1185 global $wgMemc, $wgDBname, $wgUseEnotif;
1186 $fname = 'User::saveSettings';
1188 if ( wfReadOnly() ) { return; }
1189 $this->saveNewtalk();
1190 if ( 0 == $this->mId
) { return; }
1192 $dbw =& wfGetDB( DB_MASTER
);
1193 $dbw->update( 'user',
1195 'user_name' => $this->mName
,
1196 'user_password' => $this->mPassword
,
1197 'user_newpassword' => $this->mNewpassword
,
1198 'user_real_name' => $this->mRealName
,
1199 'user_email' => $this->mEmail
,
1200 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1201 'user_options' => $this->encodeOptions(),
1202 'user_touched' => $dbw->timestamp($this->mTouched
),
1203 'user_token' => $this->mToken
1204 ), array( /* WHERE */
1205 'user_id' => $this->mId
1208 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1212 * Save value of new talk flag.
1214 function saveNewtalk() {
1215 global $wgDBname, $wgMemc, $wgUseEnotif;
1217 $fname = 'User::saveNewtalk';
1219 if ( wfReadOnly() ) { return ; }
1220 $dbr =& wfGetDB( DB_SLAVE
);
1221 $dbw =& wfGetDB( DB_MASTER
);
1222 if ( $wgUseEnotif ) {
1223 if ( ! $this->getNewtalk() ) {
1224 # Delete the watchlist entry for user_talk page X watched by user X
1225 $dbw->delete( 'watchlist',
1226 array( 'wl_user' => $this->mId
,
1227 'wl_title' => $this->getTitleKey(),
1228 'wl_namespace' => NS_USER_TALK
),
1230 if ( $dbw->affectedRows() ) {
1234 # Anon users have a separate memcache space for newtalk
1235 # since they don't store their own info. Trim...
1236 $wgMemc->delete( "$wgDBname:newtalk:ip:{$this->mName}" );
1240 if ($this->getID() != 0) {
1242 $value = $this->getID();
1246 $value = $this->mName
;
1247 $key = "$wgDBname:newtalk:ip:$this->mName";
1250 $dbr =& wfGetDB( DB_SLAVE
);
1251 $dbw =& wfGetDB( DB_MASTER
);
1253 $res = $dbr->selectField('user_newtalk', $field,
1254 array($field => $value), $fname);
1257 if ($res !== false && $this->mNewtalk
== 0) {
1258 $dbw->delete('user_newtalk', array($field => $value), $fname);
1260 $wgMemc->set( $key, 0 );
1262 } else if ($res === false && $this->mNewtalk
== 1) {
1263 $dbw->insert('user_newtalk', array($field => $value), $fname);
1265 $wgMemc->set( $key, 1 );
1272 # Update user_touched, so that newtalk notifications in the client cache are invalidated
1273 if ( $changed && $this->getID() ) {
1274 $dbw->update('user',
1275 /*SET*/ array( 'user_touched' => $this->mTouched
),
1276 /*WHERE*/ array( 'user_id' => $this->getID() ),
1278 $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
1283 * Checks if a user with the given name exists, returns the ID
1285 function idForName() {
1286 $fname = 'User::idForName';
1289 $s = trim( $this->mName
);
1290 if ( 0 == strcmp( '', $s ) ) return 0;
1292 $dbr =& wfGetDB( DB_SLAVE
);
1293 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1294 if ( $id === false ) {
1301 * Add user object to the database
1303 function addToDatabase() {
1304 $fname = 'User::addToDatabase';
1305 $dbw =& wfGetDB( DB_MASTER
);
1306 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1307 $dbw->insert( 'user',
1309 'user_id' => $seqVal,
1310 'user_name' => $this->mName
,
1311 'user_password' => $this->mPassword
,
1312 'user_newpassword' => $this->mNewpassword
,
1313 'user_email' => $this->mEmail
,
1314 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1315 'user_real_name' => $this->mRealName
,
1316 'user_options' => $this->encodeOptions(),
1317 'user_token' => $this->mToken
1320 $this->mId
= $dbw->insertId();
1323 function spreadBlock() {
1325 # If the (non-anonymous) user is blocked, this function will block any IP address
1326 # that they successfully log on from.
1327 $fname = 'User::spreadBlock';
1329 wfDebug( "User:spreadBlock()\n" );
1330 if ( $this->mId
== 0 ) {
1334 $userblock = Block
::newFromDB( '', $this->mId
);
1335 if ( !$userblock->isValid() ) {
1339 # Check if this IP address is already blocked
1340 $ipblock = Block
::newFromDB( $wgIP );
1341 if ( $ipblock->isValid() ) {
1342 # Just update the timestamp
1343 $ipblock->updateTimestamp();
1347 # Make a new block object with the desired properties
1348 wfDebug( "Autoblocking {$this->mName}@{$wgIP}\n" );
1349 $ipblock->mAddress
= $wgIP;
1350 $ipblock->mUser
= 0;
1351 $ipblock->mBy
= $userblock->mBy
;
1352 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1353 $ipblock->mTimestamp
= wfTimestampNow();
1354 $ipblock->mAuto
= 1;
1355 # If the user is already blocked with an expiry date, we don't
1356 # want to pile on top of that!
1357 if($userblock->mExpiry
) {
1358 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1360 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1368 function getPageRenderingHash() {
1371 return $this->mHash
;
1374 // stubthreshold is only included below for completeness,
1375 // it will always be 0 when this function is called by parsercache.
1377 $confstr = $this->getOption( 'math' );
1378 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1379 $confstr .= '!' . $this->getOption( 'editsection' );
1380 $confstr .= '!' . $this->getOption( 'date' );
1381 $confstr .= '!' . $this->getOption( 'numberheadings' );
1382 $confstr .= '!' . $this->getOption( 'language' );
1383 $confstr .= '!' . $this->getOption( 'thumbsize' );
1384 // add in language specific options, if any
1385 $extra = $wgContLang->getExtraHashOptions();
1388 $this->mHash
= $confstr;
1392 function isAllowedToCreateAccount() {
1393 return $this->isAllowed( 'createaccount' );
1397 * Set mDataLoaded, return previous value
1398 * Use this to prevent DB access in command-line scripts or similar situations
1400 function setLoaded( $loaded ) {
1401 return wfSetVar( $this->mDataLoaded
, $loaded );
1405 * Get this user's personal page title.
1410 function getUserPage() {
1411 return Title
::makeTitle( NS_USER
, $this->mName
);
1415 * Get this user's talk page title.
1420 function getTalkPage() {
1421 $title = $this->getUserPage();
1422 return $title->getTalkPage();
1428 function getMaxID() {
1429 $dbr =& wfGetDB( DB_SLAVE
);
1430 return $dbr->selectField( 'user', 'max(user_id)', false );
1434 * Determine whether the user is a newbie. Newbies are either
1435 * anonymous IPs, or the 1% most recently created accounts.
1436 * Bots and sysops are excluded.
1437 * @return bool True if it is a newbie.
1439 function isNewbie() {
1440 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1444 * Check to see if the given clear-text password is one of the accepted passwords
1445 * @param string $password User password.
1446 * @return bool True if the given password is correct otherwise False.
1448 function checkPassword( $password ) {
1449 global $wgAuth, $wgMinimalPasswordLength;
1450 $this->loadFromDatabase();
1452 // Even though we stop people from creating passwords that
1453 // are shorter than this, doesn't mean people wont be able
1454 // to. Certain authentication plugins do NOT want to save
1455 // domain passwords in a mysql database, so we should
1456 // check this (incase $wgAuth->strict() is false).
1457 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1461 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1463 } elseif( $wgAuth->strict() ) {
1464 /* Auth plugin doesn't allow local authentication */
1467 $ep = $this->encryptPassword( $password );
1468 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1470 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1472 } elseif ( function_exists( 'iconv' ) ) {
1473 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1474 # Check for this with iconv
1475 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1476 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1484 * Initialize (if necessary) and return a session token value
1485 * which can be used in edit forms to show that the user's
1486 * login credentials aren't being hijacked with a foreign form
1489 * @param mixed $salt - Optional function-specific data for hash.
1490 * Use a string or an array of strings.
1494 function editToken( $salt = '' ) {
1495 if( !isset( $_SESSION['wsEditToken'] ) ) {
1496 $token = $this->generateToken();
1497 $_SESSION['wsEditToken'] = $token;
1499 $token = $_SESSION['wsEditToken'];
1501 if( is_array( $salt ) ) {
1502 $salt = implode( '|', $salt );
1504 return md5( $token . $salt );
1508 * Generate a hex-y looking random token for various uses.
1509 * Could be made more cryptographically sure if someone cares.
1512 function generateToken( $salt = '' ) {
1513 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1514 return md5( $token . $salt );
1518 * Check given value against the token value stored in the session.
1519 * A match should confirm that the form was submitted from the
1520 * user's own login session, not a form submission from a third-party
1523 * @param string $val - the input value to compare
1524 * @param string $salt - Optional function-specific data for hash
1528 function matchEditToken( $val, $salt = '' ) {
1529 return ( $val == $this->editToken( $salt ) );
1533 * Generate a new e-mail confirmation token and send a confirmation
1534 * mail to the user's given address.
1536 * @return mixed True on success, a WikiError object on failure.
1538 function sendConfirmationMail() {
1539 global $wgIP, $wgContLang;
1540 $url = $this->confirmationTokenUrl( $expiration );
1541 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1542 wfMsg( 'confirmemail_body',
1546 $wgContLang->timeanddate( $expiration, false ) ) );
1550 * Send an e-mail to this user's account. Does not check for
1551 * confirmed status or validity.
1553 * @param string $subject
1554 * @param string $body
1555 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1556 * @return mixed True on success, a WikiError object on failure.
1558 function sendMail( $subject, $body, $from = null ) {
1559 if( is_null( $from ) ) {
1560 global $wgPasswordSender;
1561 $from = $wgPasswordSender;
1564 require_once( 'UserMailer.php' );
1565 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1567 if( $error == '' ) {
1570 return new WikiError( $error );
1575 * Generate, store, and return a new e-mail confirmation code.
1576 * A hash (unsalted since it's used as a key) is stored.
1577 * @param &$expiration mixed output: accepts the expiration time
1581 function confirmationToken( &$expiration ) {
1582 $fname = 'User::confirmationToken';
1585 $expires = $now +
7 * 24 * 60 * 60;
1586 $expiration = wfTimestamp( TS_MW
, $expires );
1588 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1589 $hash = md5( $token );
1591 $dbw =& wfGetDB( DB_MASTER
);
1592 $dbw->update( 'user',
1593 array( 'user_email_token' => $hash,
1594 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1595 array( 'user_id' => $this->mId
),
1602 * Generate and store a new e-mail confirmation token, and return
1603 * the URL the user can use to confirm.
1604 * @param &$expiration mixed output: accepts the expiration time
1608 function confirmationTokenUrl( &$expiration ) {
1609 $token = $this->confirmationToken( $expiration );
1610 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1611 return $title->getFullUrl();
1615 * Mark the e-mail address confirmed and save.
1617 function confirmEmail() {
1618 $this->loadFromDatabase();
1619 $this->mEmailAuthenticated
= wfTimestampNow();
1620 $this->saveSettings();
1625 * Is this user allowed to send e-mails within limits of current
1626 * site configuration?
1629 function canSendEmail() {
1630 return $this->isEmailConfirmed();
1634 * Is this user allowed to receive e-mails within limits of current
1635 * site configuration?
1638 function canReceiveEmail() {
1639 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1643 * Is this user's e-mail address valid-looking and confirmed within
1644 * limits of the current site configuration?
1646 * If $wgEmailAuthentication is on, this may require the user to have
1647 * confirmed their address by returning a code or using a password
1648 * sent to the address from the wiki.
1652 function isEmailConfirmed() {
1653 global $wgEmailAuthentication;
1654 $this->loadFromDatabase();
1655 if( $this->isAnon() )
1657 if( !$this->isValidEmailAddr( $this->mEmail
) )
1659 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1665 * @param array $groups list of groups
1666 * @return array list of permission key names for given groups combined
1669 function getGroupPermissions( $groups ) {
1670 global $wgGroupPermissions;
1672 foreach( $groups as $group ) {
1673 if( isset( $wgGroupPermissions[$group] ) ) {
1674 $rights = array_merge( $rights,
1675 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1682 * @param string $group key name
1683 * @return string localized descriptive name, if provided
1686 function getGroupName( $group ) {
1687 $key = "group-$group-name";
1688 $name = wfMsg( $key );
1689 if( $name == '' ||
$name == "<$key>" ) {
1697 * Return the set of defined explicit groups.
1698 * The * and 'user' groups are not included.
1702 function getAllGroups() {
1703 global $wgGroupPermissions;
1705 array_keys( $wgGroupPermissions ),
1706 array( '*', 'user' ) );