diff --git a/RELEASE-NOTES-1.37 b/RELEASE-NOTES-1.37 index 988781a83a7..3cda0433e01 100644 --- a/RELEASE-NOTES-1.37 +++ b/RELEASE-NOTES-1.37 @@ -122,6 +122,10 @@ because of Phabricator reports. ::setHookContainer() were soft deprecated. Use ::init() to inject dependencies or override ::postInitSetup() to do any custom post-initialization configuration. +* The following functions from the User class, deprecated in 1.35, now emit + deprecation warnings: + - getOptions + - isIP * … === Other changes in 1.37 === diff --git a/includes/EditPage.php b/includes/EditPage.php index 42ecacf3ebb..b79ecb38990 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -56,6 +56,7 @@ use MediaWiki\Revision\RevisionStore; use MediaWiki\Revision\RevisionStoreRecord; use MediaWiki\Revision\SlotRecord; use MediaWiki\User\UserIdentity; +use MediaWiki\User\UserNameUtils; use MediaWiki\Watchlist\WatchlistManager; use OOUI\CheckboxInputWidget; use OOUI\DropdownInputWidget; @@ -428,6 +429,11 @@ class EditPage implements IEditObject { */ private $watchlistManager; + /** + * @var UserNameUtils + */ + private $userNameUtils; + /** * @stable to call * @param Article $article @@ -461,6 +467,7 @@ class EditPage implements IEditObject { $this->watchedItemStore = $services->getWatchedItemStore(); $this->wikiPageFactory = $services->getWikiPageFactory(); $this->watchlistManager = $services->getWatchlistManager(); + $this->userNameUtils = $services->getUserNameUtils(); $this->deprecatePublicProperty( 'mBaseRevision', '1.35', __CLASS__ ); $this->deprecatePublicProperty( 'deletedSinceEdit', '1.35', __CLASS__ ); @@ -2747,7 +2754,7 @@ class EditPage implements IEditObject { if ( $namespace === NS_USER || $namespace === NS_USER_TALK ) { $username = explode( '/', $this->mTitle->getText(), 2 )[0]; $user = User::newFromName( $username, false /* allow IP users */ ); - $ip = User::isIP( $username ); + $ip = $this->userNameUtils->isIP( $username ); $block = DatabaseBlock::newFromTarget( $user, $user ); $userExists = ( $user && $user->isRegistered() ); diff --git a/includes/ParamValidator/TypeDef/UserDef.php b/includes/ParamValidator/TypeDef/UserDef.php index 2d2b0930a06..493c4fbad13 100644 --- a/includes/ParamValidator/TypeDef/UserDef.php +++ b/includes/ParamValidator/TypeDef/UserDef.php @@ -200,7 +200,7 @@ class UserDef extends TypeDef { // An IP? $b = IPUtils::RE_IP_BYTE; if ( IPUtils::isValid( $value ) || - // See comment for User::isIP. We don't just call that function + // See comment for UserNameUtils::isIP. We don't just call that function // here because it also returns true for things like // 300.300.300.300 that are neither valid usernames nor valid IP // addresses. diff --git a/includes/Storage/DerivedPageDataUpdater.php b/includes/Storage/DerivedPageDataUpdater.php index c1c9a4f0d6c..60e78836b1c 100644 --- a/includes/Storage/DerivedPageDataUpdater.php +++ b/includes/Storage/DerivedPageDataUpdater.php @@ -48,6 +48,7 @@ use MediaWiki\Revision\RevisionStore; use MediaWiki\Revision\SlotRecord; use MediaWiki\Revision\SlotRoleRegistry; use MediaWiki\User\UserIdentity; +use MediaWiki\User\UserNameUtils; use MessageCache; use MWTimestamp; use MWUnknownContentModelException; @@ -286,6 +287,9 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface { /** @var EditResultCache */ private $editResultCache; + /** @var UserNameUtils */ + private $userNameUtils; + /** * @param WikiPage $wikiPage , * @param RevisionStore $revisionStore @@ -299,6 +303,7 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface { * @param IContentHandlerFactory $contentHandlerFactory * @param HookContainer $hookContainer * @param EditResultCache $editResultCache + * @param UserNameUtils $userNameUtils */ public function __construct( WikiPage $wikiPage, @@ -312,7 +317,8 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface { ILBFactory $loadbalancerFactory, IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, - EditResultCache $editResultCache + EditResultCache $editResultCache, + UserNameUtils $userNameUtils ) { $this->wikiPage = $wikiPage; @@ -329,6 +335,7 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface { $this->contentHandlerFactory = $contentHandlerFactory; $this->hookRunner = new HookRunner( $hookContainer ); $this->editResultCache = $editResultCache; + $this->userNameUtils = $userNameUtils; $this->logger = new NullLogger(); } @@ -1591,7 +1598,7 @@ class DerivedPageDataUpdater implements IDBAccessObject, LoggerAwareInterface { $revRecord = $this->revision; $talkPageNotificationManager = MediaWikiServices::getInstance() ->getTalkPageNotificationManager(); - if ( User::isIP( $shortTitle ) ) { + if ( $this->userNameUtils->isIP( $shortTitle ) ) { // An anonymous user $talkPageNotificationManager->setUserHasNewMessages( $recipient, $revRecord ); } elseif ( $recipient->isRegistered() ) { diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index d77259e270c..4b7cf27dd96 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -173,7 +173,12 @@ class ApiMain extends ApiBase { ] ], 'userrights' => ApiUserrights::class, - 'options' => ApiOptions::class, + 'options' => [ + 'class' => ApiOptions::class, + 'services' => [ + 'UserOptionsManager', + ], + ], 'imagerotate' => ApiImageRotate::class, 'revisiondelete' => ApiRevisionDelete::class, 'managetags' => ApiManageTags::class, diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php index 900945159a5..ae55bad578c 100644 --- a/includes/api/ApiOptions.php +++ b/includes/api/ApiOptions.php @@ -22,6 +22,7 @@ use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; +use MediaWiki\User\UserOptionsManager; /** * API module that facilitates the changing of user's preferences. @@ -33,6 +34,27 @@ class ApiOptions extends ApiBase { /** @var User User account to modify */ private $userForUpdates; + /** @var UserOptionsManager */ + private $userOptionsManager; + + /** + * @param ApiMain $main + * @param string $action + * @param UserOptionsManager|null $userOptionsManager + */ + public function __construct( + ApiMain $main, + $action, + UserOptionsManager $userOptionsManager = null + ) { + parent::__construct( $main, $action ); + /** + * This class is extended by GlobalPreferences extension. + * So it falls back to the global state. + */ + $this->userOptionsManager = $userOptionsManager ?? MediaWikiServices::getInstance()->getUserOptionsManager(); + } + /** * Changes preferences of the current user. */ @@ -99,7 +121,7 @@ class ApiOptions extends ApiBase { $htmlForm = new HTMLForm( [], $this ); } $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm ); - $validation = $field->validate( $value, $user->getOptions() ); + $validation = $field->validate( $value, $this->userOptionsManager->getOptions( $user ) ); } break; case 'registered-multiselect': diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index dad4cc6b66d..cd4e3fe4029 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -92,6 +92,7 @@ class ApiQuery extends ApiBase { 'services' => [ 'BlockRestrictionStore', 'CommentStore', + 'UserNameUtils', ], ], 'categorymembers' => ApiQueryCategoryMembers::class, @@ -142,6 +143,7 @@ class ApiQuery extends ApiBase { 'services' => [ 'CommentStore', 'UserIdentityLookup', + 'UserNameUtils', ], ], 'users' => ApiQueryUsers::class, @@ -171,7 +173,8 @@ class ApiQuery extends ApiBase { 'services' => [ 'TalkPageNotificationManager', 'WatchedItemStore', - 'UserEditTracker' + 'UserEditTracker', + 'UserOptionsLookup', ] ], 'filerepoinfo' => ApiQueryFileRepoInfo::class, diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index e5607916041..e4dc8140757 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -22,6 +22,7 @@ use MediaWiki\Block\BlockRestrictionStore; use MediaWiki\ParamValidator\TypeDef\UserDef; +use MediaWiki\User\UserNameUtils; use Wikimedia\IPUtils; use Wikimedia\Rdbms\IResultWrapper; @@ -38,21 +39,27 @@ class ApiQueryBlocks extends ApiQueryBase { /** @var CommentStore */ private $commentStore; + /** @var UserNameUtils */ + private $userNameUtils; + /** * @param ApiQuery $query * @param string $moduleName * @param BlockRestrictionStore $blockRestrictionStore * @param CommentStore $commentStore + * @param UserNameUtils $userNameUtils */ public function __construct( ApiQuery $query, $moduleName, BlockRestrictionStore $blockRestrictionStore, - CommentStore $commentStore + CommentStore $commentStore, + UserNameUtils $userNameUtils ) { parent::__construct( $query, $moduleName, 'bk' ); $this->blockRestrictionStore = $blockRestrictionStore; $this->commentStore = $commentStore; + $this->userNameUtils = $userNameUtils; } public function execute() { @@ -283,7 +290,7 @@ class ApiQueryBlocks extends ApiQueryBase { "baduser_{$encParamName}" ); } - $name = User::isIP( $user ) + $name = $this->userNameUtils->isIP( $user ) || IPUtils::isIPv6( $user ) ? $user : User::getCanonicalName( $user, 'valid' ); if ( $name === false ) { diff --git a/includes/api/ApiQueryUserContribs.php b/includes/api/ApiQueryUserContribs.php index 07a3ae624d0..faff0f8ae8d 100644 --- a/includes/api/ApiQueryUserContribs.php +++ b/includes/api/ApiQueryUserContribs.php @@ -26,6 +26,7 @@ use MediaWiki\Revision\RevisionRecord; use MediaWiki\Storage\NameTableAccessException; use MediaWiki\User\UserIdentity; use MediaWiki\User\UserIdentityLookup; +use MediaWiki\User\UserNameUtils; use Wikimedia\Rdbms\SelectQueryBuilder; /** @@ -41,21 +42,27 @@ class ApiQueryUserContribs extends ApiQueryBase { /** @var UserIdentityLookup */ private $userIdentityLookup; + /** @var UserNameUtils */ + private $userNameUtils; + /** * @param ApiQuery $query * @param string $moduleName * @param CommentStore $commentStore * @param UserIdentityLookup $userIdentityLookup + * @param UserNameUtils $userNameUtils */ public function __construct( ApiQuery $query, $moduleName, CommentStore $commentStore, - UserIdentityLookup $userIdentityLookup + UserIdentityLookup $userIdentityLookup, + UserNameUtils $userNameUtils ) { parent::__construct( $query, $moduleName, 'uc' ); $this->commentStore = $commentStore; $this->userIdentityLookup = $userIdentityLookup; + $this->userNameUtils = $userNameUtils; } private $params, $multiUserMode, $orderBy, $parentLens; @@ -187,7 +194,7 @@ class ApiQueryUserContribs extends ApiQueryBase { ); } - if ( User::isIP( $u ) || ExternalUserNames::isExternal( $u ) ) { + if ( $this->userNameUtils->isIP( $u ) || ExternalUserNames::isExternal( $u ) ) { $names[$u] = null; } else { $name = User::getCanonicalName( $u, 'valid' ); diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 476d7f04634..5da4e49e840 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -22,6 +22,7 @@ use MediaWiki\User\TalkPageNotificationManager; use MediaWiki\User\UserEditTracker; +use MediaWiki\User\UserOptionsLookup; /** * Query module to get information about the currently logged-in user @@ -55,17 +56,32 @@ class ApiQueryUserInfo extends ApiQueryBase { */ private $userEditTracker; + /** + * @var UserOptionsLookup + */ + private $userOptionsLookup; + + /** + * @param ApiQuery $query + * @param string $moduleName + * @param TalkPageNotificationManager $talkPageNotificationManager + * @param WatchedItemStore $watchedItemStore + * @param UserEditTracker $userEditTracker + * @param UserOptionsLookup $userOptionsLookup + */ public function __construct( ApiQuery $query, $moduleName, TalkPageNotificationManager $talkPageNotificationManager, WatchedItemStore $watchedItemStore, - UserEditTracker $userEditTracker + UserEditTracker $userEditTracker, + UserOptionsLookup $userOptionsLookup ) { parent::__construct( $query, $moduleName, 'ui' ); $this->talkPageNotificationManager = $talkPageNotificationManager; $this->watchedItemStore = $watchedItemStore; $this->userEditTracker = $userEditTracker; + $this->userOptionsLookup = $userOptionsLookup; } public function execute() { @@ -180,7 +196,7 @@ class ApiQueryUserInfo extends ApiQueryBase { } if ( isset( $this->prop['options'] ) ) { - $vals['options'] = $user->getOptions(); + $vals['options'] = $this->userOptionsLookup->getOptions( $user ); $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] ); } diff --git a/includes/page/Article.php b/includes/page/Article.php index ebb68c7e622..09c460a766a 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -29,6 +29,7 @@ use MediaWiki\Permissions\PermissionStatus; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Revision\RevisionStore; use MediaWiki\Revision\SlotRecord; +use MediaWiki\User\UserNameUtils; use MediaWiki\Watchlist\WatchlistManager; use Wikimedia\IPUtils; use Wikimedia\NonSerializable\NonSerializableTrait; @@ -104,6 +105,11 @@ class Article implements Page { */ private $watchlistManager; + /** + * @var UserNameUtils + */ + private $userNameUtils; + /** * @var RevisionRecord|null Revision to be shown * @@ -127,6 +133,7 @@ class Article implements Page { $this->linkRenderer = $services->getLinkRenderer(); $this->revisionStore = $services->getRevisionStore(); $this->watchlistManager = $services->getWatchlistManager(); + $this->userNameUtils = $services->getUserNameUtils(); } /** @@ -1367,7 +1374,7 @@ class Article implements Page { ) { $rootPart = explode( '/', $title->getText() )[0]; $user = User::newFromName( $rootPart, false /* allow IP users */ ); - $ip = User::isIP( $rootPart ); + $ip = $this->userNameUtils->isIP( $rootPart ); $block = DatabaseBlock::newFromTarget( $user, $user ); if ( $user && $user->isRegistered() && $user->isHidden() && diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index ed0f8b8d4b5..29e3c46ebe7 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -1857,7 +1857,8 @@ class WikiPage implements Page, IDBAccessObject, PageRecord { $services->getDBLoadBalancerFactory(), $this->getContentHandlerFactory(), $this->getHookContainer(), - $editResultCache + $editResultCache, + $services->getUserNameUtils() ); $derivedDataUpdater->setLogger( LoggerFactory::getInstance( 'SaveParse' ) ); diff --git a/includes/preferences/DefaultPreferencesFactory.php b/includes/preferences/DefaultPreferencesFactory.php index 33791d34fbe..2f26192b55a 100644 --- a/includes/preferences/DefaultPreferencesFactory.php +++ b/includes/preferences/DefaultPreferencesFactory.php @@ -230,7 +230,7 @@ class DefaultPreferencesFactory implements PreferencesFactory { $disable = !$this->permissionManager->userHasRight( $user, 'editmyoptions' ); $defaultOptions = $this->userOptionsLookup->getDefaultOptions(); - $userOptions = $user->getOptions(); + $userOptions = $this->userOptionsLookup->getOptions( $user ); $this->applyFilters( $userOptions, $defaultPreferences, 'filterForForm' ); // Add in defaults from the user foreach ( $defaultPreferences as $name => &$info ) { @@ -246,9 +246,9 @@ class DefaultPreferencesFactory implements PreferencesFactory { // Already set, no problem continue; } elseif ( $prefFromUser !== null && // Make sure we're not just pulling nothing - $field->validate( $prefFromUser, $user->getOptions() ) === true ) { + $field->validate( $prefFromUser, $this->userOptionsLookup->getOptions( $user ) ) === true ) { $info['default'] = $prefFromUser; - } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) { + } elseif ( $field->validate( $globalDefault, $this->userOptionsLookup->getOptions( $user ) ) === true ) { $info['default'] = $globalDefault; } else { $globalDefault = json_encode( $globalDefault ); @@ -1779,7 +1779,7 @@ class DefaultPreferencesFactory implements PreferencesFactory { } if ( $this->permissionManager->userHasRight( $user, 'editmyoptions' ) ) { - $oldUserOptions = $user->getOptions(); + $oldUserOptions = $this->userOptionsLookup->getOptions( $user ); foreach ( $this->getSaveBlacklist() as $b ) { unset( $formData[$b] ); diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php index 55a837caa1f..06496702324 100644 --- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php +++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php @@ -1,4 +1,8 @@ encodeJson( $tokens ) . ');'; - $options = $user->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS ); + $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); + $options = $userOptionsLookup->getOptions( $user, UserOptionsLookup::EXCLUDE_DEFAULTS ); // Optimisation: Only output this function call if the user has non-default settings. if ( $options ) { $script .= 'mw.user.options.set(' . $context->encodeJson( $options ) . ');'; diff --git a/includes/search/SearchNearMatcher.php b/includes/search/SearchNearMatcher.php index 4e60d052f48..146841ec4fe 100644 --- a/includes/search/SearchNearMatcher.php +++ b/includes/search/SearchNearMatcher.php @@ -4,6 +4,7 @@ use MediaWiki\HookContainer\HookContainer; use MediaWiki\HookContainer\HookRunner; use MediaWiki\MediaWikiServices; use MediaWiki\Page\WikiPageFactory; +use MediaWiki\User\UserNameUtils; /** * Implementation of near match title search. @@ -37,6 +38,11 @@ class SearchNearMatcher { */ private $wikiPageFactory; + /** + * @var UserNameUtils + */ + private $userNameUtils; + /** * SearchNearMatcher constructor. * @param Config $config @@ -51,6 +57,7 @@ class SearchNearMatcher { ->getLanguageConverter( $lang ); $this->wikiPageFactory = $services->getWikiPageFactory(); $this->hookRunner = new HookRunner( $hookContainer ); + $this->userNameUtils = $services->getUserNameUtils(); } /** @@ -168,8 +175,8 @@ class SearchNearMatcher { # Entering an IP address goes to the contributions page if ( $this->config->get( 'EnableSearchContributorsByIP' ) ) { - if ( ( $title->getNamespace() === NS_USER && User::isIP( $title->getText() ) ) - || User::isIP( trim( $searchterm ) ) ) { + if ( ( $title->getNamespace() === NS_USER && $this->userNameUtils->isIP( $title->getText() ) ) + || $this->userNameUtils->isIP( trim( $searchterm ) ) ) { return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() ); } } diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php index 54bcf0a6f61..b4b6071499b 100644 --- a/includes/skins/Skin.php +++ b/includes/skins/Skin.php @@ -439,7 +439,9 @@ abstract class Skin extends ContextSource { $title = $this->getRelevantTitle(); if ( $title->hasSubjectNamespace( NS_USER ) ) { $rootUser = $title->getRootText(); - if ( User::isIP( $rootUser ) ) { + $services = MediaWikiServices::getInstance(); + $userNameUtils = $services->getUserNameUtils(); + if ( $userNameUtils->isIP( $rootUser ) ) { $this->mRelevantUser = User::newFromName( $rootUser, false ); } else { $user = User::newFromName( $rootUser, false ); diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index d06c62f6ba9..fe86983af61 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -220,7 +220,7 @@ class SpecialContributions extends IncludableSpecialPage { # For IP ranges, we want the contributionsSub, but not the skin-dependent # links under 'Tools', which may include irrelevant links like 'Logs'. if ( !IPUtils::isValidRange( $target ) && - ( User::isIP( $target ) || $userObj->isRegistered() ) + ( $this->userNameUtils->isIP( $target ) || $userObj->isRegistered() ) ) { // Don't add non-existent users, because hidden users // that we add here will be removed later to pretend @@ -389,10 +389,12 @@ class SpecialContributions extends IncludableSpecialPage { if ( $isAnon ) { // Show a warning message that the user being searched for doesn't exist. - // User::isIP returns true for IP address and usemod IPs like '123.123.123.xxx', + // UserNameUtils::isIP returns true for IP address and usemod IPs like '123.123.123.xxx', // but returns false for IP ranges. We don't want to suggest either of these are // valid usernames which we would with the 'contributions-userdoesnotexist' message. - if ( !User::isIP( $userObj->getName() ) && !IPUtils::isValidRange( $userObj->getName() ) ) { + if ( !$this->userNameUtils->isIP( $userObj->getName() ) + && !IPUtils::isValidRange( $userObj->getName() ) + ) { $this->getOutput()->wrapWikiMsg( "
\n\$1\n
", [ diff --git a/includes/specials/SpecialListFiles.php b/includes/specials/SpecialListFiles.php index 9b436e6af16..296bc621739 100644 --- a/includes/specials/SpecialListFiles.php +++ b/includes/specials/SpecialListFiles.php @@ -106,7 +106,8 @@ class SpecialListFiles extends IncludableSpecialPage { $this->repoGroup, $this->loadBalancer, $this->commentStore, - $this->userCache + $this->userCache, + $this->userNameUtils ); $out = $this->getOutput(); diff --git a/includes/specials/pagers/ImageListPager.php b/includes/specials/pagers/ImageListPager.php index 7507954666b..4be4aceb507 100644 --- a/includes/specials/pagers/ImageListPager.php +++ b/includes/specials/pagers/ImageListPager.php @@ -20,6 +20,7 @@ */ use MediaWiki\Linker\LinkRenderer; +use MediaWiki\User\UserNameUtils; use Wikimedia\Rdbms\FakeResultWrapper; use Wikimedia\Rdbms\ILoadBalancer; use Wikimedia\Rdbms\IResultWrapper; @@ -80,6 +81,7 @@ class ImageListPager extends TablePager { * @param ILoadBalancer $loadBalancer * @param CommentStore $commentStore * @param UserCache $userCache + * @param UserNameUtils $userNameUtils */ public function __construct( IContextSource $context, @@ -91,7 +93,8 @@ class ImageListPager extends TablePager { RepoGroup $repoGroup, ILoadBalancer $loadBalancer, CommentStore $commentStore, - UserCache $userCache + UserCache $userCache, + UserNameUtils $userNameUtils ) { $this->setContext( $context ); @@ -109,7 +112,7 @@ class ImageListPager extends TablePager { if ( $user ) { $this->mUser = $user; } - if ( !$user || ( $user->isAnon() && !User::isIP( $user->getName() ) ) ) { + if ( !$user || ( $user->isAnon() && !$userNameUtils->isIP( $user->getName() ) ) ) { $this->outputUserDoesNotExist( $userName ); } } diff --git a/includes/user/User.php b/includes/user/User.php index a73b59bd8e8..08b9760d89b 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -282,7 +282,7 @@ class User implements Authority, IDBAccessObject, UserIdentity, UserEmailContact return $copy; } elseif ( $name === 'mOptions' ) { wfDeprecated( 'User::$mOptions', '1.35' ); - $options = $this->getOptions(); + $options = MediaWikiServices::getInstance()->getUserOptionsLookup()->getOptions( $this ); return $options; } elseif ( !property_exists( $this, $name ) ) { // T227688 - do not break $u->foo['bar'] = 1 @@ -974,12 +974,14 @@ class User implements Authority, IDBAccessObject, UserIdentity, UserEmailContact * addresses like this, if we allowed accounts like this to be created * new users could get the old edits of these anonymous users. * - * @deprecated since 1.35, use the UserNameUtils service + * @deprecated since 1.35, use the UserNameUtils service. + * Hard deprecated since 1.37. * Note that UserNameUtils::isIP does not accept IPv6 ranges, while this method does * @param string $name Name to match * @return bool */ public static function isIP( $name ) { + wfDeprecated( __METHOD__, '1.35' ); return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name ) || IPUtils::isIPv6( $name ); } @@ -2058,8 +2060,9 @@ class User implements Authority, IDBAccessObject, UserIdentity, UserEmailContact */ public function getId( $wikiId = self::LOCAL ) : int { $this->deprecateInvalidCrossWiki( $wikiId, '1.36' ); + $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils(); if ( $this->mId === null && $this->mName !== null && - ( self::isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) ) + ( $userNameUtils->isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) ) ) { // Special case, we know the user is anonymous // Note that "external" users are "local" (they have an actor ID that is relative to @@ -2652,9 +2655,11 @@ class User implements Authority, IDBAccessObject, UserIdentity, UserEmailContact * User::GETOPTIONS_EXCLUDE_DEFAULTS Exclude user options that are set * to the default value. (Since 1.25) * @return array - * @deprecated since 1.35 Use UserOptionsLookup::getOptions instead + * @deprecated since 1.35 Use UserOptionsLookup::getOptions instead. + * Hard deprecated since 1.37. */ public function getOptions( $flags = 0 ) { + wfDeprecated( __METHOD__, '1.35' ); return MediaWikiServices::getInstance() ->getUserOptionsLookup() ->getOptions( $this, $flags ); diff --git a/includes/user/UserOptionsManager.php b/includes/user/UserOptionsManager.php index bdff30811e2..eabc409aa8b 100644 --- a/includes/user/UserOptionsManager.php +++ b/includes/user/UserOptionsManager.php @@ -258,8 +258,8 @@ class UserOptionsManager extends UserOptionsLookup { * - '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 UserOptionsManager::setOptions. + * - 'special' - "preferences" that are not accessible via + * UserOptionsLookup::getOptions or UserOptionsManager::setOptions. * - 'unused' - preferences about which MediaWiki doesn't know anything. * These are usually legacy options, removed in newer versions. * diff --git a/maintenance/reassignEdits.php b/maintenance/reassignEdits.php index 40a69db3a69..ffe0e30d10b 100644 --- a/maintenance/reassignEdits.php +++ b/maintenance/reassignEdits.php @@ -169,7 +169,8 @@ class ReassignEdits extends Maintenance { * @return User */ private function initialiseUser( $username ) { - if ( User::isIP( $username ) ) { + $services = MediaWikiServices::getInstance(); + if ( $services->getUserNameUtils()->isIP( $username ) ) { $user = User::newFromName( $username, false ); $user->getActorId(); } else { diff --git a/maintenance/rollbackEdits.php b/maintenance/rollbackEdits.php index 87b0fab636c..10c6058332e 100644 --- a/maintenance/rollbackEdits.php +++ b/maintenance/rollbackEdits.php @@ -50,7 +50,8 @@ class RollbackEdits extends Maintenance { public function execute() { $user = $this->getOption( 'user' ); - $username = User::isIP( $user ) ? $user : User::getCanonicalName( $user ); + $services = MediaWikiServices::getInstance(); + $username = $services->getUserNameUtils()->isIP( $user ) ? $user : User::getCanonicalName( $user ); if ( !$username ) { $this->fatalError( 'Invalid username' ); } @@ -79,9 +80,8 @@ class RollbackEdits extends Maintenance { $doer = User::newSystemUser( 'Maintenance script', [ 'steal' => true ] ); - $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory(); - $rollbackPageFactory = MediaWikiServices::getInstance() - ->getRollbackPageFactory(); + $wikiPageFactory = $services->getWikiPageFactory(); + $rollbackPageFactory = $services->getRollbackPageFactory(); foreach ( $titles as $t ) { $page = $wikiPageFactory->newFromTitle( $t ); $this->output( 'Processing ' . $t->getPrefixedText() . '... ' ); diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index 8d50fba2906..2515cb32ccd 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -1,5 +1,6 @@ mUserMock->method( 'getInstanceForUpdate' )->willReturn( $this->mUserMock ); - // Needs to return something - $this->mUserMock->method( 'getOptions' ) - ->willReturn( [] ); - $this->mUserMock->method( 'isAllowedAny' )->willReturn( true ); // DefaultPreferencesFactory calls a ton of user methods, but we still want to list all of // them in case bugs are caused by unexpected things returning null that shouldn't. $this->mUserMock->expects( $this->never() )->method( $this->anythingBut( - 'getEffectiveGroups', 'getOptionKinds', 'getInstanceForUpdate', 'getOptions', 'getId', + 'getEffectiveGroups', 'getOptionKinds', 'getInstanceForUpdate', 'getId', 'isAnon', 'getRequest', 'isLoggedIn', 'getName', 'getGroupMemberships', 'getEditCount', 'getRegistration', 'isAllowed', 'getRealName', 'getOption', 'getStubThreshold', 'getBoolOption', 'getEmail', 'getDatePreference', 'useRCPatrol', 'useNPPatrol', @@ -65,7 +62,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase { // Empty session $this->mSession = []; - $this->mTested = new ApiOptions( $main, 'options' ); + $userOptionsManagerMock = $this->createNoOpMock( UserOptionsManager::class, [ 'getOptions' ] ); + // Needs to return something + $userOptionsManagerMock->method( 'getOptions' )->willReturn( [] ); + + $this->mTested = new ApiOptions( $main, 'options', $userOptionsManagerMock ); $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'GetPreferences' => [ diff --git a/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php b/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php index 0d07d15fedb..c5474c88e98 100644 --- a/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php +++ b/tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php @@ -3,6 +3,7 @@ use MediaWiki\MediaWikiServices; use MediaWiki\Permissions\PermissionManager; use MediaWiki\Preferences\DefaultPreferencesFactory; +use MediaWiki\User\UserOptionsLookup; use Wikimedia\TestingAccessWrapper; /** @@ -53,9 +54,14 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { * Get a basic PreferencesFactory for testing with. * @param PermissionManager $mockPM * @param Language $language + * @param UserOptionsLookup|null $userOptionsLookup * @return DefaultPreferencesFactory */ - protected function getPreferencesFactory( PermissionManager $mockPM, Language $language ) { + protected function getPreferencesFactory( + PermissionManager $mockPM, + Language $language, + UserOptionsLookup $userOptionsLookup = null + ) { $mockNsInfo = $this->createMock( NamespaceInfo::class ); $mockNsInfo->method( 'getValidNamespaces' )->willReturn( [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK @@ -76,7 +82,7 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { $services->getLanguageConverterFactory()->getLanguageConverter( $language ), $services->getLanguageNameUtils(), $services->getHookContainer(), - $services->getUserOptionsLookup() + $userOptionsLookup ?? $services->getUserOptionsLookup() ); } @@ -123,14 +129,15 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { ->willReturn( [] ); $userMock->method( 'getGroupMemberships' ) ->willReturn( [] ); - $userMock->method( 'getOptions' ) - ->willReturn( [ 'test' => 'yes' ] ); + $pm = $this->createMock( PermissionManager::class ); $pm->method( 'userHasRight' ) ->will( $this->returnValueMap( [ [ $userMock, 'editmyoptions', true ] ] ) ); - $prefs = $this->getPreferencesFactory( $pm, new Language() ) + + $userOptionsLookupMock = $this->createUserOptionsLookupMock( [ 'test' => 'yes' ], true ); + $prefs = $this->getPreferencesFactory( $pm, new Language(), $userOptionsLookupMock ) ->getFormDescriptor( $userMock, $this->context ); $this->assertArrayNotHasKey( 'showrollbackconfirmation', $prefs ); } @@ -146,15 +153,16 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { ->willReturn( [] ); $userMock->method( 'getGroupMemberships' ) ->willReturn( [] ); - $userMock->method( 'getOptions' ) - ->willReturn( [ 'test' => 'yes' ] ); + $pm = $this->createMock( PermissionManager::class ); $pm->method( 'userHasRight' ) ->will( $this->returnValueMap( [ [ $userMock, 'editmyoptions', true ], [ $userMock, 'rollback', true ] ] ) ); - $prefs = $this->getPreferencesFactory( $pm, new Language() ) + + $userOptionsLookupMock = $this->createUserOptionsLookupMock( [ 'test' => 'yes' ], true ); + $prefs = $this->getPreferencesFactory( $pm, new Language(), $userOptionsLookupMock ) ->getFormDescriptor( $userMock, $this->context ); $this->assertArrayHasKey( 'showrollbackconfirmation', $prefs ); $this->assertEquals( @@ -205,8 +213,6 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { $userMock = $this->getMockBuilder( User::class ) ->disableOriginalConstructor() ->getMock(); - $userMock->method( 'getOptions' ) - ->willReturn( $oldOptions ); $userMock->expects( $this->exactly( 2 ) ) ->method( 'setOption' ) @@ -234,6 +240,8 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { [ $userMock, 'editmyoptions', true ] ] ) ); + $userOptionsLookupMock = $this->createUserOptionsLookupMock( $oldOptions ); + $this->setTemporaryHook( 'PreferencesFormPreSave', function ( $formData, $form, $user, &$result, $oldUserOptions ) use ( $newOptions, $oldOptions, $userMock ) { @@ -250,7 +258,7 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { /** @var DefaultPreferencesFactory $factory */ $factory = TestingAccessWrapper::newFromObject( - $this->getPreferencesFactory( $pm, new Language() ) + $this->getPreferencesFactory( $pm, new Language(), $userOptionsLookupMock ) ); $factory->saveFormData( $newOptions, $form, [] ); } @@ -289,8 +297,6 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { ->willReturn( [] ); $userMock->method( 'getGroupMemberships' ) ->willReturn( [] ); - $userMock->method( 'getOptions' ) - ->willReturn( [ 'LanguageCode' => 'sr', 'variant' => 'sr' ] ); $pm = $this->createMock( PermissionManager::class ); $pm->method( 'userHasRight' ) @@ -300,7 +306,11 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { $language->method( 'getCode' ) ->willReturn( 'sr' ); - $prefs = $this->getPreferencesFactory( $pm, $language ) + $userOptionsLookupMock = $this->createUserOptionsLookupMock( + [ 'LanguageCode' => 'sr', 'variant' => 'sr' ], true + ); + + $prefs = $this->getPreferencesFactory( $pm, $language, $userOptionsLookupMock ) ->getFormDescriptor( $userMock, $this->context ); $this->assertArrayHasKey( 'default', $prefs['variant'] ); $this->assertEquals( 'sr', $prefs['variant']['default'] ); @@ -317,8 +327,6 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { ->willReturn( [ 'users' ] ); $userMock->method( 'getGroupMemberships' ) ->willReturn( [ 'users' ] ); - $userMock->method( 'getOptions' ) - ->willReturn( [] ); $pm = $this->createMock( PermissionManager::class ); $pm->method( 'userHasRight' ) @@ -328,7 +336,9 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { $language->method( 'getCode' ) ->willReturn( 'en' ); - $prefs = $this->getPreferencesFactory( $pm, $language ) + $userOptionsLookupMock = $this->createUserOptionsLookupMock( [], true ); + + $prefs = $this->getPreferencesFactory( $pm, $language, $userOptionsLookupMock ) ->getFormDescriptor( $userMock, $this->context ); $this->assertArrayHasKey( 'default', $prefs['usergroups'] ); $this->assertEquals( 'users', $prefs['usergroups']['default'] ); @@ -347,4 +357,22 @@ class DefaultPreferencesFactoryTest extends \MediaWikiIntegrationTestCase { 'SignatureValidation', ] ); } + + /** + * @param array $userOptions + * @param bool $defaultOptions + * @return UserOptionsLookup + */ + private function createUserOptionsLookupMock( array $userOptions, bool $defaultOptions = false ) { + $mock = $this->createMock( UserOptionsLookup::class ); + $mock->method( 'getOptions' )->willReturn( $userOptions ); + $services = $this->getServiceContainer(); + if ( $defaultOptions ) { + $defaults = $services->getMainConfig()->get( 'DefaultUserOptions' ); + $defaults['language'] = $services->getContentLanguage()->getCode(); + $defaults['skin'] = Skin::normalizeKey( $services->getMainConfig()->get( 'DefaultSkin' ) ); + $mock->method( 'getDefaultOptions' )->willReturn( $defaults ); + } + return $mock; + } } diff --git a/tests/phpunit/includes/specials/ImageListPagerTest.php b/tests/phpunit/includes/specials/ImageListPagerTest.php index 887a71b07c6..acaa1e9c0e1 100644 --- a/tests/phpunit/includes/specials/ImageListPagerTest.php +++ b/tests/phpunit/includes/specials/ImageListPagerTest.php @@ -27,7 +27,8 @@ class ImageListPagerTest extends MediaWikiIntegrationTestCase { $services->getRepoGroup(), $services->getDBLoadBalancer(), $services->getCommentStore(), - UserCache::singleton() + UserCache::singleton(), + $services->getUserNameUtils() ); $this->expectException( MWException::class ); $this->expectExceptionMessage( "invalid_field" ); diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php index 8814fe68bc6..c9a1582b576 100644 --- a/tests/phpunit/includes/specials/SpecialPreferencesTest.php +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -41,10 +41,6 @@ class SpecialPreferencesTest extends MediaWikiIntegrationTestCase { ] ) ); - # Needs to return something - $user->method( 'getOptions' ) - ->willReturn( [] ); - // isAnyAllowed used to return null from the mock, // thus revoke it's permissions. $this->overrideUserPermissions( $user, [] ); diff --git a/tests/phpunit/includes/user/UserTest.php b/tests/phpunit/includes/user/UserTest.php index cdd0c37d82b..b545c6c721f 100644 --- a/tests/phpunit/includes/user/UserTest.php +++ b/tests/phpunit/includes/user/UserTest.php @@ -297,6 +297,7 @@ class UserTest extends MediaWikiIntegrationTestCase { * @covers User::isIP */ public function testIsIP( $value, $result, $message ) { + $this->hideDeprecated( 'User::isIP' ); $this->assertSame( $result, $this->user->isIP( $value ), $message ); }