11 require_once( 'WatchedItem.php' );
13 # Number of characters in user_token field
14 define( 'USER_TOKEN_LENGTH', 32 );
16 # Serialized record version
17 define( 'MW_USER_VERSION', 2 );
27 var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
28 var $mEmailAuthenticated;
29 var $mRights, $mOptions;
30 var $mDataLoaded, $mNewpassword;
32 var $mBlockedby, $mBlockreason;
38 var $mVersion; // serialized version
40 /** Construct using User:loadDefaults() */
42 $this->loadDefaults();
43 $this->mVersion
= MW_USER_VERSION
;
47 * Static factory method
48 * @param string $name Username, validated by Title:newFromText()
52 function newFromName( $name ) {
53 # Force usernames to capital
55 $name = $wgContLang->ucfirst( $name );
57 # Clean up name according to title rules
58 $t = Title
::newFromText( $name );
63 # Reject various classes of invalid names
64 $canonicalName = $t->getText();
66 $canonicalName = $wgAuth->getCanonicalName( $t->getText() );
68 if( !User
::isValidUserName( $canonicalName ) ) {
73 $u->setName( $canonicalName );
74 $u->setId( $u->idFromName( $canonicalName ) );
79 * Factory method to fetch whichever use has a given email confirmation code.
80 * This code is generated when an account is created or its e-mail address
83 * If the code is invalid or has expired, returns NULL.
89 function newFromConfirmationCode( $code ) {
90 $dbr =& wfGetDB( DB_SLAVE
);
91 $name = $dbr->selectField( 'user', 'user_name', array(
92 'user_email_token' => md5( $code ),
93 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
95 if( is_string( $name ) ) {
96 return User
::newFromName( $name );
103 * Serialze sleep function, for better cache efficiency and avoidance of
104 * silly "incomplete type" errors when skins are cached
107 return array( 'mId', 'mName', 'mPassword', 'mEmail', 'mNewtalk',
108 'mEmailAuthenticated', 'mRights', 'mOptions', 'mDataLoaded',
109 'mNewpassword', 'mBlockedby', 'mBlockreason', 'mTouched',
110 'mToken', 'mRealName', 'mHash', 'mGroups' );
114 * Get username given an id.
115 * @param integer $id Database user id
116 * @return string Nickname of a user
119 function whoIs( $id ) {
120 $dbr =& wfGetDB( DB_SLAVE
);
121 return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), 'User::whoIs' );
125 * Get real username given an id.
126 * @param integer $id Database user id
127 * @return string Realname of a user
130 function whoIsReal( $id ) {
131 $dbr =& wfGetDB( DB_SLAVE
);
132 return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), 'User::whoIsReal' );
136 * Get database id given a user name
137 * @param string $name Nickname of a user
138 * @return integer|null Database user id (null: if non existent
141 function idFromName( $name ) {
142 $fname = "User::idFromName";
144 $nt = Title
::newFromText( $name );
145 if( is_null( $nt ) ) {
149 $dbr =& wfGetDB( DB_SLAVE
);
150 $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
152 if ( $s === false ) {
160 * does the string match an anonymous IPv4 address?
162 * Note: We match \d{1,3}\.\d{1,3}\.\d{1,3}\.xxx as an anonymous IP
163 * address because the usemod software would "cloak" anonymous IP
164 * addresses like this, if we allowed accounts like this to be created
165 * new users could get the old edits of these anonymous users.
170 * @param string $name Nickname of a user
173 function isIP( $name ) {
174 return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/",$name);
175 /*return preg_match("/^
176 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
177 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
178 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
179 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))
184 * Is the input a valid username?
186 * Checks if the input is a valid username, we don't want an empty string,
187 * an IP address, anything that containins slashes (would mess up subpages),
188 * is longer than the maximum allowed username size or doesn't begin with
191 * @param string $name
195 function isValidUserName( $name ) {
196 global $wgContLang, $wgMaxNameChars;
199 || User
::isIP( $name )
200 ||
strpos( $name, '/' ) !== false
201 ||
strlen( $name ) > $wgMaxNameChars
202 ||
$name != $wgContLang->ucfirst( $name ) )
205 // Ensure that the name can't be misresolved as a different title,
206 // such as with extra namespace keys at the start.
207 $parsed = Title
::newFromText( $name );
208 if( is_null( $parsed )
209 ||
$parsed->getNamespace()
210 ||
strcmp( $name, $parsed->getPrefixedText() ) )
217 * Is the input a valid password?
219 * @param string $password
223 function isValidPassword( $password ) {
224 global $wgMinimalPasswordLength;
225 return strlen( $password ) >= $wgMinimalPasswordLength;
229 * Does the string match roughly an email address ?
231 * There used to be a regular expression here, it got removed because it
232 * rejected valid addresses. Actually just check if there is '@' somewhere
233 * in the given address.
235 * @todo Check for RFC 2822 compilance
238 * @param string $addr email address
242 function isValidEmailAddr ( $addr ) {
243 return ( trim( $addr ) != '' ) &&
244 (false !== strpos( $addr, '@' ) );
248 * Count the number of edits of a user
250 * @param int $uid The user ID to check
253 function edits( $uid ) {
254 $fname = 'User::edits';
256 $dbr =& wfGetDB( DB_SLAVE
);
257 return $dbr->selectField(
258 'revision', 'count(*)',
259 array( 'rev_user' => $uid ),
265 * probably return a random password
266 * @return string probably a random password
268 * @todo Check what is doing really [AV]
270 function randomPassword() {
271 global $wgMinimalPasswordLength;
272 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
273 $l = strlen( $pwchars ) - 1;
275 $pwlength = max( 7, $wgMinimalPasswordLength );
276 $digit = mt_rand(0, $pwlength - 1);
278 for ( $i = 0; $i < $pwlength; $i++
) {
279 $np .= $i == $digit ?
chr( mt_rand(48, 57) ) : $pwchars{ mt_rand(0, $l)};
285 * Set properties to default
286 * Used at construction. It will load per language default settings only
287 * if we have an available language object.
289 function loadDefaults() {
292 $fname = 'User::loadDefaults' . $n;
293 wfProfileIn( $fname );
295 global $wgContLang, $wgDBname;
296 global $wgNamespacesToBeSearchedDefault;
299 $this->mNewtalk
= -1;
300 $this->mName
= false;
301 $this->mRealName
= $this->mEmail
= '';
302 $this->mEmailAuthenticated
= null;
303 $this->mPassword
= $this->mNewpassword
= '';
304 $this->mRights
= array();
305 $this->mGroups
= array();
306 $this->mOptions
= User
::getDefaultOptions();
308 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
309 $this->mOptions
['searchNs'.$nsnum] = $val;
311 unset( $this->mSkin
);
312 $this->mDataLoaded
= false;
313 $this->mBlockedby
= -1; # Unset
314 $this->setToken(); # Random
315 $this->mHash
= false;
317 if ( isset( $_COOKIE[$wgDBname.'LoggedOut'] ) ) {
318 $this->mTouched
= wfTimestamp( TS_MW
, $_COOKIE[$wgDBname.'LoggedOut'] );
321 $this->mTouched
= '0'; # Allow any pages to be cached
324 wfProfileOut( $fname );
328 * Combine the language default options with any site-specific options
329 * and add the default language variants.
335 function getDefaultOptions() {
337 * Site defaults will override the global/language defaults
339 global $wgContLang, $wgDefaultUserOptions;
340 $defOpt = $wgDefaultUserOptions +
$wgContLang->getDefaultUserOptions();
343 * default language setting
345 $variant = $wgContLang->getPreferredVariant();
346 $defOpt['variant'] = $variant;
347 $defOpt['language'] = $variant;
353 * Get a given default option value.
360 function getDefaultOption( $opt ) {
361 $defOpts = User
::getDefaultOptions();
362 if( isset( $defOpts[$opt] ) ) {
363 return $defOpts[$opt];
370 * Get blocking information
372 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
373 * non-critical checks are done against slaves. Check when actually saving should be done against
376 function getBlockedStatus( $bFromSlave = true ) {
377 global $wgProxyList, $wgEnableSorbs, $wgProxyWhitelist;
379 if ( -1 != $this->mBlockedby
) {
380 wfDebug( "User::getBlockedStatus: already loaded.\n" );
384 $fname = 'User::getBlockedStatus';
385 wfProfileIn( $fname );
386 wfDebug( "$fname: checking...\n" );
388 $this->mBlockedby
= 0;
392 $block = new Block();
393 $block->fromMaster( !$bFromSlave );
394 if ( $block->load( $ip , $this->mId
) ) {
395 wfDebug( "$fname: Found block.\n" );
396 $this->mBlockedby
= $block->mBy
;
397 $this->mBlockreason
= $block->mReason
;
398 if ( $this->isLoggedIn() ) {
399 $this->spreadBlock();
402 wfDebug( "$fname: No block.\n" );
406 if ( !$this->isSysop() && !in_array( $ip, $wgProxyWhitelist ) ) {
409 if ( array_key_exists( $ip, $wgProxyList ) ) {
410 $this->mBlockedby
= wfMsg( 'proxyblocker' );
411 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
415 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
416 if ( $this->inSorbsBlacklist( $ip ) ) {
417 $this->mBlockedby
= wfMsg( 'sorbs' );
418 $this->mBlockreason
= wfMsg( 'sorbsreason' );
424 wfRunHooks( 'GetBlockedStatus', array( &$this ) );
426 wfProfileOut( $fname );
429 function inSorbsBlacklist( $ip ) {
430 global $wgEnableSorbs;
431 return $wgEnableSorbs &&
432 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
435 function inOpmBlacklist( $ip ) {
437 return $wgEnableOpm &&
438 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
441 function inDnsBlacklist( $ip, $base ) {
442 $fname = 'User::inDnsBlacklist';
443 wfProfileIn( $fname );
448 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
450 for ( $i=4; $i>=1; $i-- ) {
451 $host .= $m[$i] . '.';
456 $ipList = gethostbynamel( $host );
459 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
462 wfDebug( "Requested $host, not found in $base.\n" );
466 wfProfileOut( $fname );
471 * Primitive rate limits: enforce maximum actions per time period
472 * to put a brake on flooding.
474 * Note: when using a shared cache like memcached, IP-address
475 * last-hit counters will be shared across wikis.
477 * @return bool true if a rate limiter was tripped
480 function pingLimiter( $action='edit' ) {
481 global $wgRateLimits;
482 if( !isset( $wgRateLimits[$action] ) ) {
485 if( $this->isAllowed( 'delete' ) ) {
490 global $wgMemc, $wgDBname, $wgRateLimitLog;
491 $fname = 'User::pingLimiter';
492 wfProfileIn( $fname );
494 $limits = $wgRateLimits[$action];
496 $id = $this->getId();
499 if( isset( $limits['anon'] ) && $id == 0 ) {
500 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
503 if( isset( $limits['user'] ) && $id != 0 ) {
504 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
506 if( $this->isNewbie() ) {
507 if( isset( $limits['newbie'] ) && $id != 0 ) {
508 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
510 if( isset( $limits['ip'] ) ) {
511 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
513 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
514 $subnet = $matches[1];
515 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
520 foreach( $keys as $key => $limit ) {
521 list( $max, $period ) = $limit;
522 $summary = "(limit $max in {$period}s)";
523 $count = $wgMemc->get( $key );
525 if( $count > $max ) {
526 wfDebug( "$fname: tripped! $key at $count $summary\n" );
527 if( $wgRateLimitLog ) {
528 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
532 wfDebug( "$fname: ok. $key at $count $summary\n" );
535 wfDebug( "$fname: adding record for $key $summary\n" );
536 $wgMemc->add( $key, 1, intval( $period ) );
538 $wgMemc->incr( $key );
541 wfProfileOut( $fname );
546 * Check if user is blocked
547 * @return bool True if blocked, false otherwise
549 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
550 wfDebug( "User::isBlocked: enter\n" );
551 $this->getBlockedStatus( $bFromSlave );
552 return $this->mBlockedby
!== 0;
556 * Check if user is blocked from editing a particular article
558 function isBlockedFrom( $title, $bFromSlave = false ) {
559 global $wgBlockAllowsUTEdit;
560 $fname = 'User::isBlockedFrom';
561 wfProfileIn( $fname );
562 wfDebug( "$fname: enter\n" );
564 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
565 $title->getNamespace() == NS_USER_TALK
)
568 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
570 wfDebug( "$fname: asking isBlocked()\n" );
571 $blocked = $this->isBlocked( $bFromSlave );
573 wfProfileOut( $fname );
578 * Get name of blocker
579 * @return string name of blocker
581 function blockedBy() {
582 $this->getBlockedStatus();
583 return $this->mBlockedby
;
587 * Get blocking reason
588 * @return string Blocking reason
590 function blockedFor() {
591 $this->getBlockedStatus();
592 return $this->mBlockreason
;
596 * Initialise php session
598 function SetupSession() {
599 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
600 if( $wgSessionsInMemcached ) {
601 require_once( 'MemcachedSessions.php' );
602 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
603 # If it's left on 'user' or another setting from another
604 # application, it will end up failing. Try to recover.
605 ini_set ( 'session.save_handler', 'files' );
607 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
608 session_cache_limiter( 'private, must-revalidate' );
613 * Create a new user object using data from session
616 function loadFromSession() {
617 global $wgMemc, $wgDBname;
619 if ( isset( $_SESSION['wsUserID'] ) ) {
620 if ( 0 != $_SESSION['wsUserID'] ) {
621 $sId = $_SESSION['wsUserID'];
625 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
626 $sId = intval( $_COOKIE["{$wgDBname}UserID"] );
627 $_SESSION['wsUserID'] = $sId;
631 if ( isset( $_SESSION['wsUserName'] ) ) {
632 $sName = $_SESSION['wsUserName'];
633 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
634 $sName = $_COOKIE["{$wgDBname}UserName"];
635 $_SESSION['wsUserName'] = $sName;
640 $passwordCorrect = FALSE;
641 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
642 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
643 # Expire old serialized objects; they may be corrupt.
646 if($makenew = !$user) {
647 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
650 $user->loadFromDatabase();
652 wfDebug( "User::loadFromSession() got from cache!\n" );
655 if ( isset( $_SESSION['wsToken'] ) ) {
656 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
657 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
658 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
660 return new User(); # Can't log in from session
663 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
665 if($wgMemc->set( $key, $user ))
666 wfDebug( "User::loadFromSession() successfully saved user\n" );
668 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
672 return new User(); # Can't log in from session
676 * Load a user from the database
678 function loadFromDatabase() {
679 global $wgCommandLineMode;
680 $fname = "User::loadFromDatabase";
682 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
683 # loading in a command line script, don't assume all command line scripts need it like this
684 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
685 if ( $this->mDataLoaded
) {
690 $this->mId
= intval( $this->mId
);
692 /** Anonymous user */
695 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
696 $this->mDataLoaded
= true;
698 } # the following stuff is for non-anonymous users only
700 $dbr =& wfGetDB( DB_SLAVE
);
701 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
702 'user_email_authenticated',
703 'user_real_name','user_options','user_touched', 'user_token' ),
704 array( 'user_id' => $this->mId
), $fname );
706 if ( $s !== false ) {
707 $this->mName
= $s->user_name
;
708 $this->mEmail
= $s->user_email
;
709 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
710 $this->mRealName
= $s->user_real_name
;
711 $this->mPassword
= $s->user_password
;
712 $this->mNewpassword
= $s->user_newpassword
;
713 $this->decodeOptions( $s->user_options
);
714 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
715 $this->mToken
= $s->user_token
;
717 $res = $dbr->select( 'user_groups',
719 array( 'ug_user' => $this->mId
),
721 $this->mGroups
= array();
722 while( $row = $dbr->fetchObject( $res ) ) {
723 $this->mGroups
[] = $row->ug_group
;
725 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
726 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
729 $this->mDataLoaded
= true;
732 function getID() { return $this->mId
; }
733 function setID( $v ) {
735 $this->mDataLoaded
= false;
739 $this->loadFromDatabase();
740 if ( $this->mName
=== false ) {
741 $this->mName
= wfGetIP();
746 function setName( $str ) {
747 $this->loadFromDatabase();
753 * Return the title dbkey form of the name, for eg user pages.
757 function getTitleKey() {
758 return str_replace( ' ', '_', $this->getName() );
761 function getNewtalk() {
763 $fname = 'User::getNewtalk';
764 $this->loadFromDatabase();
766 # Load the newtalk status if it is unloaded (mNewtalk=-1)
767 if( $this->mNewtalk
== -1 ) {
768 $this->mNewtalk
= 0; # reset talk page status
770 # Check memcached separately for anons, who have no
771 # entire User object stored in there.
773 global $wgDBname, $wgMemc;
774 $key = "$wgDBname:newtalk:ip:" . $this->getName();
775 $newtalk = $wgMemc->get( $key );
776 if( is_integer( $newtalk ) ) {
777 $this->mNewtalk
= $newtalk ?
1 : 0;
778 return (bool)$this->mNewtalk
;
782 $dbr =& wfGetDB( DB_SLAVE
);
783 if ( $wgUseEnotif ) {
784 $res = $dbr->select( 'watchlist',
786 array( 'wl_title' => $this->getTitleKey(),
787 'wl_namespace' => NS_USER_TALK
,
788 'wl_user' => $this->mId
,
789 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
790 'User::getNewtalk' );
791 if( $dbr->numRows($res) > 0 ) {
794 $dbr->freeResult( $res );
795 } elseif ( $this->mId
) {
796 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
798 if ( $dbr->numRows($res)>0 ) {
801 $dbr->freeResult( $res );
803 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->getName() ), $fname );
804 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
805 $dbr->freeResult( $res );
809 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
813 return ( 0 != $this->mNewtalk
);
816 function setNewtalk( $val ) {
817 $this->loadFromDatabase();
818 $this->mNewtalk
= $val;
819 $this->invalidateCache();
822 function invalidateCache() {
823 global $wgClockSkewFudge;
824 $this->loadFromDatabase();
825 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
826 # Don't forget to save the options after this or
827 # it won't take effect!
830 function validateCache( $timestamp ) {
831 $this->loadFromDatabase();
832 return ($timestamp >= $this->mTouched
);
836 * Encrypt a password.
837 * It can eventuall salt a password @see User::addSalt()
838 * @param string $p clear Password.
839 * @return string Encrypted password.
841 function encryptPassword( $p ) {
842 return wfEncryptPassword( $this->mId
, $p );
845 # Set the password and reset the random token
846 function setPassword( $str ) {
847 $this->loadFromDatabase();
849 $this->mPassword
= $this->encryptPassword( $str );
850 $this->mNewpassword
= '';
853 # Set the random token (used for persistent authentication)
854 function setToken( $token = false ) {
855 global $wgSecretKey, $wgProxyKey, $wgDBname;
857 if ( $wgSecretKey ) {
859 } elseif ( $wgProxyKey ) {
864 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
866 $this->mToken
= $token;
871 function setCookiePassword( $str ) {
872 $this->loadFromDatabase();
873 $this->mCookiePassword
= md5( $str );
876 function setNewpassword( $str ) {
877 $this->loadFromDatabase();
878 $this->mNewpassword
= $this->encryptPassword( $str );
881 function getEmail() {
882 $this->loadFromDatabase();
883 return $this->mEmail
;
886 function getEmailAuthenticationTimestamp() {
887 $this->loadFromDatabase();
888 return $this->mEmailAuthenticated
;
891 function setEmail( $str ) {
892 $this->loadFromDatabase();
893 $this->mEmail
= $str;
896 function getRealName() {
897 $this->loadFromDatabase();
898 return $this->mRealName
;
901 function setRealName( $str ) {
902 $this->loadFromDatabase();
903 $this->mRealName
= $str;
906 function getOption( $oname ) {
907 $this->loadFromDatabase();
908 if ( array_key_exists( $oname, $this->mOptions
) ) {
909 return trim( $this->mOptions
[$oname] );
915 function setOption( $oname, $val ) {
916 $this->loadFromDatabase();
917 if ( $oname == 'skin' ) {
918 # Clear cached skin, so the new one displays immediately in Special:Preferences
919 unset( $this->mSkin
);
921 $this->mOptions
[$oname] = $val;
922 $this->invalidateCache();
925 function getRights() {
926 $this->loadFromDatabase();
927 return $this->mRights
;
931 * Get the list of explicit group memberships this user has.
932 * The implicit * and user groups are not included.
933 * @return array of strings
935 function getGroups() {
936 $this->loadFromDatabase();
937 return $this->mGroups
;
941 * Get the list of implicit group memberships this user has.
942 * This includes all explicit groups, plus 'user' if logged in
943 * and '*' for all accounts.
944 * @return array of strings
946 function getEffectiveGroups() {
947 $base = array( '*' );
948 if( $this->isLoggedIn() ) {
951 return array_merge( $base, $this->getGroups() );
955 * Remove the user from the given group.
956 * This takes immediate effect.
959 function addGroup( $group ) {
960 $dbw =& wfGetDB( DB_MASTER
);
961 $dbw->insert( 'user_groups',
963 'ug_user' => $this->getID(),
964 'ug_group' => $group,
969 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
970 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
972 $this->invalidateCache();
973 $this->saveSettings();
977 * Remove the user from the given group.
978 * This takes immediate effect.
981 function removeGroup( $group ) {
982 $dbw =& wfGetDB( DB_MASTER
);
983 $dbw->delete( 'user_groups',
985 'ug_user' => $this->getID(),
986 'ug_group' => $group,
988 'User::removeGroup' );
990 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
991 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
993 $this->invalidateCache();
994 $this->saveSettings();
999 * A more legible check for non-anonymousness.
1000 * Returns true if the user is not an anonymous visitor.
1004 function isLoggedIn() {
1005 return( $this->getID() != 0 );
1009 * A more legible check for anonymousness.
1010 * Returns true if the user is an anonymous visitor.
1015 return !$this->isLoggedIn();
1019 * Check if a user is sysop
1022 function isSysop() {
1023 return $this->isAllowed( 'protect' );
1027 function isDeveloper() {
1028 return $this->isAllowed( 'siteadmin' );
1032 function isBureaucrat() {
1033 return $this->isAllowed( 'makesysop' );
1037 * Whether the user is a bot
1038 * @todo need to be migrated to the new user level management sytem
1041 $this->loadFromDatabase();
1042 return in_array( 'bot', $this->mRights
);
1046 * Check if user is allowed to access a feature / make an action
1047 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1048 * @return boolean True: action is allowed, False: action should not be allowed
1050 function isAllowed($action='') {
1051 $this->loadFromDatabase();
1052 return in_array( $action , $this->mRights
);
1056 * Load a skin if it doesn't exist or return it
1057 * @todo FIXME : need to check the old failback system [AV]
1059 function &getSkin() {
1060 global $IP, $wgRequest;
1061 if ( ! isset( $this->mSkin
) ) {
1062 $fname = 'User::getSkin';
1063 wfProfileIn( $fname );
1065 # get all skin names available
1066 $skinNames = Skin
::getSkinNames();
1069 $userSkin = $this->getOption( 'skin' );
1070 $userSkin = $wgRequest->getText('useskin', $userSkin);
1071 if ( $userSkin == '' ) { $userSkin = 'standard'; }
1073 if ( !isset( $skinNames[$userSkin] ) ) {
1074 # in case the user skin could not be found find a replacement
1078 2 => 'CologneBlue');
1079 # if phptal is enabled we should have monobook skin that
1080 # superseed the good old SkinStandard.
1081 if ( isset( $skinNames['monobook'] ) ) {
1082 $fallback[0] = 'MonoBook';
1085 if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
1086 $sn = $fallback[$userSkin];
1091 # The user skin is available
1092 $sn = $skinNames[$userSkin];
1095 # Grab the skin class and initialise it. Each skin checks for PHPTal
1096 # and will not load if it's not enabled.
1097 require_once( $IP.'/skins/'.$sn.'.php' );
1099 # Check if we got if not failback to default skin
1100 $className = 'Skin'.$sn;
1101 if( !class_exists( $className ) ) {
1102 # DO NOT die if the class isn't found. This breaks maintenance
1103 # scripts and can cause a user account to be unrecoverable
1104 # except by SQL manipulation if a previously valid skin name
1105 # is no longer valid.
1106 $className = 'SkinStandard';
1107 require_once( $IP.'/skins/Standard.php' );
1109 $this->mSkin
=& new $className;
1110 wfProfileOut( $fname );
1112 return $this->mSkin
;
1116 * @param string $title Article title to look at
1120 * Check watched status of an article
1121 * @return bool True if article is watched
1123 function isWatched( $title ) {
1124 $wl = WatchedItem
::fromUserTitle( $this, $title );
1125 return $wl->isWatched();
1131 function addWatch( $title ) {
1132 $wl = WatchedItem
::fromUserTitle( $this, $title );
1134 $this->invalidateCache();
1138 * Stop watching an article
1140 function removeWatch( $title ) {
1141 $wl = WatchedItem
::fromUserTitle( $this, $title );
1143 $this->invalidateCache();
1147 * Clear the user's notification timestamp for the given title.
1148 * If e-notif e-mails are on, they will receive notification mails on
1149 * the next change of the page if it's watched etc.
1151 function clearNotification( &$title ) {
1152 global $wgUser, $wgUseEnotif;
1154 if ( !$wgUseEnotif ) {
1158 $userid = $this->getID();
1163 // Only update the timestamp if the page is being watched.
1164 // The query to find out if it is watched is cached both in memcached and per-invocation,
1165 // and when it does have to be executed, it can be on a slave
1166 // If this is the user's newtalk page, we always update the timestamp
1167 if ($title->getNamespace() == NS_USER_TALK
&&
1168 $title->getText() == $wgUser->getName())
1171 } elseif ( $this->getID() == $wgUser->getID() ) {
1172 $watched = $title->userIsWatching();
1177 // If the page is watched by the user (or may be watched), update the timestamp on any
1178 // any matching rows
1180 $dbw =& wfGetDB( DB_MASTER
);
1181 $success = $dbw->update( 'watchlist',
1183 'wl_notificationtimestamp' => NULL
1184 ), array( /* WHERE */
1185 'wl_title' => $title->getDBkey(),
1186 'wl_namespace' => $title->getNamespace(),
1187 'wl_user' => $this->getID()
1188 ), 'User::clearLastVisited'
1196 * Resets all of the given user's page-change notification timestamps.
1197 * If e-notif e-mails are on, they will receive notification mails on
1198 * the next change of any watched page.
1200 * @param int $currentUser user ID number
1203 function clearAllNotifications( $currentUser ) {
1204 global $wgUseEnotif;
1205 if ( !$wgUseEnotif ) {
1208 if( $currentUser != 0 ) {
1210 $dbw =& wfGetDB( DB_MASTER
);
1211 $success = $dbw->update( 'watchlist',
1213 'wl_notificationtimestamp' => 0
1214 ), array( /* WHERE */
1215 'wl_user' => $currentUser
1216 ), 'UserMailer::clearAll'
1219 # we also need to clear here the "you have new message" notification for the own user_talk page
1220 # This is cleared one page view later in Article::viewUpdates();
1226 * @return string Encoding options
1228 function encodeOptions() {
1230 foreach ( $this->mOptions
as $oname => $oval ) {
1231 array_push( $a, $oname.'='.$oval );
1233 $s = implode( "\n", $a );
1240 function decodeOptions( $str ) {
1241 $a = explode( "\n", $str );
1242 foreach ( $a as $s ) {
1243 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1244 $this->mOptions
[$m[1]] = $m[2];
1249 function setCookies() {
1250 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
1251 if ( 0 == $this->mId
) return;
1252 $this->loadFromDatabase();
1253 $exp = time() +
$wgCookieExpiration;
1255 $_SESSION['wsUserID'] = $this->mId
;
1256 setcookie( $wgDBname.'UserID', $this->mId
, $exp, $wgCookiePath, $wgCookieDomain );
1258 $_SESSION['wsUserName'] = $this->getName();
1259 setcookie( $wgDBname.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain );
1261 $_SESSION['wsToken'] = $this->mToken
;
1262 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1263 setcookie( $wgDBname.'Token', $this->mToken
, $exp, $wgCookiePath, $wgCookieDomain );
1265 setcookie( $wgDBname.'Token', '', time() - 3600 );
1271 * It will clean the session cookie
1274 global $wgCookiePath, $wgCookieDomain, $wgDBname;
1275 $this->loadDefaults();
1276 $this->setLoaded( true );
1278 $_SESSION['wsUserID'] = 0;
1280 setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1281 setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
1283 # Remember when user logged out, to prevent seeing cached pages
1284 setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() +
86400, $wgCookiePath, $wgCookieDomain );
1288 * Save object settings into database
1290 function saveSettings() {
1291 global $wgMemc, $wgDBname, $wgUseEnotif;
1292 $fname = 'User::saveSettings';
1294 if ( wfReadOnly() ) { return; }
1295 $this->saveNewtalk();
1296 if ( 0 == $this->mId
) { return; }
1298 $dbw =& wfGetDB( DB_MASTER
);
1299 $dbw->update( 'user',
1301 'user_name' => $this->mName
,
1302 'user_password' => $this->mPassword
,
1303 'user_newpassword' => $this->mNewpassword
,
1304 'user_real_name' => $this->mRealName
,
1305 'user_email' => $this->mEmail
,
1306 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1307 'user_options' => $this->encodeOptions(),
1308 'user_touched' => $dbw->timestamp($this->mTouched
),
1309 'user_token' => $this->mToken
1310 ), array( /* WHERE */
1311 'user_id' => $this->mId
1314 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1318 * Save value of new talk flag.
1320 function saveNewtalk() {
1321 global $wgDBname, $wgMemc, $wgUseEnotif;
1323 $fname = 'User::saveNewtalk';
1327 if ( wfReadOnly() ) { return ; }
1328 $dbr =& wfGetDB( DB_SLAVE
);
1329 $dbw =& wfGetDB( DB_MASTER
);
1331 if ( $wgUseEnotif ) {
1332 if ( ! $this->getNewtalk() ) {
1333 # Delete the watchlist entry for user_talk page X watched by user X
1334 $dbw->delete( 'watchlist',
1335 array( 'wl_user' => $this->mId
,
1336 'wl_title' => $this->getTitleKey(),
1337 'wl_namespace' => NS_USER_TALK
),
1339 if ( $dbw->affectedRows() ) {
1343 # Anon users have a separate memcache space for newtalk
1344 # since they don't store their own info. Trim...
1345 $wgMemc->delete( "$wgDBname:newtalk:ip:" . $this->getName() );
1349 if ($this->getID() != 0) {
1351 $value = $this->getID();
1355 $value = $this->getName();
1356 $key = "$wgDBname:newtalk:ip:$value";
1359 $dbr =& wfGetDB( DB_SLAVE
);
1360 $dbw =& wfGetDB( DB_MASTER
);
1362 $res = $dbr->selectField('user_newtalk', $field,
1363 array($field => $value), $fname);
1366 if ($res !== false && $this->mNewtalk
== 0) {
1367 $dbw->delete('user_newtalk', array($field => $value), $fname);
1369 $wgMemc->set( $key, 0 );
1371 } else if ($res === false && $this->mNewtalk
== 1) {
1372 $dbw->insert('user_newtalk', array($field => $value), $fname);
1374 $wgMemc->set( $key, 1 );
1381 # Update user_touched, so that newtalk notifications in the client cache are invalidated
1382 if ( $changed && $this->getID() ) {
1383 $dbw->update('user',
1384 /*SET*/ array( 'user_touched' => $this->mTouched
),
1385 /*WHERE*/ array( 'user_id' => $this->getID() ),
1387 $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
1392 * Checks if a user with the given name exists, returns the ID
1394 function idForName() {
1395 $fname = 'User::idForName';
1398 $s = trim( $this->getName() );
1399 if ( 0 == strcmp( '', $s ) ) return 0;
1401 $dbr =& wfGetDB( DB_SLAVE
);
1402 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1403 if ( $id === false ) {
1410 * Add user object to the database
1412 function addToDatabase() {
1413 $fname = 'User::addToDatabase';
1414 $dbw =& wfGetDB( DB_MASTER
);
1415 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1416 $dbw->insert( 'user',
1418 'user_id' => $seqVal,
1419 'user_name' => $this->mName
,
1420 'user_password' => $this->mPassword
,
1421 'user_newpassword' => $this->mNewpassword
,
1422 'user_email' => $this->mEmail
,
1423 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated
),
1424 'user_real_name' => $this->mRealName
,
1425 'user_options' => $this->encodeOptions(),
1426 'user_token' => $this->mToken
1429 $this->mId
= $dbw->insertId();
1432 function spreadBlock() {
1433 # If the (non-anonymous) user is blocked, this function will block any IP address
1434 # that they successfully log on from.
1435 $fname = 'User::spreadBlock';
1437 wfDebug( "User:spreadBlock()\n" );
1438 if ( $this->mId
== 0 ) {
1442 $userblock = Block
::newFromDB( '', $this->mId
);
1443 if ( !$userblock->isValid() ) {
1447 # Check if this IP address is already blocked
1448 $ipblock = Block
::newFromDB( wfGetIP() );
1449 if ( $ipblock->isValid() ) {
1450 # If the user is already blocked. Then check if the autoblock would
1451 # excede the user block. If it would excede, then do nothing, else
1452 # prolong block time
1453 if ($userblock->mExpiry
&&
1454 ($userblock->mExpiry
< Block
::getAutoblockExpiry($ipblock->mTimestamp
))) {
1457 # Just update the timestamp
1458 $ipblock->updateTimestamp();
1462 # Make a new block object with the desired properties
1463 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1464 $ipblock->mAddress
= wfGetIP();
1465 $ipblock->mUser
= 0;
1466 $ipblock->mBy
= $userblock->mBy
;
1467 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1468 $ipblock->mTimestamp
= wfTimestampNow();
1469 $ipblock->mAuto
= 1;
1470 # If the user is already blocked with an expiry date, we don't
1471 # want to pile on top of that!
1472 if($userblock->mExpiry
) {
1473 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1475 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1483 function getPageRenderingHash() {
1486 return $this->mHash
;
1489 // stubthreshold is only included below for completeness,
1490 // it will always be 0 when this function is called by parsercache.
1492 $confstr = $this->getOption( 'math' );
1493 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1494 $confstr .= '!' . $this->getOption( 'date' );
1495 $confstr .= '!' . ($this->getOption( 'numberheadings' ) ?
'1' : '');
1496 $confstr .= '!' . $this->getOption( 'language' );
1497 $confstr .= '!' . $this->getOption( 'thumbsize' );
1498 // add in language specific options, if any
1499 $extra = $wgContLang->getExtraHashOptions();
1502 $this->mHash
= $confstr;
1506 function isAllowedToCreateAccount() {
1507 return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
1511 * Set mDataLoaded, return previous value
1512 * Use this to prevent DB access in command-line scripts or similar situations
1514 function setLoaded( $loaded ) {
1515 return wfSetVar( $this->mDataLoaded
, $loaded );
1519 * Get this user's personal page title.
1524 function getUserPage() {
1525 return Title
::makeTitle( NS_USER
, $this->getName() );
1529 * Get this user's talk page title.
1534 function getTalkPage() {
1535 $title = $this->getUserPage();
1536 return $title->getTalkPage();
1542 function getMaxID() {
1543 $dbr =& wfGetDB( DB_SLAVE
);
1544 return $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1548 * Determine whether the user is a newbie. Newbies are either
1549 * anonymous IPs, or the 1% most recently created accounts.
1550 * Bots and sysops are excluded.
1551 * @return bool True if it is a newbie.
1553 function isNewbie() {
1554 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1558 * Check to see if the given clear-text password is one of the accepted passwords
1559 * @param string $password User password.
1560 * @return bool True if the given password is correct otherwise False.
1562 function checkPassword( $password ) {
1563 global $wgAuth, $wgMinimalPasswordLength;
1564 $this->loadFromDatabase();
1566 // Even though we stop people from creating passwords that
1567 // are shorter than this, doesn't mean people wont be able
1568 // to. Certain authentication plugins do NOT want to save
1569 // domain passwords in a mysql database, so we should
1570 // check this (incase $wgAuth->strict() is false).
1571 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1575 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1577 } elseif( $wgAuth->strict() ) {
1578 /* Auth plugin doesn't allow local authentication */
1581 $ep = $this->encryptPassword( $password );
1582 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1584 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1586 } elseif ( function_exists( 'iconv' ) ) {
1587 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1588 # Check for this with iconv
1589 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1590 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1598 * Initialize (if necessary) and return a session token value
1599 * which can be used in edit forms to show that the user's
1600 * login credentials aren't being hijacked with a foreign form
1603 * @param mixed $salt - Optional function-specific data for hash.
1604 * Use a string or an array of strings.
1608 function editToken( $salt = '' ) {
1609 if( !isset( $_SESSION['wsEditToken'] ) ) {
1610 $token = $this->generateToken();
1611 $_SESSION['wsEditToken'] = $token;
1613 $token = $_SESSION['wsEditToken'];
1615 if( is_array( $salt ) ) {
1616 $salt = implode( '|', $salt );
1618 return md5( $token . $salt );
1622 * Generate a hex-y looking random token for various uses.
1623 * Could be made more cryptographically sure if someone cares.
1626 function generateToken( $salt = '' ) {
1627 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1628 return md5( $token . $salt );
1632 * Check given value against the token value stored in the session.
1633 * A match should confirm that the form was submitted from the
1634 * user's own login session, not a form submission from a third-party
1637 * @param string $val - the input value to compare
1638 * @param string $salt - Optional function-specific data for hash
1642 function matchEditToken( $val, $salt = '' ) {
1646 if ( !isset( $_SESSION['wsEditToken'] ) ) {
1647 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1648 $mckey = memsess_key( session_id() );
1649 $uname = @posix_uname();
1650 $msg = "wsEditToken not set!\n" .
1651 'apache server=' . $uname['nodename'] . "\n" .
1652 'session_id = ' . session_id() . "\n" .
1653 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1654 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1655 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1657 @error_log( $msg, 3, $logfile );
1660 return ( $val == $this->editToken( $salt ) );
1664 * Generate a new e-mail confirmation token and send a confirmation
1665 * mail to the user's given address.
1667 * @return mixed True on success, a WikiError object on failure.
1669 function sendConfirmationMail() {
1671 $url = $this->confirmationTokenUrl( $expiration );
1672 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1673 wfMsg( 'confirmemail_body',
1677 $wgContLang->timeanddate( $expiration, false ) ) );
1681 * Send an e-mail to this user's account. Does not check for
1682 * confirmed status or validity.
1684 * @param string $subject
1685 * @param string $body
1686 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1687 * @return mixed True on success, a WikiError object on failure.
1689 function sendMail( $subject, $body, $from = null ) {
1690 if( is_null( $from ) ) {
1691 global $wgPasswordSender;
1692 $from = $wgPasswordSender;
1695 require_once( 'UserMailer.php' );
1696 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1698 if( $error == '' ) {
1701 return new WikiError( $error );
1706 * Generate, store, and return a new e-mail confirmation code.
1707 * A hash (unsalted since it's used as a key) is stored.
1708 * @param &$expiration mixed output: accepts the expiration time
1712 function confirmationToken( &$expiration ) {
1713 $fname = 'User::confirmationToken';
1716 $expires = $now +
7 * 24 * 60 * 60;
1717 $expiration = wfTimestamp( TS_MW
, $expires );
1719 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1720 $hash = md5( $token );
1722 $dbw =& wfGetDB( DB_MASTER
);
1723 $dbw->update( 'user',
1724 array( 'user_email_token' => $hash,
1725 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1726 array( 'user_id' => $this->mId
),
1733 * Generate and store a new e-mail confirmation token, and return
1734 * the URL the user can use to confirm.
1735 * @param &$expiration mixed output: accepts the expiration time
1739 function confirmationTokenUrl( &$expiration ) {
1740 $token = $this->confirmationToken( $expiration );
1741 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1742 return $title->getFullUrl();
1746 * Mark the e-mail address confirmed and save.
1748 function confirmEmail() {
1749 $this->loadFromDatabase();
1750 $this->mEmailAuthenticated
= wfTimestampNow();
1751 $this->saveSettings();
1756 * Is this user allowed to send e-mails within limits of current
1757 * site configuration?
1760 function canSendEmail() {
1761 return $this->isEmailConfirmed();
1765 * Is this user allowed to receive e-mails within limits of current
1766 * site configuration?
1769 function canReceiveEmail() {
1770 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1774 * Is this user's e-mail address valid-looking and confirmed within
1775 * limits of the current site configuration?
1777 * If $wgEmailAuthentication is on, this may require the user to have
1778 * confirmed their address by returning a code or using a password
1779 * sent to the address from the wiki.
1783 function isEmailConfirmed() {
1784 global $wgEmailAuthentication;
1785 $this->loadFromDatabase();
1786 if( $this->isAnon() )
1788 if( !$this->isValidEmailAddr( $this->mEmail
) )
1790 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1796 * @param array $groups list of groups
1797 * @return array list of permission key names for given groups combined
1800 function getGroupPermissions( $groups ) {
1801 global $wgGroupPermissions;
1803 foreach( $groups as $group ) {
1804 if( isset( $wgGroupPermissions[$group] ) ) {
1805 $rights = array_merge( $rights,
1806 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1813 * @param string $group key name
1814 * @return string localized descriptive name, if provided
1817 function getGroupName( $group ) {
1818 $key = "group-$group-name";
1819 $name = wfMsg( $key );
1820 if( $name == '' ||
$name == "<$key>" ) {
1828 * Return the set of defined explicit groups.
1829 * The * and 'user' groups are not included.
1833 function getAllGroups() {
1834 global $wgGroupPermissions;
1836 array_keys( $wgGroupPermissions ),
1837 array( '*', 'user' ) );