11 require_once( 'WatchedItem.php' );
13 # Number of characters in user_token field
14 define( 'USER_TOKEN_LENGTH', 32 );
16 # Serialized record version
17 define( 'MW_USER_VERSION', 2 );
27 var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
28 var $mEmailAuthenticated;
29 var $mRights, $mOptions;
30 var $mDataLoaded, $mNewpassword;
32 var $mBlockedby, $mBlockreason;
38 var $mVersion; // serialized version
40 /** Construct using User:loadDefaults() */
42 $this->loadDefaults();
43 $this->mVersion
= MW_USER_VERSION
;
47 * Static factory method
48 * @param string $name Username, validated by Title:newFromText()
52 function newFromName( $name ) {
53 # Force usernames to capital
55 $name = $wgContLang->ucfirst( $name );
57 # Clean up name according to title rules
58 $t = Title
::newFromText( $name );
63 # Reject various classes of invalid names
64 $canonicalName = $t->getText();
66 $canonicalName = $wgAuth->getCanonicalName( $t->getText() );
68 if( !User
::isValidUserName( $canonicalName ) ) {
73 $u->setName( $canonicalName );
74 $u->setId( $u->idFromName( $canonicalName ) );
79 * Factory method to fetch whichever use has a given email confirmation code.
80 * This code is generated when an account is created or its e-mail address
83 * If the code is invalid or has expired, returns NULL.
89 function newFromConfirmationCode( $code ) {
90 $dbr =& wfGetDB( DB_SLAVE
);
91 $name = $dbr->selectField( 'user', 'user_name', array(
92 'user_email_token' => md5( $code ),
93 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
95 if( is_string( $name ) ) {
96 return User
::newFromName( $name );
103 * Serialze sleep function, for better cache efficiency and avoidance of
104 * silly "incomplete type" errors when skins are cached
107 return array( 'mId', 'mName', 'mPassword', 'mEmail', 'mNewtalk',
108 'mEmailAuthenticated', 'mRights', 'mOptions', 'mDataLoaded',
109 'mNewpassword', 'mBlockedby', 'mBlockreason', 'mTouched',
110 'mToken', 'mRealName', 'mHash', 'mGroups' );
114 * Get username given an id.
115 * @param integer $id Database user id
116 * @return string Nickname of a user
119 function whoIs( $id ) {
120 $dbr =& wfGetDB( DB_SLAVE
);
121 return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), 'User::whoIs' );
125 * Get real username given an id.
126 * @param integer $id Database user id
127 * @return string Realname of a user
130 function whoIsReal( $id ) {
131 $dbr =& wfGetDB( DB_SLAVE
);
132 return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), 'User::whoIsReal' );
136 * Get database id given a user name
137 * @param string $name Nickname of a user
138 * @return integer|null Database user id (null: if non existent
141 function idFromName( $name ) {
142 $fname = "User::idFromName";
144 $nt = Title
::newFromText( $name );
145 if( is_null( $nt ) ) {
149 $dbr =& wfGetDB( DB_SLAVE
);
150 $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
152 if ( $s === false ) {
160 * does the string match an anonymous IPv4 address?
162 * Note: We match \d{1,3}\.\d{1,3}\.\d{1,3}\.xxx as an anonymous IP
163 * address because the usemod software would "cloak" anonymous IP
164 * addresses like this, if we allowed accounts like this to be created
165 * new users could get the old edits of these anonymous users.
170 * @param string $name Nickname of a user
173 function isIP( $name ) {
174 return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/",$name);
175 /*return preg_match("/^
176 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
177 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
178 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
179 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))
184 * Is the input a valid username?
186 * Checks if the input is a valid username, we don't want an empty string,
187 * an IP address, anything that containins slashes (would mess up subpages),
188 * is longer than the maximum allowed username size or doesn't begin with
191 * @param string $name
195 function isValidUserName( $name ) {
196 global $wgContLang, $wgMaxNameChars;
199 || User
::isIP( $name )
200 ||
strpos( $name, '/' ) !== false
201 ||
strlen( $name ) > $wgMaxNameChars
202 ||
$name != $wgContLang->ucfirst( $name ) )
209 * Is the input a valid password?
211 * @param string $password
215 function isValidPassword( $password ) {
216 global $wgMinimalPasswordLength;
217 return strlen( $password ) >= $wgMinimalPasswordLength;
221 * does the string match roughly an email address ?
223 * @todo Check for RFC 2822 compilance
226 * @param string $addr email address
230 function isValidEmailAddr ( $addr ) {
231 # There used to be a regular expression here, it got removed because it
232 # rejected valid addresses.
233 return ( trim( $addr ) != '' ) &&
234 (false !== strpos( $addr, '@' ) );
238 * Count the number of edits of a user
240 * @param int $uid The user ID to check
243 function edits( $uid ) {
244 $fname = 'User::edits';
246 $dbr =& wfGetDB( DB_SLAVE
);
247 return $dbr->selectField(
248 'revision', 'count(*)',
249 array( 'rev_user' => $uid ),
255 * probably return a random password
256 * @return string probably a random password
258 * @todo Check what is doing really [AV]
260 function randomPassword() {
261 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
262 $l = strlen( $pwchars ) - 1;
264 $np = $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
265 $pwchars{mt_rand( 0, $l )} . chr( mt_rand(48, 57) ) .
266 $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
267 $pwchars{mt_rand( 0, $l )};
272 * Set properties to default
273 * Used at construction. It will load per language default settings only
274 * if we have an available language object.
276 function loadDefaults() {
279 $fname = 'User::loadDefaults' . $n;
280 wfProfileIn( $fname );
282 global $wgContLang, $wgDBname;
283 global $wgNamespacesToBeSearchedDefault;
286 $this->mNewtalk
= -1;
287 $this->mName
= false;
288 $this->mRealName
= $this->mEmail
= '';
289 $this->mEmailAuthenticated
= null;
290 $this->mPassword
= $this->mNewpassword
= '';
291 $this->mRights
= array();
292 $this->mGroups
= array();
293 $this->mOptions
= User
::getDefaultOptions();
295 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
296 $this->mOptions
['searchNs'.$nsnum] = $val;
298 unset( $this->mSkin
);
299 $this->mDataLoaded
= false;
300 $this->mBlockedby
= -1; # Unset
301 $this->setToken(); # Random
302 $this->mHash
= false;
304 if ( isset( $_COOKIE[$wgDBname.'LoggedOut'] ) ) {
305 $this->mTouched
= wfTimestamp( TS_MW
, $_COOKIE[$wgDBname.'LoggedOut'] );
308 $this->mTouched
= '0'; # Allow any pages to be cached
311 wfProfileOut( $fname );
315 * Combine the language default options with any site-specific options
316 * and add the default language variants.
322 function getDefaultOptions() {
324 * Site defaults will override the global/language defaults
326 global $wgContLang, $wgDefaultUserOptions;
327 $defOpt = $wgDefaultUserOptions +
$wgContLang->getDefaultUserOptions();
330 * default language setting
332 $variant = $wgContLang->getPreferredVariant();
333 $defOpt['variant'] = $variant;
334 $defOpt['language'] = $variant;
340 * Get a given default option value.
347 function getDefaultOption( $opt ) {
348 $defOpts = User
::getDefaultOptions();
349 if( isset( $defOpts[$opt] ) ) {
350 return $defOpts[$opt];
357 * Get blocking information
359 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
360 * non-critical checks are done against slaves. Check when actually saving should be done against
363 * Note that even if $bFromSlave is false, the check is done first against slave, then master.
364 * The logic is that if blocked on slave, we'll assume it's either blocked on master or
365 * just slightly outta sync and soon corrected - safer to block slightly more that less.
366 * And it's cheaper to check slave first, then master if needed, than master always.
368 function getBlockedStatus( $bFromSlave = true ) {
369 global $wgBlockCache, $wgProxyList, $wgEnableSorbs, $wgProxyWhitelist;
371 if ( -1 != $this->mBlockedby
) {
372 wfDebug( "User::getBlockedStatus: already loaded.\n" );
376 $fname = 'User::getBlockedStatus';
377 wfProfileIn( $fname );
378 wfDebug( "$fname: checking...\n" );
380 $this->mBlockedby
= 0;
384 $block = new Block();
385 $block->forUpdate( $bFromSlave );
386 if ( $block->load( $ip , $this->mId
) ) {
387 wfDebug( "$fname: Found block.\n" );
388 $this->mBlockedby
= $block->mBy
;
389 $this->mBlockreason
= $block->mReason
;
390 if ( $this->isLoggedIn() ) {
391 $this->spreadBlock();
394 wfDebug( "$fname: No block.\n" );
398 if ( !$this->mBlockedby
) {
399 # Check first against slave, and optionally from master.
400 wfDebug( "$fname: Checking range blocks\n" );
401 $block = $wgBlockCache->get( $ip, true );
402 if ( !$block && !$bFromSlave )
404 # Not blocked: check against master, to make sure.
405 $wgBlockCache->clearLocal( );
406 $block = $wgBlockCache->get( $ip, false );
408 if ( $block !== false ) {
409 $this->mBlockedby
= $block->mBy
;
410 $this->mBlockreason
= $block->mReason
;
415 if ( !$this->isSysop() && !in_array( $ip, $wgProxyWhitelist ) ) {
418 if ( array_key_exists( $ip, $wgProxyList ) ) {
419 $this->mBlockedby
= wfMsg( 'proxyblocker' );
420 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
424 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
425 if ( $this->inSorbsBlacklist( $ip ) ) {
426 $this->mBlockedby
= wfMsg( 'sorbs' );
427 $this->mBlockreason
= wfMsg( 'sorbsreason' );
431 wfProfileOut( $fname );
434 function inSorbsBlacklist( $ip ) {
435 global $wgEnableSorbs;
436 return $wgEnableSorbs &&
437 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
440 function inOpmBlacklist( $ip ) {
442 return $wgEnableOpm &&
443 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
446 function inDnsBlacklist( $ip, $base ) {
447 $fname = 'User::inDnsBlacklist';
448 wfProfileIn( $fname );
453 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
455 for ( $i=4; $i>=1; $i-- ) {
456 $host .= $m[$i] . '.';
461 $ipList = gethostbynamel( $host );
464 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
467 wfDebug( "Requested $host, not found in $base.\n" );
471 wfProfileOut( $fname );
476 * Primitive rate limits: enforce maximum actions per time period
477 * to put a brake on flooding.
479 * Note: when using a shared cache like memcached, IP-address
480 * last-hit counters will be shared across wikis.
482 * @return bool true if a rate limiter was tripped
485 function pingLimiter( $action='edit' ) {
486 global $wgRateLimits;
487 if( !isset( $wgRateLimits[$action] ) ) {
490 if( $this->isAllowed( 'delete' ) ) {
495 global $wgMemc, $wgDBname, $wgRateLimitLog;
496 $fname = 'User::pingLimiter';
497 wfProfileIn( $fname );
499 $limits = $wgRateLimits[$action];
501 $id = $this->getId();
504 if( isset( $limits['anon'] ) && $id == 0 ) {
505 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
508 if( isset( $limits['user'] ) && $id != 0 ) {
509 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
511 if( $this->isNewbie() ) {
512 if( isset( $limits['newbie'] ) && $id != 0 ) {
513 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
515 if( isset( $limits['ip'] ) ) {
516 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
518 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
519 $subnet = $matches[1];
520 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
525 foreach( $keys as $key => $limit ) {
526 list( $max, $period ) = $limit;
527 $summary = "(limit $max in {$period}s)";
528 $count = $wgMemc->get( $key );
530 if( $count > $max ) {
531 wfDebug( "$fname: tripped! $key at $count $summary\n" );
532 if( $wgRateLimitLog ) {
533 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
537 wfDebug( "$fname: ok. $key at $count $summary\n" );
540 wfDebug( "$fname: adding record for $key $summary\n" );
541 $wgMemc->add( $key, 1, intval( $period ) );
543 $wgMemc->incr( $key );
546 wfProfileOut( $fname );
551 * Check if user is blocked
552 * @return bool True if blocked, false otherwise
554 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
555 wfDebug( "User::isBlocked: enter\n" );
556 $this->getBlockedStatus( $bFromSlave );
557 return $this->mBlockedby
!== 0;
561 * Check if user is blocked from editing a particular article
563 function isBlockedFrom( $title, $bFromSlave = false ) {
564 global $wgBlockAllowsUTEdit;
565 $fname = 'User::isBlockedFrom';
566 wfProfileIn( $fname );
567 wfDebug( "$fname: enter\n" );
569 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
570 $title->getNamespace() == NS_USER_TALK
)
573 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
575 wfDebug( "$fname: asking isBlocked()\n" );
576 $blocked = $this->isBlocked( $bFromSlave );
578 wfProfileOut( $fname );
583 * Get name of blocker
584 * @return string name of blocker
586 function blockedBy() {
587 $this->getBlockedStatus();
588 return $this->mBlockedby
;
592 * Get blocking reason
593 * @return string Blocking reason
595 function blockedFor() {
596 $this->getBlockedStatus();
597 return $this->mBlockreason
;
601 * Initialise php session
603 function SetupSession() {
604 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
605 if( $wgSessionsInMemcached ) {
606 require_once( 'MemcachedSessions.php' );
607 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
608 # If it's left on 'user' or another setting from another
609 # application, it will end up failing. Try to recover.
610 ini_set ( 'session.save_handler', 'files' );
612 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
613 session_cache_limiter( 'private, must-revalidate' );
618 * Create a new user object using data from session
621 function loadFromSession() {
622 global $wgMemc, $wgDBname;
624 if ( isset( $_SESSION['wsUserID'] ) ) {
625 if ( 0 != $_SESSION['wsUserID'] ) {
626 $sId = $_SESSION['wsUserID'];
630 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
631 $sId = intval( $_COOKIE["{$wgDBname}UserID"] );
632 $_SESSION['wsUserID'] = $sId;
636 if ( isset( $_SESSION['wsUserName'] ) ) {
637 $sName = $_SESSION['wsUserName'];
638 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
639 $sName = $_COOKIE["{$wgDBname}UserName"];
640 $_SESSION['wsUserName'] = $sName;
645 $passwordCorrect = FALSE;
646 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
647 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
648 # Expire old serialized objects; they may be corrupt.
651 if($makenew = !$user) {
652 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
655 $user->loadFromDatabase();
657 wfDebug( "User::loadFromSession() got from cache!\n" );
660 if ( isset( $_SESSION['wsToken'] ) ) {
661 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
662 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
663 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
665 return new User(); # Can't log in from session
668 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
670 if($wgMemc->set( $key, $user ))
671 wfDebug( "User::loadFromSession() successfully saved user\n" );
673 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
677 return new User(); # Can't log in from session
681 * Load a user from the database
683 function loadFromDatabase() {
684 global $wgCommandLineMode;
685 $fname = "User::loadFromDatabase";
687 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
688 # loading in a command line script, don't assume all command line scripts need it like this
689 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
690 if ( $this->mDataLoaded
) {
695 $this->mId
= intval( $this->mId
);
697 /** Anonymous user */
700 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
701 $this->mDataLoaded
= true;
703 } # the following stuff is for non-anonymous users only
705 $dbr =& wfGetDB( DB_SLAVE
);
706 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
707 'user_email_authenticated',
708 'user_real_name','user_options','user_touched', 'user_token' ),
709 array( 'user_id' => $this->mId
), $fname );
711 if ( $s !== false ) {
712 $this->mName
= $s->user_name
;
713 $this->mEmail
= $s->user_email
;
714 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
715 $this->mRealName
= $s->user_real_name
;
716 $this->mPassword
= $s->user_password
;
717 $this->mNewpassword
= $s->user_newpassword
;
718 $this->decodeOptions( $s->user_options
);
719 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
720 $this->mToken
= $s->user_token
;
722 $res = $dbr->select( 'user_groups',
724 array( 'ug_user' => $this->mId
),
726 $this->mGroups
= array();
727 while( $row = $dbr->fetchObject( $res ) ) {
728 $this->mGroups
[] = $row->ug_group
;
730 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
731 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
734 $this->mDataLoaded
= true;
737 function getID() { return $this->mId
; }
738 function setID( $v ) {
740 $this->mDataLoaded
= false;
744 $this->loadFromDatabase();
745 if ( $this->mName
=== false ) {
746 $this->mName
= wfGetIP();
751 function setName( $str ) {
752 $this->loadFromDatabase();
758 * Return the title dbkey form of the name, for eg user pages.
762 function getTitleKey() {
763 return str_replace( ' ', '_', $this->getName() );
766 function getNewtalk() {
768 $fname = 'User::getNewtalk';
769 $this->loadFromDatabase();
771 # Load the newtalk status if it is unloaded (mNewtalk=-1)
772 if( $this->mNewtalk
== -1 ) {
773 $this->mNewtalk
= 0; # reset talk page status
775 # Check memcached separately for anons, who have no
776 # entire User object stored in there.
778 global $wgDBname, $wgMemc;
779 $key = "$wgDBname:newtalk:ip:" . $this->getName();
780 $newtalk = $wgMemc->get( $key );
781 if( is_integer( $newtalk ) ) {
782 $this->mNewtalk
= $newtalk ?
1 : 0;
783 return (bool)$this->mNewtalk
;
787 $dbr =& wfGetDB( DB_SLAVE
);
788 if ( $wgUseEnotif ) {
789 $res = $dbr->select( 'watchlist',
791 array( 'wl_title' => $this->getTitleKey(),
792 'wl_namespace' => NS_USER_TALK
,
793 'wl_user' => $this->mId
,
794 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
795 'User::getNewtalk' );
796 if( $dbr->numRows($res) > 0 ) {
799 $dbr->freeResult( $res );
800 } elseif ( $this->mId
) {
801 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
803 if ( $dbr->numRows($res)>0 ) {
806 $dbr->freeResult( $res );
808 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->getName() ), $fname );
809 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
810 $dbr->freeResult( $res );
814 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
818 return ( 0 != $this->mNewtalk
);
821 function setNewtalk( $val ) {
822 $this->loadFromDatabase();
823 $this->mNewtalk
= $val;
824 $this->invalidateCache();
827 function invalidateCache() {
828 global $wgClockSkewFudge;
829 $this->loadFromDatabase();
830 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
831 # Don't forget to save the options after this or
832 # it won't take effect!
835 function validateCache( $timestamp ) {
836 $this->loadFromDatabase();
837 return ($timestamp >= $this->mTouched
);
841 * Encrypt a password.
842 * It can eventuall salt a password @see User::addSalt()
843 * @param string $p clear Password.
844 * @return string Encrypted password.
846 function encryptPassword( $p ) {
847 return wfEncryptPassword( $this->mId
, $p );
850 # Set the password and reset the random token
851 function setPassword( $str ) {
852 $this->loadFromDatabase();
854 $this->mPassword
= $this->encryptPassword( $str );
855 $this->mNewpassword
= '';
858 # Set the random token (used for persistent authentication)
859 function setToken( $token = false ) {
860 global $wgSecretKey, $wgProxyKey, $wgDBname;
862 if ( $wgSecretKey ) {
864 } elseif ( $wgProxyKey ) {
869 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
871 $this->mToken
= $token;
876 function setCookiePassword( $str ) {
877 $this->loadFromDatabase();
878 $this->mCookiePassword
= md5( $str );
881 function setNewpassword( $str ) {
882 $this->loadFromDatabase();
883 $this->mNewpassword
= $this->encryptPassword( $str );
886 function getEmail() {
887 $this->loadFromDatabase();
888 return $this->mEmail
;
891 function getEmailAuthenticationTimestamp() {
892 $this->loadFromDatabase();
893 return $this->mEmailAuthenticated
;
896 function setEmail( $str ) {
897 $this->loadFromDatabase();
898 $this->mEmail
= $str;
901 function getRealName() {
902 $this->loadFromDatabase();
903 return $this->mRealName
;
906 function setRealName( $str ) {
907 $this->loadFromDatabase();
908 $this->mRealName
= $str;
911 function getOption( $oname ) {
912 $this->loadFromDatabase();
913 if ( array_key_exists( $oname, $this->mOptions
) ) {
914 return trim( $this->mOptions
[$oname] );
920 function setOption( $oname, $val ) {
921 $this->loadFromDatabase();
922 if ( $oname == 'skin' ) {
923 # Clear cached skin, so the new one displays immediately in Special:Preferences
924 unset( $this->mSkin
);
926 $this->mOptions
[$oname] = $val;
927 $this->invalidateCache();
930 function getRights() {
931 $this->loadFromDatabase();
932 return $this->mRights
;
936 * Get the list of explicit group memberships this user has.
937 * The implicit * and user groups are not included.
938 * @return array of strings
940 function getGroups() {
941 $this->loadFromDatabase();
942 return $this->mGroups
;
946 * Get the list of implicit group memberships this user has.
947 * This includes all explicit groups, plus 'user' if logged in
948 * and '*' for all accounts.
949 * @return array of strings
951 function getEffectiveGroups() {
952 $base = array( '*' );
953 if( $this->isLoggedIn() ) {
956 return array_merge( $base, $this->getGroups() );
960 * Remove the user from the given group.
961 * This takes immediate effect.
964 function addGroup( $group ) {
965 $dbw =& wfGetDB( DB_MASTER
);
966 $dbw->insert( 'user_groups',
968 'ug_user' => $this->getID(),
969 'ug_group' => $group,
974 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
975 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
977 $this->invalidateCache();
978 $this->saveSettings();
982 * Remove the user from the given group.
983 * This takes immediate effect.
986 function removeGroup( $group ) {
987 $dbw =& wfGetDB( DB_MASTER
);
988 $dbw->delete( 'user_groups',
990 'ug_user' => $this->getID(),
991 'ug_group' => $group,
993 'User::removeGroup' );
995 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
996 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
998 $this->invalidateCache();
999 $this->saveSettings();
1004 * A more legible check for non-anonymousness.
1005 * Returns true if the user is not an anonymous visitor.
1009 function isLoggedIn() {
1010 return( $this->getID() != 0 );
1014 * A more legible check for anonymousness.
1015 * Returns true if the user is an anonymous visitor.
1020 return !$this->isLoggedIn();
1024 * Check if a user is sysop
1025 * Die with backtrace. Use User:isAllowed() instead.
1028 function isSysop() {
1029 return $this->isAllowed( 'protect' );
1033 function isDeveloper() {
1034 return $this->isAllowed( 'siteadmin' );
1038 function isBureaucrat() {
1039 return $this->isAllowed( 'makesysop' );
1043 * Whether the user is a bot
1044 * @todo need to be migrated to the new user level management sytem
1047 $this->loadFromDatabase();
1048 return in_array( 'bot', $this->mRights
);
1052 * Check if user is allowed to access a feature / make an action
1053 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1054 * @return boolean True: action is allowed, False: action should not be allowed
1056 function isAllowed($action='') {
1057 $this->loadFromDatabase();
1058 return in_array( $action , $this->mRights
);
1062 * Load a skin if it doesn't exist or return it
1063 * @todo FIXME : need to check the old failback system [AV]
1065 function &getSkin() {
1066 global $IP, $wgRequest;
1067 if ( ! isset( $this->mSkin
) ) {
1068 $fname = 'User::getSkin';
1069 wfProfileIn( $fname );
1071 # get all skin names available
1072 $skinNames = Skin
::getSkinNames();
1075 $userSkin = $this->getOption( 'skin' );
1076 $userSkin = $wgRequest->getText('useskin', $userSkin);
1077 if ( $userSkin == '' ) { $userSkin = 'standard'; }
1079 if ( !isset( $skinNames[$userSkin] ) ) {
1080 # in case the user skin could not be found find a replacement
1084 2 => 'CologneBlue');
1085 # if phptal is enabled we should have monobook skin that
1086 # superseed the good old SkinStandard.
1087 if ( isset( $skinNames['monobook'] ) ) {
1088 $fallback[0] = 'MonoBook';
1091 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
1092 $sn = $fallback[$userSkin];
1097 # The user skin is available
1098 $sn = $skinNames[$userSkin];
1101 # Grab the skin class and initialise it. Each skin checks for PHPTal
1102 # and will not load if it's not enabled.
1103 require_once( $IP.'/skins/'.$sn.'.php' );
1105 # Check if we got if not failback to default skin
1106 $className = 'Skin'.$sn;
1107 if( !class_exists( $className ) ) {
1108 # DO NOT die if the class isn't found. This breaks maintenance
1109 # scripts and can cause a user account to be unrecoverable
1110 # except by SQL manipulation if a previously valid skin name
1111 # is no longer valid.
1112 $className = 'SkinStandard';
1113 require_once( $IP.'/skins/Standard.php' );
1115 $this->mSkin
=& new $className;
1116 wfProfileOut( $fname );
1118 return $this->mSkin
;
1122 * @param string $title Article title to look at
1126 * Check watched status of an article
1127 * @return bool True if article is watched
1129 function isWatched( $title ) {
1130 $wl = WatchedItem
::fromUserTitle( $this, $title );
1131 return $wl->isWatched();
1137 function addWatch( $title ) {
1138 $wl = WatchedItem
::fromUserTitle( $this, $title );
1140 $this->invalidateCache();
1144 * Stop watching an article
1146 function removeWatch( $title ) {
1147 $wl = WatchedItem
::fromUserTitle( $this, $title );
1149 $this->invalidateCache();
1153 * Clear the user's notification timestamp for the given title.
1154 * If e-notif e-mails are on, they will receive notification mails on
1155 * the next change of the page if it's watched etc.
1157 function clearNotification( &$title ) {
1158 global $wgUser, $wgUseEnotif;
1160 if ( !$wgUseEnotif ) {
1164 $userid = $this->getID();
1169 // Only update the timestamp if the page is being watched.
1170 // The query to find out if it is watched is cached both in memcached and per-invocation,
1171 // and when it does have to be executed, it can be on a slave
1172 // If this is the user's newtalk page, we always update the timestamp
1173 if ($title->getNamespace() == NS_USER_TALK
&&
1174 $title->getText() == $wgUser->getName())
1177 } elseif ( $this->getID() == $wgUser->getID() ) {
1178 $watched = $title->userIsWatching();
1183 // If the page is watched by the user (or may be watched), update the timestamp on any
1184 // any matching rows
1186 $dbw =& wfGetDB( DB_MASTER
);
1187 $success = $dbw->update( 'watchlist',
1189 'wl_notificationtimestamp' => NULL
1190 ), array( /* WHERE */
1191 'wl_title' => $title->getDBkey(),
1192 'wl_namespace' => $title->getNamespace(),
1193 'wl_user' => $this->getID()
1194 ), 'User::clearLastVisited'
1202 * Resets all of the given user's page-change notification timestamps.
1203 * If e-notif e-mails are on, they will receive notification mails on
1204 * the next change of any watched page.
1206 * @param int $currentUser user ID number
1209 function clearAllNotifications( $currentUser ) {
1210 global $wgUseEnotif;
1211 if ( !$wgUseEnotif ) {
1214 if( $currentUser != 0 ) {
1216 $dbw =& wfGetDB( DB_MASTER
);
1217 $success = $dbw->update( 'watchlist',
1219 'wl_notificationtimestamp' => 0
1220 ), array( /* WHERE */
1221 'wl_user' => $currentUser
1222 ), 'UserMailer::clearAll'
1225 # we also need to clear here the "you have new message" notification for the own user_talk page
1226 # This is cleared one page view later in Article::viewUpdates();
1232 * @return string Encoding options
1234 function encodeOptions() {
1236 foreach ( $this->mOptions
as $oname => $oval ) {
1237 array_push( $a, $oname.'='.$oval );
1239 $s = implode( "\n", $a );
1246 function decodeOptions( $str ) {
1247 $a = explode( "\n", $str );
1248 foreach ( $a as $s ) {
1249 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1250 $this->mOptions
[$m[1]] = $m[2];
1255 function setCookies() {
1256 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
1257 if ( 0 == $this->mId
) return;
1258 $this->loadFromDatabase();
1259 $exp = time() +
$wgCookieExpiration;
1261 $_SESSION['wsUserID'] = $this->mId
;
1262 setcookie( $wgDBname.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain );
1264 $_SESSION['wsUserName'] = $this->getName();
1265 setcookie( $wgDBname.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain );
1267 $_SESSION['wsToken'] = $this->mToken
;
1268 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1269 setcookie( $wgDBname.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain );
1271 setcookie( $wgDBname.'Token', '', time() - 3600 );
1277 * It will clean the session cookie
1280 global $wgCookiePath, $wgCookieDomain, $wgDBname;
1281 $this->loadDefaults();
1282 $this->setLoaded( true );
1284 $_SESSION['wsUserID'] = 0;
1286 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1287 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1289 # Remember when user logged out, to prevent seeing cached pages
1290 setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain );
1294 * Save object settings into database
1296 function saveSettings() {
1297 global $wgMemc, $wgDBname, $wgUseEnotif;
1298 $fname = 'User::saveSettings';
1300 if ( wfReadOnly() ) { return; }
1301 $this->saveNewtalk();
1302 if ( 0 == $this->mId
) { return; }
1304 $dbw =& wfGetDB( DB_MASTER
);
1305 $dbw->update( 'user',
1307 'user_name' => $this->mName
,
1308 'user_password' => $this->mPassword
,
1309 'user_newpassword' => $this->mNewpassword
,
1310 'user_real_name' => $this->mRealName
,
1311 'user_email' => $this->mEmail
,
1312 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1313 'user_options' => $this->encodeOptions(),
1314 'user_touched' => $dbw->timestamp($this->mTouched
),
1315 'user_token' => $this->mToken
1316 ), array( /* WHERE */
1317 'user_id' => $this->mId
1320 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1324 * Save value of new talk flag.
1326 function saveNewtalk() {
1327 global $wgDBname, $wgMemc, $wgUseEnotif;
1329 $fname = 'User::saveNewtalk';
1333 if ( wfReadOnly() ) { return ; }
1334 $dbr =& wfGetDB( DB_SLAVE
);
1335 $dbw =& wfGetDB( DB_MASTER
);
1337 if ( $wgUseEnotif ) {
1338 if ( ! $this->getNewtalk() ) {
1339 # Delete the watchlist entry for user_talk page X watched by user X
1340 $dbw->delete( 'watchlist',
1341 array( 'wl_user' => $this->mId
,
1342 'wl_title' => $this->getTitleKey(),
1343 'wl_namespace' => NS_USER_TALK
),
1345 if ( $dbw->affectedRows() ) {
1349 # Anon users have a separate memcache space for newtalk
1350 # since they don't store their own info. Trim...
1351 $wgMemc->delete( "$wgDBname:newtalk:ip:" . $this->getName() );
1355 if ($this->getID() != 0) {
1357 $value = $this->getID();
1361 $value = $this->getName();
1362 $key = "$wgDBname:newtalk:ip:$value";
1365 $dbr =& wfGetDB( DB_SLAVE
);
1366 $dbw =& wfGetDB( DB_MASTER
);
1368 $res = $dbr->selectField('user_newtalk', $field,
1369 array($field => $value), $fname);
1372 if ($res !== false && $this->mNewtalk
== 0) {
1373 $dbw->delete('user_newtalk', array($field => $value), $fname);
1375 $wgMemc->set( $key, 0 );
1377 } else if ($res === false && $this->mNewtalk
== 1) {
1378 $dbw->insert('user_newtalk', array($field => $value), $fname);
1380 $wgMemc->set( $key, 1 );
1387 # Update user_touched, so that newtalk notifications in the client cache are invalidated
1388 if ( $changed && $this->getID() ) {
1389 $dbw->update('user',
1390 /*SET*/ array( 'user_touched' => $this->mTouched
),
1391 /*WHERE*/ array( 'user_id' => $this->getID() ),
1393 $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
1398 * Checks if a user with the given name exists, returns the ID
1400 function idForName() {
1401 $fname = 'User::idForName';
1404 $s = trim( $this->getName() );
1405 if ( 0 == strcmp( '', $s ) ) return 0;
1407 $dbr =& wfGetDB( DB_SLAVE
);
1408 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1409 if ( $id === false ) {
1416 * Add user object to the database
1418 function addToDatabase() {
1419 $fname = 'User::addToDatabase';
1420 $dbw =& wfGetDB( DB_MASTER
);
1421 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1422 $dbw->insert( 'user',
1424 'user_id' => $seqVal,
1425 'user_name' => $this->mName
,
1426 'user_password' => $this->mPassword
,
1427 'user_newpassword' => $this->mNewpassword
,
1428 'user_email' => $this->mEmail
,
1429 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1430 'user_real_name' => $this->mRealName
,
1431 'user_options' => $this->encodeOptions(),
1432 'user_token' => $this->mToken
1435 $this->mId
= $dbw->insertId();
1438 function spreadBlock() {
1439 # If the (non-anonymous) user is blocked, this function will block any IP address
1440 # that they successfully log on from.
1441 $fname = 'User::spreadBlock';
1443 wfDebug( "User:spreadBlock()\n" );
1444 if ( $this->mId
== 0 ) {
1448 $userblock = Block
::newFromDB( '', $this->mId
);
1449 if ( !$userblock->isValid() ) {
1453 # Check if this IP address is already blocked
1454 $ipblock = Block
::newFromDB( wfGetIP() );
1455 if ( $ipblock->isValid() ) {
1456 # If the user is already blocked. Then check if the autoblock would
1457 # excede the user block. If it would excede, then do nothing, else
1458 # prolong block time
1459 if ($userblock->mExpiry
&&
1460 ($userblock->mExpiry
< Block
::getAutoblockExpiry($ipblock->mTimestamp
))) {
1463 # Just update the timestamp
1464 $ipblock->updateTimestamp();
1468 # Make a new block object with the desired properties
1469 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1470 $ipblock->mAddress
= wfGetIP();
1471 $ipblock->mUser
= 0;
1472 $ipblock->mBy
= $userblock->mBy
;
1473 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1474 $ipblock->mTimestamp
= wfTimestampNow();
1475 $ipblock->mAuto
= 1;
1476 # If the user is already blocked with an expiry date, we don't
1477 # want to pile on top of that!
1478 if($userblock->mExpiry
) {
1479 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1481 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1489 function getPageRenderingHash() {
1492 return $this->mHash
;
1495 // stubthreshold is only included below for completeness,
1496 // it will always be 0 when this function is called by parsercache.
1498 $confstr = $this->getOption( 'math' );
1499 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1500 $confstr .= '!' . $this->getOption( 'date' );
1501 $confstr .= '!' . $this->getOption( 'numberheadings' );
1502 $confstr .= '!' . $this->getOption( 'language' );
1503 $confstr .= '!' . $this->getOption( 'thumbsize' );
1504 // add in language specific options, if any
1505 $extra = $wgContLang->getExtraHashOptions();
1508 $this->mHash
= $confstr;
1512 function isAllowedToCreateAccount() {
1513 return $this->isAllowed( 'createaccount' );
1517 * Set mDataLoaded, return previous value
1518 * Use this to prevent DB access in command-line scripts or similar situations
1520 function setLoaded( $loaded ) {
1521 return wfSetVar( $this->mDataLoaded
, $loaded );
1525 * Get this user's personal page title.
1530 function getUserPage() {
1531 return Title
::makeTitle( NS_USER
, $this->getName() );
1535 * Get this user's talk page title.
1540 function getTalkPage() {
1541 $title = $this->getUserPage();
1542 return $title->getTalkPage();
1548 function getMaxID() {
1549 $dbr =& wfGetDB( DB_SLAVE
);
1550 return $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1554 * Determine whether the user is a newbie. Newbies are either
1555 * anonymous IPs, or the 1% most recently created accounts.
1556 * Bots and sysops are excluded.
1557 * @return bool True if it is a newbie.
1559 function isNewbie() {
1560 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1564 * Check to see if the given clear-text password is one of the accepted passwords
1565 * @param string $password User password.
1566 * @return bool True if the given password is correct otherwise False.
1568 function checkPassword( $password ) {
1569 global $wgAuth, $wgMinimalPasswordLength;
1570 $this->loadFromDatabase();
1572 // Even though we stop people from creating passwords that
1573 // are shorter than this, doesn't mean people wont be able
1574 // to. Certain authentication plugins do NOT want to save
1575 // domain passwords in a mysql database, so we should
1576 // check this (incase $wgAuth->strict() is false).
1577 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1581 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1583 } elseif( $wgAuth->strict() ) {
1584 /* Auth plugin doesn't allow local authentication */
1587 $ep = $this->encryptPassword( $password );
1588 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1590 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1592 } elseif ( function_exists( 'iconv' ) ) {
1593 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1594 # Check for this with iconv
1595 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1596 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1604 * Initialize (if necessary) and return a session token value
1605 * which can be used in edit forms to show that the user's
1606 * login credentials aren't being hijacked with a foreign form
1609 * @param mixed $salt - Optional function-specific data for hash.
1610 * Use a string or an array of strings.
1614 function editToken( $salt = '' ) {
1615 if( !isset( $_SESSION['wsEditToken'] ) ) {
1616 $token = $this->generateToken();
1617 $_SESSION['wsEditToken'] = $token;
1619 $token = $_SESSION['wsEditToken'];
1621 if( is_array( $salt ) ) {
1622 $salt = implode( '|', $salt );
1624 return md5( $token . $salt );
1628 * Generate a hex-y looking random token for various uses.
1629 * Could be made more cryptographically sure if someone cares.
1632 function generateToken( $salt = '' ) {
1633 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1634 return md5( $token . $salt );
1638 * Check given value against the token value stored in the session.
1639 * A match should confirm that the form was submitted from the
1640 * user's own login session, not a form submission from a third-party
1643 * @param string $val - the input value to compare
1644 * @param string $salt - Optional function-specific data for hash
1648 function matchEditToken( $val, $salt = '' ) {
1652 if ( !isset( $_SESSION['wsEditToken'] ) ) {
1653 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1654 $mckey = memsess_key( session_id() );
1655 $uname = @posix_uname();
1656 $msg = "wsEditToken not set!\n" .
1657 'apache server=' . $uname['nodename'] . "\n" .
1658 'session_id = ' . session_id() . "\n" .
1659 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1660 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1661 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1663 @error_log( $msg, 3, $logfile );
1666 return ( $val == $this->editToken( $salt ) );
1670 * Generate a new e-mail confirmation token and send a confirmation
1671 * mail to the user's given address.
1673 * @return mixed True on success, a WikiError object on failure.
1675 function sendConfirmationMail() {
1677 $url = $this->confirmationTokenUrl( $expiration );
1678 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1679 wfMsg( 'confirmemail_body',
1683 $wgContLang->timeanddate( $expiration, false ) ) );
1687 * Send an e-mail to this user's account. Does not check for
1688 * confirmed status or validity.
1690 * @param string $subject
1691 * @param string $body
1692 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1693 * @return mixed True on success, a WikiError object on failure.
1695 function sendMail( $subject, $body, $from = null ) {
1696 if( is_null( $from ) ) {
1697 global $wgPasswordSender;
1698 $from = $wgPasswordSender;
1701 require_once( 'UserMailer.php' );
1702 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1704 if( $error == '' ) {
1707 return new WikiError( $error );
1712 * Generate, store, and return a new e-mail confirmation code.
1713 * A hash (unsalted since it's used as a key) is stored.
1714 * @param &$expiration mixed output: accepts the expiration time
1718 function confirmationToken( &$expiration ) {
1719 $fname = 'User::confirmationToken';
1722 $expires = $now +
7 * 24 * 60 * 60;
1723 $expiration = wfTimestamp( TS_MW
, $expires );
1725 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1726 $hash = md5( $token );
1728 $dbw =& wfGetDB( DB_MASTER
);
1729 $dbw->update( 'user',
1730 array( 'user_email_token' => $hash,
1731 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1732 array( 'user_id' => $this->mId
),
1739 * Generate and store a new e-mail confirmation token, and return
1740 * the URL the user can use to confirm.
1741 * @param &$expiration mixed output: accepts the expiration time
1745 function confirmationTokenUrl( &$expiration ) {
1746 $token = $this->confirmationToken( $expiration );
1747 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1748 return $title->getFullUrl();
1752 * Mark the e-mail address confirmed and save.
1754 function confirmEmail() {
1755 $this->loadFromDatabase();
1756 $this->mEmailAuthenticated
= wfTimestampNow();
1757 $this->saveSettings();
1762 * Is this user allowed to send e-mails within limits of current
1763 * site configuration?
1766 function canSendEmail() {
1767 return $this->isEmailConfirmed();
1771 * Is this user allowed to receive e-mails within limits of current
1772 * site configuration?
1775 function canReceiveEmail() {
1776 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1780 * Is this user's e-mail address valid-looking and confirmed within
1781 * limits of the current site configuration?
1783 * If $wgEmailAuthentication is on, this may require the user to have
1784 * confirmed their address by returning a code or using a password
1785 * sent to the address from the wiki.
1789 function isEmailConfirmed() {
1790 global $wgEmailAuthentication;
1791 $this->loadFromDatabase();
1792 if( $this->isAnon() )
1794 if( !$this->isValidEmailAddr( $this->mEmail
) )
1796 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1802 * @param array $groups list of groups
1803 * @return array list of permission key names for given groups combined
1806 function getGroupPermissions( $groups ) {
1807 global $wgGroupPermissions;
1809 foreach( $groups as $group ) {
1810 if( isset( $wgGroupPermissions[$group] ) ) {
1811 $rights = array_merge( $rights,
1812 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1819 * @param string $group key name
1820 * @return string localized descriptive name, if provided
1823 function getGroupName( $group ) {
1824 $key = "group-$group-name";
1825 $name = wfMsg( $key );
1826 if( $name == '' ||
$name == "<$key>" ) {
1834 * Return the set of defined explicit groups.
1835 * The * and 'user' groups are not included.
1839 function getAllGroups() {
1840 global $wgGroupPermissions;
1842 array_keys( $wgGroupPermissions ),
1843 array( '*', 'user' ) );