define( 'USER_TOKEN_LENGTH', 32 );
# Serialized record version
-define( 'MW_USER_VERSION', 2 );
+define( 'MW_USER_VERSION', 3 );
/**
*
var $mHash;
var $mGroups;
var $mVersion; // serialized version
+ var $mRegistration;
/** Construct using User:loadDefaults() */
function User() {
return array( 'mId', 'mName', 'mPassword', 'mEmail', 'mNewtalk',
'mEmailAuthenticated', 'mRights', 'mOptions', 'mDataLoaded',
'mNewpassword', 'mBlockedby', 'mBlockreason', 'mTouched',
- 'mToken', 'mRealName', 'mHash', 'mGroups' );
+ 'mToken', 'mRealName', 'mHash', 'mGroups', 'mRegistration' );
}
/**
$this->mTouched = '0'; # Allow any pages to be cached
}
+ $this->mRegistration = wfTimestamp( TS_MW );
+
wfProfileOut( $fname );
}
} else {
wfDebug( "User::loadFromSession() got from cache!\n" );
}
-
+
if ( isset( $_SESSION['wsToken'] ) ) {
$passwordCorrect = $_SESSION['wsToken'] == $user->mToken;
} else if ( isset( $_COOKIE["{$wgDBname}Token"] ) ) {
$dbr =& wfGetDB( DB_SLAVE );
$s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
'user_email_authenticated',
- 'user_real_name','user_options','user_touched', 'user_token' ),
+ 'user_real_name','user_options','user_touched', 'user_token', 'user_registration' ),
array( 'user_id' => $this->mId ), $fname );
if ( $s !== false ) {
$this->decodeOptions( $s->user_options );
$this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
$this->mToken = $s->user_token;
+ $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration );
$res = $dbr->select( 'user_groups',
array( 'ug_group' ),
while( $row = $dbr->fetchObject( $res ) ) {
$this->mGroups[] = $row->ug_group;
}
- $effectiveGroups = array_merge( array( '*', 'user' ), $this->mGroups );
+ $implicitGroups = array( '*', 'user' );
+
+ global $wgAutoConfirmAge;
+ $accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration );
+ if( $accountAge >= $wgAutoConfirmAge ) {
+ $implicitGroups[] = 'autoconfirmed';
+ }
+
+ $effectiveGroups = array_merge( $implicitGroups, $this->mGroups );
$this->mRights = $this->getGroupPermissions( $effectiveGroups );
}
}
function getNewtalk() {
- global $wgUseEnotif;
- $fname = 'User::getNewtalk';
$this->loadFromDatabase();
# Load the newtalk status if it is unloaded (mNewtalk=-1)
- if( $this->mNewtalk == -1 ) {
- $this->mNewtalk = 0; # reset talk page status
+ if( $this->mNewtalk === -1 ) {
+ $this->mNewtalk = false; # reset talk page status
# Check memcached separately for anons, who have no
# entire User object stored in there.
$key = "$wgDBname:newtalk:ip:" . $this->getName();
$newtalk = $wgMemc->get( $key );
if( is_integer( $newtalk ) ) {
- $this->mNewtalk = $newtalk ? 1 : 0;
- return (bool)$this->mNewtalk;
- }
- }
-
- $dbr =& wfGetDB( DB_SLAVE );
- if ( $wgUseEnotif ) {
- $res = $dbr->select( 'watchlist',
- array( 'wl_user' ),
- array( 'wl_title' => $this->getTitleKey(),
- 'wl_namespace' => NS_USER_TALK,
- 'wl_user' => $this->mId,
- 'wl_notificationtimestamp ' . $dbr->notNullTimestamp() ),
- 'User::getNewtalk' );
- if( $dbr->numRows($res) > 0 ) {
- $this->mNewtalk = 1;
- }
- $dbr->freeResult( $res );
- } elseif ( $this->mId ) {
- $res = $dbr->select( 'user_newtalk', 1, array( 'user_id' => $this->mId ), $fname );
-
- if ( $dbr->numRows($res)>0 ) {
- $this->mNewtalk= 1;
+ $this->mNewtalk = (bool)$newtalk;
+ } else {
+ $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
+ $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 );
}
- $dbr->freeResult( $res );
} else {
- $res = $dbr->select( 'user_newtalk', 1, array( 'user_ip' => $this->getName() ), $fname );
- $this->mNewtalk = $dbr->numRows( $res ) > 0 ? 1 : 0;
- $dbr->freeResult( $res );
- }
-
- if( !$this->mId ) {
- $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 );
+ $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
}
}
- return ( 0 != $this->mNewtalk );
+ return (bool)$this->mNewtalk;
}
+ /**
+ * Perform a user_newtalk check on current slaves; if the memcached data
+ * is funky we don't want newtalk state to get stuck on save, as that's
+ * damn annoying.
+ *
+ * @param string $field
+ * @param mixed $id
+ * @return bool
+ * @access private
+ */
+ function checkNewtalk( $field, $id ) {
+ $fname = 'User::checkNewtalk';
+ $dbr =& wfGetDB( DB_SLAVE );
+ $ok = $dbr->selectField( 'user_newtalk', $field,
+ array( $field => $id ), $fname );
+ return $ok !== false;
+ }
+
+ /**
+ * Add or update the
+ * @param string $field
+ * @param mixed $id
+ * @access private
+ */
+ function updateNewtalk( $field, $id ) {
+ $fname = 'User::updateNewtalk';
+ if( $this->checkNewtalk( $field, $id ) ) {
+ wfDebug( "$fname already set ($field, $id), ignoring\n" );
+ return false;
+ }
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->insert( 'user_newtalk',
+ array( $field => $id ),
+ $fname,
+ 'IGNORE' );
+ wfDebug( "$fname: set on ($field, $id)\n" );
+ return true;
+ }
+
+ /**
+ * Clear the new messages flag for the given user
+ * @param string $field
+ * @param mixed $id
+ * @access private
+ */
+ function deleteNewtalk( $field, $id ) {
+ $fname = 'User::deleteNewtalk';
+ if( !$this->checkNewtalk( $field, $id ) ) {
+ wfDebug( "$fname: already gone ($field, $id), ignoring\n" );
+ return false;
+ }
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->delete( 'user_newtalk',
+ array( $field => $id ),
+ $fname );
+ wfDebug( "$fname: killed on ($field, $id)\n" );
+ return true;
+ }
+
+ /**
+ * Update the 'You have new messages!' status.
+ * @param bool $val
+ */
function setNewtalk( $val ) {
+ if( wfReadOnly() ) {
+ return;
+ }
+
$this->loadFromDatabase();
$this->mNewtalk = $val;
- $this->invalidateCache();
+
+ $fname = 'User::setNewtalk';
+
+ if( $this->isAnon() ) {
+ $field = 'user_ip';
+ $id = $this->getName();
+ } else {
+ $field = 'user_id';
+ $id = $this->getId();
+ }
+
+ if( $val ) {
+ $changed = $this->updateNewtalk( $field, $id );
+ } else {
+ $changed = $this->deleteNewtalk( $field, $id );
+ }
+
+ if( $changed ) {
+ if( $this->isAnon() ) {
+ // Anons have a separate memcached space, since
+ // user records aren't kept for them.
+ global $wgDBname, $wgMemc;
+ $key = "$wgDBname:newtalk:ip:$value";
+ $wgMemc->set( $key, $val ? 1 : 0 );
+ } else {
+ if( $val ) {
+ // Make sure the user page is watched, so a notification
+ // will be sent out if enabled.
+ $this->addWatch( $this->getTalkPage() );
+ }
+ }
+ $this->invalidateCache();
+ $this->saveSettings();
+ }
}
function invalidateCache() {
}
/**
- * Remove the user from the given group.
+ * Add the user to the given group.
* This takes immediate effect.
* @string $group
*/
* @return boolean True: action is allowed, False: action should not be allowed
*/
function isAllowed($action='') {
+ if ( $action === '' )
+ // In the spirit of DWIM
+ return true;
+
$this->loadFromDatabase();
return in_array( $action , $this->mRights );
}
$fname = 'User::getSkin';
wfProfileIn( $fname );
- # get all skin names available
- $skinNames = Skin::getSkinNames();
-
# get the user skin
$userSkin = $this->getOption( 'skin' );
- $userSkin = $wgRequest->getText('useskin', $userSkin);
- if ( $userSkin == '' ) { $userSkin = 'standard'; }
-
- if ( !isset( $skinNames[$userSkin] ) ) {
- # in case the user skin could not be found find a replacement
- $fallback = array(
- 0 => 'Standard',
- 1 => 'Nostalgia',
- 2 => 'CologneBlue');
- # if phptal is enabled we should have monobook skin that
- # superseed the good old SkinStandard.
- if ( isset( $skinNames['monobook'] ) ) {
- $fallback[0] = 'MonoBook';
- }
-
- if(is_numeric($userSkin) && isset( $fallback[$userSkin]) ){
- $sn = $fallback[$userSkin];
- } else {
- $sn = 'Standard';
- }
- } else {
- # The user skin is available
- $sn = $skinNames[$userSkin];
- }
-
- # Grab the skin class and initialise it. Each skin checks for PHPTal
- # and will not load if it's not enabled.
- require_once( $IP.'/skins/'.$sn.'.php' );
-
- # Check if we got if not failback to default skin
- $className = 'Skin'.$sn;
- if( !class_exists( $className ) ) {
- # DO NOT die if the class isn't found. This breaks maintenance
- # scripts and can cause a user account to be unrecoverable
- # except by SQL manipulation if a previously valid skin name
- # is no longer valid.
- $className = 'SkinStandard';
- require_once( $IP.'/skins/Standard.php' );
- }
- $this->mSkin =& new $className;
+ $userSkin = $wgRequest->getVal('useskin', $userSkin);
+
+ $this->mSkin =& Skin::newFromKey( $userSkin );
wfProfileOut( $fname );
}
return $this->mSkin;
function clearNotification( &$title ) {
global $wgUser, $wgUseEnotif;
- if ( !$wgUseEnotif ) {
+ if ($title->getNamespace() == NS_USER_TALK &&
+ $title->getText() == $this->getName() ) {
+ $this->setNewtalk( false );
+ }
+
+ if( !$wgUseEnotif ) {
return;
}
- $userid = $this->getID();
- if ($userid==0) {
+ if( $this->isAnon() ) {
+ // Nothing else to do...
return;
}
function clearAllNotifications( $currentUser ) {
global $wgUseEnotif;
if ( !$wgUseEnotif ) {
+ $this->setNewtalk( false );
return;
}
if( $currentUser != 0 ) {
}
function setCookies() {
- global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgDBname;
+ global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgDBname;
if ( 0 == $this->mId ) return;
$this->loadFromDatabase();
$exp = time() + $wgCookieExpiration;
$_SESSION['wsUserID'] = $this->mId;
- setcookie( $wgDBname.'UserID', $this->mId, $exp, $wgCookiePath, $wgCookieDomain );
+ setcookie( $wgDBname.'UserID', $this->mId, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
$_SESSION['wsUserName'] = $this->getName();
- setcookie( $wgDBname.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain );
+ setcookie( $wgDBname.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
$_SESSION['wsToken'] = $this->mToken;
if ( 1 == $this->getOption( 'rememberpassword' ) ) {
- setcookie( $wgDBname.'Token', $this->mToken, $exp, $wgCookiePath, $wgCookieDomain );
+ setcookie( $wgDBname.'Token', $this->mToken, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
} else {
setcookie( $wgDBname.'Token', '', time() - 3600 );
}
* It will clean the session cookie
*/
function logout() {
- global $wgCookiePath, $wgCookieDomain, $wgDBname;
+ global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgDBname;
$this->loadDefaults();
$this->setLoaded( true );
$_SESSION['wsUserID'] = 0;
- setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
- setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain );
+ setcookie( $wgDBname.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
+ setcookie( $wgDBname.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
# Remember when user logged out, to prevent seeing cached pages
- setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() + 86400, $wgCookiePath, $wgCookieDomain );
+ setcookie( $wgDBname.'LoggedOut', wfTimestampNow(), time() + 86400, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
}
/**
$fname = 'User::saveSettings';
if ( wfReadOnly() ) { return; }
- $this->saveNewtalk();
if ( 0 == $this->mId ) { return; }
$dbw =& wfGetDB( DB_MASTER );
$wgMemc->delete( "$wgDBname:user:id:$this->mId" );
}
- /**
- * Save value of new talk flag.
- */
- function saveNewtalk() {
- global $wgDBname, $wgMemc, $wgUseEnotif;
-
- $fname = 'User::saveNewtalk';
-
- $changed = false;
-
- if ( wfReadOnly() ) { return ; }
- $dbr =& wfGetDB( DB_SLAVE );
- $dbw =& wfGetDB( DB_MASTER );
- $changed = false;
- if ( $wgUseEnotif ) {
- if ( ! $this->getNewtalk() ) {
- # Delete the watchlist entry for user_talk page X watched by user X
- $dbw->delete( 'watchlist',
- array( 'wl_user' => $this->mId,
- 'wl_title' => $this->getTitleKey(),
- 'wl_namespace' => NS_USER_TALK ),
- $fname );
- if ( $dbw->affectedRows() ) {
- $changed = true;
- }
- if( !$this->mId ) {
- # Anon users have a separate memcache space for newtalk
- # since they don't store their own info. Trim...
- $wgMemc->delete( "$wgDBname:newtalk:ip:" . $this->getName() );
- }
- }
- } else {
- if ($this->getID() != 0) {
- $field = 'user_id';
- $value = $this->getID();
- $key = false;
- } else {
- $field = 'user_ip';
- $value = $this->getName();
- $key = "$wgDBname:newtalk:ip:$value";
- }
-
- $dbr =& wfGetDB( DB_SLAVE );
- $dbw =& wfGetDB( DB_MASTER );
-
- $res = $dbr->selectField('user_newtalk', $field,
- array($field => $value), $fname);
-
- $changed = true;
- if ($res !== false && $this->mNewtalk == 0) {
- $dbw->delete('user_newtalk', array($field => $value), $fname);
- if ( $key ) {
- $wgMemc->set( $key, 0 );
- }
- } else if ($res === false && $this->mNewtalk == 1) {
- $dbw->insert('user_newtalk', array($field => $value), $fname);
- if ( $key ) {
- $wgMemc->set( $key, 1 );
- }
- } else {
- $changed = false;
- }
- }
-
- # Update user_touched, so that newtalk notifications in the client cache are invalidated
- if ( $changed && $this->getID() ) {
- $dbw->update('user',
- /*SET*/ array( 'user_touched' => $this->mTouched ),
- /*WHERE*/ array( 'user_id' => $this->getID() ),
- $fname);
- $wgMemc->set( "$wgDBname:user:id:{$this->mId}", $this, 86400 );
- }
- }
/**
* Checks if a user with the given name exists, returns the ID
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
'user_options' => $this->encodeOptions(),
- 'user_token' => $this->mToken
+ 'user_token' => $this->mToken,
+ 'user_registration' => $dbw->timestamp( $this->mRegistration ),
), $fname
);
$this->mId = $dbw->insertId();
}
+ /**
+ * Generate a string which will be different for any combination of
+ * user options which would produce different parser output.
+ * This will be used as part of the hash key for the parser cache,
+ * so users will the same options can share the same cached data
+ * safely.
+ *
+ * Extensions which require it should install 'PageRenderingHash' hook,
+ * which will give them a chance to modify this key based on their own
+ * settings.
+ *
+ * @return string
+ */
function getPageRenderingHash() {
global $wgContLang;
if( $this->mHash ){
// add in language specific options, if any
$extra = $wgContLang->getExtraHashOptions();
$confstr .= $extra;
+
+ // Give a chance for extensions to modify the hash, if they have
+ // extra options or other effects on the parser cache.
+ wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
$this->mHash = $confstr;
- return $confstr ;
+ return $confstr;
}
function isAllowedToCreateAccount() {
* @static
*/
function getMaxID() {
- $dbr =& wfGetDB( DB_SLAVE );
- return $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
+ static $res; // cache
+
+ if ( isset( $res ) )
+ return $res;
+ else {
+ $dbr =& wfGetDB( DB_SLAVE );
+ return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
+ }
}
/**
* Determine whether the user is a newbie. Newbies are either
- * anonymous IPs, or the 1% most recently created accounts.
- * Bots and sysops are excluded.
+ * anonymous IPs, or the most recently created accounts.
* @return bool True if it is a newbie.
*/
function isNewbie() {
- return $this->isAnon() || $this->mId > User::getMaxID() * 0.99 && !$this->isAllowed( 'delete' ) && !$this->isBot();
+ return !$this->isAllowed( 'autoconfirmed' );
}
/**
}
require_once( 'UserMailer.php' );
- $error = userMailer( $this->getEmail(), $from, $subject, $body );
+ $to = new MailAddress( $this );
+ $sender = new MailAddress( $from );
+ $error = userMailer( $to, $sender, $subject, $body );
if( $error == '' ) {
return true;
global $wgGroupPermissions;
return array_diff(
array_keys( $wgGroupPermissions ),
- array( '*', 'user' ) );
+ array( '*', 'user', 'autoconfirmed' ) );
}
-
+
}
?>