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', 3 );
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
41 /** Construct using User:loadDefaults() */
43 $this->loadDefaults();
44 $this->mVersion
= MW_USER_VERSION
;
48 * Static factory method
49 * @param string $name Username, validated by Title:newFromText()
53 function newFromName( $name ) {
54 # Force usernames to capital
56 $name = $wgContLang->ucfirst( $name );
58 # Clean up name according to title rules
59 $t = Title
::newFromText( $name );
64 # Reject various classes of invalid names
65 $canonicalName = $t->getText();
67 $canonicalName = $wgAuth->getCanonicalName( $t->getText() );
69 if( !User
::isValidUserName( $canonicalName ) ) {
74 $u->setName( $canonicalName );
75 $u->setId( $u->idFromName( $canonicalName ) );
80 * Factory method to fetch whichever use has a given email confirmation code.
81 * This code is generated when an account is created or its e-mail address
84 * If the code is invalid or has expired, returns NULL.
90 function newFromConfirmationCode( $code ) {
91 $dbr =& wfGetDB( DB_SLAVE
);
92 $name = $dbr->selectField( 'user', 'user_name', array(
93 'user_email_token' => md5( $code ),
94 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
96 if( is_string( $name ) ) {
97 return User
::newFromName( $name );
104 * Serialze sleep function, for better cache efficiency and avoidance of
105 * silly "incomplete type" errors when skins are cached
108 return array( 'mId', 'mName', 'mPassword', 'mEmail', 'mNewtalk',
109 'mEmailAuthenticated', 'mRights', 'mOptions', 'mDataLoaded',
110 'mNewpassword', 'mBlockedby', 'mBlockreason', 'mTouched',
111 'mToken', 'mRealName', 'mHash', 'mGroups', 'mRegistration' );
115 * Get username given an id.
116 * @param integer $id Database user id
117 * @return string Nickname of a user
120 function whoIs( $id ) {
121 $dbr =& wfGetDB( DB_SLAVE
);
122 return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), 'User::whoIs' );
126 * Get real username given an id.
127 * @param integer $id Database user id
128 * @return string Realname of a user
131 function whoIsReal( $id ) {
132 $dbr =& wfGetDB( DB_SLAVE
);
133 return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), 'User::whoIsReal' );
137 * Get database id given a user name
138 * @param string $name Nickname of a user
139 * @return integer|null Database user id (null: if non existent
142 function idFromName( $name ) {
143 $fname = "User::idFromName";
145 $nt = Title
::newFromText( $name );
146 if( is_null( $nt ) ) {
150 $dbr =& wfGetDB( DB_SLAVE
);
151 $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
153 if ( $s === false ) {
161 * does the string match an anonymous IPv4 address?
163 * Note: We match \d{1,3}\.\d{1,3}\.\d{1,3}\.xxx as an anonymous IP
164 * address because the usemod software would "cloak" anonymous IP
165 * addresses like this, if we allowed accounts like this to be created
166 * new users could get the old edits of these anonymous users.
171 * @param string $name Nickname of a user
174 function isIP( $name ) {
175 return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/",$name);
176 /*return preg_match("/^
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]))\.
180 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))
185 * Is the input a valid username?
187 * Checks if the input is a valid username, we don't want an empty string,
188 * an IP address, anything that containins slashes (would mess up subpages),
189 * is longer than the maximum allowed username size or doesn't begin with
192 * @param string $name
196 function isValidUserName( $name ) {
197 global $wgContLang, $wgMaxNameChars;
200 || User
::isIP( $name )
201 ||
strpos( $name, '/' ) !== false
202 ||
strlen( $name ) > $wgMaxNameChars
203 ||
$name != $wgContLang->ucfirst( $name ) )
206 // Ensure that the name can't be misresolved as a different title,
207 // such as with extra namespace keys at the start.
208 $parsed = Title
::newFromText( $name );
209 if( is_null( $parsed )
210 ||
$parsed->getNamespace()
211 ||
strcmp( $name, $parsed->getPrefixedText() ) )
214 // Check an additional blacklist of troublemaker characters.
215 // Should these be merged into the title char list?
216 $unicodeBlacklist = '/[' .
217 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
218 '\x{00a0}' . # non-breaking space
219 '\x{2000}-\x{200f}' . # various whitespace
220 '\x{2028}-\x{202f}' . # breaks and control chars
221 '\x{3000}' . # ideographic space
222 '\x{e000}-\x{f8ff}' . # private use
224 if( preg_match( $unicodeBlacklist, $name ) ) {
232 * Is the input a valid password?
234 * @param string $password
238 function isValidPassword( $password ) {
239 global $wgMinimalPasswordLength;
240 return strlen( $password ) >= $wgMinimalPasswordLength;
244 * Does the string match roughly an email address ?
246 * There used to be a regular expression here, it got removed because it
247 * rejected valid addresses. Actually just check if there is '@' somewhere
248 * in the given address.
250 * @todo Check for RFC 2822 compilance
253 * @param string $addr email address
257 function isValidEmailAddr ( $addr ) {
258 return ( trim( $addr ) != '' ) &&
259 (false !== strpos( $addr, '@' ) );
263 * Count the number of edits of a user
265 * @param int $uid The user ID to check
268 function edits( $uid ) {
269 $fname = 'User::edits';
271 $dbr =& wfGetDB( DB_SLAVE
);
272 return $dbr->selectField(
273 'revision', 'count(*)',
274 array( 'rev_user' => $uid ),
280 * probably return a random password
281 * @return string probably a random password
283 * @todo Check what is doing really [AV]
285 function randomPassword() {
286 global $wgMinimalPasswordLength;
287 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
288 $l = strlen( $pwchars ) - 1;
290 $pwlength = max( 7, $wgMinimalPasswordLength );
291 $digit = mt_rand(0, $pwlength - 1);
293 for ( $i = 0; $i < $pwlength; $i++
) {
294 $np .= $i == $digit ?
chr( mt_rand(48, 57) ) : $pwchars{ mt_rand(0, $l)};
300 * Set properties to default
301 * Used at construction. It will load per language default settings only
302 * if we have an available language object.
304 function loadDefaults() {
307 $fname = 'User::loadDefaults' . $n;
308 wfProfileIn( $fname );
310 global $wgCookiePrefix;
311 global $wgNamespacesToBeSearchedDefault;
314 $this->mNewtalk
= -1;
315 $this->mName
= false;
316 $this->mRealName
= $this->mEmail
= '';
317 $this->mEmailAuthenticated
= null;
318 $this->mPassword
= $this->mNewpassword
= '';
319 $this->mRights
= array();
320 $this->mGroups
= array();
321 $this->mOptions
= User
::getDefaultOptions();
323 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
324 $this->mOptions
['searchNs'.$nsnum] = $val;
326 unset( $this->mSkin
);
327 $this->mDataLoaded
= false;
328 $this->mBlockedby
= -1; # Unset
329 $this->setToken(); # Random
330 $this->mHash
= false;
332 if ( isset( $_COOKIE[$wgCookiePrefix.'LoggedOut'] ) ) {
333 $this->mTouched
= wfTimestamp( TS_MW
, $_COOKIE[$wgCookiePrefix.'LoggedOut'] );
336 $this->mTouched
= '0'; # Allow any pages to be cached
339 $this->mRegistration
= wfTimestamp( TS_MW
);
341 wfProfileOut( $fname );
345 * Combine the language default options with any site-specific options
346 * and add the default language variants.
352 function getDefaultOptions() {
354 * Site defaults will override the global/language defaults
356 global $wgContLang, $wgDefaultUserOptions;
357 $defOpt = $wgDefaultUserOptions +
$wgContLang->getDefaultUserOptions();
360 * default language setting
362 $variant = $wgContLang->getPreferredVariant();
363 $defOpt['variant'] = $variant;
364 $defOpt['language'] = $variant;
370 * Get a given default option value.
377 function getDefaultOption( $opt ) {
378 $defOpts = User
::getDefaultOptions();
379 if( isset( $defOpts[$opt] ) ) {
380 return $defOpts[$opt];
387 * Get blocking information
389 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
390 * non-critical checks are done against slaves. Check when actually saving should be done against
393 function getBlockedStatus( $bFromSlave = true ) {
394 global $wgEnableSorbs, $wgProxyWhitelist;
396 if ( -1 != $this->mBlockedby
) {
397 wfDebug( "User::getBlockedStatus: already loaded.\n" );
401 $fname = 'User::getBlockedStatus';
402 wfProfileIn( $fname );
403 wfDebug( "$fname: checking...\n" );
405 $this->mBlockedby
= 0;
409 $block = new Block();
410 $block->fromMaster( !$bFromSlave );
411 if ( $block->load( $ip , $this->mId
) ) {
412 wfDebug( "$fname: Found block.\n" );
413 $this->mBlockedby
= $block->mBy
;
414 $this->mBlockreason
= $block->mReason
;
415 if ( $this->isLoggedIn() ) {
416 $this->spreadBlock();
419 wfDebug( "$fname: No block.\n" );
423 if ( !$this->isSysop() && !in_array( $ip, $wgProxyWhitelist ) ) {
426 if ( wfIsLocallyBlockedProxy( $ip ) ) {
427 $this->mBlockedby
= wfMsg( 'proxyblocker' );
428 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
432 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
433 if ( $this->inSorbsBlacklist( $ip ) ) {
434 $this->mBlockedby
= wfMsg( 'sorbs' );
435 $this->mBlockreason
= wfMsg( 'sorbsreason' );
441 wfRunHooks( 'GetBlockedStatus', array( &$this ) );
443 wfProfileOut( $fname );
446 function inSorbsBlacklist( $ip ) {
447 global $wgEnableSorbs;
448 return $wgEnableSorbs &&
449 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
452 function inOpmBlacklist( $ip ) {
454 return $wgEnableOpm &&
455 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
458 function inDnsBlacklist( $ip, $base ) {
459 $fname = 'User::inDnsBlacklist';
460 wfProfileIn( $fname );
465 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
467 for ( $i=4; $i>=1; $i-- ) {
468 $host .= $m[$i] . '.';
473 $ipList = gethostbynamel( $host );
476 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
479 wfDebug( "Requested $host, not found in $base.\n" );
483 wfProfileOut( $fname );
488 * Primitive rate limits: enforce maximum actions per time period
489 * to put a brake on flooding.
491 * Note: when using a shared cache like memcached, IP-address
492 * last-hit counters will be shared across wikis.
494 * @return bool true if a rate limiter was tripped
497 function pingLimiter( $action='edit' ) {
498 global $wgRateLimits;
499 if( !isset( $wgRateLimits[$action] ) ) {
502 if( $this->isAllowed( 'delete' ) ) {
507 global $wgMemc, $wgDBname, $wgRateLimitLog;
508 $fname = 'User::pingLimiter';
509 wfProfileIn( $fname );
511 $limits = $wgRateLimits[$action];
513 $id = $this->getId();
516 if( isset( $limits['anon'] ) && $id == 0 ) {
517 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
520 if( isset( $limits['user'] ) && $id != 0 ) {
521 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
523 if( $this->isNewbie() ) {
524 if( isset( $limits['newbie'] ) && $id != 0 ) {
525 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
527 if( isset( $limits['ip'] ) ) {
528 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
530 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
531 $subnet = $matches[1];
532 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
537 foreach( $keys as $key => $limit ) {
538 list( $max, $period ) = $limit;
539 $summary = "(limit $max in {$period}s)";
540 $count = $wgMemc->get( $key );
542 if( $count > $max ) {
543 wfDebug( "$fname: tripped! $key at $count $summary\n" );
544 if( $wgRateLimitLog ) {
545 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
549 wfDebug( "$fname: ok. $key at $count $summary\n" );
552 wfDebug( "$fname: adding record for $key $summary\n" );
553 $wgMemc->add( $key, 1, intval( $period ) );
555 $wgMemc->incr( $key );
558 wfProfileOut( $fname );
563 * Check if user is blocked
564 * @return bool True if blocked, false otherwise
566 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
567 wfDebug( "User::isBlocked: enter\n" );
568 $this->getBlockedStatus( $bFromSlave );
569 return $this->mBlockedby
!== 0;
573 * Check if user is blocked from editing a particular article
575 function isBlockedFrom( $title, $bFromSlave = false ) {
576 global $wgBlockAllowsUTEdit;
577 $fname = 'User::isBlockedFrom';
578 wfProfileIn( $fname );
579 wfDebug( "$fname: enter\n" );
581 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
582 $title->getNamespace() == NS_USER_TALK
)
585 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
587 wfDebug( "$fname: asking isBlocked()\n" );
588 $blocked = $this->isBlocked( $bFromSlave );
590 wfProfileOut( $fname );
595 * Get name of blocker
596 * @return string name of blocker
598 function blockedBy() {
599 $this->getBlockedStatus();
600 return $this->mBlockedby
;
604 * Get blocking reason
605 * @return string Blocking reason
607 function blockedFor() {
608 $this->getBlockedStatus();
609 return $this->mBlockreason
;
613 * Initialise php session
615 function SetupSession() {
616 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
617 if( $wgSessionsInMemcached ) {
618 require_once( 'MemcachedSessions.php' );
619 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
620 # If it's left on 'user' or another setting from another
621 # application, it will end up failing. Try to recover.
622 ini_set ( 'session.save_handler', 'files' );
624 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
625 session_cache_limiter( 'private, must-revalidate' );
630 * Create a new user object using data from session
633 function loadFromSession() {
634 global $wgMemc, $wgDBname, $wgCookiePrefix;
636 if ( isset( $_SESSION['wsUserID'] ) ) {
637 if ( 0 != $_SESSION['wsUserID'] ) {
638 $sId = $_SESSION['wsUserID'];
642 } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
643 $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
644 $_SESSION['wsUserID'] = $sId;
648 if ( isset( $_SESSION['wsUserName'] ) ) {
649 $sName = $_SESSION['wsUserName'];
650 } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
651 $sName = $_COOKIE["{$wgCookiePrefix}UserName"];
652 $_SESSION['wsUserName'] = $sName;
657 $passwordCorrect = FALSE;
658 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
659 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
660 # Expire old serialized objects; they may be corrupt.
663 if($makenew = !$user) {
664 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
667 $user->loadFromDatabase();
669 wfDebug( "User::loadFromSession() got from cache!\n" );
672 if ( isset( $_SESSION['wsToken'] ) ) {
673 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
674 } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) {
675 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgCookiePrefix}Token"];
677 return new User(); # Can't log in from session
680 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
682 if($wgMemc->set( $key, $user ))
683 wfDebug( "User::loadFromSession() successfully saved user\n" );
685 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
689 return new User(); # Can't log in from session
693 * Load a user from the database
695 function loadFromDatabase() {
696 $fname = "User::loadFromDatabase";
698 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
699 # loading in a command line script, don't assume all command line scripts need it like this
700 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
701 if ( $this->mDataLoaded
) {
706 $this->mId
= intval( $this->mId
);
708 /** Anonymous user */
711 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
712 $this->mDataLoaded
= true;
714 } # the following stuff is for non-anonymous users only
716 $dbr =& wfGetDB( DB_SLAVE
);
717 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
718 'user_email_authenticated',
719 'user_real_name','user_options','user_touched', 'user_token', 'user_registration' ),
720 array( 'user_id' => $this->mId
), $fname );
722 if ( $s !== false ) {
723 $this->mName
= $s->user_name
;
724 $this->mEmail
= $s->user_email
;
725 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
726 $this->mRealName
= $s->user_real_name
;
727 $this->mPassword
= $s->user_password
;
728 $this->mNewpassword
= $s->user_newpassword
;
729 $this->decodeOptions( $s->user_options
);
730 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
731 $this->mToken
= $s->user_token
;
732 $this->mRegistration
= wfTimestampOrNull( TS_MW
, $s->user_registration
);
734 $res = $dbr->select( 'user_groups',
736 array( 'ug_user' => $this->mId
),
738 $this->mGroups
= array();
739 while( $row = $dbr->fetchObject( $res ) ) {
740 $this->mGroups
[] = $row->ug_group
;
742 $implicitGroups = array( '*', 'user' );
744 global $wgAutoConfirmAge;
745 $accountAge = time() - wfTimestampOrNull( TS_UNIX
, $this->mRegistration
);
746 if( $accountAge >= $wgAutoConfirmAge ) {
747 $implicitGroups[] = 'autoconfirmed';
750 $effectiveGroups = array_merge( $implicitGroups, $this->mGroups
);
751 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
754 $this->mDataLoaded
= true;
757 function getID() { return $this->mId
; }
758 function setID( $v ) {
760 $this->mDataLoaded
= false;
764 $this->loadFromDatabase();
765 if ( $this->mName
=== false ) {
766 $this->mName
= wfGetIP();
771 function setName( $str ) {
772 $this->loadFromDatabase();
778 * Return the title dbkey form of the name, for eg user pages.
782 function getTitleKey() {
783 return str_replace( ' ', '_', $this->getName() );
786 function getNewtalk() {
787 $this->loadFromDatabase();
789 # Load the newtalk status if it is unloaded (mNewtalk=-1)
790 if( $this->mNewtalk
=== -1 ) {
791 $this->mNewtalk
= false; # reset talk page status
793 # Check memcached separately for anons, who have no
794 # entire User object stored in there.
796 global $wgDBname, $wgMemc;
797 $key = "$wgDBname:newtalk:ip:" . $this->getName();
798 $newtalk = $wgMemc->get( $key );
799 if( is_integer( $newtalk ) ) {
800 $this->mNewtalk
= (bool)$newtalk;
802 $this->mNewtalk
= $this->checkNewtalk( 'user_ip', $this->getName() );
803 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
806 $this->mNewtalk
= $this->checkNewtalk( 'user_id', $this->mId
);
810 return (bool)$this->mNewtalk
;
814 * Return the talk page(s) this user has new messages on.
816 function getNewMessageLinks() {
819 if (!wfRunHooks('UserRetrieveNewTalks', array(&$this, &$talks)))
822 if (!$this->getNewtalk())
824 $up = $this->getUserPage();
825 $utp = $up->getTalkPage();
826 return array(array("wiki" => $wgDBname, "link" => $utp->getLocalURL()));
831 * Perform a user_newtalk check on current slaves; if the memcached data
832 * is funky we don't want newtalk state to get stuck on save, as that's
835 * @param string $field
840 function checkNewtalk( $field, $id ) {
841 $fname = 'User::checkNewtalk';
842 $dbr =& wfGetDB( DB_SLAVE
);
843 $ok = $dbr->selectField( 'user_newtalk', $field,
844 array( $field => $id ), $fname );
845 return $ok !== false;
850 * @param string $field
854 function updateNewtalk( $field, $id ) {
855 $fname = 'User::updateNewtalk';
856 if( $this->checkNewtalk( $field, $id ) ) {
857 wfDebug( "$fname already set ($field, $id), ignoring\n" );
860 $dbw =& wfGetDB( DB_MASTER
);
861 $dbw->insert( 'user_newtalk',
862 array( $field => $id ),
865 wfDebug( "$fname: set on ($field, $id)\n" );
870 * Clear the new messages flag for the given user
871 * @param string $field
875 function deleteNewtalk( $field, $id ) {
876 $fname = 'User::deleteNewtalk';
877 if( !$this->checkNewtalk( $field, $id ) ) {
878 wfDebug( "$fname: already gone ($field, $id), ignoring\n" );
881 $dbw =& wfGetDB( DB_MASTER
);
882 $dbw->delete( 'user_newtalk',
883 array( $field => $id ),
885 wfDebug( "$fname: killed on ($field, $id)\n" );
890 * Update the 'You have new messages!' status.
893 function setNewtalk( $val ) {
898 $this->loadFromDatabase();
899 $this->mNewtalk
= $val;
901 $fname = 'User::setNewtalk';
903 if( $this->isAnon() ) {
905 $id = $this->getName();
908 $id = $this->getId();
912 $changed = $this->updateNewtalk( $field, $id );
914 $changed = $this->deleteNewtalk( $field, $id );
918 if( $this->isAnon() ) {
919 // Anons have a separate memcached space, since
920 // user records aren't kept for them.
921 global $wgDBname, $wgMemc;
922 $key = "$wgDBname:newtalk:ip:$val";
923 $wgMemc->set( $key, $val ?
1 : 0 );
926 // Make sure the user page is watched, so a notification
927 // will be sent out if enabled.
928 $this->addWatch( $this->getTalkPage() );
931 $this->invalidateCache();
932 $this->saveSettings();
936 function invalidateCache() {
937 global $wgClockSkewFudge;
938 $this->loadFromDatabase();
939 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
940 # Don't forget to save the options after this or
941 # it won't take effect!
944 function validateCache( $timestamp ) {
945 $this->loadFromDatabase();
946 return ($timestamp >= $this->mTouched
);
950 * Encrypt a password.
951 * It can eventuall salt a password @see User::addSalt()
952 * @param string $p clear Password.
953 * @return string Encrypted password.
955 function encryptPassword( $p ) {
956 return wfEncryptPassword( $this->mId
, $p );
959 # Set the password and reset the random token
960 function setPassword( $str ) {
961 $this->loadFromDatabase();
963 $this->mPassword
= $this->encryptPassword( $str );
964 $this->mNewpassword
= '';
967 # Set the random token (used for persistent authentication)
968 function setToken( $token = false ) {
969 global $wgSecretKey, $wgProxyKey, $wgDBname;
971 if ( $wgSecretKey ) {
973 } elseif ( $wgProxyKey ) {
978 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
980 $this->mToken
= $token;
985 function setCookiePassword( $str ) {
986 $this->loadFromDatabase();
987 $this->mCookiePassword
= md5( $str );
990 function setNewpassword( $str ) {
991 $this->loadFromDatabase();
992 $this->mNewpassword
= $this->encryptPassword( $str );
995 function getEmail() {
996 $this->loadFromDatabase();
997 return $this->mEmail
;
1000 function getEmailAuthenticationTimestamp() {
1001 $this->loadFromDatabase();
1002 return $this->mEmailAuthenticated
;
1005 function setEmail( $str ) {
1006 $this->loadFromDatabase();
1007 $this->mEmail
= $str;
1010 function getRealName() {
1011 $this->loadFromDatabase();
1012 return $this->mRealName
;
1015 function setRealName( $str ) {
1016 $this->loadFromDatabase();
1017 $this->mRealName
= $str;
1021 * @param string $oname The option to check
1024 function getOption( $oname ) {
1025 $this->loadFromDatabase();
1026 if ( array_key_exists( $oname, $this->mOptions
) ) {
1027 return trim( $this->mOptions
[$oname] );
1034 * @param string $oname The option to check
1035 * @return bool False if the option is not selected, true if it is
1037 function getBoolOption( $oname ) {
1038 return (bool)$this->getOption( $oname );
1041 function setOption( $oname, $val ) {
1042 $this->loadFromDatabase();
1043 if ( $oname == 'skin' ) {
1044 # Clear cached skin, so the new one displays immediately in Special:Preferences
1045 unset( $this->mSkin
);
1047 $this->mOptions
[$oname] = $val;
1048 $this->invalidateCache();
1051 function getRights() {
1052 $this->loadFromDatabase();
1053 return $this->mRights
;
1057 * Get the list of explicit group memberships this user has.
1058 * The implicit * and user groups are not included.
1059 * @return array of strings
1061 function getGroups() {
1062 $this->loadFromDatabase();
1063 return $this->mGroups
;
1067 * Get the list of implicit group memberships this user has.
1068 * This includes all explicit groups, plus 'user' if logged in
1069 * and '*' for all accounts.
1070 * @return array of strings
1072 function getEffectiveGroups() {
1073 $base = array( '*' );
1074 if( $this->isLoggedIn() ) {
1077 return array_merge( $base, $this->getGroups() );
1081 * Add the user to the given group.
1082 * This takes immediate effect.
1085 function addGroup( $group ) {
1086 $dbw =& wfGetDB( DB_MASTER
);
1087 $dbw->insert( 'user_groups',
1089 'ug_user' => $this->getID(),
1090 'ug_group' => $group,
1093 array( 'IGNORE' ) );
1095 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
1096 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
1098 $this->invalidateCache();
1099 $this->saveSettings();
1103 * Remove the user from the given group.
1104 * This takes immediate effect.
1107 function removeGroup( $group ) {
1108 $dbw =& wfGetDB( DB_MASTER
);
1109 $dbw->delete( 'user_groups',
1111 'ug_user' => $this->getID(),
1112 'ug_group' => $group,
1114 'User::removeGroup' );
1116 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
1117 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
1119 $this->invalidateCache();
1120 $this->saveSettings();
1125 * A more legible check for non-anonymousness.
1126 * Returns true if the user is not an anonymous visitor.
1130 function isLoggedIn() {
1131 return( $this->getID() != 0 );
1135 * A more legible check for anonymousness.
1136 * Returns true if the user is an anonymous visitor.
1141 return !$this->isLoggedIn();
1145 * Check if a user is sysop
1148 function isSysop() {
1149 return $this->isAllowed( 'protect' );
1153 function isDeveloper() {
1154 return $this->isAllowed( 'siteadmin' );
1158 function isBureaucrat() {
1159 return $this->isAllowed( 'makesysop' );
1163 * Whether the user is a bot
1164 * @todo need to be migrated to the new user level management sytem
1167 $this->loadFromDatabase();
1168 return in_array( 'bot', $this->mRights
);
1172 * Check if user is allowed to access a feature / make an action
1173 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1174 * @return boolean True: action is allowed, False: action should not be allowed
1176 function isAllowed($action='') {
1177 if ( $action === '' )
1178 // In the spirit of DWIM
1181 $this->loadFromDatabase();
1182 return in_array( $action , $this->mRights
);
1186 * Load a skin if it doesn't exist or return it
1187 * @todo FIXME : need to check the old failback system [AV]
1189 function &getSkin() {
1190 global $IP, $wgRequest;
1191 if ( ! isset( $this->mSkin
) ) {
1192 $fname = 'User::getSkin';
1193 wfProfileIn( $fname );
1196 $userSkin = $this->getOption( 'skin' );
1197 $userSkin = $wgRequest->getVal('useskin', $userSkin);
1199 $this->mSkin
=& Skin
::newFromKey( $userSkin );
1200 wfProfileOut( $fname );
1202 return $this->mSkin
;
1206 * @param string $title Article title to look at
1210 * Check watched status of an article
1211 * @return bool True if article is watched
1213 function isWatched( $title ) {
1214 $wl = WatchedItem
::fromUserTitle( $this, $title );
1215 return $wl->isWatched();
1221 function addWatch( $title ) {
1222 $wl = WatchedItem
::fromUserTitle( $this, $title );
1224 $this->invalidateCache();
1228 * Stop watching an article
1230 function removeWatch( $title ) {
1231 $wl = WatchedItem
::fromUserTitle( $this, $title );
1233 $this->invalidateCache();
1237 * Clear the user's notification timestamp for the given title.
1238 * If e-notif e-mails are on, they will receive notification mails on
1239 * the next change of the page if it's watched etc.
1241 function clearNotification( &$title ) {
1242 global $wgUser, $wgUseEnotif;
1245 if ($title->getNamespace() == NS_USER_TALK
&&
1246 $title->getText() == $this->getName() ) {
1247 if (!wfRunHooks('UserClearNewTalkNotification', array(&$this)))
1249 $this->setNewtalk( false );
1252 if( !$wgUseEnotif ) {
1256 if( $this->isAnon() ) {
1257 // Nothing else to do...
1261 // Only update the timestamp if the page is being watched.
1262 // The query to find out if it is watched is cached both in memcached and per-invocation,
1263 // and when it does have to be executed, it can be on a slave
1264 // If this is the user's newtalk page, we always update the timestamp
1265 if ($title->getNamespace() == NS_USER_TALK
&&
1266 $title->getText() == $wgUser->getName())
1269 } elseif ( $this->getID() == $wgUser->getID() ) {
1270 $watched = $title->userIsWatching();
1275 // If the page is watched by the user (or may be watched), update the timestamp on any
1276 // any matching rows
1278 $dbw =& wfGetDB( DB_MASTER
);
1279 $success = $dbw->update( 'watchlist',
1281 'wl_notificationtimestamp' => NULL
1282 ), array( /* WHERE */
1283 'wl_title' => $title->getDBkey(),
1284 'wl_namespace' => $title->getNamespace(),
1285 'wl_user' => $this->getID()
1286 ), 'User::clearLastVisited'
1294 * Resets all of the given user's page-change notification timestamps.
1295 * If e-notif e-mails are on, they will receive notification mails on
1296 * the next change of any watched page.
1298 * @param int $currentUser user ID number
1301 function clearAllNotifications( $currentUser ) {
1302 global $wgUseEnotif;
1303 if ( !$wgUseEnotif ) {
1304 $this->setNewtalk( false );
1307 if( $currentUser != 0 ) {
1309 $dbw =& wfGetDB( DB_MASTER
);
1310 $success = $dbw->update( 'watchlist',
1312 'wl_notificationtimestamp' => 0
1313 ), array( /* WHERE */
1314 'wl_user' => $currentUser
1315 ), 'UserMailer::clearAll'
1318 # we also need to clear here the "you have new message" notification for the own user_talk page
1319 # This is cleared one page view later in Article::viewUpdates();
1325 * @return string Encoding options
1327 function encodeOptions() {
1329 foreach ( $this->mOptions
as $oname => $oval ) {
1330 array_push( $a, $oname.'='.$oval );
1332 $s = implode( "\n", $a );
1339 function decodeOptions( $str ) {
1340 $a = explode( "\n", $str );
1341 foreach ( $a as $s ) {
1342 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1343 $this->mOptions
[$m[1]] = $m[2];
1348 function setCookies() {
1349 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
1350 if ( 0 == $this->mId
) return;
1351 $this->loadFromDatabase();
1352 $exp = time() +
$wgCookieExpiration;
1354 $_SESSION['wsUserID'] = $this->mId
;
1355 setcookie( $wgCookiePrefix.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1357 $_SESSION['wsUserName'] = $this->getName();
1358 setcookie( $wgCookiePrefix.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1360 $_SESSION['wsToken'] = $this->mToken
;
1361 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1362 setcookie( $wgCookiePrefix.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1364 setcookie( $wgCookiePrefix.'Token', '', time() - 3600 );
1370 * It will clean the session cookie
1373 global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
1374 $this->loadDefaults();
1375 $this->setLoaded( true );
1377 $_SESSION['wsUserID'] = 0;
1379 setcookie( $wgCookiePrefix.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1380 setcookie( $wgCookiePrefix.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1382 # Remember when user logged out, to prevent seeing cached pages
1383 setcookie( $wgCookiePrefix.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1387 * Save object settings into database
1389 function saveSettings() {
1390 global $wgMemc, $wgDBname;
1391 $fname = 'User::saveSettings';
1393 if ( wfReadOnly() ) { return; }
1394 if ( 0 == $this->mId
) { return; }
1396 $dbw =& wfGetDB( DB_MASTER
);
1397 $dbw->update( 'user',
1399 'user_name' => $this->mName
,
1400 'user_password' => $this->mPassword
,
1401 'user_newpassword' => $this->mNewpassword
,
1402 'user_real_name' => $this->mRealName
,
1403 'user_email' => $this->mEmail
,
1404 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1405 'user_options' => $this->encodeOptions(),
1406 'user_touched' => $dbw->timestamp($this->mTouched
),
1407 'user_token' => $this->mToken
1408 ), array( /* WHERE */
1409 'user_id' => $this->mId
1412 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1417 * Checks if a user with the given name exists, returns the ID
1419 function idForName() {
1420 $fname = 'User::idForName';
1423 $s = trim( $this->getName() );
1424 if ( 0 == strcmp( '', $s ) ) return 0;
1426 $dbr =& wfGetDB( DB_SLAVE
);
1427 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1428 if ( $id === false ) {
1435 * Add user object to the database
1437 function addToDatabase() {
1438 $fname = 'User::addToDatabase';
1439 $dbw =& wfGetDB( DB_MASTER
);
1440 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1441 $dbw->insert( 'user',
1443 'user_id' => $seqVal,
1444 'user_name' => $this->mName
,
1445 'user_password' => $this->mPassword
,
1446 'user_newpassword' => $this->mNewpassword
,
1447 'user_email' => $this->mEmail
,
1448 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1449 'user_real_name' => $this->mRealName
,
1450 'user_options' => $this->encodeOptions(),
1451 'user_token' => $this->mToken
,
1452 'user_registration' => $dbw->timestamp( $this->mRegistration
),
1455 $this->mId
= $dbw->insertId();
1458 function spreadBlock() {
1459 # If the (non-anonymous) user is blocked, this function will block any IP address
1460 # that they successfully log on from.
1461 $fname = 'User::spreadBlock';
1463 wfDebug( "User:spreadBlock()\n" );
1464 if ( $this->mId
== 0 ) {
1468 $userblock = Block
::newFromDB( '', $this->mId
);
1469 if ( !$userblock->isValid() ) {
1473 # Check if this IP address is already blocked
1474 $ipblock = Block
::newFromDB( wfGetIP() );
1475 if ( $ipblock->isValid() ) {
1476 # If the user is already blocked. Then check if the autoblock would
1477 # excede the user block. If it would excede, then do nothing, else
1478 # prolong block time
1479 if ($userblock->mExpiry
&&
1480 ($userblock->mExpiry
< Block
::getAutoblockExpiry($ipblock->mTimestamp
))) {
1483 # Just update the timestamp
1484 $ipblock->updateTimestamp();
1488 # Make a new block object with the desired properties
1489 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1490 $ipblock->mAddress
= wfGetIP();
1491 $ipblock->mUser
= 0;
1492 $ipblock->mBy
= $userblock->mBy
;
1493 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1494 $ipblock->mTimestamp
= wfTimestampNow();
1495 $ipblock->mAuto
= 1;
1496 # If the user is already blocked with an expiry date, we don't
1497 # want to pile on top of that!
1498 if($userblock->mExpiry
) {
1499 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1501 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1510 * Generate a string which will be different for any combination of
1511 * user options which would produce different parser output.
1512 * This will be used as part of the hash key for the parser cache,
1513 * so users will the same options can share the same cached data
1516 * Extensions which require it should install 'PageRenderingHash' hook,
1517 * which will give them a chance to modify this key based on their own
1522 function getPageRenderingHash() {
1525 return $this->mHash
;
1528 // stubthreshold is only included below for completeness,
1529 // it will always be 0 when this function is called by parsercache.
1531 $confstr = $this->getOption( 'math' );
1532 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1533 $confstr .= '!' . $this->getOption( 'date' );
1534 $confstr .= '!' . ($this->getOption( 'numberheadings' ) ?
'1' : '');
1535 $confstr .= '!' . $this->getOption( 'language' );
1536 $confstr .= '!' . $this->getOption( 'thumbsize' );
1537 // add in language specific options, if any
1538 $extra = $wgContLang->getExtraHashOptions();
1541 // Give a chance for extensions to modify the hash, if they have
1542 // extra options or other effects on the parser cache.
1543 wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
1545 $this->mHash
= $confstr;
1549 function isAllowedToCreateAccount() {
1550 return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
1554 * Set mDataLoaded, return previous value
1555 * Use this to prevent DB access in command-line scripts or similar situations
1557 function setLoaded( $loaded ) {
1558 return wfSetVar( $this->mDataLoaded
, $loaded );
1562 * Get this user's personal page title.
1567 function getUserPage() {
1568 return Title
::makeTitle( NS_USER
, $this->getName() );
1572 * Get this user's talk page title.
1577 function getTalkPage() {
1578 $title = $this->getUserPage();
1579 return $title->getTalkPage();
1585 function getMaxID() {
1586 static $res; // cache
1588 if ( isset( $res ) )
1591 $dbr =& wfGetDB( DB_SLAVE
);
1592 return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1597 * Determine whether the user is a newbie. Newbies are either
1598 * anonymous IPs, or the most recently created accounts.
1599 * @return bool True if it is a newbie.
1601 function isNewbie() {
1602 return !$this->isAllowed( 'autoconfirmed' );
1606 * Check to see if the given clear-text password is one of the accepted passwords
1607 * @param string $password User password.
1608 * @return bool True if the given password is correct otherwise False.
1610 function checkPassword( $password ) {
1611 global $wgAuth, $wgMinimalPasswordLength;
1612 $this->loadFromDatabase();
1614 // Even though we stop people from creating passwords that
1615 // are shorter than this, doesn't mean people wont be able
1616 // to. Certain authentication plugins do NOT want to save
1617 // domain passwords in a mysql database, so we should
1618 // check this (incase $wgAuth->strict() is false).
1619 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1623 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1625 } elseif( $wgAuth->strict() ) {
1626 /* Auth plugin doesn't allow local authentication */
1629 $ep = $this->encryptPassword( $password );
1630 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1632 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1634 } elseif ( function_exists( 'iconv' ) ) {
1635 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1636 # Check for this with iconv
1637 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1638 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1646 * Initialize (if necessary) and return a session token value
1647 * which can be used in edit forms to show that the user's
1648 * login credentials aren't being hijacked with a foreign form
1651 * @param mixed $salt - Optional function-specific data for hash.
1652 * Use a string or an array of strings.
1656 function editToken( $salt = '' ) {
1657 if( !isset( $_SESSION['wsEditToken'] ) ) {
1658 $token = $this->generateToken();
1659 $_SESSION['wsEditToken'] = $token;
1661 $token = $_SESSION['wsEditToken'];
1663 if( is_array( $salt ) ) {
1664 $salt = implode( '|', $salt );
1666 return md5( $token . $salt );
1670 * Generate a hex-y looking random token for various uses.
1671 * Could be made more cryptographically sure if someone cares.
1674 function generateToken( $salt = '' ) {
1675 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1676 return md5( $token . $salt );
1680 * Check given value against the token value stored in the session.
1681 * A match should confirm that the form was submitted from the
1682 * user's own login session, not a form submission from a third-party
1685 * @param string $val - the input value to compare
1686 * @param string $salt - Optional function-specific data for hash
1690 function matchEditToken( $val, $salt = '' ) {
1692 $sessionToken = $this->editToken( $salt );
1693 if ( $val != $sessionToken ) {
1694 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1695 $mckey = memsess_key( session_id() );
1696 $uname = @posix_uname
();
1697 $msg = date('r') . "\nEdit token mismatch, expected $sessionToken got $val\n" .
1698 'apache server=' . $uname['nodename'] . "\n" .
1699 'session_id = ' . session_id() . "\n" .
1700 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1701 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1702 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1704 @error_log
( $msg, 3, $logfile );
1706 return $val == $sessionToken;
1710 * Generate a new e-mail confirmation token and send a confirmation
1711 * mail to the user's given address.
1713 * @return mixed True on success, a WikiError object on failure.
1715 function sendConfirmationMail() {
1717 $url = $this->confirmationTokenUrl( $expiration );
1718 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1719 wfMsg( 'confirmemail_body',
1723 $wgContLang->timeanddate( $expiration, false ) ) );
1727 * Send an e-mail to this user's account. Does not check for
1728 * confirmed status or validity.
1730 * @param string $subject
1731 * @param string $body
1732 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1733 * @return mixed True on success, a WikiError object on failure.
1735 function sendMail( $subject, $body, $from = null ) {
1736 if( is_null( $from ) ) {
1737 global $wgPasswordSender;
1738 $from = $wgPasswordSender;
1741 require_once( 'UserMailer.php' );
1742 $to = new MailAddress( $this );
1743 $sender = new MailAddress( $from );
1744 $error = userMailer( $to, $sender, $subject, $body );
1746 if( $error == '' ) {
1749 return new WikiError( $error );
1754 * Generate, store, and return a new e-mail confirmation code.
1755 * A hash (unsalted since it's used as a key) is stored.
1756 * @param &$expiration mixed output: accepts the expiration time
1760 function confirmationToken( &$expiration ) {
1761 $fname = 'User::confirmationToken';
1764 $expires = $now +
7 * 24 * 60 * 60;
1765 $expiration = wfTimestamp( TS_MW
, $expires );
1767 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1768 $hash = md5( $token );
1770 $dbw =& wfGetDB( DB_MASTER
);
1771 $dbw->update( 'user',
1772 array( 'user_email_token' => $hash,
1773 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1774 array( 'user_id' => $this->mId
),
1781 * Generate and store a new e-mail confirmation token, and return
1782 * the URL the user can use to confirm.
1783 * @param &$expiration mixed output: accepts the expiration time
1787 function confirmationTokenUrl( &$expiration ) {
1788 $token = $this->confirmationToken( $expiration );
1789 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1790 return $title->getFullUrl();
1794 * Mark the e-mail address confirmed and save.
1796 function confirmEmail() {
1797 $this->loadFromDatabase();
1798 $this->mEmailAuthenticated
= wfTimestampNow();
1799 $this->saveSettings();
1804 * Is this user allowed to send e-mails within limits of current
1805 * site configuration?
1808 function canSendEmail() {
1809 return $this->isEmailConfirmed();
1813 * Is this user allowed to receive e-mails within limits of current
1814 * site configuration?
1817 function canReceiveEmail() {
1818 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1822 * Is this user's e-mail address valid-looking and confirmed within
1823 * limits of the current site configuration?
1825 * If $wgEmailAuthentication is on, this may require the user to have
1826 * confirmed their address by returning a code or using a password
1827 * sent to the address from the wiki.
1831 function isEmailConfirmed() {
1832 global $wgEmailAuthentication;
1833 $this->loadFromDatabase();
1834 if( $this->isAnon() )
1836 if( !$this->isValidEmailAddr( $this->mEmail
) )
1838 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1844 * @param array $groups list of groups
1845 * @return array list of permission key names for given groups combined
1848 function getGroupPermissions( $groups ) {
1849 global $wgGroupPermissions;
1851 foreach( $groups as $group ) {
1852 if( isset( $wgGroupPermissions[$group] ) ) {
1853 $rights = array_merge( $rights,
1854 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1861 * @param string $group key name
1862 * @return string localized descriptive name, if provided
1865 function getGroupName( $group ) {
1866 $key = "group-$group-name";
1867 $name = wfMsg( $key );
1868 if( $name == '' ||
$name == "<$key>" ) {
1876 * Return the set of defined explicit groups.
1877 * The * and 'user' groups are not included.
1881 function getAllGroups() {
1882 global $wgGroupPermissions;
1884 array_keys( $wgGroupPermissions ),
1885 array( '*', 'user', 'autoconfirmed' ) );