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 ), '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?
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
) {
366 wfDebug( "User::getBlockedStatus: already loaded.\n" );
370 $fname = 'User::getBlockedStatus';
371 wfProfileIn( $fname );
372 wfDebug( "$fname: checking...\n" );
374 $this->mBlockedby
= 0;
377 $block = new Block();
378 $block->forUpdate( $bFromSlave );
379 if ( $block->load( $wgIP , $this->mId
) ) {
380 wfDebug( "$fname: Found block.\n" );
381 $this->mBlockedby
= $block->mBy
;
382 $this->mBlockreason
= $block->mReason
;
383 if ( $this->isLoggedIn() ) {
384 $this->spreadBlock();
387 wfDebug( "$fname: No block.\n" );
391 if ( !$this->mBlockedby
) {
392 # Check first against slave, and optionally from master.
393 wfDebug( "$fname: Checking range blocks\n" );
394 $block = $wgBlockCache->get( $wgIP, true );
395 if ( !$block && !$bFromSlave )
397 # Not blocked: check against master, to make sure.
398 $wgBlockCache->clearLocal( );
399 $block = $wgBlockCache->get( $wgIP, false );
401 if ( $block !== false ) {
402 $this->mBlockedby
= $block->mBy
;
403 $this->mBlockreason
= $block->mReason
;
408 if ( !$this->isSysop() && !in_array( $wgIP, $wgProxyWhitelist ) ) {
411 if ( array_key_exists( $wgIP, $wgProxyList ) ) {
412 $this->mBlockedby
= wfMsg( 'proxyblocker' );
413 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
417 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
418 if ( $this->inSorbsBlacklist( $wgIP ) ) {
419 $this->mBlockedby
= wfMsg( 'sorbs' );
420 $this->mBlockreason
= wfMsg( 'sorbsreason' );
424 wfProfileOut( $fname );
427 function inSorbsBlacklist( $ip ) {
428 global $wgEnableSorbs;
429 return $wgEnableSorbs &&
430 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
433 function inOpmBlacklist( $ip ) {
435 return $wgEnableOpm &&
436 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
439 function inDnsBlacklist( $ip, $base ) {
440 $fname = 'User::inDnsBlacklist';
441 wfProfileIn( $fname );
446 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
448 for ( $i=4; $i>=1; $i-- ) {
449 $host .= $m[$i] . '.';
454 $ipList = gethostbynamel( $host );
457 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
460 wfDebug( "Requested $host, not found in $base.\n" );
464 wfProfileOut( $fname );
469 * Primitive rate limits: enforce maximum actions per time period
470 * to put a brake on flooding.
472 * Note: when using a shared cache like memcached, IP-address
473 * last-hit counters will be shared across wikis.
475 * @return bool true if a rate limiter was tripped
478 function pingLimiter( $action='edit' ) {
479 global $wgRateLimits;
480 if( !isset( $wgRateLimits[$action] ) ) {
483 if( $this->isAllowed( 'delete' ) ) {
488 global $wgMemc, $wgIP, $wgDBname, $wgRateLimitLog;
489 $fname = 'User::pingLimiter';
490 wfProfileIn( $fname );
492 $limits = $wgRateLimits[$action];
494 $id = $this->getId();
496 if( isset( $limits['anon'] ) && $id == 0 ) {
497 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
500 if( isset( $limits['user'] ) && $id != 0 ) {
501 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
503 if( $this->isNewbie() ) {
504 if( isset( $limits['newbie'] ) && $id != 0 ) {
505 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
507 if( isset( $limits['ip'] ) ) {
508 $keys["mediawiki:limiter:$action:ip:$wgIP"] = $limits['ip'];
510 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $wgIP, $matches ) ) {
511 $subnet = $matches[1];
512 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
517 foreach( $keys as $key => $limit ) {
518 list( $max, $period ) = $limit;
519 $summary = "(limit $max in {$period}s)";
520 $count = $wgMemc->get( $key );
522 if( $count > $max ) {
523 wfDebug( "$fname: tripped! $key at $count $summary\n" );
524 if( $wgRateLimitLog ) {
525 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
529 wfDebug( "$fname: ok. $key at $count $summary\n" );
532 wfDebug( "$fname: adding record for $key $summary\n" );
533 $wgMemc->add( $key, 1, intval( $period ) );
535 $wgMemc->incr( $key );
538 wfProfileOut( $fname );
543 * Check if user is blocked
544 * @return bool True if blocked, false otherwise
546 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
547 wfDebug( "User::isBlocked: enter\n" );
548 $this->getBlockedStatus( $bFromSlave );
549 return $this->mBlockedby
!== 0;
553 * Check if user is blocked from editing a particular article
555 function isBlockedFrom( $title, $bFromSlave = false ) {
556 global $wgBlockAllowsUTEdit;
557 $fname = 'User::isBlockedFrom';
558 wfProfileIn( $fname );
559 wfDebug( "$fname: enter\n" );
561 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
562 $title->getNamespace() == NS_USER_TALK
)
565 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
567 wfDebug( "$fname: asking isBlocked()\n" );
568 $blocked = $this->isBlocked( $bFromSlave );
570 wfProfileOut( $fname );
575 * Get name of blocker
576 * @return string name of blocker
578 function blockedBy() {
579 $this->getBlockedStatus();
580 return $this->mBlockedby
;
584 * Get blocking reason
585 * @return string Blocking reason
587 function blockedFor() {
588 $this->getBlockedStatus();
589 return $this->mBlockreason
;
593 * Initialise php session
595 function SetupSession() {
596 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
597 if( $wgSessionsInMemcached ) {
598 require_once( 'MemcachedSessions.php' );
599 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
600 # If it's left on 'user' or another setting from another
601 # application, it will end up failing. Try to recover.
602 ini_set ( 'session.save_handler', 'files' );
604 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
605 session_cache_limiter( 'private, must-revalidate' );
610 * Read datas from session
613 function loadFromSession() {
614 global $wgMemc, $wgDBname;
616 if ( isset( $_SESSION['wsUserID'] ) ) {
617 if ( 0 != $_SESSION['wsUserID'] ) {
618 $sId = $_SESSION['wsUserID'];
622 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
623 $sId = intval( $_COOKIE["{$wgDBname}UserID"] );
624 $_SESSION['wsUserID'] = $sId;
628 if ( isset( $_SESSION['wsUserName'] ) ) {
629 $sName = $_SESSION['wsUserName'];
630 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
631 $sName = $_COOKIE["{$wgDBname}UserName"];
632 $_SESSION['wsUserName'] = $sName;
637 $passwordCorrect = FALSE;
638 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
639 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
640 # Expire old serialized objects; they may be corrupt.
643 if($makenew = !$user) {
644 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
647 $user->loadFromDatabase();
649 wfDebug( "User::loadFromSession() got from cache!\n" );
652 if ( isset( $_SESSION['wsToken'] ) ) {
653 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
654 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
655 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
657 return new User(); # Can't log in from session
660 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
662 if($wgMemc->set( $key, $user ))
663 wfDebug( "User::loadFromSession() successfully saved user\n" );
665 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
669 return new User(); # Can't log in from session
673 * Load a user from the database
675 function loadFromDatabase() {
676 global $wgCommandLineMode;
677 $fname = "User::loadFromDatabase";
679 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
680 # loading in a command line script, don't assume all command line scripts need it like this
681 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
682 if ( $this->mDataLoaded
) {
687 $this->mId
= intval( $this->mId
);
689 /** Anonymous user */
692 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
693 $this->mDataLoaded
= true;
695 } # the following stuff is for non-anonymous users only
697 $dbr =& wfGetDB( DB_SLAVE
);
698 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
699 'user_email_authenticated',
700 'user_real_name','user_options','user_touched', 'user_token' ),
701 array( 'user_id' => $this->mId
), $fname );
703 if ( $s !== false ) {
704 $this->mName
= $s->user_name
;
705 $this->mEmail
= $s->user_email
;
706 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
707 $this->mRealName
= $s->user_real_name
;
708 $this->mPassword
= $s->user_password
;
709 $this->mNewpassword
= $s->user_newpassword
;
710 $this->decodeOptions( $s->user_options
);
711 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
712 $this->mToken
= $s->user_token
;
714 $res = $dbr->select( 'user_groups',
716 array( 'ug_user' => $this->mId
),
718 $this->mGroups
= array();
719 while( $row = $dbr->fetchObject( $res ) ) {
720 $this->mGroups
[] = $row->ug_group
;
722 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
723 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
726 $this->mDataLoaded
= true;
729 function getID() { return $this->mId
; }
730 function setID( $v ) {
732 $this->mDataLoaded
= false;
736 $this->loadFromDatabase();
740 function setName( $str ) {
741 $this->loadFromDatabase();
747 * Return the title dbkey form of the name, for eg user pages.
751 function getTitleKey() {
752 return str_replace( ' ', '_', $this->getName() );
755 function getNewtalk() {
757 $fname = 'User::getNewtalk';
758 $this->loadFromDatabase();
760 # Load the newtalk status if it is unloaded (mNewtalk=-1)
761 if( $this->mNewtalk
== -1 ) {
762 $this->mNewtalk
= 0; # reset talk page status
764 # Check memcached separately for anons, who have no
765 # entire User object stored in there.
767 global $wgDBname, $wgMemc;
768 $key = "$wgDBname:newtalk:ip:{$this->mName}";
769 $newtalk = $wgMemc->get( $key );
770 if( is_integer( $newtalk ) ) {
771 $this->mNewtalk
= $newtalk ?
1 : 0;
772 return (bool)$this->mNewtalk
;
776 $dbr =& wfGetDB( DB_SLAVE
);
777 if ( $wgUseEnotif ) {
778 $res = $dbr->select( 'watchlist',
780 array( 'wl_title' => $this->getTitleKey(),
781 'wl_namespace' => NS_USER_TALK
,
782 'wl_user' => $this->mId
,
783 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
784 'User::getNewtalk' );
785 if( $dbr->numRows($res) > 0 ) {
788 $dbr->freeResult( $res );
789 } elseif ( $this->mId
) {
790 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
792 if ( $dbr->numRows($res)>0 ) {
795 $dbr->freeResult( $res );
797 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->mName
), $fname );
798 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
799 $dbr->freeResult( $res );
803 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
807 return ( 0 != $this->mNewtalk
);
810 function setNewtalk( $val ) {
811 $this->loadFromDatabase();
812 $this->mNewtalk
= $val;
813 $this->invalidateCache();
816 function invalidateCache() {
817 global $wgClockSkewFudge;
818 $this->loadFromDatabase();
819 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
820 # Don't forget to save the options after this or
821 # it won't take effect!
824 function validateCache( $timestamp ) {
825 $this->loadFromDatabase();
826 return ($timestamp >= $this->mTouched
);
830 * Encrypt a password.
831 * It can eventuall salt a password @see User::addSalt()
832 * @param string $p clear Password.
833 * @return string Encrypted password.
835 function encryptPassword( $p ) {
836 return wfEncryptPassword( $this->mId
, $p );
839 # Set the password and reset the random token
840 function setPassword( $str ) {
841 $this->loadFromDatabase();
843 $this->mPassword
= $this->encryptPassword( $str );
844 $this->mNewpassword
= '';
847 # Set the random token (used for persistent authentication)
848 function setToken( $token = false ) {
849 global $wgSecretKey, $wgProxyKey, $wgDBname;
851 if ( $wgSecretKey ) {
853 } elseif ( $wgProxyKey ) {
858 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
860 $this->mToken
= $token;
865 function setCookiePassword( $str ) {
866 $this->loadFromDatabase();
867 $this->mCookiePassword
= md5( $str );
870 function setNewpassword( $str ) {
871 $this->loadFromDatabase();
872 $this->mNewpassword
= $this->encryptPassword( $str );
875 function getEmail() {
876 $this->loadFromDatabase();
877 return $this->mEmail
;
880 function getEmailAuthenticationTimestamp() {
881 $this->loadFromDatabase();
882 return $this->mEmailAuthenticated
;
885 function setEmail( $str ) {
886 $this->loadFromDatabase();
887 $this->mEmail
= $str;
890 function getRealName() {
891 $this->loadFromDatabase();
892 return $this->mRealName
;
895 function setRealName( $str ) {
896 $this->loadFromDatabase();
897 $this->mRealName
= $str;
900 function getOption( $oname ) {
901 $this->loadFromDatabase();
902 if ( array_key_exists( $oname, $this->mOptions
) ) {
903 return trim( $this->mOptions
[$oname] );
909 function setOption( $oname, $val ) {
910 $this->loadFromDatabase();
911 if ( $oname == 'skin' ) {
912 # Clear cached skin, so the new one displays immediately in Special:Preferences
913 unset( $this->mSkin
);
915 $this->mOptions
[$oname] = $val;
916 $this->invalidateCache();
919 function getRights() {
920 $this->loadFromDatabase();
921 return $this->mRights
;
925 * Get the list of explicit group memberships this user has.
926 * The implicit * and user groups are not included.
927 * @return array of strings
929 function getGroups() {
930 $this->loadFromDatabase();
931 return $this->mGroups
;
935 * Get the list of implicit group memberships this user has.
936 * This includes all explicit groups, plus 'user' if logged in
937 * and '*' for all accounts.
938 * @return array of strings
940 function getEffectiveGroups() {
941 $base = array( '*' );
942 if( $this->isLoggedIn() ) {
945 return array_merge( $base, $this->getGroups() );
949 * Remove the user from the given group.
950 * This takes immediate effect.
953 function addGroup( $group ) {
954 $dbw =& wfGetDB( DB_MASTER
);
955 $dbw->insert( 'user_groups',
957 'ug_user' => $this->getID(),
958 'ug_group' => $group,
963 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
964 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
966 $this->invalidateCache();
967 $this->saveSettings();
971 * Remove the user from the given group.
972 * This takes immediate effect.
975 function removeGroup( $group ) {
976 $dbw =& wfGetDB( DB_MASTER
);
977 $dbw->delete( 'user_groups',
979 'ug_user' => $this->getID(),
980 'ug_group' => $group,
982 'User::removeGroup' );
984 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
985 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
987 $this->invalidateCache();
988 $this->saveSettings();
993 * A more legible check for non-anonymousness.
994 * Returns true if the user is not an anonymous visitor.
998 function isLoggedIn() {
999 return( $this->getID() != 0 );
1003 * A more legible check for anonymousness.
1004 * Returns true if the user is an anonymous visitor.
1009 return !$this->isLoggedIn();
1013 * Check if a user is sysop
1014 * Die with backtrace. Use User:isAllowed() instead.
1017 function isSysop() {
1018 return $this->isAllowed( 'protect' );
1022 function isDeveloper() {
1023 return $this->isAllowed( 'siteadmin' );
1027 function isBureaucrat() {
1028 return $this->isAllowed( 'makesysop' );
1032 * Whether the user is a bot
1033 * @todo need to be migrated to the new user level management sytem
1036 $this->loadFromDatabase();
1037 return in_array( 'bot', $this->mRights
);
1041 * Check if user is allowed to access a feature / make an action
1042 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1043 * @return boolean True: action is allowed, False: action should not be allowed
1045 function isAllowed($action='') {
1046 $this->loadFromDatabase();
1047 return in_array( $action , $this->mRights
);
1051 * Load a skin if it doesn't exist or return it
1052 * @todo FIXME : need to check the old failback system [AV]
1054 function &getSkin() {
1055 global $IP, $wgRequest;
1056 if ( ! isset( $this->mSkin
) ) {
1057 $fname = 'User::getSkin';
1058 wfProfileIn( $fname );
1060 # get all skin names available
1061 $skinNames = Skin
::getSkinNames();
1064 $userSkin = $this->getOption( 'skin' );
1065 $userSkin = $wgRequest->getText('useskin', $userSkin);
1066 if ( $userSkin == '' ) { $userSkin = 'standard'; }
1068 if ( !isset( $skinNames[$userSkin] ) ) {
1069 # in case the user skin could not be found find a replacement
1073 2 => 'CologneBlue');
1074 # if phptal is enabled we should have monobook skin that
1075 # superseed the good old SkinStandard.
1076 if ( isset( $skinNames['monobook'] ) ) {
1077 $fallback[0] = 'MonoBook';
1080 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
1081 $sn = $fallback[$userSkin];
1086 # The user skin is available
1087 $sn = $skinNames[$userSkin];
1090 # Grab the skin class and initialise it. Each skin checks for PHPTal
1091 # and will not load if it's not enabled.
1092 require_once( $IP.'/skins/'.$sn.'.php' );
1094 # Check if we got if not failback to default skin
1095 $className = 'Skin'.$sn;
1096 if( !class_exists( $className ) ) {
1097 # DO NOT die if the class isn't found. This breaks maintenance
1098 # scripts and can cause a user account to be unrecoverable
1099 # except by SQL manipulation if a previously valid skin name
1100 # is no longer valid.
1101 $className = 'SkinStandard';
1102 require_once( $IP.'/skins/Standard.php' );
1104 $this->mSkin
=& new $className;
1105 wfProfileOut( $fname );
1107 return $this->mSkin
;
1111 * @param string $title Article title to look at
1115 * Check watched status of an article
1116 * @return bool True if article is watched
1118 function isWatched( $title ) {
1119 $wl = WatchedItem
::fromUserTitle( $this, $title );
1120 return $wl->isWatched();
1126 function addWatch( $title ) {
1127 $wl = WatchedItem
::fromUserTitle( $this, $title );
1129 $this->invalidateCache();
1133 * Stop watching an article
1135 function removeWatch( $title ) {
1136 $wl = WatchedItem
::fromUserTitle( $this, $title );
1138 $this->invalidateCache();
1142 * Clear the user's notification timestamp for the given title.
1143 * If e-notif e-mails are on, they will receive notification mails on
1144 * the next change of the page if it's watched etc.
1146 function clearNotification( &$title ) {
1147 global $wgUser, $wgUseEnotif;
1149 if ( !$wgUseEnotif ) {
1153 $userid = $this->getID();
1158 // Only update the timestamp if the page is being watched.
1159 // The query to find out if it is watched is cached both in memcached and per-invocation,
1160 // and when it does have to be executed, it can be on a slave
1161 // If this is the user's newtalk page, we always update the timestamp
1162 if ($title->getNamespace() == NS_USER_TALK
&&
1163 $title->getText() == $wgUser->getName())
1166 } elseif ( $this->getID() == $wgUser->getID() ) {
1167 $watched = $title->userIsWatching();
1172 // If the page is watched by the user (or may be watched), update the timestamp on any
1173 // any matching rows
1175 $dbw =& wfGetDB( DB_MASTER
);
1176 $success = $dbw->update( 'watchlist',
1178 'wl_notificationtimestamp' => NULL
1179 ), array( /* WHERE */
1180 'wl_title' => $title->getDBkey(),
1181 'wl_namespace' => $title->getNamespace(),
1182 'wl_user' => $this->getID()
1183 ), 'User::clearLastVisited'
1191 * Resets all of the given user's page-change notification timestamps.
1192 * If e-notif e-mails are on, they will receive notification mails on
1193 * the next change of any watched page.
1195 * @param int $currentUser user ID number
1198 function clearAllNotifications( $currentUser ) {
1199 global $wgUseEnotif;
1200 if ( !$wgUseEnotif ) {
1203 if( $currentUser != 0 ) {
1205 $dbw =& wfGetDB( DB_MASTER
);
1206 $success = $dbw->update( 'watchlist',
1208 'wl_notificationtimestamp' => 0
1209 ), array( /* WHERE */
1210 'wl_user' => $currentUser
1211 ), 'UserMailer::clearAll'
1214 # we also need to clear here the "you have new message" notification for the own user_talk page
1215 # This is cleared one page view later in Article::viewUpdates();
1221 * @return string Encoding options
1223 function encodeOptions() {
1225 foreach ( $this->mOptions
as $oname => $oval ) {
1226 array_push( $a, $oname.'='.$oval );
1228 $s = implode( "\n", $a );
1235 function decodeOptions( $str ) {
1236 $a = explode( "\n", $str );
1237 foreach ( $a as $s ) {
1238 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1239 $this->mOptions
[$m[1]] = $m[2];
1244 function setCookies() {
1245 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
1246 if ( 0 == $this->mId
) return;
1247 $this->loadFromDatabase();
1248 $exp = time() +
$wgCookieExpiration;
1250 $_SESSION['wsUserID'] = $this->mId
;
1251 setcookie( $wgDBname.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain );
1253 $_SESSION['wsUserName'] = $this->mName
;
1254 setcookie( $wgDBname.'UserName', $this->mName
, $exp, $wgCookiePath, $wgCookieDomain );
1256 $_SESSION['wsToken'] = $this->mToken
;
1257 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1258 setcookie( $wgDBname.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain );
1260 setcookie( $wgDBname.'Token', '', time() - 3600 );
1266 * It will clean the session cookie
1269 global $wgCookiePath, $wgCookieDomain, $wgDBname, $wgIP;
1270 $this->loadDefaults();
1271 $this->setLoaded( true );
1273 $_SESSION['wsUserID'] = 0;
1275 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1276 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1278 # Remember when user logged out, to prevent seeing cached pages
1279 setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain );
1283 * Save object settings into database
1285 function saveSettings() {
1286 global $wgMemc, $wgDBname, $wgUseEnotif;
1287 $fname = 'User::saveSettings';
1289 if ( wfReadOnly() ) { return; }
1290 $this->saveNewtalk();
1291 if ( 0 == $this->mId
) { return; }
1293 $dbw =& wfGetDB( DB_MASTER
);
1294 $dbw->update( 'user',
1296 'user_name' => $this->mName
,
1297 'user_password' => $this->mPassword
,
1298 'user_newpassword' => $this->mNewpassword
,
1299 'user_real_name' => $this->mRealName
,
1300 'user_email' => $this->mEmail
,
1301 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1302 'user_options' => $this->encodeOptions(),
1303 'user_touched' => $dbw->timestamp($this->mTouched
),
1304 'user_token' => $this->mToken
1305 ), array( /* WHERE */
1306 'user_id' => $this->mId
1309 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1313 * Save value of new talk flag.
1315 function saveNewtalk() {
1316 global $wgDBname, $wgMemc, $wgUseEnotif;
1318 $fname = 'User::saveNewtalk';
1322 if ( wfReadOnly() ) { return ; }
1323 $dbr =& wfGetDB( DB_SLAVE
);
1324 $dbw =& wfGetDB( DB_MASTER
);
1326 if ( $wgUseEnotif ) {
1327 if ( ! $this->getNewtalk() ) {
1328 # Delete the watchlist entry for user_talk page X watched by user X
1329 $dbw->delete( 'watchlist',
1330 array( 'wl_user' => $this->mId
,
1331 'wl_title' => $this->getTitleKey(),
1332 'wl_namespace' => NS_USER_TALK
),
1334 if ( $dbw->affectedRows() ) {
1338 # Anon users have a separate memcache space for newtalk
1339 # since they don't store their own info. Trim...
1340 $wgMemc->delete( "$wgDBname:newtalk:ip:{$this->mName}" );
1344 if ($this->getID() != 0) {
1346 $value = $this->getID();
1350 $value = $this->mName
;
1351 $key = "$wgDBname:newtalk:ip:$this->mName";
1354 $dbr =& wfGetDB( DB_SLAVE
);
1355 $dbw =& wfGetDB( DB_MASTER
);
1357 $res = $dbr->selectField('user_newtalk', $field,
1358 array($field => $value), $fname);
1361 if ($res !== false && $this->mNewtalk
== 0) {
1362 $dbw->delete('user_newtalk', array($field => $value), $fname);
1364 $wgMemc->set( $key, 0 );
1366 } else if ($res === false && $this->mNewtalk
== 1) {
1367 $dbw->insert('user_newtalk', array($field => $value), $fname);
1369 $wgMemc->set( $key, 1 );
1376 # Update user_touched, so that newtalk notifications in the client cache are invalidated
1377 if ( $changed && $this->getID() ) {
1378 $dbw->update('user',
1379 /*SET*/ array( 'user_touched' => $this->mTouched
),
1380 /*WHERE*/ array( 'user_id' => $this->getID() ),
1382 $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
1387 * Checks if a user with the given name exists, returns the ID
1389 function idForName() {
1390 $fname = 'User::idForName';
1393 $s = trim( $this->mName
);
1394 if ( 0 == strcmp( '', $s ) ) return 0;
1396 $dbr =& wfGetDB( DB_SLAVE
);
1397 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1398 if ( $id === false ) {
1405 * Add user object to the database
1407 function addToDatabase() {
1408 $fname = 'User::addToDatabase';
1409 $dbw =& wfGetDB( DB_MASTER
);
1410 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1411 $dbw->insert( 'user',
1413 'user_id' => $seqVal,
1414 'user_name' => $this->mName
,
1415 'user_password' => $this->mPassword
,
1416 'user_newpassword' => $this->mNewpassword
,
1417 'user_email' => $this->mEmail
,
1418 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1419 'user_real_name' => $this->mRealName
,
1420 'user_options' => $this->encodeOptions(),
1421 'user_token' => $this->mToken
1424 $this->mId
= $dbw->insertId();
1427 function spreadBlock() {
1429 # If the (non-anonymous) user is blocked, this function will block any IP address
1430 # that they successfully log on from.
1431 $fname = 'User::spreadBlock';
1433 wfDebug( "User:spreadBlock()\n" );
1434 if ( $this->mId
== 0 ) {
1438 $userblock = Block
::newFromDB( '', $this->mId
);
1439 if ( !$userblock->isValid() ) {
1443 # Check if this IP address is already blocked
1444 $ipblock = Block
::newFromDB( $wgIP );
1445 if ( $ipblock->isValid() ) {
1446 # Just update the timestamp
1447 $ipblock->updateTimestamp();
1451 # Make a new block object with the desired properties
1452 wfDebug( "Autoblocking {$this->mName}@{$wgIP}\n" );
1453 $ipblock->mAddress
= $wgIP;
1454 $ipblock->mUser
= 0;
1455 $ipblock->mBy
= $userblock->mBy
;
1456 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1457 $ipblock->mTimestamp
= wfTimestampNow();
1458 $ipblock->mAuto
= 1;
1459 # If the user is already blocked with an expiry date, we don't
1460 # want to pile on top of that!
1461 if($userblock->mExpiry
) {
1462 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1464 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1472 function getPageRenderingHash() {
1475 return $this->mHash
;
1478 // stubthreshold is only included below for completeness,
1479 // it will always be 0 when this function is called by parsercache.
1481 $confstr = $this->getOption( 'math' );
1482 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1483 $confstr .= '!' . $this->getOption( 'date' );
1484 $confstr .= '!' . $this->getOption( 'numberheadings' );
1485 $confstr .= '!' . $this->getOption( 'language' );
1486 $confstr .= '!' . $this->getOption( 'thumbsize' );
1487 // add in language specific options, if any
1488 $extra = $wgContLang->getExtraHashOptions();
1491 $this->mHash
= $confstr;
1495 function isAllowedToCreateAccount() {
1496 return $this->isAllowed( 'createaccount' );
1500 * Set mDataLoaded, return previous value
1501 * Use this to prevent DB access in command-line scripts or similar situations
1503 function setLoaded( $loaded ) {
1504 return wfSetVar( $this->mDataLoaded
, $loaded );
1508 * Get this user's personal page title.
1513 function getUserPage() {
1514 return Title
::makeTitle( NS_USER
, $this->mName
);
1518 * Get this user's talk page title.
1523 function getTalkPage() {
1524 $title = $this->getUserPage();
1525 return $title->getTalkPage();
1531 function getMaxID() {
1532 $dbr =& wfGetDB( DB_SLAVE
);
1533 return $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1537 * Determine whether the user is a newbie. Newbies are either
1538 * anonymous IPs, or the 1% most recently created accounts.
1539 * Bots and sysops are excluded.
1540 * @return bool True if it is a newbie.
1542 function isNewbie() {
1543 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1547 * Check to see if the given clear-text password is one of the accepted passwords
1548 * @param string $password User password.
1549 * @return bool True if the given password is correct otherwise False.
1551 function checkPassword( $password ) {
1552 global $wgAuth, $wgMinimalPasswordLength;
1553 $this->loadFromDatabase();
1555 // Even though we stop people from creating passwords that
1556 // are shorter than this, doesn't mean people wont be able
1557 // to. Certain authentication plugins do NOT want to save
1558 // domain passwords in a mysql database, so we should
1559 // check this (incase $wgAuth->strict() is false).
1560 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1564 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1566 } elseif( $wgAuth->strict() ) {
1567 /* Auth plugin doesn't allow local authentication */
1570 $ep = $this->encryptPassword( $password );
1571 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1573 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1575 } elseif ( function_exists( 'iconv' ) ) {
1576 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1577 # Check for this with iconv
1578 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1579 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1587 * Initialize (if necessary) and return a session token value
1588 * which can be used in edit forms to show that the user's
1589 * login credentials aren't being hijacked with a foreign form
1592 * @param mixed $salt - Optional function-specific data for hash.
1593 * Use a string or an array of strings.
1597 function editToken( $salt = '' ) {
1598 if( !isset( $_SESSION['wsEditToken'] ) ) {
1599 $token = $this->generateToken();
1600 $_SESSION['wsEditToken'] = $token;
1602 $token = $_SESSION['wsEditToken'];
1604 if( is_array( $salt ) ) {
1605 $salt = implode( '|', $salt );
1607 return md5( $token . $salt );
1611 * Generate a hex-y looking random token for various uses.
1612 * Could be made more cryptographically sure if someone cares.
1615 function generateToken( $salt = '' ) {
1616 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1617 return md5( $token . $salt );
1621 * Check given value against the token value stored in the session.
1622 * A match should confirm that the form was submitted from the
1623 * user's own login session, not a form submission from a third-party
1626 * @param string $val - the input value to compare
1627 * @param string $salt - Optional function-specific data for hash
1631 function matchEditToken( $val, $salt = '' ) {
1635 if ( !isset( $_SESSION['wsEditToken'] ) ) {
1636 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1637 $mckey = memsess_key( session_id() );
1638 $uname = @posix_uname();
1639 $msg = "wsEditToken not set!\n" .
1640 'apache server=' . $uname['nodename'] . "\n" .
1641 'session_id = ' . session_id() . "\n" .
1642 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1643 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1644 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1646 @error_log( $msg, 3, $logfile );
1649 return ( $val == $this->editToken( $salt ) );
1653 * Generate a new e-mail confirmation token and send a confirmation
1654 * mail to the user's given address.
1656 * @return mixed True on success, a WikiError object on failure.
1658 function sendConfirmationMail() {
1659 global $wgIP, $wgContLang;
1660 $url = $this->confirmationTokenUrl( $expiration );
1661 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1662 wfMsg( 'confirmemail_body',
1666 $wgContLang->timeanddate( $expiration, false ) ) );
1670 * Send an e-mail to this user's account. Does not check for
1671 * confirmed status or validity.
1673 * @param string $subject
1674 * @param string $body
1675 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1676 * @return mixed True on success, a WikiError object on failure.
1678 function sendMail( $subject, $body, $from = null ) {
1679 if( is_null( $from ) ) {
1680 global $wgPasswordSender;
1681 $from = $wgPasswordSender;
1684 require_once( 'UserMailer.php' );
1685 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1687 if( $error == '' ) {
1690 return new WikiError( $error );
1695 * Generate, store, and return a new e-mail confirmation code.
1696 * A hash (unsalted since it's used as a key) is stored.
1697 * @param &$expiration mixed output: accepts the expiration time
1701 function confirmationToken( &$expiration ) {
1702 $fname = 'User::confirmationToken';
1705 $expires = $now +
7 * 24 * 60 * 60;
1706 $expiration = wfTimestamp( TS_MW
, $expires );
1708 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1709 $hash = md5( $token );
1711 $dbw =& wfGetDB( DB_MASTER
);
1712 $dbw->update( 'user',
1713 array( 'user_email_token' => $hash,
1714 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1715 array( 'user_id' => $this->mId
),
1722 * Generate and store a new e-mail confirmation token, and return
1723 * the URL the user can use to confirm.
1724 * @param &$expiration mixed output: accepts the expiration time
1728 function confirmationTokenUrl( &$expiration ) {
1729 $token = $this->confirmationToken( $expiration );
1730 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1731 return $title->getFullUrl();
1735 * Mark the e-mail address confirmed and save.
1737 function confirmEmail() {
1738 $this->loadFromDatabase();
1739 $this->mEmailAuthenticated
= wfTimestampNow();
1740 $this->saveSettings();
1745 * Is this user allowed to send e-mails within limits of current
1746 * site configuration?
1749 function canSendEmail() {
1750 return $this->isEmailConfirmed();
1754 * Is this user allowed to receive e-mails within limits of current
1755 * site configuration?
1758 function canReceiveEmail() {
1759 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1763 * Is this user's e-mail address valid-looking and confirmed within
1764 * limits of the current site configuration?
1766 * If $wgEmailAuthentication is on, this may require the user to have
1767 * confirmed their address by returning a code or using a password
1768 * sent to the address from the wiki.
1772 function isEmailConfirmed() {
1773 global $wgEmailAuthentication;
1774 $this->loadFromDatabase();
1775 if( $this->isAnon() )
1777 if( !$this->isValidEmailAddr( $this->mEmail
) )
1779 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1785 * @param array $groups list of groups
1786 * @return array list of permission key names for given groups combined
1789 function getGroupPermissions( $groups ) {
1790 global $wgGroupPermissions;
1792 foreach( $groups as $group ) {
1793 if( isset( $wgGroupPermissions[$group] ) ) {
1794 $rights = array_merge( $rights,
1795 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1802 * @param string $group key name
1803 * @return string localized descriptive name, if provided
1806 function getGroupName( $group ) {
1807 $key = "group-$group-name";
1808 $name = wfMsg( $key );
1809 if( $name == '' ||
$name == "<$key>" ) {
1817 * Return the set of defined explicit groups.
1818 * The * and 'user' groups are not included.
1822 function getAllGroups() {
1823 global $wgGroupPermissions;
1825 array_keys( $wgGroupPermissions ),
1826 array( '*', 'user' ) );