'emailaddress',
);
+ /**
+ * @return array
+ */
+ static function getSaveBlacklist() {
+ return self::$saveBlacklist;
+ }
+
/**
* @throws MWException
* @param $user User
}
// show a preview of the old signature first
- $oldsigWikiText = $wgParser->preSaveTransform( "~~~", $context->getTitle(), $user, ParserOptions::newFromContext( $context ) );
+ $oldsigWikiText = $wgParser->preSaveTransform(
+ '~~~',
+ $context->getTitle(),
+ $user,
+ ParserOptions::newFromContext( $context )
+ );
$oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true );
$defaultPreferences['oldsig'] = array(
'type' => 'info',
$defaultPreferences['fancysig'] = array(
'type' => 'toggle',
'label-message' => 'tog-fancysig',
- 'help-message' => 'prefs-help-signature', // show general help about signature at the bottom of the section
+ // show general help about signature at the bottom of the section
+ 'help-message' => 'prefs-help-signature',
'section' => 'personal/signature'
);
$minDiff = $tz[1];
$tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
} elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
- !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) ) )
- {
+ !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
+ ) {
# Timezone offset can vary with DST
$userTZ = timezone_open( $tz[2] );
if ( $userTZ !== false ) {
}
$defaultPreferences['stubthreshold'] = array(
- 'type' => 'selectorother',
+ 'type' => 'select',
'section' => 'rendering/advancedrendering',
'options' => $stubThresholdOptions,
'size' => 20,
'label-raw' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
);
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['showtoc'] = array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-showtoc',
- );
- }
- $defaultPreferences['nocache'] = array(
- 'type' => 'toggle',
- 'label-message' => 'tog-nocache',
- 'section' => 'rendering/advancedrendering',
- );
$defaultPreferences['showhiddencats'] = array(
'type' => 'toggle',
'section' => 'rendering/advancedrendering',
'label-message' => 'tog-showhiddencats'
);
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['justify'] = array(
- 'type' => 'toggle',
- 'section' => 'rendering/advancedrendering',
- 'label-message' => 'tog-justify',
- );
- }
-
$defaultPreferences['numberheadings'] = array(
'type' => 'toggle',
'section' => 'rendering/advancedrendering',
global $wgAllowUserCssPrefs;
## Editing #####################################
- if ( $wgAllowUserCssPrefs ) {
- $defaultPreferences['editsection'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-editsection',
- );
- }
$defaultPreferences['editsectiononrightclick'] = array(
'type' => 'toggle',
'section' => 'editing/advancedediting',
static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) {
global $wgContLang, $wgVectorUseSimpleSearch;
- ## Search #####################################
- $defaultPreferences['searchlimit'] = array(
- 'type' => 'int',
- 'label-message' => 'resultsperpage',
- 'section' => 'searchoptions/displaysearchoptions',
- 'min' => 0,
- );
-
if ( $wgVectorUseSimpleSearch ) {
$defaultPreferences['vector-simplesearch'] = array(
'type' => 'toggle',
);
}
- $defaultPreferences['disablesuggest'] = array(
- 'type' => 'toggle',
- 'label-message' => 'mwsuggest-disable',
- 'section' => 'searchoptions/displaysearchoptions',
- );
-
$defaultPreferences['searcheverything'] = array(
'type' => 'toggle',
'label-message' => 'searcheverything-enable',
$linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
}
- $display = $sn . ' ' . $context->msg( 'parentheses', $context->getLanguage()->pipeList( $linkTools ) )->text();
+ $display = $sn . ' ' . $context->msg(
+ 'parentheses',
+ $context->getLanguage()->pipeList( $linkTools )
+ )->text();
$ret[$display] = $skinkey;
}
$form->msg( 'badsiglength' )->numParams( $wgMaxSigChars )->text() );
} elseif ( isset( $alldata['fancysig'] ) &&
$alldata['fancysig'] &&
- false === $wgParser->validateSig( $signature ) ) {
- return Xml::element( 'span', array( 'class' => 'error' ), $form->msg( 'badsig' )->text() );
+ $wgParser->validateSig( $signature ) === false
+ ) {
+ return Xml::element(
+ 'span',
+ array( 'class' => 'error' ),
+ $form->msg( 'badsig' )->text()
+ );
} else {
return true;
}
* @param array $remove array of items to remove
* @return HtmlForm
*/
- static function getFormObject( $user, IContextSource $context, $formClass = 'PreferencesForm', array $remove = array() ) {
+ static function getFormObject(
+ $user,
+ IContextSource $context,
+ $formClass = 'PreferencesForm',
+ array $remove = array()
+ ) {
$formDescriptor = Preferences::getPreferences( $user, $context );
if ( count( $remove ) ) {
$removeKeys = array_flip( $remove );
$timestamp = MWTimestamp::getLocalInstance();
// Check that $wgLocalTZoffset is the same as the local time zone offset
if ( $wgLocalTZoffset == $timestamp->format( 'Z' ) / 60 ) {
- $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $timestamp->getTimezone()->getName() )->text();
+ $server_tz_msg = $context->msg(
+ 'timezoneuseserverdefault',
+ $timestamp->getTimezone()->getName()
+ )->text();
} else {
- $tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
+ $tzstring = sprintf(
+ '%+03d:%02d',
+ floor( $wgLocalTZoffset / 60 ),
+ abs( $wgLocalTZoffset ) % 60
+ );
$server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
}
$opt[$server_tz_msg] = "System|$wgLocalTZoffset";
# If users have saved a value for a preference which has subsequently been disabled
# via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
# is subsequently re-enabled
- # TODO: maintenance script to actually delete these
foreach ( $wgHiddenPrefs as $pref ) {
# If the user has not set a non-default value here, the default will be returned
# and subsequently discarded
$user->setOption( $key, $value );
}
+ wfRunHooks( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) );
$user->saveSettings();
}
* user_name and user_real_name are not provided because the whole row
* will be loaded once more from the database when accessing them.
*
- * @param array $row A row from the user table
+ * @param stdClass $row A row from the user table
* @param array $data Further data to load into the object (see User::loadFromRow for valid keys)
* @return User
*/
/**
* Initialize this object from a row from the user table.
*
- * @param array $row Row from the user table to load.
+ * @param stdClass $row Row from the user table to load.
* @param array $data Further user data to load into the object
*
* user_groups Array with groups out of the user_groups table
// Proxy blocking
if ( !$block instanceof Block && $ip !== null && !$this->isAllowed( 'proxyunbannable' )
- && !in_array( $ip, $wgProxyWhitelist ) )
- {
+ && !in_array( $ip, $wgProxyWhitelist )
+ ) {
// Local list
if ( self::isLocallyBlockedProxy( $ip ) ) {
$block = new Block;
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
// If a user's name is suppressed, they cannot make edits anywhere
- if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() &&
- $title->getNamespace() == NS_USER_TALK ) {
+ if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
+ && $title->getNamespace() == NS_USER_TALK ) {
$blocked = false;
wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
}
/**
* Set the password for a password reminder or new account email
*
- * @param string $str New password to set
+ * @param $str New password to set or null to set an invalid
+ * password hash meaning that the user will not be able to use it
* @param bool $throttle If true, reset the throttle timestamp to the present
*/
public function setNewpassword( $str, $throttle = true ) {
$this->load();
- $this->mNewpassword = self::crypt( $str );
- if ( $throttle ) {
- $this->mNewpassTime = wfTimestampNow();
+
+ if ( $str === null ) {
+ $this->mNewpassword = '';
+ $this->mNewpassTime = null;
+ } else {
+ $this->mNewpassword = self::crypt( $str );
+ if ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
+ }
}
}
* - 'registered-checkmatrix' - as above, using the 'checkmatrix' type.
* - 'userjs' - preferences with names starting with 'userjs-', intended to
* be used by user scripts.
+ * - 'special' - "preferences" that are not accessible via User::getOptions
+ * or User::setOptions.
* - 'unused' - preferences about which MediaWiki doesn't know anything.
* These are usually legacy options, removed in newer versions.
*
'registered-multiselect',
'registered-checkmatrix',
'userjs',
+ 'special',
'unused'
);
}
$prefs = Preferences::getPreferences( $this, $context );
$mapping = array();
+ // Pull out the "special" options, so they don't get converted as
+ // multiselect or checkmatrix.
+ $specialOptions = array_fill_keys( Preferences::getSaveBlacklist(), true );
+ foreach ( $specialOptions as $name => $value ) {
+ unset( $prefs[$name] );
+ }
+
// Multiselect and checkmatrix options are stored in the database with
// one key per option, each having a boolean value. Extract those keys.
$multiselectOptions = array();
$mapping[$key] = 'registered-multiselect';
} elseif ( isset( $checkmatrixOptions[$key] ) ) {
$mapping[$key] = 'registered-checkmatrix';
+ } elseif ( isset( $specialOptions[$key] ) ) {
+ $mapping[$key] = 'special';
} elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
$mapping[$key] = 'userjs';
} else {
* the next change of the page if it's watched etc.
* @note If the user doesn't have 'editmywatchlist', this will do nothing.
* @param $title Title of the article to look at
+ * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
*/
- public function clearNotification( &$title ) {
+ public function clearNotification( &$title, $oldid = 0 ) {
global $wgUseEnotif, $wgShowUpdatedMarker;
// Do nothing if the database is locked to writes
return;
}
- if ( $title->getNamespace() == NS_USER_TALK &&
- $title->getText() == $this->getName() ) {
- if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this ) ) ) {
+ // If we're working on user's talk page, we should update the talk page message indicator
+ if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
+ if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this, $oldid ) ) ) {
return;
}
- $this->setNewtalk( false );
+
+ $nextid = $oldid ? $title->getNextRevisionID( $oldid ) : null;
+
+ if ( !$oldid || !$nextid ) {
+ // If we're looking at the latest revision, we should definitely clear it
+ $this->setNewtalk( false );
+ } else {
+ // Otherwise we should update its revision, if it's present
+ if ( $this->getNewtalk() ) {
+ // Naturally the other one won't clear by itself
+ $this->setNewtalk( false );
+ $this->setNewtalk( true, Revision::newFromId( $nextid ) );
+ }
+ }
}
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
// and when it does have to be executed, it can be on a slave
// If this is the user's newtalk page, we always update the timestamp
$force = '';
- if ( $title->getNamespace() == NS_USER_TALK &&
- $title->getText() == $this->getName() )
- {
+ if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
$force = 'force';
}
- $this->getWatchedItem( $title )->resetNotificationTimestamp( $force );
+ $this->getWatchedItem( $title )->resetNotificationTimestamp( $force, $oldid );
}
/**
if ( $id != 0 ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'watchlist',
- array( /* SET */
- 'wl_notificationtimestamp' => null
- ), array( /* WHERE */
- 'wl_user' => $id
- ), __METHOD__
+ array( /* SET */ 'wl_notificationtimestamp' => null ),
+ array( /* WHERE */ 'wl_user' => $id ),
+ __METHOD__
);
- # We also need to clear here the "you have new message" notification for the own user_talk page
- # This is cleared one page view later in Article::viewUpdates();
+ // We also need to clear here the "you have new message" notification for the own user_talk page;
+ // it's cleared one page view later in WikiPage::doViewUpdates().
}
}
return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
}
- /**
- * 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 with 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.
- *
- * @deprecated since 1.17 use the ParserOptions object to get the relevant options
- * @return string Page rendering hash
- */
- public function getPageRenderingHash() {
- wfDeprecated( __METHOD__, '1.17' );
-
- global $wgRenderHashAppend, $wgLang, $wgContLang;
- if ( $this->mHash ) {
- return $this->mHash;
- }
-
- // stubthreshold is only included below for completeness,
- // since it disables the parser cache, its value will always
- // be 0 when this function is called by parsercache.
-
- $confstr = $this->getOption( 'math' );
- $confstr .= '!' . $this->getStubThreshold();
- $confstr .= '!' . ( $this->getOption( 'numberheadings' ) ? '1' : '' );
- $confstr .= '!' . $wgLang->getCode();
- $confstr .= '!' . $this->getOption( 'thumbsize' );
- // add in language specific options, if any
- $extra = $wgContLang->getExtraHashOptions();
- $confstr .= $extra;
-
- // Since the skin could be overloading link(), it should be
- // included here but in practice, none of our skins do that.
-
- $confstr .= $wgRenderHashAppend;
-
- // 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 ) );
-
- // Make it a valid memcached key fragment
- $confstr = str_replace( ' ', '_', $confstr );
- $this->mHash = $confstr;
- return $confstr;
- }
-
/**
* Get whether the user is explicitly blocked from account creation.
* @return bool|Block
// Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
// Check for this with iconv
$cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
- if ( $cp1252Password != $password &&
- self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) )
- {
+ if ( $cp1252Password != $password
+ && self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId )
+ ) {
return true;
}
}
*/
public function sendMail( $subject, $body, $from = null, $replyto = null ) {
if ( is_null( $from ) ) {
- global $wgPasswordSender, $wgPasswordSenderName;
- $sender = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
+ global $wgPasswordSender;
+ $sender = new MailAddress( $wgPasswordSender,
+ wfMessage( 'emailsender' )->inContentLanguage()->text() );
} else {
$sender = new MailAddress( $from );
}
* @ingroup API
*/
class ApiOptions extends ApiBase {
-
/**
* Changes preferences of the current user.
*/
}
if ( $params['reset'] ) {
- $user->resetOptions( $params['resetkinds'] );
+ $user->resetOptions( $params['resetkinds'], $this->getContext() );
$changed = true;
}
$validation = true;
}
break;
+ case 'special':
+ $validation = "cannot be set by this module";
+ break;
case 'unused':
default:
$validation = "not a valid preference";
'token' => 'An options token previously obtained through the action=tokens',
'reset' => 'Resets preferences to the site defaults',
'resetkinds' => 'List of types of options to reset when the "reset" option is set',
- 'change' => 'List of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters. If no value is given (not even an equals sign), e.g., optionname|otheroption|..., the option will be reset to its default value',
+ 'change' => 'List of changes, formatted name=value (e.g. skin=vector), ' .
+ 'value cannot contain pipe characters. If no value is given (not ' .
+ 'even an equals sign), e.g., optionname|otheroption|..., the ' .
+ 'option will be reset to its default value',
'optionname' => 'A name of a option which should have an optionvalue set',
- 'optionvalue' => 'A value of the option specified by the optionname, can contain pipe characters',
+ 'optionvalue' => 'A value of the option specified by the optionname, ' .
+ 'can contain pipe characters',
);
}
return array(
'Change preferences of the current user',
'Only options which are registered in core or in one of installed extensions,',
- 'or as options with keys prefixed with \'userjs-\' (intended to be used by user scripts), can be set.'
+ 'or as options with keys prefixed with \'userjs-\' (intended to be used by user',
+ 'scripts), can be set.'
);
}
return array(
'api.php?action=options&reset=&token=123ABC',
'api.php?action=options&change=skin=vector|hideminor=1&token=123ABC',
- 'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
+ 'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&' .
+ 'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
);
}
}
* @group API
* @group Database
* @group medium
+ *
+ * @covers ApiOptions
*/
class ApiOptionsTest extends MediaWikiLangTestCase {
- private $mTested, $mUserMock, $mContext, $mSession;
+ /** @var PHPUnit_Framework_MockObject_MockObject */
+ private $mUserMock;
+ /** @var ApiOptions */
+ private $mTested;
+ private $mSession;
+ /** @var DerivativeContext */
+ private $mContext;
private $mOldGetPreferencesHooks = false;
return true;
}
+ /**
+ * @param IContextSource $context
+ * @param array|null $options
+ *
+ * @return array
+ */
public function getOptionKinds( IContextSource $context, $options = null ) {
// Match with above.
$kinds = array(
'testmultiselect-opt2' => 'registered-multiselect',
'testmultiselect-opt3' => 'registered-multiselect',
'testmultiselect-opt4' => 'registered-multiselect',
+ 'special' => 'special',
);
if ( $options === null ) {
$this->assertEquals( self::$Success, $response );
}
+ public function testSpecialOption() {
+ $this->mUserMock->expects( $this->never() )
+ ->method( 'resetOptions' );
+
+ $this->mUserMock->expects( $this->never() )
+ ->method( 'saveSettings' );
+
+ $request = $this->getSampleRequest( array(
+ 'change' => 'special=1'
+ ) );
+
+ $response = $this->executeQuery( $request );
+
+ $this->assertEquals( array(
+ 'options' => 'success',
+ 'warnings' => array(
+ 'options' => array(
+ '*' => "Validation error for 'special': cannot be set by this module"
+ )
+ )
+ ), $response );
+ }
+
public function testUnknownOption() {
$this->mUserMock->expects( $this->never() )
->method( 'resetOptions' );