diff --git a/includes/preferences/PreferencesFactory.php b/includes/preferences/PreferencesFactory.php index 924e3ad..b19c690 100644 --- a/includes/preferences/PreferencesFactory.php +++ b/includes/preferences/PreferencesFactory.php @@ -17,12 +17,46 @@ * * @file */ + +namespace MediaWiki\Preferences; + +use CentralIdLookup; +use DateTime; +use DateTimeZone; +use Exception; +use Hooks; +use Html; +use HTMLForm; +use HTMLFormField; +use IContextSource; +use Language; +use LanguageCode; +use LanguageConverter; use MediaWiki\Auth\AuthManager; use MediaWiki\Auth\PasswordAuthenticationRequest; use MediaWiki\MediaWikiServices; +use MWException; +use MWNamespace; +use MWTimestamp; +use OOUI\ButtonWidget; +use OOUI\HtmlSnippet; +use OutputPage; +use Parser; +use ParserOptions; +use PreferencesForm; +use Skin; +use SpecialPage; +use Status; +use Title; +use User; +use UserGroupMembership; +use Xml; /** - * We're now using the HTMLForm object with some customisation to generate the + * The PreferencesFactory is a MediaWiki service that provides the definitions of preferences for + * a given user. These definitions are in the form of HTMLForm descriptors. + * + * PreferencesForm (a subclass of HTMLForm) is used to generate the * Preferences form. This object handles generic submission, CSRF protection, * layout and other logic in a reusable manner. We subclass it as a PreferencesForm * to make some minor customisations. @@ -46,26 +80,31 @@ use MediaWiki\MediaWikiServices; * Once fields have been retrieved and validated, submission logic is handed * over to the tryUISubmit static method of this class. */ -class Preferences { - /** @var array */ - protected static $saveFilters = [ - 'timecorrection' => [ 'Preferences', 'filterTimezoneInput' ], - 'rclimit' => [ 'Preferences', 'filterIntval' ], - 'wllimit' => [ 'Preferences', 'filterIntval' ], - 'searchlimit' => [ 'Preferences', 'filterIntval' ], - ]; +class PreferencesFactory { // Stuff that shouldn't be saved as a preference. - private static $saveBlacklist = [ + protected $saveBlacklist = [ 'realname', 'emailaddress', ]; /** + * @return callable[] + */ + protected function getSaveFilters() { + return [ + 'timecorrection' => [ $this, 'filterTimezoneInput' ], + 'rclimit' => [ $this, 'filterIntval' ], + 'wllimit' => [ $this, 'filterIntval' ], + 'searchlimit' => [ $this, 'filterIntval' ], + ]; + } + + /** * @return array */ - static function getSaveBlacklist() { - return self::$saveBlacklist; + public function getSaveBlacklist() { + return $this->saveBlacklist; } /** @@ -74,29 +113,27 @@ class Preferences { * @param IContextSource $context * @return array|null */ - static function getPreferences( $user, IContextSource $context ) { + public function getPreferences( $user, IContextSource $context ) { OutputPage::setupOOUI( strtolower( $context->getSkin()->getSkinName() ), $context->getLanguage()->getDir() ); - - $defaultPreferences = []; - - self::profilePreferences( $user, $context, $defaultPreferences ); - self::skinPreferences( $user, $context, $defaultPreferences ); - self::datetimePreferences( $user, $context, $defaultPreferences ); - self::filesPreferences( $user, $context, $defaultPreferences ); - self::renderingPreferences( $user, $context, $defaultPreferences ); - self::editingPreferences( $user, $context, $defaultPreferences ); - self::rcPreferences( $user, $context, $defaultPreferences ); - self::watchlistPreferences( $user, $context, $defaultPreferences ); - self::searchPreferences( $user, $context, $defaultPreferences ); - self::miscPreferences( $user, $context, $defaultPreferences ); - - Hooks::run( 'GetPreferences', [ $user, &$defaultPreferences ] ); - - self::loadPreferenceValues( $user, $context, $defaultPreferences ); - return $defaultPreferences; + $preferences = []; + $this->profilePreferences( $user, $context, $preferences ); + $this->skinPreferences( $user, $context, $preferences ); + $this->datetimePreferences( $user, $context, $preferences ); + $this->filesPreferences( $user, $context, $preferences ); + $this->renderingPreferences( $user, $context, $preferences ); + $this->editingPreferences( $user, $context, $preferences ); + $this->rcPreferences( $user, $context, $preferences ); + $this->watchlistPreferences( $user, $context, $preferences ); + $this->searchPreferences( $user, $context, $preferences ); + $this->miscPreferences( $user, $context, $preferences ); + + Hooks::run( 'GetPreferences', [ $user, &$preferences ] ); + + $this->loadPreferenceValues( $user, $context, $preferences ); + return $preferences; } /** @@ -107,7 +144,7 @@ class Preferences { * @param array &$defaultPreferences Array to load values for * @return array|null */ - static function loadPreferenceValues( $user, $context, &$defaultPreferences ) { + public function loadPreferenceValues( $user, $context, &$defaultPreferences ) { # # Remove preferences that wikis don't want to use foreach ( $context->getConfig()->get( 'HiddenPrefs' ) as $pref ) { if ( isset( $defaultPreferences[$pref] ) ) { @@ -124,7 +161,7 @@ class Preferences { # # Prod in defaults from the user foreach ( $defaultPreferences as $name => &$info ) { $prefFromUser = self::getOptionFromUser( $name, $info, $user ); - if ( $disable && !in_array( $name, self::$saveBlacklist ) ) { + if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) { $info['disabled'] = 'disabled'; } $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation @@ -157,7 +194,7 @@ class Preferences { * @param User $user * @return array|string */ - static function getOptionFromUser( $name, $info, $user ) { + public function getOptionFromUser( $name, $info, $user ) { $val = $user->getOption( $name ); // Handling for multiselect preferences @@ -200,7 +237,7 @@ class Preferences { * @param array &$defaultPreferences * @return void */ - static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) { global $wgContLang, $wgParser; $authManager = AuthManager::singleton(); @@ -317,7 +354,7 @@ class Preferences { if ( $canEditPrivateInfo && $authManager->allowsAuthenticationDataChange( new PasswordAuthenticationRequest(), false )->isGood() ) { - $link = new OOUI\ButtonWidget( [ + $link = new ButtonWidget( [ 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ), @@ -444,9 +481,9 @@ class Preferences { 'type' => $authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info', 'maxlength' => $config->get( 'MaxSigChars' ), 'label-message' => 'yournick', - 'validation-callback' => [ 'Preferences', 'validateSignature' ], + 'validation-callback' => [ $this, 'validateSignature' ], 'section' => 'personal/signature', - 'filter-callback' => [ 'Preferences', 'cleanSignature' ], + 'filter-callback' => [ $this, 'cleanSignature' ], ]; $defaultPreferences['fancysig'] = [ 'type' => 'toggle', @@ -471,12 +508,12 @@ class Preferences { $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : ''; if ( $canEditPrivateInfo && $authManager->allowsPropertyChange( 'emailaddress' ) ) { - $link = new OOUI\ButtonWidget( [ + $emailAddressMsg = $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail'; + $link = new ButtonWidget( [ 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ), - 'label' => - $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(), + 'label' => $context->msg( $emailAddressMsg )->text(), ] ); $emailAddress .= $emailAddress == '' ? $link : ( '
' . $link ); @@ -514,7 +551,7 @@ class Preferences { } else { $disableEmailPrefs = true; $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '
' . - new OOUI\ButtonWidget( [ + new ButtonWidget( [ 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(), 'label' => $context->msg( 'emailconfirmlink' )->text(), ] ); @@ -615,7 +652,7 @@ class Preferences { * @param array &$defaultPreferences * @return void */ - static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) { # # Skin ##################################### // Skin selector, if there is at least one valid skin @@ -664,7 +701,7 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function filesPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function filesPreferences( $user, IContextSource $context, &$defaultPreferences ) { # # Files ##################################### $defaultPreferences['imagesize'] = [ 'type' => 'select', @@ -686,7 +723,7 @@ class Preferences { * @param array &$defaultPreferences * @return void */ - static function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) { # # Date and time ##################################### $dateOptions = self::getDateOptions( $context ); if ( $dateOptions ) { @@ -763,7 +800,7 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) { # # Diffs #################################### $defaultPreferences['diffonly'] = [ 'type' => 'toggle', @@ -825,7 +862,7 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) { # # Editing ##################################### $defaultPreferences['editsectiononrightclick'] = [ 'type' => 'toggle', @@ -897,7 +934,7 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) { $config = $context->getConfig(); $rcMaxAge = $config->get( 'RCMaxAge' ); # # RecentChanges ##################################### @@ -991,7 +1028,9 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function watchlistPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function watchlistPreferences( + $user, IContextSource $context, &$defaultPreferences + ) { $config = $context->getConfig(); $watchlistdaysMax = ceil( $config->get( 'RCMaxAge' ) / ( 3600 * 24 ) ); @@ -1006,13 +1045,12 @@ class Preferences { $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) { // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear - $editWatchlistLinks .= - new OOUI\ButtonWidget( [ - 'href' => SpecialPage::getTitleFor( $mode[0], $mode[1] )->getLinkURL(), - 'label' => new OOUI\HtmlSnippet( - $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() - ), - ] ); + $editWatchlistLinks .= new ButtonWidget( [ + 'href' => SpecialPage::getTitleFor( $mode[0], $mode[1] )->getLinkURL(), + 'label' => new HtmlSnippet( + $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() + ), + ] ); } $defaultPreferences['editwatchlist'] = [ @@ -1134,8 +1172,7 @@ class Preferences { $defaultPreferences['watchlisttoken'] = [ 'type' => 'api', ]; - - $tokenButton = new OOUI\ButtonWidget( [ + $tokenButton = new ButtonWidget( [ 'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ), @@ -1157,7 +1194,7 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) { foreach ( MWNamespace::getValidNamespaces() as $n ) { $defaultPreferences['searchNs' . $n] = [ 'type' => 'api', @@ -1171,7 +1208,7 @@ class Preferences { * @param IContextSource $context * @param array &$defaultPreferences */ - static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) { + public function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) { } /** @@ -1179,7 +1216,7 @@ class Preferences { * @param IContextSource $context * @return array Text/links to display as key; $skinkey as value */ - static function generateSkinOptions( $user, IContextSource $context ) { + public function generateSkinOptions( $user, IContextSource $context ) { $ret = []; $mptitle = Title::newMainPage(); @@ -1249,7 +1286,7 @@ class Preferences { * @param IContextSource $context * @return array */ - static function getDateOptions( IContextSource $context ) { + public function getDateOptions( IContextSource $context ) { $lang = $context->getLanguage(); $dateopts = $lang->getDatePreferences(); @@ -1283,7 +1320,7 @@ class Preferences { * @param IContextSource $context * @return array */ - static function getImageSizes( IContextSource $context ) { + public function getImageSizes( IContextSource $context ) { $ret = []; $pixels = $context->msg( 'unit-pixel' )->text(); @@ -1300,7 +1337,7 @@ class Preferences { * @param IContextSource $context * @return array */ - static function getThumbSizes( IContextSource $context ) { + public function getThumbSizes( IContextSource $context ) { $ret = []; $pixels = $context->msg( 'unit-pixel' )->text(); @@ -1318,15 +1355,15 @@ class Preferences { * @param HTMLForm $form * @return bool|string */ - static function validateSignature( $signature, $alldata, $form ) { + public function validateSignature( $signature, $alldata, $form ) { global $wgParser; $maxSigChars = $form->getConfig()->get( 'MaxSigChars' ); if ( mb_strlen( $signature ) > $maxSigChars ) { return Xml::element( 'span', [ 'class' => 'error' ], $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() ); } elseif ( isset( $alldata['fancysig'] ) && - $alldata['fancysig'] && - $wgParser->validateSig( $signature ) === false + $alldata['fancysig'] && + $wgParser->validateSig( $signature ) === false ) { return Xml::element( 'span', @@ -1344,7 +1381,7 @@ class Preferences { * @param HTMLForm $form * @return string */ - static function cleanSignature( $signature, $alldata, $form ) { + public function cleanSignature( $signature, $alldata, $form ) { if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) { global $wgParser; $signature = $wgParser->cleanSig( $signature ); @@ -1363,7 +1400,7 @@ class Preferences { * @param array $remove Array of items to remove * @return PreferencesForm|HTMLForm */ - static function getFormObject( + public function getFormObject( $user, IContextSource $context, $formClass = 'PreferencesForm', @@ -1372,7 +1409,7 @@ class Preferences { // We use ButtonWidgets in some of the getPreferences() functions $context->getOutput()->enableOOUI(); - $formDescriptor = self::getPreferences( $user, $context ); + $formDescriptor = $this->getPreferences( $user, $context ); if ( count( $remove ) ) { $removeKeys = array_flip( $remove ); $formDescriptor = array_diff_key( $formDescriptor, $removeKeys ); @@ -1397,7 +1434,7 @@ class Preferences { # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save' $htmlForm->setSubmitTooltip( 'preferences-save' ); $htmlForm->setSubmitID( 'prefcontrol' ); - $htmlForm->setSubmitCallback( [ 'Preferences', 'tryFormSubmit' ] ); + $htmlForm->setSubmitCallback( [ $this, 'tryFormSubmit' ] ); return $htmlForm; } @@ -1406,7 +1443,7 @@ class Preferences { * @param IContextSource $context * @return array */ - static function getTimezoneOptions( IContextSource $context ) { + public function getTimezoneOptions( IContextSource $context ) { $opt = []; $localTZoffset = $context->getConfig()->get( 'LocalTZoffset' ); @@ -1451,7 +1488,7 @@ class Preferences { * @param array $alldata * @return int */ - static function filterIntval( $value, $alldata ) { + public static function filterIntval( $value, $alldata ) { return intval( $value ); } @@ -1460,7 +1497,7 @@ class Preferences { * @param array $alldata * @return string */ - static function filterTimezoneInput( $tz, $alldata ) { + public static function filterTimezoneInput( $tz, $alldata ) { $data = explode( '|', $tz, 3 ); switch ( $data[0] ) { case 'ZoneInfo': @@ -1512,7 +1549,7 @@ class Preferences { * @param PreferencesForm $form * @return bool|Status|string */ - static function tryFormSubmit( $formData, $form ) { + public function tryFormSubmit( $formData, $form ) { $user = $form->getModifiedUser(); $hiddenPrefs = $form->getConfig()->get( 'HiddenPrefs' ); $result = true; @@ -1523,9 +1560,9 @@ class Preferences { // Filter input foreach ( array_keys( $formData ) as $name ) { - if ( isset( self::$saveFilters[$name] ) ) { - $formData[$name] = - call_user_func( self::$saveFilters[$name], $formData[$name], $formData ); + $filters = $this->getSaveFilters(); + if ( isset( $filters[$name] ) ) { + $formData[$name] = call_user_func( $filters[$name], $formData[$name], $formData ); } } @@ -1543,7 +1580,7 @@ class Preferences { if ( $user->isAllowed( 'editmyoptions' ) ) { $oldUserOptions = $user->getOptions(); - foreach ( self::$saveBlacklist as $b ) { + foreach ( $this->getSaveBlacklist() as $b ) { unset( $formData[$b] ); } @@ -1569,7 +1606,7 @@ class Preferences { ); } - MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] ); + AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] ); $user->saveSettings(); return $result; @@ -1580,8 +1617,8 @@ class Preferences { * @param PreferencesForm $form * @return Status */ - public static function tryUISubmit( $formData, $form ) { - $res = self::tryFormSubmit( $formData, $form ); + public function tryUISubmit( $formData, $form ) { + $res = $this->tryFormSubmit( $formData, $form ); if ( $res ) { $urlOptions = []; @@ -1612,7 +1649,7 @@ class Preferences { * preferences and the region * @since 1.26 */ - public static function getTimeZoneList( Language $language ) { + public function getTimeZoneList( Language $language ) { $identifiers = DateTimeZone::listIdentifiers(); if ( $identifiers === false ) { return [];