use LanguageConverter;
use MediaWiki\Auth\AuthManager;
use MediaWiki\Auth\PasswordAuthenticationRequest;
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use MessageLocalizer;
use MWException;
-use MWNamespace;
use MWTimestamp;
+use NamespaceInfo;
use OutputPage;
use Parser;
use ParserOptions;
-use PreferencesFormLegacy;
+use PreferencesFormOOUI;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Skin;
class DefaultPreferencesFactory implements PreferencesFactory {
use LoggerAwareTrait;
- /** @var Config */
- protected $config;
+ /** @var ServiceOptions */
+ protected $options;
/** @var Language The wiki's content language. */
protected $contLang;
/** @var LinkRenderer */
protected $linkRenderer;
+ /** @var NamespaceInfo */
+ protected $nsInfo;
+
+ /**
+ * TODO Make this a const when we drop HHVM support (T192166)
+ *
+ * @var array
+ * @since 1.34
+ */
+ public static $constructorOptions = [
+ 'AllowUserCss',
+ 'AllowUserCssPrefs',
+ 'AllowUserJs',
+ 'DefaultSkin',
+ 'DisableLangConversion',
+ 'EmailAuthentication',
+ 'EmailConfirmToEdit',
+ 'EnableEmail',
+ 'EnableUserEmail',
+ 'EnableUserEmailBlacklist',
+ 'EnotifMinorEdits',
+ 'EnotifRevealEditorAddress',
+ 'EnotifUserTalk',
+ 'EnotifWatchlist',
+ 'HiddenPrefs',
+ 'ImageLimits',
+ 'LanguageCode',
+ 'LocalTZoffset',
+ 'MaxSigChars',
+ 'RCMaxAge',
+ 'RCShowWatchingUsers',
+ 'RCWatchCategoryMembership',
+ 'SecureLogin',
+ 'ThumbLimits',
+ ];
+
/**
- * @param Config $config
+ * Do not call this directly. Get it from MediaWikiServices.
+ *
+ * @param array|Config $options Config accepted for backwards compatibility
* @param Language $contLang
* @param AuthManager $authManager
* @param LinkRenderer $linkRenderer
+ * @param NamespaceInfo|null $nsInfo
*/
public function __construct(
- Config $config,
+ $options,
Language $contLang,
AuthManager $authManager,
- LinkRenderer $linkRenderer
+ LinkRenderer $linkRenderer,
+ NamespaceInfo $nsInfo = null
) {
- $this->config = $config;
+ if ( $options instanceof Config ) {
+ wfDeprecated( __METHOD__ . ' with Config parameter', '1.34' );
+ $options = new ServiceOptions( self::$constructorOptions, $options );
+ }
+
+ $options->assertRequiredOptions( self::$constructorOptions );
+
+ if ( !$nsInfo ) {
+ wfDeprecated( __METHOD__ . ' with no NamespaceInfo argument', '1.34' );
+ $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+ }
+ $this->options = $options;
$this->contLang = $contLang;
$this->authManager = $authManager;
$this->linkRenderer = $linkRenderer;
+ $this->nsInfo = $nsInfo;
$this->logger = new NullLogger();
}
User $user, IContextSource $context, &$defaultPreferences
) {
# # Remove preferences that wikis don't want to use
- foreach ( $this->config->get( 'HiddenPrefs' ) as $pref ) {
+ foreach ( $this->options->get( 'HiddenPrefs' ) as $pref ) {
if ( isset( $defaultPreferences[$pref] ) ) {
unset( $defaultPreferences[$pref] );
}
];
}
// Only show prefershttps if secure login is turned on
- if ( $this->config->get( 'SecureLogin' ) && $canIPUseHTTPS ) {
+ if ( $this->options->get( 'SecureLogin' ) && $canIPUseHTTPS ) {
$defaultPreferences['prefershttps'] = [
'type' => 'toggle',
'label-message' => 'tog-prefershttps',
}
$languages = Language::fetchLanguageNames( null, 'mwfile' );
- $languageCode = $this->config->get( 'LanguageCode' );
+ $languageCode = $this->options->get( 'LanguageCode' );
if ( !array_key_exists( $languageCode, $languages ) ) {
$languages[$languageCode] = $languageCode;
// Sort the array again
];
// see if there are multiple language variants to choose from
- if ( !$this->config->get( 'DisableLangConversion' ) ) {
+ if ( !$this->options->get( 'DisableLangConversion' ) ) {
foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
if ( $langCode == $this->contLang->getCode() ) {
if ( !$this->contLang->hasVariants() ) {
];
$defaultPreferences['nickname'] = [
'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
- 'maxlength' => $this->config->get( 'MaxSigChars' ),
+ 'maxlength' => $this->options->get( 'MaxSigChars' ),
'label-message' => 'yournick',
'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
return $this->validateSignature( $signature, $alldata, $form );
# # Email stuff
- if ( $this->config->get( 'EnableEmail' ) ) {
+ if ( $this->options->get( 'EnableEmail' ) ) {
if ( $canViewPrivateInfo ) {
- $helpMessages[] = $this->config->get( 'EmailConfirmToEdit' )
+ $helpMessages[] = $this->options->get( 'EmailConfirmToEdit' )
? 'prefs-help-email-required'
: 'prefs-help-email';
- if ( $this->config->get( 'EnableUserEmail' ) ) {
+ if ( $this->options->get( 'EnableUserEmail' ) ) {
// additional messages when users can send email to each other
$helpMessages[] = 'prefs-help-email-others';
}
$disableEmailPrefs = false;
- if ( $this->config->get( 'EmailAuthentication' ) ) {
+ if ( $this->options->get( 'EmailAuthentication' ) ) {
$emailauthenticationclass = 'mw-email-not-authenticated';
if ( $user->getEmail() ) {
if ( $user->getEmailAuthenticationTimestamp() ) {
}
}
- if ( $this->config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
+ if ( $this->options->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
$defaultPreferences['disablemail'] = [
'id' => 'wpAllowEmail',
'type' => 'toggle',
'disabled' => $disableEmailPrefs,
];
- if ( $this->config->get( 'EnableUserEmailBlacklist' ) ) {
+ if ( $this->options->get( 'EnableUserEmailBlacklist' ) ) {
$defaultPreferences['email-blacklist'] = [
'type' => 'usersmultiselect',
'label-message' => 'email-blacklist-label',
}
}
- if ( $this->config->get( 'EnotifWatchlist' ) ) {
+ if ( $this->options->get( 'EnotifWatchlist' ) ) {
$defaultPreferences['enotifwatchlistpages'] = [
'type' => 'toggle',
'section' => 'personal/email',
'disabled' => $disableEmailPrefs,
];
}
- if ( $this->config->get( 'EnotifUserTalk' ) ) {
+ if ( $this->options->get( 'EnotifUserTalk' ) ) {
$defaultPreferences['enotifusertalkpages'] = [
'type' => 'toggle',
'section' => 'personal/email',
'disabled' => $disableEmailPrefs,
];
}
- if ( $this->config->get( 'EnotifUserTalk' ) || $this->config->get( 'EnotifWatchlist' ) ) {
- if ( $this->config->get( 'EnotifMinorEdits' ) ) {
+ if ( $this->options->get( 'EnotifUserTalk' ) ||
+ $this->options->get( 'EnotifWatchlist' ) ) {
+ if ( $this->options->get( 'EnotifMinorEdits' ) ) {
$defaultPreferences['enotifminoredits'] = [
'type' => 'toggle',
'section' => 'personal/email',
];
}
- if ( $this->config->get( 'EnotifRevealEditorAddress' ) ) {
+ if ( $this->options->get( 'EnotifRevealEditorAddress' ) ) {
$defaultPreferences['enotifrevealaddr'] = [
'type' => 'toggle',
'section' => 'personal/email',
];
}
- $allowUserCss = $this->config->get( 'AllowUserCss' );
- $allowUserJs = $this->config->get( 'AllowUserJs' );
+ $allowUserCss = $this->options->get( 'AllowUserCss' );
+ $allowUserJs = $this->options->get( 'AllowUserJs' );
# Create links to user CSS/JS pages for all skins
# This code is basically copied from generateSkinOptions(). It'd
# be nice to somehow merge this back in there to avoid redundancy.
];
# # Page Rendering ##############################
- if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
+ if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
$defaultPreferences['underline'] = [
'type' => 'select',
'options' => [
'label-message' => 'tog-editondblclick',
];
- if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
+ if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
$defaultPreferences['editfont'] = [
'type' => 'select',
'section' => 'editing/editor',
* @param array &$defaultPreferences
*/
protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
- $rcMaxAge = $this->config->get( 'RCMaxAge' );
+ $rcMaxAge = $this->options->get( 'RCMaxAge' );
# # RecentChanges #####################################
$defaultPreferences['rcdays'] = [
'type' => 'float',
'type' => 'api',
];
- if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
+ if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
$defaultPreferences['hidecategorization'] = [
'type' => 'toggle',
'label-message' => 'tog-hidecategorization',
];
}
- if ( $this->config->get( 'RCShowWatchingUsers' ) ) {
+ if ( $this->options->get( 'RCShowWatchingUsers' ) ) {
$defaultPreferences['shownumberswatching'] = [
'type' => 'toggle',
'section' => 'rc/advancedrc',
protected function watchlistPreferences(
User $user, IContextSource $context, &$defaultPreferences
) {
- $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
+ $watchlistdaysMax = ceil( $this->options->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
# # Watchlist #####################################
if ( $user->isAllowed( 'editmywatchlist' ) ) {
'label-message' => 'tog-watchlisthideliu',
];
- if ( !\SpecialWatchlist::checkStructuredFilterUiEnabled(
- $this->config,
- $user
- ) ) {
+ if ( !\SpecialWatchlist::checkStructuredFilterUiEnabled( $user ) ) {
$defaultPreferences['watchlistreloadautomatically'] = [
'type' => 'toggle',
'section' => 'watchlist/advancedwatchlist',
'label-message' => 'tog-watchlistunwatchlinks',
];
- if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
+ if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
$defaultPreferences['watchlisthidecategorization'] = [
'type' => 'toggle',
'section' => 'watchlist/changeswatchlist',
* @param array &$defaultPreferences
*/
protected function searchPreferences( &$defaultPreferences ) {
- foreach ( MWNamespace::getValidNamespaces() as $n ) {
+ foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
$defaultPreferences['searchNs' . $n] = [
'type' => 'api',
];
# Only show skins that aren't disabled in $wgSkipSkins
$validSkinNames = Skin::getAllowedSkins();
+ $allInstalledSkins = Skin::getSkinNames();
+
+ // Display the installed skin the user has specifically requested via useskin=….
+ $useSkin = $context->getRequest()->getRawVal( 'useskin' );
+ if ( isset( $allInstalledSkins[$useSkin] )
+ && $context->msg( "skinname-$useSkin" )->exists()
+ ) {
+ $validSkinNames[$useSkin] = $useSkin;
+ }
+
+ // Display the skin if the user has set it as a preference already before it was hidden.
+ $currentUserSkin = $user->getOption( 'skin' );
+ if ( isset( $allInstalledSkins[$currentUserSkin] )
+ && $context->msg( "skinname-$useSkin" )->exists()
+ ) {
+ $validSkinNames[$currentUserSkin] = $currentUserSkin;
+ }
foreach ( $validSkinNames as $skinkey => &$skinname ) {
$msg = $context->msg( "skinname-{$skinkey}" );
}
}
- $defaultSkin = $this->config->get( 'DefaultSkin' );
- $allowUserCss = $this->config->get( 'AllowUserCss' );
- $allowUserJs = $this->config->get( 'AllowUserJs' );
+ $defaultSkin = $this->options->get( 'DefaultSkin' );
+ $allowUserCss = $this->options->get( 'AllowUserCss' );
+ $allowUserJs = $this->options->get( 'AllowUserJs' );
# Sort by the internal name, so that the ordering is the same for each display language,
# especially if some skin names are translated to use a different alphabet and some are not.
$ret = [];
$pixels = $l10n->msg( 'unit-pixel' )->text();
- foreach ( $this->config->get( 'ImageLimits' ) as $index => $limits ) {
+ foreach ( $this->options->get( 'ImageLimits' ) as $index => $limits ) {
// Note: A left-to-right marker (U+200E) is inserted, see T144386
$display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
$ret[$display] = $index;
$ret = [];
$pixels = $l10n->msg( 'unit-pixel' )->text();
- foreach ( $this->config->get( 'ThumbLimits' ) as $index => $size ) {
+ foreach ( $this->options->get( 'ThumbLimits' ) as $index => $size ) {
$display = $size . $pixels;
$ret[$display] = $index;
}
* @return bool|string
*/
protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
- $maxSigChars = $this->config->get( 'MaxSigChars' );
+ $maxSigChars = $this->options->get( 'MaxSigChars' );
if ( mb_strlen( $signature ) > $maxSigChars ) {
return Xml::element( 'span', [ 'class' => 'error' ],
$form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
public function getForm(
User $user,
IContextSource $context,
- $formClass = PreferencesFormLegacy::class,
+ $formClass = PreferencesFormOOUI::class,
array $remove = []
) {
// We use ButtonWidgets in some of the getPreferences() functions
}
/**
- * @var $htmlForm HTMLForm
+ * @var HTMLForm $htmlForm
*/
$htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
+ // This allows users to opt-in to hidden skins. While this should be discouraged and is not
+ // discoverable, this allows users to still use hidden skins while preventing new users from
+ // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
+ // in the resulting URL.
+ $htmlForm->setAction( $context->getTitle()->getLocalURL( [
+ 'useskin' => $context->getRequest()->getRawVal( 'useskin' )
+ ] ) );
+
$htmlForm->setModifiedUser( $user );
$htmlForm->setId( 'mw-prefs-form' );
$htmlForm->setAutocomplete( 'off' );
protected function getTimezoneOptions( IContextSource $context ) {
$opt = [];
- $localTZoffset = $this->config->get( 'LocalTZoffset' );
+ $localTZoffset = $this->options->get( 'LocalTZoffset' );
$timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
$timestamp = MWTimestamp::getLocalInstance();
protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
/** @var \User $user */
$user = $form->getModifiedUser();
- $hiddenPrefs = $this->config->get( 'HiddenPrefs' );
+ $hiddenPrefs = $this->options->get( 'HiddenPrefs' );
$result = true;
if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {