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 ) )
205 // Ensure that the name can't be misresolved as a different title,
206 // such as with extra namespace keys at the start.
207 $parsed = Title
::newFromText( $name );
208 if( is_null( $parsed )
209 ||
$parsed->getNamespace()
210 ||
strcmp( $name, $parsed->getPrefixedText() ) )
217 * Is the input a valid password?
219 * @param string $password
223 function isValidPassword( $password ) {
224 global $wgMinimalPasswordLength;
225 return strlen( $password ) >= $wgMinimalPasswordLength;
229 * Does the string match roughly an email address ?
231 * There used to be a regular expression here, it got removed because it
232 * rejected valid addresses. Actually just check if there is '@' somewhere
233 * in the given address.
235 * @todo Check for RFC 2822 compilance
238 * @param string $addr email address
242 function isValidEmailAddr ( $addr ) {
243 return ( trim( $addr ) != '' ) &&
244 (false !== strpos( $addr, '@' ) );
248 * Count the number of edits of a user
250 * @param int $uid The user ID to check
253 function edits( $uid ) {
254 $fname = 'User::edits';
256 $dbr =& wfGetDB( DB_SLAVE
);
257 return $dbr->selectField(
258 'revision', 'count(*)',
259 array( 'rev_user' => $uid ),
265 * probably return a random password
266 * @return string probably a random password
268 * @todo Check what is doing really [AV]
270 function randomPassword() {
271 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
272 $l = strlen( $pwchars ) - 1;
274 $np = $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
275 $pwchars{mt_rand( 0, $l )} . chr( mt_rand(48, 57) ) .
276 $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
277 $pwchars{mt_rand( 0, $l )};
282 * Set properties to default
283 * Used at construction. It will load per language default settings only
284 * if we have an available language object.
286 function loadDefaults() {
289 $fname = 'User::loadDefaults' . $n;
290 wfProfileIn( $fname );
292 global $wgContLang, $wgDBname;
293 global $wgNamespacesToBeSearchedDefault;
296 $this->mNewtalk
= -1;
297 $this->mName
= false;
298 $this->mRealName
= $this->mEmail
= '';
299 $this->mEmailAuthenticated
= null;
300 $this->mPassword
= $this->mNewpassword
= '';
301 $this->mRights
= array();
302 $this->mGroups
= array();
303 $this->mOptions
= User
::getDefaultOptions();
305 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
306 $this->mOptions
['searchNs'.$nsnum] = $val;
308 unset( $this->mSkin
);
309 $this->mDataLoaded
= false;
310 $this->mBlockedby
= -1; # Unset
311 $this->setToken(); # Random
312 $this->mHash
= false;
314 if ( isset( $_COOKIE[$wgDBname.'LoggedOut'] ) ) {
315 $this->mTouched
= wfTimestamp( TS_MW
, $_COOKIE[$wgDBname.'LoggedOut'] );
318 $this->mTouched
= '0'; # Allow any pages to be cached
321 wfProfileOut( $fname );
325 * Combine the language default options with any site-specific options
326 * and add the default language variants.
332 function getDefaultOptions() {
334 * Site defaults will override the global/language defaults
336 global $wgContLang, $wgDefaultUserOptions;
337 $defOpt = $wgDefaultUserOptions +
$wgContLang->getDefaultUserOptions();
340 * default language setting
342 $variant = $wgContLang->getPreferredVariant();
343 $defOpt['variant'] = $variant;
344 $defOpt['language'] = $variant;
350 * Get a given default option value.
357 function getDefaultOption( $opt ) {
358 $defOpts = User
::getDefaultOptions();
359 if( isset( $defOpts[$opt] ) ) {
360 return $defOpts[$opt];
367 * Get blocking information
369 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
370 * non-critical checks are done against slaves. Check when actually saving should be done against
373 * Note that even if $bFromSlave is false, the check is done first against slave, then master.
374 * The logic is that if blocked on slave, we'll assume it's either blocked on master or
375 * just slightly outta sync and soon corrected - safer to block slightly more that less.
376 * And it's cheaper to check slave first, then master if needed, than master always.
378 function getBlockedStatus( $bFromSlave = true ) {
379 global $wgBlockCache, $wgProxyList, $wgEnableSorbs, $wgProxyWhitelist;
381 if ( -1 != $this->mBlockedby
) {
382 wfDebug( "User::getBlockedStatus: already loaded.\n" );
386 $fname = 'User::getBlockedStatus';
387 wfProfileIn( $fname );
388 wfDebug( "$fname: checking...\n" );
390 $this->mBlockedby
= 0;
394 $block = new Block();
395 $block->forUpdate( $bFromSlave );
396 if ( $block->load( $ip , $this->mId
) ) {
397 wfDebug( "$fname: Found block.\n" );
398 $this->mBlockedby
= $block->mBy
;
399 $this->mBlockreason
= $block->mReason
;
400 if ( $this->isLoggedIn() ) {
401 $this->spreadBlock();
404 wfDebug( "$fname: No block.\n" );
408 if ( !$this->mBlockedby
) {
409 # Check first against slave, and optionally from master.
410 wfDebug( "$fname: Checking range blocks\n" );
411 $block = $wgBlockCache->get( $ip, true );
412 if ( !$block && !$bFromSlave )
414 # Not blocked: check against master, to make sure.
415 $wgBlockCache->clearLocal( );
416 $block = $wgBlockCache->get( $ip, false );
418 if ( $block !== false ) {
419 $this->mBlockedby
= $block->mBy
;
420 $this->mBlockreason
= $block->mReason
;
425 if ( !$this->isSysop() && !in_array( $ip, $wgProxyWhitelist ) ) {
428 if ( array_key_exists( $ip, $wgProxyList ) ) {
429 $this->mBlockedby
= wfMsg( 'proxyblocker' );
430 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
434 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
435 if ( $this->inSorbsBlacklist( $ip ) ) {
436 $this->mBlockedby
= wfMsg( 'sorbs' );
437 $this->mBlockreason
= wfMsg( 'sorbsreason' );
443 wfRunHooks( 'GetBlockedStatus', array( &$this ) );
445 wfProfileOut( $fname );
448 function inSorbsBlacklist( $ip ) {
449 global $wgEnableSorbs;
450 return $wgEnableSorbs &&
451 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
454 function inOpmBlacklist( $ip ) {
456 return $wgEnableOpm &&
457 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
460 function inDnsBlacklist( $ip, $base ) {
461 $fname = 'User::inDnsBlacklist';
462 wfProfileIn( $fname );
467 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
469 for ( $i=4; $i>=1; $i-- ) {
470 $host .= $m[$i] . '.';
475 $ipList = gethostbynamel( $host );
478 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
481 wfDebug( "Requested $host, not found in $base.\n" );
485 wfProfileOut( $fname );
490 * Primitive rate limits: enforce maximum actions per time period
491 * to put a brake on flooding.
493 * Note: when using a shared cache like memcached, IP-address
494 * last-hit counters will be shared across wikis.
496 * @return bool true if a rate limiter was tripped
499 function pingLimiter( $action='edit' ) {
500 global $wgRateLimits;
501 if( !isset( $wgRateLimits[$action] ) ) {
504 if( $this->isAllowed( 'delete' ) ) {
509 global $wgMemc, $wgDBname, $wgRateLimitLog;
510 $fname = 'User::pingLimiter';
511 wfProfileIn( $fname );
513 $limits = $wgRateLimits[$action];
515 $id = $this->getId();
518 if( isset( $limits['anon'] ) && $id == 0 ) {
519 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
522 if( isset( $limits['user'] ) && $id != 0 ) {
523 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
525 if( $this->isNewbie() ) {
526 if( isset( $limits['newbie'] ) && $id != 0 ) {
527 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
529 if( isset( $limits['ip'] ) ) {
530 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
532 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
533 $subnet = $matches[1];
534 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
539 foreach( $keys as $key => $limit ) {
540 list( $max, $period ) = $limit;
541 $summary = "(limit $max in {$period}s)";
542 $count = $wgMemc->get( $key );
544 if( $count > $max ) {
545 wfDebug( "$fname: tripped! $key at $count $summary\n" );
546 if( $wgRateLimitLog ) {
547 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
551 wfDebug( "$fname: ok. $key at $count $summary\n" );
554 wfDebug( "$fname: adding record for $key $summary\n" );
555 $wgMemc->add( $key, 1, intval( $period ) );
557 $wgMemc->incr( $key );
560 wfProfileOut( $fname );
565 * Check if user is blocked
566 * @return bool True if blocked, false otherwise
568 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
569 wfDebug( "User::isBlocked: enter\n" );
570 $this->getBlockedStatus( $bFromSlave );
571 return $this->mBlockedby
!== 0;
575 * Check if user is blocked from editing a particular article
577 function isBlockedFrom( $title, $bFromSlave = false ) {
578 global $wgBlockAllowsUTEdit;
579 $fname = 'User::isBlockedFrom';
580 wfProfileIn( $fname );
581 wfDebug( "$fname: enter\n" );
583 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
584 $title->getNamespace() == NS_USER_TALK
)
587 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
589 wfDebug( "$fname: asking isBlocked()\n" );
590 $blocked = $this->isBlocked( $bFromSlave );
592 wfProfileOut( $fname );
597 * Get name of blocker
598 * @return string name of blocker
600 function blockedBy() {
601 $this->getBlockedStatus();
602 return $this->mBlockedby
;
606 * Get blocking reason
607 * @return string Blocking reason
609 function blockedFor() {
610 $this->getBlockedStatus();
611 return $this->mBlockreason
;
615 * Initialise php session
617 function SetupSession() {
618 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
619 if( $wgSessionsInMemcached ) {
620 require_once( 'MemcachedSessions.php' );
621 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
622 # If it's left on 'user' or another setting from another
623 # application, it will end up failing. Try to recover.
624 ini_set ( 'session.save_handler', 'files' );
626 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
627 session_cache_limiter( 'private, must-revalidate' );
632 * Create a new user object using data from session
635 function loadFromSession() {
636 global $wgMemc, $wgDBname;
638 if ( isset( $_SESSION['wsUserID'] ) ) {
639 if ( 0 != $_SESSION['wsUserID'] ) {
640 $sId = $_SESSION['wsUserID'];
644 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
645 $sId = intval( $_COOKIE["{$wgDBname}UserID"] );
646 $_SESSION['wsUserID'] = $sId;
650 if ( isset( $_SESSION['wsUserName'] ) ) {
651 $sName = $_SESSION['wsUserName'];
652 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
653 $sName = $_COOKIE["{$wgDBname}UserName"];
654 $_SESSION['wsUserName'] = $sName;
659 $passwordCorrect = FALSE;
660 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
661 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
662 # Expire old serialized objects; they may be corrupt.
665 if($makenew = !$user) {
666 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
669 $user->loadFromDatabase();
671 wfDebug( "User::loadFromSession() got from cache!\n" );
674 if ( isset( $_SESSION['wsToken'] ) ) {
675 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
676 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
677 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
679 return new User(); # Can't log in from session
682 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
684 if($wgMemc->set( $key, $user ))
685 wfDebug( "User::loadFromSession() successfully saved user\n" );
687 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
691 return new User(); # Can't log in from session
695 * Load a user from the database
697 function loadFromDatabase() {
698 global $wgCommandLineMode;
699 $fname = "User::loadFromDatabase";
701 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
702 # loading in a command line script, don't assume all command line scripts need it like this
703 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
704 if ( $this->mDataLoaded
) {
709 $this->mId
= intval( $this->mId
);
711 /** Anonymous user */
714 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
715 $this->mDataLoaded
= true;
717 } # the following stuff is for non-anonymous users only
719 $dbr =& wfGetDB( DB_SLAVE
);
720 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
721 'user_email_authenticated',
722 'user_real_name','user_options','user_touched', 'user_token' ),
723 array( 'user_id' => $this->mId
), $fname );
725 if ( $s !== false ) {
726 $this->mName
= $s->user_name
;
727 $this->mEmail
= $s->user_email
;
728 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
729 $this->mRealName
= $s->user_real_name
;
730 $this->mPassword
= $s->user_password
;
731 $this->mNewpassword
= $s->user_newpassword
;
732 $this->decodeOptions( $s->user_options
);
733 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
734 $this->mToken
= $s->user_token
;
736 $res = $dbr->select( 'user_groups',
738 array( 'ug_user' => $this->mId
),
740 $this->mGroups
= array();
741 while( $row = $dbr->fetchObject( $res ) ) {
742 $this->mGroups
[] = $row->ug_group
;
744 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
745 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
748 $this->mDataLoaded
= true;
751 function getID() { return $this->mId
; }
752 function setID( $v ) {
754 $this->mDataLoaded
= false;
758 $this->loadFromDatabase();
759 if ( $this->mName
=== false ) {
760 $this->mName
= wfGetIP();
765 function setName( $str ) {
766 $this->loadFromDatabase();
772 * Return the title dbkey form of the name, for eg user pages.
776 function getTitleKey() {
777 return str_replace( ' ', '_', $this->getName() );
780 function getNewtalk() {
782 $fname = 'User::getNewtalk';
783 $this->loadFromDatabase();
785 # Load the newtalk status if it is unloaded (mNewtalk=-1)
786 if( $this->mNewtalk
== -1 ) {
787 $this->mNewtalk
= 0; # reset talk page status
789 # Check memcached separately for anons, who have no
790 # entire User object stored in there.
792 global $wgDBname, $wgMemc;
793 $key = "$wgDBname:newtalk:ip:" . $this->getName();
794 $newtalk = $wgMemc->get( $key );
795 if( is_integer( $newtalk ) ) {
796 $this->mNewtalk
= $newtalk ?
1 : 0;
797 return (bool)$this->mNewtalk
;
801 $dbr =& wfGetDB( DB_SLAVE
);
802 if ( $wgUseEnotif ) {
803 $res = $dbr->select( 'watchlist',
805 array( 'wl_title' => $this->getTitleKey(),
806 'wl_namespace' => NS_USER_TALK
,
807 'wl_user' => $this->mId
,
808 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
809 'User::getNewtalk' );
810 if( $dbr->numRows($res) > 0 ) {
813 $dbr->freeResult( $res );
814 } elseif ( $this->mId
) {
815 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
817 if ( $dbr->numRows($res)>0 ) {
820 $dbr->freeResult( $res );
822 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->getName() ), $fname );
823 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
824 $dbr->freeResult( $res );
828 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
832 return ( 0 != $this->mNewtalk
);
835 function setNewtalk( $val ) {
836 $this->loadFromDatabase();
837 $this->mNewtalk
= $val;
838 $this->invalidateCache();
841 function invalidateCache() {
842 global $wgClockSkewFudge;
843 $this->loadFromDatabase();
844 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
845 # Don't forget to save the options after this or
846 # it won't take effect!
849 function validateCache( $timestamp ) {
850 $this->loadFromDatabase();
851 return ($timestamp >= $this->mTouched
);
855 * Encrypt a password.
856 * It can eventuall salt a password @see User::addSalt()
857 * @param string $p clear Password.
858 * @return string Encrypted password.
860 function encryptPassword( $p ) {
861 return wfEncryptPassword( $this->mId
, $p );
864 # Set the password and reset the random token
865 function setPassword( $str ) {
866 $this->loadFromDatabase();
868 $this->mPassword
= $this->encryptPassword( $str );
869 $this->mNewpassword
= '';
872 # Set the random token (used for persistent authentication)
873 function setToken( $token = false ) {
874 global $wgSecretKey, $wgProxyKey, $wgDBname;
876 if ( $wgSecretKey ) {
878 } elseif ( $wgProxyKey ) {
883 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
885 $this->mToken
= $token;
890 function setCookiePassword( $str ) {
891 $this->loadFromDatabase();
892 $this->mCookiePassword
= md5( $str );
895 function setNewpassword( $str ) {
896 $this->loadFromDatabase();
897 $this->mNewpassword
= $this->encryptPassword( $str );
900 function getEmail() {
901 $this->loadFromDatabase();
902 return $this->mEmail
;
905 function getEmailAuthenticationTimestamp() {
906 $this->loadFromDatabase();
907 return $this->mEmailAuthenticated
;
910 function setEmail( $str ) {
911 $this->loadFromDatabase();
912 $this->mEmail
= $str;
915 function getRealName() {
916 $this->loadFromDatabase();
917 return $this->mRealName
;
920 function setRealName( $str ) {
921 $this->loadFromDatabase();
922 $this->mRealName
= $str;
925 function getOption( $oname ) {
926 $this->loadFromDatabase();
927 if ( array_key_exists( $oname, $this->mOptions
) ) {
928 return trim( $this->mOptions
[$oname] );
934 function setOption( $oname, $val ) {
935 $this->loadFromDatabase();
936 if ( $oname == 'skin' ) {
937 # Clear cached skin, so the new one displays immediately in Special:Preferences
938 unset( $this->mSkin
);
940 $this->mOptions
[$oname] = $val;
941 $this->invalidateCache();
944 function getRights() {
945 $this->loadFromDatabase();
946 return $this->mRights
;
950 * Get the list of explicit group memberships this user has.
951 * The implicit * and user groups are not included.
952 * @return array of strings
954 function getGroups() {
955 $this->loadFromDatabase();
956 return $this->mGroups
;
960 * Get the list of implicit group memberships this user has.
961 * This includes all explicit groups, plus 'user' if logged in
962 * and '*' for all accounts.
963 * @return array of strings
965 function getEffectiveGroups() {
966 $base = array( '*' );
967 if( $this->isLoggedIn() ) {
970 return array_merge( $base, $this->getGroups() );
974 * Remove the user from the given group.
975 * This takes immediate effect.
978 function addGroup( $group ) {
979 $dbw =& wfGetDB( DB_MASTER
);
980 $dbw->insert( 'user_groups',
982 'ug_user' => $this->getID(),
983 'ug_group' => $group,
988 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
989 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
991 $this->invalidateCache();
992 $this->saveSettings();
996 * Remove the user from the given group.
997 * This takes immediate effect.
1000 function removeGroup( $group ) {
1001 $dbw =& wfGetDB( DB_MASTER
);
1002 $dbw->delete( 'user_groups',
1004 'ug_user' => $this->getID(),
1005 'ug_group' => $group,
1007 'User::removeGroup' );
1009 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
1010 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
1012 $this->invalidateCache();
1013 $this->saveSettings();
1018 * A more legible check for non-anonymousness.
1019 * Returns true if the user is not an anonymous visitor.
1023 function isLoggedIn() {
1024 return( $this->getID() != 0 );
1028 * A more legible check for anonymousness.
1029 * Returns true if the user is an anonymous visitor.
1034 return !$this->isLoggedIn();
1038 * Check if a user is sysop
1041 function isSysop() {
1042 return $this->isAllowed( 'protect' );
1046 function isDeveloper() {
1047 return $this->isAllowed( 'siteadmin' );
1051 function isBureaucrat() {
1052 return $this->isAllowed( 'makesysop' );
1056 * Whether the user is a bot
1057 * @todo need to be migrated to the new user level management sytem
1060 $this->loadFromDatabase();
1061 return in_array( 'bot', $this->mRights
);
1065 * Check if user is allowed to access a feature / make an action
1066 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1067 * @return boolean True: action is allowed, False: action should not be allowed
1069 function isAllowed($action='') {
1070 $this->loadFromDatabase();
1071 return in_array( $action , $this->mRights
);
1075 * Load a skin if it doesn't exist or return it
1076 * @todo FIXME : need to check the old failback system [AV]
1078 function &getSkin() {
1079 global $IP, $wgRequest;
1080 if ( ! isset( $this->mSkin
) ) {
1081 $fname = 'User::getSkin';
1082 wfProfileIn( $fname );
1084 # get all skin names available
1085 $skinNames = Skin
::getSkinNames();
1088 $userSkin = $this->getOption( 'skin' );
1089 $userSkin = $wgRequest->getText('useskin', $userSkin);
1090 if ( $userSkin == '' ) { $userSkin = 'standard'; }
1092 if ( !isset( $skinNames[$userSkin] ) ) {
1093 # in case the user skin could not be found find a replacement
1097 2 => 'CologneBlue');
1098 # if phptal is enabled we should have monobook skin that
1099 # superseed the good old SkinStandard.
1100 if ( isset( $skinNames['monobook'] ) ) {
1101 $fallback[0] = 'MonoBook';
1104 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
1105 $sn = $fallback[$userSkin];
1110 # The user skin is available
1111 $sn = $skinNames[$userSkin];
1114 # Grab the skin class and initialise it. Each skin checks for PHPTal
1115 # and will not load if it's not enabled.
1116 require_once( $IP.'/skins/'.$sn.'.php' );
1118 # Check if we got if not failback to default skin
1119 $className = 'Skin'.$sn;
1120 if( !class_exists( $className ) ) {
1121 # DO NOT die if the class isn't found. This breaks maintenance
1122 # scripts and can cause a user account to be unrecoverable
1123 # except by SQL manipulation if a previously valid skin name
1124 # is no longer valid.
1125 $className = 'SkinStandard';
1126 require_once( $IP.'/skins/Standard.php' );
1128 $this->mSkin
=& new $className;
1129 wfProfileOut( $fname );
1131 return $this->mSkin
;
1135 * @param string $title Article title to look at
1139 * Check watched status of an article
1140 * @return bool True if article is watched
1142 function isWatched( $title ) {
1143 $wl = WatchedItem
::fromUserTitle( $this, $title );
1144 return $wl->isWatched();
1150 function addWatch( $title ) {
1151 $wl = WatchedItem
::fromUserTitle( $this, $title );
1153 $this->invalidateCache();
1157 * Stop watching an article
1159 function removeWatch( $title ) {
1160 $wl = WatchedItem
::fromUserTitle( $this, $title );
1162 $this->invalidateCache();
1166 * Clear the user's notification timestamp for the given title.
1167 * If e-notif e-mails are on, they will receive notification mails on
1168 * the next change of the page if it's watched etc.
1170 function clearNotification( &$title ) {
1171 global $wgUser, $wgUseEnotif;
1173 if ( !$wgUseEnotif ) {
1177 $userid = $this->getID();
1182 // Only update the timestamp if the page is being watched.
1183 // The query to find out if it is watched is cached both in memcached and per-invocation,
1184 // and when it does have to be executed, it can be on a slave
1185 // If this is the user's newtalk page, we always update the timestamp
1186 if ($title->getNamespace() == NS_USER_TALK
&&
1187 $title->getText() == $wgUser->getName())
1190 } elseif ( $this->getID() == $wgUser->getID() ) {
1191 $watched = $title->userIsWatching();
1196 // If the page is watched by the user (or may be watched), update the timestamp on any
1197 // any matching rows
1199 $dbw =& wfGetDB( DB_MASTER
);
1200 $success = $dbw->update( 'watchlist',
1202 'wl_notificationtimestamp' => NULL
1203 ), array( /* WHERE */
1204 'wl_title' => $title->getDBkey(),
1205 'wl_namespace' => $title->getNamespace(),
1206 'wl_user' => $this->getID()
1207 ), 'User::clearLastVisited'
1215 * Resets all of the given user's page-change notification timestamps.
1216 * If e-notif e-mails are on, they will receive notification mails on
1217 * the next change of any watched page.
1219 * @param int $currentUser user ID number
1222 function clearAllNotifications( $currentUser ) {
1223 global $wgUseEnotif;
1224 if ( !$wgUseEnotif ) {
1227 if( $currentUser != 0 ) {
1229 $dbw =& wfGetDB( DB_MASTER
);
1230 $success = $dbw->update( 'watchlist',
1232 'wl_notificationtimestamp' => 0
1233 ), array( /* WHERE */
1234 'wl_user' => $currentUser
1235 ), 'UserMailer::clearAll'
1238 # we also need to clear here the "you have new message" notification for the own user_talk page
1239 # This is cleared one page view later in Article::viewUpdates();
1245 * @return string Encoding options
1247 function encodeOptions() {
1249 foreach ( $this->mOptions
as $oname => $oval ) {
1250 array_push( $a, $oname.'='.$oval );
1252 $s = implode( "\n", $a );
1259 function decodeOptions( $str ) {
1260 $a = explode( "\n", $str );
1261 foreach ( $a as $s ) {
1262 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1263 $this->mOptions
[$m[1]] = $m[2];
1268 function setCookies() {
1269 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
1270 if ( 0 == $this->mId
) return;
1271 $this->loadFromDatabase();
1272 $exp = time() +
$wgCookieExpiration;
1274 $_SESSION['wsUserID'] = $this->mId
;
1275 setcookie( $wgDBname.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain );
1277 $_SESSION['wsUserName'] = $this->getName();
1278 setcookie( $wgDBname.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain );
1280 $_SESSION['wsToken'] = $this->mToken
;
1281 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1282 setcookie( $wgDBname.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain );
1284 setcookie( $wgDBname.'Token', '', time() - 3600 );
1290 * It will clean the session cookie
1293 global $wgCookiePath, $wgCookieDomain, $wgDBname;
1294 $this->loadDefaults();
1295 $this->setLoaded( true );
1297 $_SESSION['wsUserID'] = 0;
1299 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1300 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1302 # Remember when user logged out, to prevent seeing cached pages
1303 setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain );
1307 * Save object settings into database
1309 function saveSettings() {
1310 global $wgMemc, $wgDBname, $wgUseEnotif;
1311 $fname = 'User::saveSettings';
1313 if ( wfReadOnly() ) { return; }
1314 $this->saveNewtalk();
1315 if ( 0 == $this->mId
) { return; }
1317 $dbw =& wfGetDB( DB_MASTER
);
1318 $dbw->update( 'user',
1320 'user_name' => $this->mName
,
1321 'user_password' => $this->mPassword
,
1322 'user_newpassword' => $this->mNewpassword
,
1323 'user_real_name' => $this->mRealName
,
1324 'user_email' => $this->mEmail
,
1325 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1326 'user_options' => $this->encodeOptions(),
1327 'user_touched' => $dbw->timestamp($this->mTouched
),
1328 'user_token' => $this->mToken
1329 ), array( /* WHERE */
1330 'user_id' => $this->mId
1333 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1337 * Save value of new talk flag.
1339 function saveNewtalk() {
1340 global $wgDBname, $wgMemc, $wgUseEnotif;
1342 $fname = 'User::saveNewtalk';
1346 if ( wfReadOnly() ) { return ; }
1347 $dbr =& wfGetDB( DB_SLAVE
);
1348 $dbw =& wfGetDB( DB_MASTER
);
1350 if ( $wgUseEnotif ) {
1351 if ( ! $this->getNewtalk() ) {
1352 # Delete the watchlist entry for user_talk page X watched by user X
1353 $dbw->delete( 'watchlist',
1354 array( 'wl_user' => $this->mId
,
1355 'wl_title' => $this->getTitleKey(),
1356 'wl_namespace' => NS_USER_TALK
),
1358 if ( $dbw->affectedRows() ) {
1362 # Anon users have a separate memcache space for newtalk
1363 # since they don't store their own info. Trim...
1364 $wgMemc->delete( "$wgDBname:newtalk:ip:" . $this->getName() );
1368 if ($this->getID() != 0) {
1370 $value = $this->getID();
1374 $value = $this->getName();
1375 $key = "$wgDBname:newtalk:ip:$value";
1378 $dbr =& wfGetDB( DB_SLAVE
);
1379 $dbw =& wfGetDB( DB_MASTER
);
1381 $res = $dbr->selectField('user_newtalk', $field,
1382 array($field => $value), $fname);
1385 if ($res !== false && $this->mNewtalk
== 0) {
1386 $dbw->delete('user_newtalk', array($field => $value), $fname);
1388 $wgMemc->set( $key, 0 );
1390 } else if ($res === false && $this->mNewtalk
== 1) {
1391 $dbw->insert('user_newtalk', array($field => $value), $fname);
1393 $wgMemc->set( $key, 1 );
1400 # Update user_touched, so that newtalk notifications in the client cache are invalidated
1401 if ( $changed && $this->getID() ) {
1402 $dbw->update('user',
1403 /*SET*/ array( 'user_touched' => $this->mTouched
),
1404 /*WHERE*/ array( 'user_id' => $this->getID() ),
1406 $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
1411 * Checks if a user with the given name exists, returns the ID
1413 function idForName() {
1414 $fname = 'User::idForName';
1417 $s = trim( $this->getName() );
1418 if ( 0 == strcmp( '', $s ) ) return 0;
1420 $dbr =& wfGetDB( DB_SLAVE
);
1421 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1422 if ( $id === false ) {
1429 * Add user object to the database
1431 function addToDatabase() {
1432 $fname = 'User::addToDatabase';
1433 $dbw =& wfGetDB( DB_MASTER
);
1434 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1435 $dbw->insert( 'user',
1437 'user_id' => $seqVal,
1438 'user_name' => $this->mName
,
1439 'user_password' => $this->mPassword
,
1440 'user_newpassword' => $this->mNewpassword
,
1441 'user_email' => $this->mEmail
,
1442 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1443 'user_real_name' => $this->mRealName
,
1444 'user_options' => $this->encodeOptions(),
1445 'user_token' => $this->mToken
1448 $this->mId
= $dbw->insertId();
1451 function spreadBlock() {
1452 # If the (non-anonymous) user is blocked, this function will block any IP address
1453 # that they successfully log on from.
1454 $fname = 'User::spreadBlock';
1456 wfDebug( "User:spreadBlock()\n" );
1457 if ( $this->mId
== 0 ) {
1461 $userblock = Block
::newFromDB( '', $this->mId
);
1462 if ( !$userblock->isValid() ) {
1466 # Check if this IP address is already blocked
1467 $ipblock = Block
::newFromDB( wfGetIP() );
1468 if ( $ipblock->isValid() ) {
1469 # If the user is already blocked. Then check if the autoblock would
1470 # excede the user block. If it would excede, then do nothing, else
1471 # prolong block time
1472 if ($userblock->mExpiry
&&
1473 ($userblock->mExpiry
< Block
::getAutoblockExpiry($ipblock->mTimestamp
))) {
1476 # Just update the timestamp
1477 $ipblock->updateTimestamp();
1481 # Make a new block object with the desired properties
1482 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1483 $ipblock->mAddress
= wfGetIP();
1484 $ipblock->mUser
= 0;
1485 $ipblock->mBy
= $userblock->mBy
;
1486 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1487 $ipblock->mTimestamp
= wfTimestampNow();
1488 $ipblock->mAuto
= 1;
1489 # If the user is already blocked with an expiry date, we don't
1490 # want to pile on top of that!
1491 if($userblock->mExpiry
) {
1492 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1494 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1502 function getPageRenderingHash() {
1505 return $this->mHash
;
1508 // stubthreshold is only included below for completeness,
1509 // it will always be 0 when this function is called by parsercache.
1511 $confstr = $this->getOption( 'math' );
1512 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1513 $confstr .= '!' . $this->getOption( 'date' );
1514 $confstr .= '!' . ($this->getOption( 'numberheadings' ) ?
'1' : '');
1515 $confstr .= '!' . $this->getOption( 'language' );
1516 $confstr .= '!' . $this->getOption( 'thumbsize' );
1517 // add in language specific options, if any
1518 $extra = $wgContLang->getExtraHashOptions();
1521 $this->mHash
= $confstr;
1525 function isAllowedToCreateAccount() {
1526 return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
1530 * Set mDataLoaded, return previous value
1531 * Use this to prevent DB access in command-line scripts or similar situations
1533 function setLoaded( $loaded ) {
1534 return wfSetVar( $this->mDataLoaded
, $loaded );
1538 * Get this user's personal page title.
1543 function getUserPage() {
1544 return Title
::makeTitle( NS_USER
, $this->getName() );
1548 * Get this user's talk page title.
1553 function getTalkPage() {
1554 $title = $this->getUserPage();
1555 return $title->getTalkPage();
1561 function getMaxID() {
1562 $dbr =& wfGetDB( DB_SLAVE
);
1563 return $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1567 * Determine whether the user is a newbie. Newbies are either
1568 * anonymous IPs, or the 1% most recently created accounts.
1569 * Bots and sysops are excluded.
1570 * @return bool True if it is a newbie.
1572 function isNewbie() {
1573 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1577 * Check to see if the given clear-text password is one of the accepted passwords
1578 * @param string $password User password.
1579 * @return bool True if the given password is correct otherwise False.
1581 function checkPassword( $password ) {
1582 global $wgAuth, $wgMinimalPasswordLength;
1583 $this->loadFromDatabase();
1585 // Even though we stop people from creating passwords that
1586 // are shorter than this, doesn't mean people wont be able
1587 // to. Certain authentication plugins do NOT want to save
1588 // domain passwords in a mysql database, so we should
1589 // check this (incase $wgAuth->strict() is false).
1590 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1594 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1596 } elseif( $wgAuth->strict() ) {
1597 /* Auth plugin doesn't allow local authentication */
1600 $ep = $this->encryptPassword( $password );
1601 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1603 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1605 } elseif ( function_exists( 'iconv' ) ) {
1606 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1607 # Check for this with iconv
1608 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1609 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1617 * Initialize (if necessary) and return a session token value
1618 * which can be used in edit forms to show that the user's
1619 * login credentials aren't being hijacked with a foreign form
1622 * @param mixed $salt - Optional function-specific data for hash.
1623 * Use a string or an array of strings.
1627 function editToken( $salt = '' ) {
1628 if( !isset( $_SESSION['wsEditToken'] ) ) {
1629 $token = $this->generateToken();
1630 $_SESSION['wsEditToken'] = $token;
1632 $token = $_SESSION['wsEditToken'];
1634 if( is_array( $salt ) ) {
1635 $salt = implode( '|', $salt );
1637 return md5( $token . $salt );
1641 * Generate a hex-y looking random token for various uses.
1642 * Could be made more cryptographically sure if someone cares.
1645 function generateToken( $salt = '' ) {
1646 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1647 return md5( $token . $salt );
1651 * Check given value against the token value stored in the session.
1652 * A match should confirm that the form was submitted from the
1653 * user's own login session, not a form submission from a third-party
1656 * @param string $val - the input value to compare
1657 * @param string $salt - Optional function-specific data for hash
1661 function matchEditToken( $val, $salt = '' ) {
1665 if ( !isset( $_SESSION['wsEditToken'] ) ) {
1666 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1667 $mckey = memsess_key( session_id() );
1668 $uname = @posix_uname();
1669 $msg = "wsEditToken not set!\n" .
1670 'apache server=' . $uname['nodename'] . "\n" .
1671 'session_id = ' . session_id() . "\n" .
1672 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1673 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1674 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1676 @error_log( $msg, 3, $logfile );
1679 return ( $val == $this->editToken( $salt ) );
1683 * Generate a new e-mail confirmation token and send a confirmation
1684 * mail to the user's given address.
1686 * @return mixed True on success, a WikiError object on failure.
1688 function sendConfirmationMail() {
1690 $url = $this->confirmationTokenUrl( $expiration );
1691 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1692 wfMsg( 'confirmemail_body',
1696 $wgContLang->timeanddate( $expiration, false ) ) );
1700 * Send an e-mail to this user's account. Does not check for
1701 * confirmed status or validity.
1703 * @param string $subject
1704 * @param string $body
1705 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1706 * @return mixed True on success, a WikiError object on failure.
1708 function sendMail( $subject, $body, $from = null ) {
1709 if( is_null( $from ) ) {
1710 global $wgPasswordSender;
1711 $from = $wgPasswordSender;
1714 require_once( 'UserMailer.php' );
1715 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1717 if( $error == '' ) {
1720 return new WikiError( $error );
1725 * Generate, store, and return a new e-mail confirmation code.
1726 * A hash (unsalted since it's used as a key) is stored.
1727 * @param &$expiration mixed output: accepts the expiration time
1731 function confirmationToken( &$expiration ) {
1732 $fname = 'User::confirmationToken';
1735 $expires = $now +
7 * 24 * 60 * 60;
1736 $expiration = wfTimestamp( TS_MW
, $expires );
1738 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1739 $hash = md5( $token );
1741 $dbw =& wfGetDB( DB_MASTER
);
1742 $dbw->update( 'user',
1743 array( 'user_email_token' => $hash,
1744 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1745 array( 'user_id' => $this->mId
),
1752 * Generate and store a new e-mail confirmation token, and return
1753 * the URL the user can use to confirm.
1754 * @param &$expiration mixed output: accepts the expiration time
1758 function confirmationTokenUrl( &$expiration ) {
1759 $token = $this->confirmationToken( $expiration );
1760 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1761 return $title->getFullUrl();
1765 * Mark the e-mail address confirmed and save.
1767 function confirmEmail() {
1768 $this->loadFromDatabase();
1769 $this->mEmailAuthenticated
= wfTimestampNow();
1770 $this->saveSettings();
1775 * Is this user allowed to send e-mails within limits of current
1776 * site configuration?
1779 function canSendEmail() {
1780 return $this->isEmailConfirmed();
1784 * Is this user allowed to receive e-mails within limits of current
1785 * site configuration?
1788 function canReceiveEmail() {
1789 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1793 * Is this user's e-mail address valid-looking and confirmed within
1794 * limits of the current site configuration?
1796 * If $wgEmailAuthentication is on, this may require the user to have
1797 * confirmed their address by returning a code or using a password
1798 * sent to the address from the wiki.
1802 function isEmailConfirmed() {
1803 global $wgEmailAuthentication;
1804 $this->loadFromDatabase();
1805 if( $this->isAnon() )
1807 if( !$this->isValidEmailAddr( $this->mEmail
) )
1809 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1815 * @param array $groups list of groups
1816 * @return array list of permission key names for given groups combined
1819 function getGroupPermissions( $groups ) {
1820 global $wgGroupPermissions;
1822 foreach( $groups as $group ) {
1823 if( isset( $wgGroupPermissions[$group] ) ) {
1824 $rights = array_merge( $rights,
1825 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1832 * @param string $group key name
1833 * @return string localized descriptive name, if provided
1836 function getGroupName( $group ) {
1837 $key = "group-$group-name";
1838 $name = wfMsg( $key );
1839 if( $name == '' ||
$name == "<$key>" ) {
1847 * Return the set of defined explicit groups.
1848 * The * and 'user' groups are not included.
1852 function getAllGroups() {
1853 global $wgGroupPermissions;
1855 array_keys( $wgGroupPermissions ),
1856 array( '*', 'user' ) );