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 ) {
55 # Force usernames to capital
57 $name = $wgContLang->ucfirst( $name );
59 # Clean up name according to title rules
60 $t = Title
::newFromText( $name );
65 # Reject various classes of invalid names
66 $canonicalName = $t->getText();
68 $canonicalName = $wgAuth->getCanonicalName( $t->getText() );
70 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' );
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 ) );
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 ) );
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?
164 * @param string $name Nickname of a user
167 function isIP( $name ) {
168 return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/",$name);
169 /*return preg_match("/^
170 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
171 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
172 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
173 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))
178 * Is the input a valid username?
180 * Checks if the input is a valid username, we don't want an empty string,
181 * an IP address, anything that containins slashes (would mess up subpages),
182 * is longer than the maximum allowed username size or doesn't begin with
185 * @param string $name
189 function isValidUserName( $name ) {
190 global $wgContLang, $wgMaxNameChars;
193 || User
::isIP( $name )
194 ||
strpos( $name, '/' ) !== false
195 ||
strlen( $name ) > $wgMaxNameChars
196 ||
$name != $wgContLang->ucfirst( $name ) )
203 * Is the input a valid password?
205 * @param string $password
209 function isValidPassword( $password ) {
210 global $wgMinimalPasswordLength;
211 return strlen( $password ) >= $wgMinimalPasswordLength;
215 * does the string match roughly an email address ?
217 * @todo Check for RFC 2822 compilance
220 * @param string $addr email address
224 function isValidEmailAddr ( $addr ) {
225 # There used to be a regular expression here, it got removed because it
226 # rejected valid addresses.
227 return ( trim( $addr ) != '' ) &&
228 (false !== strpos( $addr, '@' ) );
232 * Count the number of edits of a user
234 * @param int $uid The user ID to check
237 function edits( $uid ) {
238 $fname = 'User::edits';
240 $dbr =& wfGetDB( DB_SLAVE
);
241 return $dbr->selectField(
242 'revision', 'count(*)',
243 array( 'rev_user' => $uid ),
249 * probably return a random password
250 * @return string probably a random password
252 * @todo Check what is doing really [AV]
254 function randomPassword() {
255 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
256 $l = strlen( $pwchars ) - 1;
258 $np = $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
259 $pwchars{mt_rand( 0, $l )} . chr( mt_rand(48, 57) ) .
260 $pwchars{mt_rand( 0, $l )} . $pwchars{mt_rand( 0, $l )} .
261 $pwchars{mt_rand( 0, $l )};
266 * Set properties to default
267 * Used at construction. It will load per language default settings only
268 * if we have an available language object.
270 function loadDefaults() {
273 $fname = 'User::loadDefaults' . $n;
274 wfProfileIn( $fname );
276 global $wgContLang, $wgIP, $wgDBname;
277 global $wgNamespacesToBeSearchedDefault;
280 $this->mNewtalk
= -1;
281 $this->mName
= $wgIP;
282 $this->mRealName
= $this->mEmail
= '';
283 $this->mEmailAuthenticated
= null;
284 $this->mPassword
= $this->mNewpassword
= '';
285 $this->mRights
= array();
286 $this->mGroups
= array();
287 $this->mOptions
= User
::getDefaultOptions();
289 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
290 $this->mOptions
['searchNs'.$nsnum] = $val;
292 unset( $this->mSkin
);
293 $this->mDataLoaded
= false;
294 $this->mBlockedby
= -1; # Unset
295 $this->setToken(); # Random
296 $this->mHash
= false;
298 if ( isset( $_COOKIE[$wgDBname.'LoggedOut'] ) ) {
299 $this->mTouched
= wfTimestamp( TS_MW
, $_COOKIE[$wgDBname.'LoggedOut'] );
302 $this->mTouched
= '0'; # Allow any pages to be cached
305 wfProfileOut( $fname );
309 * Combine the language default options with any site-specific options
310 * and add the default language variants.
316 function getDefaultOptions() {
318 * Site defaults will override the global/language defaults
320 global $wgContLang, $wgDefaultUserOptions;
321 $defOpt = $wgDefaultUserOptions +
$wgContLang->getDefaultUserOptions();
324 * default language setting
326 $variant = $wgContLang->getPreferredVariant();
327 $defOpt['variant'] = $variant;
328 $defOpt['language'] = $variant;
334 * Get a given default option value.
341 function getDefaultOption( $opt ) {
342 $defOpts = User
::getDefaultOptions();
343 if( isset( $defOpts[$opt] ) ) {
344 return $defOpts[$opt];
351 * Get blocking information
353 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
354 * non-critical checks are done against slaves. Check when actually saving should be done against
357 * Note that even if $bFromSlave is false, the check is done first against slave, then master.
358 * The logic is that if blocked on slave, we'll assume it's either blocked on master or
359 * just slightly outta sync and soon corrected - safer to block slightly more that less.
360 * And it's cheaper to check slave first, then master if needed, than master always.
362 function getBlockedStatus( $bFromSlave = true ) {
363 global $wgIP, $wgBlockCache, $wgProxyList, $wgEnableSorbs, $wgProxyWhitelist;
365 if ( -1 != $this->mBlockedby
) { return; }
367 $fname = 'User::getBlockedStatus';
368 wfProfileIn( $fname );
370 $this->mBlockedby
= 0;
373 $block = new Block();
374 $block->forUpdate( $bFromSlave );
375 if ( $block->load( $wgIP , $this->mId
) ) {
376 $this->mBlockedby
= $block->mBy
;
377 $this->mBlockreason
= $block->mReason
;
378 if ( $this->isLoggedIn() ) {
379 $this->spreadBlock();
384 if ( !$this->mBlockedby
) {
385 # Check first against slave, and optionally from master.
386 $block = $wgBlockCache->get( $wgIP, true );
387 if ( !$block && !$bFromSlave )
389 # Not blocked: check against master, to make sure.
390 $wgBlockCache->clearLocal( );
391 $block = $wgBlockCache->get( $wgIP, false );
393 if ( $block !== false ) {
394 $this->mBlockedby
= $block->mBy
;
395 $this->mBlockreason
= $block->mReason
;
400 if ( !$this->isSysop() && !in_array( $wgIP, $wgProxyWhitelist ) ) {
403 if ( array_key_exists( $wgIP, $wgProxyList ) ) {
404 $this->mBlockedby
= wfMsg( 'proxyblocker' );
405 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
409 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
410 if ( $this->inSorbsBlacklist( $wgIP ) ) {
411 $this->mBlockedby
= wfMsg( 'sorbs' );
412 $this->mBlockreason
= wfMsg( 'sorbsreason' );
416 wfProfileOut( $fname );
419 function inSorbsBlacklist( $ip ) {
420 global $wgEnableSorbs;
421 return $wgEnableSorbs &&
422 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
425 function inOpmBlacklist( $ip ) {
427 return $wgEnableOpm &&
428 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
431 function inDnsBlacklist( $ip, $base ) {
432 $fname = 'User::inDnsBlacklist';
433 wfProfileIn( $fname );
438 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
440 for ( $i=4; $i>=1; $i-- ) {
441 $host .= $m[$i] . '.';
446 $ipList = gethostbynamel( $host );
449 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
452 wfDebug( "Requested $host, not found in $base.\n" );
456 wfProfileOut( $fname );
461 * Primitive rate limits: enforce maximum actions per time period
462 * to put a brake on flooding.
464 * Note: when using a shared cache like memcached, IP-address
465 * last-hit counters will be shared across wikis.
467 * @return bool true if a rate limiter was tripped
470 function pingLimiter( $action='edit' ) {
471 global $wgRateLimits;
472 if( !isset( $wgRateLimits[$action] ) ) {
475 if( $this->isAllowed( 'delete' ) ) {
480 global $wgMemc, $wgIP, $wgDBname, $wgRateLimitLog;
481 $fname = 'User::pingLimiter';
482 wfProfileIn( $fname );
484 $limits = $wgRateLimits[$action];
486 $id = $this->getId();
488 if( isset( $limits['anon'] ) && $id == 0 ) {
489 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
492 if( isset( $limits['user'] ) && $id != 0 ) {
493 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
495 if( $this->isNewbie() ) {
496 if( isset( $limits['newbie'] ) && $id != 0 ) {
497 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
499 if( isset( $limits['ip'] ) ) {
500 $keys["mediawiki:limiter:$action:ip:$wgIP"] = $limits['ip'];
502 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $wgIP, $matches ) ) {
503 $subnet = $matches[1];
504 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
509 foreach( $keys as $key => $limit ) {
510 list( $max, $period ) = $limit;
511 $summary = "(limit $max in {$period}s)";
512 $count = $wgMemc->get( $key );
514 if( $count > $max ) {
515 wfDebug( "$fname: tripped! $key at $count $summary\n" );
516 if( $wgRateLimitLog ) {
517 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
521 wfDebug( "$fname: ok. $key at $count $summary\n" );
524 wfDebug( "$fname: adding record for $key $summary\n" );
525 $wgMemc->add( $key, 1, intval( $period ) );
527 $wgMemc->incr( $key );
530 wfProfileOut( $fname );
535 * Check if user is blocked
536 * @return bool True if blocked, false otherwise
538 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
539 $this->getBlockedStatus( $bFromSlave );
540 return $this->mBlockedby
!== 0;
544 * Check if user is blocked from editing a particular article
546 function isBlockedFrom( $title, $bFromSlave = false ) {
547 global $wgBlockAllowsUTEdit;
548 $fname = 'User::isBlockedFrom';
549 wfProfileIn( $fname );
551 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
552 $title->getNamespace() == NS_USER_TALK
)
556 $blocked = $this->isBlocked( $bFromSlave );
558 wfProfileOut( $fname );
563 * Get name of blocker
564 * @return string name of blocker
566 function blockedBy() {
567 $this->getBlockedStatus();
568 return $this->mBlockedby
;
572 * Get blocking reason
573 * @return string Blocking reason
575 function blockedFor() {
576 $this->getBlockedStatus();
577 return $this->mBlockreason
;
581 * Initialise php session
583 function SetupSession() {
584 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
585 if( $wgSessionsInMemcached ) {
586 require_once( 'MemcachedSessions.php' );
587 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
588 # If it's left on 'user' or another setting from another
589 # application, it will end up failing. Try to recover.
590 ini_set ( 'session.save_handler', 'files' );
592 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
593 session_cache_limiter( 'private, must-revalidate' );
598 * Read datas from session
601 function loadFromSession() {
602 global $wgMemc, $wgDBname;
604 if ( isset( $_SESSION['wsUserID'] ) ) {
605 if ( 0 != $_SESSION['wsUserID'] ) {
606 $sId = $_SESSION['wsUserID'];
610 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
611 $sId = intval( $_COOKIE["{$wgDBname}UserID"] );
612 $_SESSION['wsUserID'] = $sId;
616 if ( isset( $_SESSION['wsUserName'] ) ) {
617 $sName = $_SESSION['wsUserName'];
618 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
619 $sName = $_COOKIE["{$wgDBname}UserName"];
620 $_SESSION['wsUserName'] = $sName;
625 $passwordCorrect = FALSE;
626 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
627 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
628 # Expire old serialized objects; they may be corrupt.
631 if($makenew = !$user) {
632 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
635 $user->loadFromDatabase();
637 wfDebug( "User::loadFromSession() got from cache!\n" );
640 if ( isset( $_SESSION['wsToken'] ) ) {
641 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
642 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
643 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
645 return new User(); # Can't log in from session
648 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
650 if($wgMemc->set( $key, $user ))
651 wfDebug( "User::loadFromSession() successfully saved user\n" );
653 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
657 return new User(); # Can't log in from session
661 * Load a user from the database
663 function loadFromDatabase() {
664 global $wgCommandLineMode;
665 $fname = "User::loadFromDatabase";
667 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
668 # loading in a command line script, don't assume all command line scripts need it like this
669 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
670 if ( $this->mDataLoaded
) {
675 $this->mId
= intval( $this->mId
);
677 /** Anonymous user */
680 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
681 $this->mDataLoaded
= true;
683 } # the following stuff is for non-anonymous users only
685 $dbr =& wfGetDB( DB_SLAVE
);
686 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
687 'user_email_authenticated',
688 'user_real_name','user_options','user_touched', 'user_token' ),
689 array( 'user_id' => $this->mId
), $fname );
691 if ( $s !== false ) {
692 $this->mName
= $s->user_name
;
693 $this->mEmail
= $s->user_email
;
694 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
695 $this->mRealName
= $s->user_real_name
;
696 $this->mPassword
= $s->user_password
;
697 $this->mNewpassword
= $s->user_newpassword
;
698 $this->decodeOptions( $s->user_options
);
699 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
700 $this->mToken
= $s->user_token
;
702 $res = $dbr->select( 'user_groups',
704 array( 'ug_user' => $this->mId
),
706 $this->mGroups
= array();
707 while( $row = $dbr->fetchObject( $res ) ) {
708 $this->mGroups
[] = $row->ug_group
;
710 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
711 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
714 $this->mDataLoaded
= true;
717 function getID() { return $this->mId
; }
718 function setID( $v ) {
720 $this->mDataLoaded
= false;
724 $this->loadFromDatabase();
728 function setName( $str ) {
729 $this->loadFromDatabase();
735 * Return the title dbkey form of the name, for eg user pages.
739 function getTitleKey() {
740 return str_replace( ' ', '_', $this->getName() );
743 function getNewtalk() {
745 $fname = 'User::getNewtalk';
746 $this->loadFromDatabase();
748 # Load the newtalk status if it is unloaded (mNewtalk=-1)
749 if( $this->mNewtalk
== -1 ) {
750 $this->mNewtalk
= 0; # reset talk page status
752 # Check memcached separately for anons, who have no
753 # entire User object stored in there.
755 global $wgDBname, $wgMemc;
756 $key = "$wgDBname:newtalk:ip:{$this->mName}";
757 $newtalk = $wgMemc->get( $key );
758 if( is_integer( $newtalk ) ) {
759 $this->mNewtalk
= $newtalk ?
1 : 0;
760 return (bool)$this->mNewtalk
;
764 $dbr =& wfGetDB( DB_SLAVE
);
765 if ( $wgUseEnotif ) {
766 $res = $dbr->select( 'watchlist',
768 array( 'wl_title' => $this->getTitleKey(),
769 'wl_namespace' => NS_USER_TALK
,
770 'wl_user' => $this->mId
,
771 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
772 'User::getNewtalk' );
773 if( $dbr->numRows($res) > 0 ) {
776 $dbr->freeResult( $res );
777 } elseif ( $this->mId
) {
778 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
780 if ( $dbr->numRows($res)>0 ) {
783 $dbr->freeResult( $res );
785 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->mName
), $fname );
786 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
787 $dbr->freeResult( $res );
791 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
795 return ( 0 != $this->mNewtalk
);
798 function setNewtalk( $val ) {
799 $this->loadFromDatabase();
800 $this->mNewtalk
= $val;
801 $this->invalidateCache();
804 function invalidateCache() {
805 global $wgClockSkewFudge;
806 $this->loadFromDatabase();
807 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
808 # Don't forget to save the options after this or
809 # it won't take effect!
812 function validateCache( $timestamp ) {
813 $this->loadFromDatabase();
814 return ($timestamp >= $this->mTouched
);
818 * Encrypt a password.
819 * It can eventuall salt a password @see User::addSalt()
820 * @param string $p clear Password.
821 * @return string Encrypted password.
823 function encryptPassword( $p ) {
824 return wfEncryptPassword( $this->mId
, $p );
827 # Set the password and reset the random token
828 function setPassword( $str ) {
829 $this->loadFromDatabase();
831 $this->mPassword
= $this->encryptPassword( $str );
832 $this->mNewpassword
= '';
835 # Set the random token (used for persistent authentication)
836 function setToken( $token = false ) {
837 global $wgSecretKey, $wgProxyKey, $wgDBname;
839 if ( $wgSecretKey ) {
841 } elseif ( $wgProxyKey ) {
846 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
848 $this->mToken
= $token;
853 function setCookiePassword( $str ) {
854 $this->loadFromDatabase();
855 $this->mCookiePassword
= md5( $str );
858 function setNewpassword( $str ) {
859 $this->loadFromDatabase();
860 $this->mNewpassword
= $this->encryptPassword( $str );
863 function getEmail() {
864 $this->loadFromDatabase();
865 return $this->mEmail
;
868 function getEmailAuthenticationTimestamp() {
869 $this->loadFromDatabase();
870 return $this->mEmailAuthenticated
;
873 function setEmail( $str ) {
874 $this->loadFromDatabase();
875 $this->mEmail
= $str;
878 function getRealName() {
879 $this->loadFromDatabase();
880 return $this->mRealName
;
883 function setRealName( $str ) {
884 $this->loadFromDatabase();
885 $this->mRealName
= $str;
888 function getOption( $oname ) {
889 $this->loadFromDatabase();
890 if ( array_key_exists( $oname, $this->mOptions
) ) {
891 return trim( $this->mOptions
[$oname] );
897 function setOption( $oname, $val ) {
898 $this->loadFromDatabase();
899 if ( $oname == 'skin' ) {
900 # Clear cached skin, so the new one displays immediately in Special:Preferences
901 unset( $this->mSkin
);
903 $this->mOptions
[$oname] = $val;
904 $this->invalidateCache();
907 function getRights() {
908 $this->loadFromDatabase();
909 return $this->mRights
;
913 * Get the list of explicit group memberships this user has.
914 * The implicit * and user groups are not included.
915 * @return array of strings
917 function getGroups() {
918 $this->loadFromDatabase();
919 return $this->mGroups
;
923 * Get the list of implicit group memberships this user has.
924 * This includes all explicit groups, plus 'user' if logged in
925 * and '*' for all accounts.
926 * @return array of strings
928 function getEffectiveGroups() {
929 $base = array( '*' );
930 if( $this->isLoggedIn() ) {
933 return array_merge( $base, $this->getGroups() );
937 * Remove the user from the given group.
938 * This takes immediate effect.
941 function addGroup( $group ) {
942 $dbw =& wfGetDB( DB_MASTER
);
943 $dbw->insert( 'user_groups',
945 'ug_user' => $this->getID(),
946 'ug_group' => $group,
951 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
952 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
954 $this->invalidateCache();
955 $this->saveSettings();
959 * Remove the user from the given group.
960 * This takes immediate effect.
963 function removeGroup( $group ) {
964 $dbw =& wfGetDB( DB_MASTER
);
965 $dbw->delete( 'user_groups',
967 'ug_user' => $this->getID(),
968 'ug_group' => $group,
970 'User::removeGroup' );
972 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
973 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
975 $this->invalidateCache();
976 $this->saveSettings();
981 * A more legible check for non-anonymousness.
982 * Returns true if the user is not an anonymous visitor.
986 function isLoggedIn() {
987 return( $this->getID() != 0 );
991 * A more legible check for anonymousness.
992 * Returns true if the user is an anonymous visitor.
997 return !$this->isLoggedIn();
1001 * Check if a user is sysop
1002 * Die with backtrace. Use User:isAllowed() instead.
1005 function isSysop() {
1006 return $this->isAllowed( 'protect' );
1010 function isDeveloper() {
1011 return $this->isAllowed( 'siteadmin' );
1015 function isBureaucrat() {
1016 return $this->isAllowed( 'makesysop' );
1020 * Whether the user is a bot
1021 * @todo need to be migrated to the new user level management sytem
1024 $this->loadFromDatabase();
1025 return in_array( 'bot', $this->mRights
);
1029 * Check if user is allowed to access a feature / make an action
1030 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1031 * @return boolean True: action is allowed, False: action should not be allowed
1033 function isAllowed($action='') {
1034 $this->loadFromDatabase();
1035 return in_array( $action , $this->mRights
);
1039 * Load a skin if it doesn't exist or return it
1040 * @todo FIXME : need to check the old failback system [AV]
1042 function &getSkin() {
1043 global $IP, $wgRequest;
1044 if ( ! isset( $this->mSkin
) ) {
1045 $fname = 'User::getSkin';
1046 wfProfileIn( $fname );
1048 # get all skin names available
1049 $skinNames = Skin
::getSkinNames();
1052 $userSkin = $this->getOption( 'skin' );
1053 $userSkin = $wgRequest->getText('useskin', $userSkin);
1054 if ( $userSkin == '' ) { $userSkin = 'standard'; }
1056 if ( !isset( $skinNames[$userSkin] ) ) {
1057 # in case the user skin could not be found find a replacement
1061 2 => 'CologneBlue');
1062 # if phptal is enabled we should have monobook skin that
1063 # superseed the good old SkinStandard.
1064 if ( isset( $skinNames['monobook'] ) ) {
1065 $fallback[0] = 'MonoBook';
1068 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
1069 $sn = $fallback[$userSkin];
1074 # The user skin is available
1075 $sn = $skinNames[$userSkin];
1078 # Grab the skin class and initialise it. Each skin checks for PHPTal
1079 # and will not load if it's not enabled.
1080 require_once( $IP.'/skins/'.$sn.'.php' );
1082 # Check if we got if not failback to default skin
1083 $className = 'Skin'.$sn;
1084 if( !class_exists( $className ) ) {
1085 # DO NOT die if the class isn't found. This breaks maintenance
1086 # scripts and can cause a user account to be unrecoverable
1087 # except by SQL manipulation if a previously valid skin name
1088 # is no longer valid.
1089 $className = 'SkinStandard';
1090 require_once( $IP.'/skins/Standard.php' );
1092 $this->mSkin
=& new $className;
1093 wfProfileOut( $fname );
1095 return $this->mSkin
;
1099 * @param string $title Article title to look at
1103 * Check watched status of an article
1104 * @return bool True if article is watched
1106 function isWatched( $title ) {
1107 $wl = WatchedItem
::fromUserTitle( $this, $title );
1108 return $wl->isWatched();
1114 function addWatch( $title ) {
1115 $wl = WatchedItem
::fromUserTitle( $this, $title );
1117 $this->invalidateCache();
1121 * Stop watching an article
1123 function removeWatch( $title ) {
1124 $wl = WatchedItem
::fromUserTitle( $this, $title );
1126 $this->invalidateCache();
1130 * Clear the user's notification timestamp for the given title.
1131 * If e-notif e-mails are on, they will receive notification mails on
1132 * the next change of the page if it's watched etc.
1134 function clearNotification( &$title ) {
1135 global $wgUser, $wgUseEnotif;
1137 if ( !$wgUseEnotif ) {
1141 $userid = $this->getID();
1146 // Only update the timestamp if the page is being watched.
1147 // The query to find out if it is watched is cached both in memcached and per-invocation,
1148 // and when it does have to be executed, it can be on a slave
1149 // If this is the user's newtalk page, we always update the timestamp
1150 if ($title->getNamespace() == NS_USER_TALK
&&
1151 $title->getText() == $wgUser->getName())
1154 } elseif ( $this->getID() == $wgUser->getID() ) {
1155 $watched = $title->userIsWatching();
1160 // If the page is watched by the user (or may be watched), update the timestamp on any
1161 // any matching rows
1163 $dbw =& wfGetDB( DB_MASTER
);
1164 $success = $dbw->update( 'watchlist',
1166 'wl_notificationtimestamp' => NULL
1167 ), array( /* WHERE */
1168 'wl_title' => $title->getDBkey(),
1169 'wl_namespace' => $title->getNamespace(),
1170 'wl_user' => $this->getID()
1171 ), 'User::clearLastVisited'
1179 * Resets all of the given user's page-change notification timestamps.
1180 * If e-notif e-mails are on, they will receive notification mails on
1181 * the next change of any watched page.
1183 * @param int $currentUser user ID number
1186 function clearAllNotifications( $currentUser ) {
1187 global $wgUseEnotif;
1188 if ( !$wgUseEnotif ) {
1191 if( $currentUser != 0 ) {
1193 $dbw =& wfGetDB( DB_MASTER
);
1194 $success = $dbw->update( 'watchlist',
1196 'wl_notificationtimestamp' => 0
1197 ), array( /* WHERE */
1198 'wl_user' => $currentUser
1199 ), 'UserMailer::clearAll'
1202 # we also need to clear here the "you have new message" notification for the own user_talk page
1203 # This is cleared one page view later in Article::viewUpdates();
1209 * @return string Encoding options
1211 function encodeOptions() {
1213 foreach ( $this->mOptions
as $oname => $oval ) {
1214 array_push( $a, $oname.'='.$oval );
1216 $s = implode( "\n", $a );
1223 function decodeOptions( $str ) {
1224 $a = explode( "\n", $str );
1225 foreach ( $a as $s ) {
1226 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1227 $this->mOptions
[$m[1]] = $m[2];
1232 function setCookies() {
1233 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
1234 if ( 0 == $this->mId
) return;
1235 $this->loadFromDatabase();
1236 $exp = time() +
$wgCookieExpiration;
1238 $_SESSION['wsUserID'] = $this->mId
;
1239 setcookie( $wgDBname.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain );
1241 $_SESSION['wsUserName'] = $this->mName
;
1242 setcookie( $wgDBname.'UserName', $this->mName
, $exp, $wgCookiePath, $wgCookieDomain );
1244 $_SESSION['wsToken'] = $this->mToken
;
1245 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1246 setcookie( $wgDBname.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain );
1248 setcookie( $wgDBname.'Token', '', time() - 3600 );
1254 * It will clean the session cookie
1257 global $wgCookiePath, $wgCookieDomain, $wgDBname, $wgIP;
1258 $this->loadDefaults();
1259 $this->setLoaded( true );
1261 $_SESSION['wsUserID'] = 0;
1263 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1264 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1266 # Remember when user logged out, to prevent seeing cached pages
1267 setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain );
1271 * Save object settings into database
1273 function saveSettings() {
1274 global $wgMemc, $wgDBname, $wgUseEnotif;
1275 $fname = 'User::saveSettings';
1277 if ( wfReadOnly() ) { return; }
1278 $this->saveNewtalk();
1279 if ( 0 == $this->mId
) { return; }
1281 $dbw =& wfGetDB( DB_MASTER
);
1282 $dbw->update( 'user',
1284 'user_name' => $this->mName
,
1285 'user_password' => $this->mPassword
,
1286 'user_newpassword' => $this->mNewpassword
,
1287 'user_real_name' => $this->mRealName
,
1288 'user_email' => $this->mEmail
,
1289 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1290 'user_options' => $this->encodeOptions(),
1291 'user_touched' => $dbw->timestamp($this->mTouched
),
1292 'user_token' => $this->mToken
1293 ), array( /* WHERE */
1294 'user_id' => $this->mId
1297 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1301 * Save value of new talk flag.
1303 function saveNewtalk() {
1304 global $wgDBname, $wgMemc, $wgUseEnotif;
1306 $fname = 'User::saveNewtalk';
1310 if ( wfReadOnly() ) { return ; }
1311 $dbr =& wfGetDB( DB_SLAVE
);
1312 $dbw =& wfGetDB( DB_MASTER
);
1314 if ( $wgUseEnotif ) {
1315 if ( ! $this->getNewtalk() ) {
1316 # Delete the watchlist entry for user_talk page X watched by user X
1317 $dbw->delete( 'watchlist',
1318 array( 'wl_user' => $this->mId
,
1319 'wl_title' => $this->getTitleKey(),
1320 'wl_namespace' => NS_USER_TALK
),
1322 if ( $dbw->affectedRows() ) {
1326 # Anon users have a separate memcache space for newtalk
1327 # since they don't store their own info. Trim...
1328 $wgMemc->delete( "$wgDBname:newtalk:ip:{$this->mName}" );
1332 if ($this->getID() != 0) {
1334 $value = $this->getID();
1338 $value = $this->mName
;
1339 $key = "$wgDBname:newtalk:ip:$this->mName";
1342 $dbr =& wfGetDB( DB_SLAVE
);
1343 $dbw =& wfGetDB( DB_MASTER
);
1345 $res = $dbr->selectField('user_newtalk', $field,
1346 array($field => $value), $fname);
1349 if ($res !== false && $this->mNewtalk
== 0) {
1350 $dbw->delete('user_newtalk', array($field => $value), $fname);
1352 $wgMemc->set( $key, 0 );
1354 } else if ($res === false && $this->mNewtalk
== 1) {
1355 $dbw->insert('user_newtalk', array($field => $value), $fname);
1357 $wgMemc->set( $key, 1 );
1364 # Update user_touched, so that newtalk notifications in the client cache are invalidated
1365 if ( $changed && $this->getID() ) {
1366 $dbw->update('user',
1367 /*SET*/ array( 'user_touched' => $this->mTouched
),
1368 /*WHERE*/ array( 'user_id' => $this->getID() ),
1370 $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
1375 * Checks if a user with the given name exists, returns the ID
1377 function idForName() {
1378 $fname = 'User::idForName';
1381 $s = trim( $this->mName
);
1382 if ( 0 == strcmp( '', $s ) ) return 0;
1384 $dbr =& wfGetDB( DB_SLAVE
);
1385 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1386 if ( $id === false ) {
1393 * Add user object to the database
1395 function addToDatabase() {
1396 $fname = 'User::addToDatabase';
1397 $dbw =& wfGetDB( DB_MASTER
);
1398 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1399 $dbw->insert( 'user',
1401 'user_id' => $seqVal,
1402 'user_name' => $this->mName
,
1403 'user_password' => $this->mPassword
,
1404 'user_newpassword' => $this->mNewpassword
,
1405 'user_email' => $this->mEmail
,
1406 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1407 'user_real_name' => $this->mRealName
,
1408 'user_options' => $this->encodeOptions(),
1409 'user_token' => $this->mToken
1412 $this->mId
= $dbw->insertId();
1415 function spreadBlock() {
1417 # If the (non-anonymous) user is blocked, this function will block any IP address
1418 # that they successfully log on from.
1419 $fname = 'User::spreadBlock';
1421 wfDebug( "User:spreadBlock()\n" );
1422 if ( $this->mId
== 0 ) {
1426 $userblock = Block
::newFromDB( '', $this->mId
);
1427 if ( !$userblock->isValid() ) {
1431 # Check if this IP address is already blocked
1432 $ipblock = Block
::newFromDB( $wgIP );
1433 if ( $ipblock->isValid() ) {
1434 # Just update the timestamp
1435 $ipblock->updateTimestamp();
1439 # Make a new block object with the desired properties
1440 wfDebug( "Autoblocking {$this->mName}@{$wgIP}\n" );
1441 $ipblock->mAddress
= $wgIP;
1442 $ipblock->mUser
= 0;
1443 $ipblock->mBy
= $userblock->mBy
;
1444 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1445 $ipblock->mTimestamp
= wfTimestampNow();
1446 $ipblock->mAuto
= 1;
1447 # If the user is already blocked with an expiry date, we don't
1448 # want to pile on top of that!
1449 if($userblock->mExpiry
) {
1450 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1452 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1460 function getPageRenderingHash() {
1463 return $this->mHash
;
1466 // stubthreshold is only included below for completeness,
1467 // it will always be 0 when this function is called by parsercache.
1469 $confstr = $this->getOption( 'math' );
1470 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1471 $confstr .= '!' . $this->getOption( 'date' );
1472 $confstr .= '!' . $this->getOption( 'numberheadings' );
1473 $confstr .= '!' . $this->getOption( 'language' );
1474 $confstr .= '!' . $this->getOption( 'thumbsize' );
1475 // add in language specific options, if any
1476 $extra = $wgContLang->getExtraHashOptions();
1479 $this->mHash
= $confstr;
1483 function isAllowedToCreateAccount() {
1484 return $this->isAllowed( 'createaccount' );
1488 * Set mDataLoaded, return previous value
1489 * Use this to prevent DB access in command-line scripts or similar situations
1491 function setLoaded( $loaded ) {
1492 return wfSetVar( $this->mDataLoaded
, $loaded );
1496 * Get this user's personal page title.
1501 function getUserPage() {
1502 return Title
::makeTitle( NS_USER
, $this->mName
);
1506 * Get this user's talk page title.
1511 function getTalkPage() {
1512 $title = $this->getUserPage();
1513 return $title->getTalkPage();
1519 function getMaxID() {
1520 $dbr =& wfGetDB( DB_SLAVE
);
1521 return $dbr->selectField( 'user', 'max(user_id)', false );
1525 * Determine whether the user is a newbie. Newbies are either
1526 * anonymous IPs, or the 1% most recently created accounts.
1527 * Bots and sysops are excluded.
1528 * @return bool True if it is a newbie.
1530 function isNewbie() {
1531 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1535 * Check to see if the given clear-text password is one of the accepted passwords
1536 * @param string $password User password.
1537 * @return bool True if the given password is correct otherwise False.
1539 function checkPassword( $password ) {
1540 global $wgAuth, $wgMinimalPasswordLength;
1541 $this->loadFromDatabase();
1543 // Even though we stop people from creating passwords that
1544 // are shorter than this, doesn't mean people wont be able
1545 // to. Certain authentication plugins do NOT want to save
1546 // domain passwords in a mysql database, so we should
1547 // check this (incase $wgAuth->strict() is false).
1548 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1552 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1554 } elseif( $wgAuth->strict() ) {
1555 /* Auth plugin doesn't allow local authentication */
1558 $ep = $this->encryptPassword( $password );
1559 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1561 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1563 } elseif ( function_exists( 'iconv' ) ) {
1564 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1565 # Check for this with iconv
1566 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1567 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1575 * Initialize (if necessary) and return a session token value
1576 * which can be used in edit forms to show that the user's
1577 * login credentials aren't being hijacked with a foreign form
1580 * @param mixed $salt - Optional function-specific data for hash.
1581 * Use a string or an array of strings.
1585 function editToken( $salt = '' ) {
1586 if( !isset( $_SESSION['wsEditToken'] ) ) {
1587 $token = $this->generateToken();
1588 $_SESSION['wsEditToken'] = $token;
1590 $token = $_SESSION['wsEditToken'];
1592 if( is_array( $salt ) ) {
1593 $salt = implode( '|', $salt );
1595 return md5( $token . $salt );
1599 * Generate a hex-y looking random token for various uses.
1600 * Could be made more cryptographically sure if someone cares.
1603 function generateToken( $salt = '' ) {
1604 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1605 return md5( $token . $salt );
1609 * Check given value against the token value stored in the session.
1610 * A match should confirm that the form was submitted from the
1611 * user's own login session, not a form submission from a third-party
1614 * @param string $val - the input value to compare
1615 * @param string $salt - Optional function-specific data for hash
1619 function matchEditToken( $val, $salt = '' ) {
1623 if ( !isset( $_SESSION['wsEditToken'] ) ) {
1624 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1625 $mckey = memsess_key( session_id() );
1626 $uname = @posix_uname();
1627 $msg = "wsEditToken not set!\n" .
1628 'apache server=' . $uname['nodename'] . "\n" .
1629 'session_id = ' . session_id() . "\n" .
1630 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1631 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1632 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1634 @error_log( $msg, 3, $logfile );
1637 return ( $val == $this->editToken( $salt ) );
1641 * Generate a new e-mail confirmation token and send a confirmation
1642 * mail to the user's given address.
1644 * @return mixed True on success, a WikiError object on failure.
1646 function sendConfirmationMail() {
1647 global $wgIP, $wgContLang;
1648 $url = $this->confirmationTokenUrl( $expiration );
1649 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1650 wfMsg( 'confirmemail_body',
1654 $wgContLang->timeanddate( $expiration, false ) ) );
1658 * Send an e-mail to this user's account. Does not check for
1659 * confirmed status or validity.
1661 * @param string $subject
1662 * @param string $body
1663 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1664 * @return mixed True on success, a WikiError object on failure.
1666 function sendMail( $subject, $body, $from = null ) {
1667 if( is_null( $from ) ) {
1668 global $wgPasswordSender;
1669 $from = $wgPasswordSender;
1672 require_once( 'UserMailer.php' );
1673 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1675 if( $error == '' ) {
1678 return new WikiError( $error );
1683 * Generate, store, and return a new e-mail confirmation code.
1684 * A hash (unsalted since it's used as a key) is stored.
1685 * @param &$expiration mixed output: accepts the expiration time
1689 function confirmationToken( &$expiration ) {
1690 $fname = 'User::confirmationToken';
1693 $expires = $now +
7 * 24 * 60 * 60;
1694 $expiration = wfTimestamp( TS_MW
, $expires );
1696 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1697 $hash = md5( $token );
1699 $dbw =& wfGetDB( DB_MASTER
);
1700 $dbw->update( 'user',
1701 array( 'user_email_token' => $hash,
1702 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1703 array( 'user_id' => $this->mId
),
1710 * Generate and store a new e-mail confirmation token, and return
1711 * the URL the user can use to confirm.
1712 * @param &$expiration mixed output: accepts the expiration time
1716 function confirmationTokenUrl( &$expiration ) {
1717 $token = $this->confirmationToken( $expiration );
1718 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1719 return $title->getFullUrl();
1723 * Mark the e-mail address confirmed and save.
1725 function confirmEmail() {
1726 $this->loadFromDatabase();
1727 $this->mEmailAuthenticated
= wfTimestampNow();
1728 $this->saveSettings();
1733 * Is this user allowed to send e-mails within limits of current
1734 * site configuration?
1737 function canSendEmail() {
1738 return $this->isEmailConfirmed();
1742 * Is this user allowed to receive e-mails within limits of current
1743 * site configuration?
1746 function canReceiveEmail() {
1747 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1751 * Is this user's e-mail address valid-looking and confirmed within
1752 * limits of the current site configuration?
1754 * If $wgEmailAuthentication is on, this may require the user to have
1755 * confirmed their address by returning a code or using a password
1756 * sent to the address from the wiki.
1760 function isEmailConfirmed() {
1761 global $wgEmailAuthentication;
1762 $this->loadFromDatabase();
1763 if( $this->isAnon() )
1765 if( !$this->isValidEmailAddr( $this->mEmail
) )
1767 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1773 * @param array $groups list of groups
1774 * @return array list of permission key names for given groups combined
1777 function getGroupPermissions( $groups ) {
1778 global $wgGroupPermissions;
1780 foreach( $groups as $group ) {
1781 if( isset( $wgGroupPermissions[$group] ) ) {
1782 $rights = array_merge( $rights,
1783 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1790 * @param string $group key name
1791 * @return string localized descriptive name, if provided
1794 function getGroupName( $group ) {
1795 $key = "group-$group-name";
1796 $name = wfMsg( $key );
1797 if( $name == '' ||
$name == "<$key>" ) {
1805 * Return the set of defined explicit groups.
1806 * The * and 'user' groups are not included.
1810 function getAllGroups() {
1811 global $wgGroupPermissions;
1813 array_keys( $wgGroupPermissions ),
1814 array( '*', 'user' ) );