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, $wgDBname;
277 global $wgNamespacesToBeSearchedDefault;
280 $this->mNewtalk
= -1;
281 $this->mName
= false;
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 $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;
378 $block = new Block();
379 $block->forUpdate( $bFromSlave );
380 if ( $block->load( $ip , $this->mId
) ) {
381 wfDebug( "$fname: Found block.\n" );
382 $this->mBlockedby
= $block->mBy
;
383 $this->mBlockreason
= $block->mReason
;
384 if ( $this->isLoggedIn() ) {
385 $this->spreadBlock();
388 wfDebug( "$fname: No block.\n" );
392 if ( !$this->mBlockedby
) {
393 # Check first against slave, and optionally from master.
394 wfDebug( "$fname: Checking range blocks\n" );
395 $block = $wgBlockCache->get( $ip, true );
396 if ( !$block && !$bFromSlave )
398 # Not blocked: check against master, to make sure.
399 $wgBlockCache->clearLocal( );
400 $block = $wgBlockCache->get( $ip, false );
402 if ( $block !== false ) {
403 $this->mBlockedby
= $block->mBy
;
404 $this->mBlockreason
= $block->mReason
;
409 if ( !$this->isSysop() && !in_array( $ip, $wgProxyWhitelist ) ) {
412 if ( array_key_exists( $ip, $wgProxyList ) ) {
413 $this->mBlockedby
= wfMsg( 'proxyblocker' );
414 $this->mBlockreason
= wfMsg( 'proxyblockreason' );
418 if ( !$this->mBlockedby
&& $wgEnableSorbs && !$this->getID() ) {
419 if ( $this->inSorbsBlacklist( $ip ) ) {
420 $this->mBlockedby
= wfMsg( 'sorbs' );
421 $this->mBlockreason
= wfMsg( 'sorbsreason' );
425 wfProfileOut( $fname );
428 function inSorbsBlacklist( $ip ) {
429 global $wgEnableSorbs;
430 return $wgEnableSorbs &&
431 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
434 function inOpmBlacklist( $ip ) {
436 return $wgEnableOpm &&
437 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
440 function inDnsBlacklist( $ip, $base ) {
441 $fname = 'User::inDnsBlacklist';
442 wfProfileIn( $fname );
447 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
449 for ( $i=4; $i>=1; $i-- ) {
450 $host .= $m[$i] . '.';
455 $ipList = gethostbynamel( $host );
458 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
461 wfDebug( "Requested $host, not found in $base.\n" );
465 wfProfileOut( $fname );
470 * Primitive rate limits: enforce maximum actions per time period
471 * to put a brake on flooding.
473 * Note: when using a shared cache like memcached, IP-address
474 * last-hit counters will be shared across wikis.
476 * @return bool true if a rate limiter was tripped
479 function pingLimiter( $action='edit' ) {
480 global $wgRateLimits;
481 if( !isset( $wgRateLimits[$action] ) ) {
484 if( $this->isAllowed( 'delete' ) ) {
489 global $wgMemc, $wgDBname, $wgRateLimitLog;
490 $fname = 'User::pingLimiter';
491 wfProfileIn( $fname );
493 $limits = $wgRateLimits[$action];
495 $id = $this->getId();
498 if( isset( $limits['anon'] ) && $id == 0 ) {
499 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
502 if( isset( $limits['user'] ) && $id != 0 ) {
503 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
505 if( $this->isNewbie() ) {
506 if( isset( $limits['newbie'] ) && $id != 0 ) {
507 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
509 if( isset( $limits['ip'] ) ) {
510 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
512 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
513 $subnet = $matches[1];
514 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
519 foreach( $keys as $key => $limit ) {
520 list( $max, $period ) = $limit;
521 $summary = "(limit $max in {$period}s)";
522 $count = $wgMemc->get( $key );
524 if( $count > $max ) {
525 wfDebug( "$fname: tripped! $key at $count $summary\n" );
526 if( $wgRateLimitLog ) {
527 @error_log
( wfTimestamp( TS_MW
) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
531 wfDebug( "$fname: ok. $key at $count $summary\n" );
534 wfDebug( "$fname: adding record for $key $summary\n" );
535 $wgMemc->add( $key, 1, intval( $period ) );
537 $wgMemc->incr( $key );
540 wfProfileOut( $fname );
545 * Check if user is blocked
546 * @return bool True if blocked, false otherwise
548 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
549 wfDebug( "User::isBlocked: enter\n" );
550 $this->getBlockedStatus( $bFromSlave );
551 return $this->mBlockedby
!== 0;
555 * Check if user is blocked from editing a particular article
557 function isBlockedFrom( $title, $bFromSlave = false ) {
558 global $wgBlockAllowsUTEdit;
559 $fname = 'User::isBlockedFrom';
560 wfProfileIn( $fname );
561 wfDebug( "$fname: enter\n" );
563 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
564 $title->getNamespace() == NS_USER_TALK
)
567 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
569 wfDebug( "$fname: asking isBlocked()\n" );
570 $blocked = $this->isBlocked( $bFromSlave );
572 wfProfileOut( $fname );
577 * Get name of blocker
578 * @return string name of blocker
580 function blockedBy() {
581 $this->getBlockedStatus();
582 return $this->mBlockedby
;
586 * Get blocking reason
587 * @return string Blocking reason
589 function blockedFor() {
590 $this->getBlockedStatus();
591 return $this->mBlockreason
;
595 * Initialise php session
597 function SetupSession() {
598 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
599 if( $wgSessionsInMemcached ) {
600 require_once( 'MemcachedSessions.php' );
601 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
602 # If it's left on 'user' or another setting from another
603 # application, it will end up failing. Try to recover.
604 ini_set ( 'session.save_handler', 'files' );
606 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
607 session_cache_limiter( 'private, must-revalidate' );
612 * Create a new user object using data from session
615 function loadFromSession() {
616 global $wgMemc, $wgDBname;
618 if ( isset( $_SESSION['wsUserID'] ) ) {
619 if ( 0 != $_SESSION['wsUserID'] ) {
620 $sId = $_SESSION['wsUserID'];
624 } else if ( isset( $_COOKIE["{$wgDBname}UserID"] ) ) {
625 $sId = intval( $_COOKIE["{$wgDBname}UserID"] );
626 $_SESSION['wsUserID'] = $sId;
630 if ( isset( $_SESSION['wsUserName'] ) ) {
631 $sName = $_SESSION['wsUserName'];
632 } else if ( isset( $_COOKIE["{$wgDBname}UserName"] ) ) {
633 $sName = $_COOKIE["{$wgDBname}UserName"];
634 $_SESSION['wsUserName'] = $sName;
639 $passwordCorrect = FALSE;
640 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
641 if( !is_object( $user ) ||
$user->mVersion
< MW_USER_VERSION
) {
642 # Expire old serialized objects; they may be corrupt.
645 if($makenew = !$user) {
646 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
649 $user->loadFromDatabase();
651 wfDebug( "User::loadFromSession() got from cache!\n" );
654 if ( isset( $_SESSION['wsToken'] ) ) {
655 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken
;
656 } else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
657 $passwordCorrect = $user->mToken
== $_COOKIE["{$wgDBname}Token"];
659 return new User(); # Can't log in from session
662 if ( ( $sName == $user->mName
) && $passwordCorrect ) {
664 if($wgMemc->set( $key, $user ))
665 wfDebug( "User::loadFromSession() successfully saved user\n" );
667 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
671 return new User(); # Can't log in from session
675 * Load a user from the database
677 function loadFromDatabase() {
678 global $wgCommandLineMode;
679 $fname = "User::loadFromDatabase";
681 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
682 # loading in a command line script, don't assume all command line scripts need it like this
683 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
684 if ( $this->mDataLoaded
) {
689 $this->mId
= intval( $this->mId
);
691 /** Anonymous user */
694 $this->mRights
= $this->getGroupPermissions( array( '*' ) );
695 $this->mDataLoaded
= true;
697 } # the following stuff is for non-anonymous users only
699 $dbr =& wfGetDB( DB_SLAVE
);
700 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
701 'user_email_authenticated',
702 'user_real_name','user_options','user_touched', 'user_token' ),
703 array( 'user_id' => $this->mId
), $fname );
705 if ( $s !== false ) {
706 $this->mName
= $s->user_name
;
707 $this->mEmail
= $s->user_email
;
708 $this->mEmailAuthenticated
= wfTimestampOrNull( TS_MW
, $s->user_email_authenticated
);
709 $this->mRealName
= $s->user_real_name
;
710 $this->mPassword
= $s->user_password
;
711 $this->mNewpassword
= $s->user_newpassword
;
712 $this->decodeOptions( $s->user_options
);
713 $this->mTouched
= wfTimestamp(TS_MW
,$s->user_touched
);
714 $this->mToken
= $s->user_token
;
716 $res = $dbr->select( 'user_groups',
718 array( 'ug_user' => $this->mId
),
720 $this->mGroups
= array();
721 while( $row = $dbr->fetchObject( $res ) ) {
722 $this->mGroups
[] = $row->ug_group
;
724 $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups
);
725 $this->mRights
= $this->getGroupPermissions( $effectiveGroups );
728 $this->mDataLoaded
= true;
731 function getID() { return $this->mId
; }
732 function setID( $v ) {
734 $this->mDataLoaded
= false;
738 $this->loadFromDatabase();
739 if ( $this->mName
=== false ) {
740 $this->mName
= wfGetIP();
745 function setName( $str ) {
746 $this->loadFromDatabase();
752 * Return the title dbkey form of the name, for eg user pages.
756 function getTitleKey() {
757 return str_replace( ' ', '_', $this->getName() );
760 function getNewtalk() {
762 $fname = 'User::getNewtalk';
763 $this->loadFromDatabase();
765 # Load the newtalk status if it is unloaded (mNewtalk=-1)
766 if( $this->mNewtalk
== -1 ) {
767 $this->mNewtalk
= 0; # reset talk page status
769 # Check memcached separately for anons, who have no
770 # entire User object stored in there.
772 global $wgDBname, $wgMemc;
773 $key = "$wgDBname:newtalk:ip:" . $this->getName();
774 $newtalk = $wgMemc->get( $key );
775 if( is_integer( $newtalk ) ) {
776 $this->mNewtalk
= $newtalk ?
1 : 0;
777 return (bool)$this->mNewtalk
;
781 $dbr =& wfGetDB( DB_SLAVE
);
782 if ( $wgUseEnotif ) {
783 $res = $dbr->select( 'watchlist',
785 array( 'wl_title' => $this->getTitleKey(),
786 'wl_namespace' => NS_USER_TALK
,
787 'wl_user' => $this->mId
,
788 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
789 'User::getNewtalk' );
790 if( $dbr->numRows($res) > 0 ) {
793 $dbr->freeResult( $res );
794 } elseif ( $this->mId
) {
795 $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId
), $fname );
797 if ( $dbr->numRows($res)>0 ) {
800 $dbr->freeResult( $res );
802 $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->getName() ), $fname );
803 $this->mNewtalk
= $dbr->numRows( $res ) > 0 ?
1 : 0;
804 $dbr->freeResult( $res );
808 $wgMemc->set( $key, $this->mNewtalk
, time() ); // + 1800 );
812 return ( 0 != $this->mNewtalk
);
815 function setNewtalk( $val ) {
816 $this->loadFromDatabase();
817 $this->mNewtalk
= $val;
818 $this->invalidateCache();
821 function invalidateCache() {
822 global $wgClockSkewFudge;
823 $this->loadFromDatabase();
824 $this->mTouched
= wfTimestamp(TS_MW
, time() +
$wgClockSkewFudge );
825 # Don't forget to save the options after this or
826 # it won't take effect!
829 function validateCache( $timestamp ) {
830 $this->loadFromDatabase();
831 return ($timestamp >= $this->mTouched
);
835 * Encrypt a password.
836 * It can eventuall salt a password @see User::addSalt()
837 * @param string $p clear Password.
838 * @return string Encrypted password.
840 function encryptPassword( $p ) {
841 return wfEncryptPassword( $this->mId
, $p );
844 # Set the password and reset the random token
845 function setPassword( $str ) {
846 $this->loadFromDatabase();
848 $this->mPassword
= $this->encryptPassword( $str );
849 $this->mNewpassword
= '';
852 # Set the random token (used for persistent authentication)
853 function setToken( $token = false ) {
854 global $wgSecretKey, $wgProxyKey, $wgDBname;
856 if ( $wgSecretKey ) {
858 } elseif ( $wgProxyKey ) {
863 $this->mToken
= md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId
);
865 $this->mToken
= $token;
870 function setCookiePassword( $str ) {
871 $this->loadFromDatabase();
872 $this->mCookiePassword
= md5( $str );
875 function setNewpassword( $str ) {
876 $this->loadFromDatabase();
877 $this->mNewpassword
= $this->encryptPassword( $str );
880 function getEmail() {
881 $this->loadFromDatabase();
882 return $this->mEmail
;
885 function getEmailAuthenticationTimestamp() {
886 $this->loadFromDatabase();
887 return $this->mEmailAuthenticated
;
890 function setEmail( $str ) {
891 $this->loadFromDatabase();
892 $this->mEmail
= $str;
895 function getRealName() {
896 $this->loadFromDatabase();
897 return $this->mRealName
;
900 function setRealName( $str ) {
901 $this->loadFromDatabase();
902 $this->mRealName
= $str;
905 function getOption( $oname ) {
906 $this->loadFromDatabase();
907 if ( array_key_exists( $oname, $this->mOptions
) ) {
908 return trim( $this->mOptions
[$oname] );
914 function setOption( $oname, $val ) {
915 $this->loadFromDatabase();
916 if ( $oname == 'skin' ) {
917 # Clear cached skin, so the new one displays immediately in Special:Preferences
918 unset( $this->mSkin
);
920 $this->mOptions
[$oname] = $val;
921 $this->invalidateCache();
924 function getRights() {
925 $this->loadFromDatabase();
926 return $this->mRights
;
930 * Get the list of explicit group memberships this user has.
931 * The implicit * and user groups are not included.
932 * @return array of strings
934 function getGroups() {
935 $this->loadFromDatabase();
936 return $this->mGroups
;
940 * Get the list of implicit group memberships this user has.
941 * This includes all explicit groups, plus 'user' if logged in
942 * and '*' for all accounts.
943 * @return array of strings
945 function getEffectiveGroups() {
946 $base = array( '*' );
947 if( $this->isLoggedIn() ) {
950 return array_merge( $base, $this->getGroups() );
954 * Remove the user from the given group.
955 * This takes immediate effect.
958 function addGroup( $group ) {
959 $dbw =& wfGetDB( DB_MASTER
);
960 $dbw->insert( 'user_groups',
962 'ug_user' => $this->getID(),
963 'ug_group' => $group,
968 $this->mGroups
= array_merge( $this->mGroups
, array( $group ) );
969 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
971 $this->invalidateCache();
972 $this->saveSettings();
976 * Remove the user from the given group.
977 * This takes immediate effect.
980 function removeGroup( $group ) {
981 $dbw =& wfGetDB( DB_MASTER
);
982 $dbw->delete( 'user_groups',
984 'ug_user' => $this->getID(),
985 'ug_group' => $group,
987 'User::removeGroup' );
989 $this->mGroups
= array_diff( $this->mGroups
, array( $group ) );
990 $this->mRights
= User
::getGroupPermissions( $this->getEffectiveGroups() );
992 $this->invalidateCache();
993 $this->saveSettings();
998 * A more legible check for non-anonymousness.
999 * Returns true if the user is not an anonymous visitor.
1003 function isLoggedIn() {
1004 return( $this->getID() != 0 );
1008 * A more legible check for anonymousness.
1009 * Returns true if the user is an anonymous visitor.
1014 return !$this->isLoggedIn();
1018 * Check if a user is sysop
1019 * Die with backtrace. Use User:isAllowed() instead.
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 # Just update the timestamp
1451 $ipblock->updateTimestamp();
1455 # Make a new block object with the desired properties
1456 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1457 $ipblock->mAddress
= wfGetIP();
1458 $ipblock->mUser
= 0;
1459 $ipblock->mBy
= $userblock->mBy
;
1460 $ipblock->mReason
= wfMsg( 'autoblocker', $this->getName(), $userblock->mReason
);
1461 $ipblock->mTimestamp
= wfTimestampNow();
1462 $ipblock->mAuto
= 1;
1463 # If the user is already blocked with an expiry date, we don't
1464 # want to pile on top of that!
1465 if($userblock->mExpiry
) {
1466 $ipblock->mExpiry
= min ( $userblock->mExpiry
, Block
::getAutoblockExpiry( $ipblock->mTimestamp
));
1468 $ipblock->mExpiry
= Block
::getAutoblockExpiry( $ipblock->mTimestamp
);
1476 function getPageRenderingHash() {
1479 return $this->mHash
;
1482 // stubthreshold is only included below for completeness,
1483 // it will always be 0 when this function is called by parsercache.
1485 $confstr = $this->getOption( 'math' );
1486 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1487 $confstr .= '!' . $this->getOption( 'date' );
1488 $confstr .= '!' . $this->getOption( 'numberheadings' );
1489 $confstr .= '!' . $this->getOption( 'language' );
1490 $confstr .= '!' . $this->getOption( 'thumbsize' );
1491 // add in language specific options, if any
1492 $extra = $wgContLang->getExtraHashOptions();
1495 $this->mHash
= $confstr;
1499 function isAllowedToCreateAccount() {
1500 return $this->isAllowed( 'createaccount' );
1504 * Set mDataLoaded, return previous value
1505 * Use this to prevent DB access in command-line scripts or similar situations
1507 function setLoaded( $loaded ) {
1508 return wfSetVar( $this->mDataLoaded
, $loaded );
1512 * Get this user's personal page title.
1517 function getUserPage() {
1518 return Title
::makeTitle( NS_USER
, $this->getName() );
1522 * Get this user's talk page title.
1527 function getTalkPage() {
1528 $title = $this->getUserPage();
1529 return $title->getTalkPage();
1535 function getMaxID() {
1536 $dbr =& wfGetDB( DB_SLAVE
);
1537 return $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1541 * Determine whether the user is a newbie. Newbies are either
1542 * anonymous IPs, or the 1% most recently created accounts.
1543 * Bots and sysops are excluded.
1544 * @return bool True if it is a newbie.
1546 function isNewbie() {
1547 return $this->isAnon() ||
$this->mId
> User
::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
1551 * Check to see if the given clear-text password is one of the accepted passwords
1552 * @param string $password User password.
1553 * @return bool True if the given password is correct otherwise False.
1555 function checkPassword( $password ) {
1556 global $wgAuth, $wgMinimalPasswordLength;
1557 $this->loadFromDatabase();
1559 // Even though we stop people from creating passwords that
1560 // are shorter than this, doesn't mean people wont be able
1561 // to. Certain authentication plugins do NOT want to save
1562 // domain passwords in a mysql database, so we should
1563 // check this (incase $wgAuth->strict() is false).
1564 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1568 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1570 } elseif( $wgAuth->strict() ) {
1571 /* Auth plugin doesn't allow local authentication */
1574 $ep = $this->encryptPassword( $password );
1575 if ( 0 == strcmp( $ep, $this->mPassword
) ) {
1577 } elseif ( ($this->mNewpassword
!= '') && (0 == strcmp( $ep, $this->mNewpassword
)) ) {
1579 } elseif ( function_exists( 'iconv' ) ) {
1580 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1581 # Check for this with iconv
1582 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1583 if ( 0 == strcmp( $cp1252hash, $this->mPassword
) ) {
1591 * Initialize (if necessary) and return a session token value
1592 * which can be used in edit forms to show that the user's
1593 * login credentials aren't being hijacked with a foreign form
1596 * @param mixed $salt - Optional function-specific data for hash.
1597 * Use a string or an array of strings.
1601 function editToken( $salt = '' ) {
1602 if( !isset( $_SESSION['wsEditToken'] ) ) {
1603 $token = $this->generateToken();
1604 $_SESSION['wsEditToken'] = $token;
1606 $token = $_SESSION['wsEditToken'];
1608 if( is_array( $salt ) ) {
1609 $salt = implode( '|', $salt );
1611 return md5( $token . $salt );
1615 * Generate a hex-y looking random token for various uses.
1616 * Could be made more cryptographically sure if someone cares.
1619 function generateToken( $salt = '' ) {
1620 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1621 return md5( $token . $salt );
1625 * Check given value against the token value stored in the session.
1626 * A match should confirm that the form was submitted from the
1627 * user's own login session, not a form submission from a third-party
1630 * @param string $val - the input value to compare
1631 * @param string $salt - Optional function-specific data for hash
1635 function matchEditToken( $val, $salt = '' ) {
1639 if ( !isset( $_SESSION['wsEditToken'] ) ) {
1640 $logfile = '/home/wikipedia/logs/session_debug/session.log';
1641 $mckey = memsess_key( session_id() );
1642 $uname = @posix_uname();
1643 $msg = "wsEditToken not set!\n" .
1644 'apache server=' . $uname['nodename'] . "\n" .
1645 'session_id = ' . session_id() . "\n" .
1646 '$_SESSION=' . var_export( $_SESSION, true ) . "\n" .
1647 '$_COOKIE=' . var_export( $_COOKIE, true ) . "\n" .
1648 "mc get($mckey) = " . var_export( $wgMemc->get( $mckey ), true ) . "\n\n\n";
1650 @error_log( $msg, 3, $logfile );
1653 return ( $val == $this->editToken( $salt ) );
1657 * Generate a new e-mail confirmation token and send a confirmation
1658 * mail to the user's given address.
1660 * @return mixed True on success, a WikiError object on failure.
1662 function sendConfirmationMail() {
1664 $url = $this->confirmationTokenUrl( $expiration );
1665 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1666 wfMsg( 'confirmemail_body',
1670 $wgContLang->timeanddate( $expiration, false ) ) );
1674 * Send an e-mail to this user's account. Does not check for
1675 * confirmed status or validity.
1677 * @param string $subject
1678 * @param string $body
1679 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1680 * @return mixed True on success, a WikiError object on failure.
1682 function sendMail( $subject, $body, $from = null ) {
1683 if( is_null( $from ) ) {
1684 global $wgPasswordSender;
1685 $from = $wgPasswordSender;
1688 require_once( 'UserMailer.php' );
1689 $error = userMailer( $this->getEmail(), $from, $subject, $body );
1691 if( $error == '' ) {
1694 return new WikiError( $error );
1699 * Generate, store, and return a new e-mail confirmation code.
1700 * A hash (unsalted since it's used as a key) is stored.
1701 * @param &$expiration mixed output: accepts the expiration time
1705 function confirmationToken( &$expiration ) {
1706 $fname = 'User::confirmationToken';
1709 $expires = $now +
7 * 24 * 60 * 60;
1710 $expiration = wfTimestamp( TS_MW
, $expires );
1712 $token = $this->generateToken( $this->mId
. $this->mEmail
. $expires );
1713 $hash = md5( $token );
1715 $dbw =& wfGetDB( DB_MASTER
);
1716 $dbw->update( 'user',
1717 array( 'user_email_token' => $hash,
1718 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1719 array( 'user_id' => $this->mId
),
1726 * Generate and store a new e-mail confirmation token, and return
1727 * the URL the user can use to confirm.
1728 * @param &$expiration mixed output: accepts the expiration time
1732 function confirmationTokenUrl( &$expiration ) {
1733 $token = $this->confirmationToken( $expiration );
1734 $title = Title
::makeTitle( NS_SPECIAL
, 'Confirmemail/' . $token );
1735 return $title->getFullUrl();
1739 * Mark the e-mail address confirmed and save.
1741 function confirmEmail() {
1742 $this->loadFromDatabase();
1743 $this->mEmailAuthenticated
= wfTimestampNow();
1744 $this->saveSettings();
1749 * Is this user allowed to send e-mails within limits of current
1750 * site configuration?
1753 function canSendEmail() {
1754 return $this->isEmailConfirmed();
1758 * Is this user allowed to receive e-mails within limits of current
1759 * site configuration?
1762 function canReceiveEmail() {
1763 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1767 * Is this user's e-mail address valid-looking and confirmed within
1768 * limits of the current site configuration?
1770 * If $wgEmailAuthentication is on, this may require the user to have
1771 * confirmed their address by returning a code or using a password
1772 * sent to the address from the wiki.
1776 function isEmailConfirmed() {
1777 global $wgEmailAuthentication;
1778 $this->loadFromDatabase();
1779 if( $this->isAnon() )
1781 if( !$this->isValidEmailAddr( $this->mEmail
) )
1783 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1789 * @param array $groups list of groups
1790 * @return array list of permission key names for given groups combined
1793 function getGroupPermissions( $groups ) {
1794 global $wgGroupPermissions;
1796 foreach( $groups as $group ) {
1797 if( isset( $wgGroupPermissions[$group] ) ) {
1798 $rights = array_merge( $rights,
1799 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1806 * @param string $group key name
1807 * @return string localized descriptive name, if provided
1810 function getGroupName( $group ) {
1811 $key = "group-$group-name";
1812 $name = wfMsg( $key );
1813 if( $name == '' ||
$name == "<$key>" ) {
1821 * Return the set of defined explicit groups.
1822 * The * and 'user' groups are not included.
1826 function getAllGroups() {
1827 global $wgGroupPermissions;
1829 array_keys( $wgGroupPermissions ),
1830 array( '*', 'user' ) );