diff --git a/includes/resourceloader/DerivativeResourceLoaderContext.php b/includes/resourceloader/DerivativeResourceLoaderContext.php index 5525a2222d5..aac3d452532 100644 --- a/includes/resourceloader/DerivativeResourceLoaderContext.php +++ b/includes/resourceloader/DerivativeResourceLoaderContext.php @@ -20,6 +20,8 @@ */ use MediaWiki\MediaWikiServices; +use MediaWiki\User\UserFactory; +use MediaWiki\User\UserIdentity; /** * A mutable version of ResourceLoaderContext. @@ -48,6 +50,8 @@ class DerivativeResourceLoaderContext extends ResourceLoaderContext { protected $skin = self::INHERIT_VALUE; /** @var int|string|null */ protected $user = self::INHERIT_VALUE; + /** @var int|UserIdentity|null|false */ + protected $userIdentity = self::INHERIT_VALUE; /** @var int|User|null */ protected $userObj = self::INHERIT_VALUE; /** @var int */ @@ -129,6 +133,25 @@ class DerivativeResourceLoaderContext extends ResourceLoaderContext { return $this->user; } + public function getUserIdentity(): ?UserIdentity { + if ( $this->userIdentity === self::INHERIT_VALUE ) { + return $this->context->getUserIdentity(); + } + if ( $this->userIdentity === false ) { + $username = $this->getUser(); + if ( $username === null ) { + // Anonymous user + $this->userIdentity = null; + } else { + // Use provided username if valid + $this->userIdentity = MediaWikiServices::getInstance() + ->getUserFactory() + ->newFromName( $username, UserFactory::RIGOR_VALID ); + } + } + return $this->userIdentity; + } + public function getUserObj(): User { if ( $this->userObj === self::INHERIT_VALUE ) { return $this->context->getUserObj(); @@ -152,6 +175,7 @@ class DerivativeResourceLoaderContext extends ResourceLoaderContext { $this->hash = null; // Clear getUserObj cache $this->userObj = null; + $this->userIdentity = false; } public function getDebug(): int { diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php index 4144c5b6431..89d6e0683d5 100644 --- a/includes/resourceloader/ResourceLoaderContext.php +++ b/includes/resourceloader/ResourceLoaderContext.php @@ -23,6 +23,8 @@ use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; use MediaWiki\Page\PageReferenceValue; +use MediaWiki\User\UserFactory; +use MediaWiki\User\UserIdentity; use Psr\Log\LoggerInterface; /** @@ -81,6 +83,8 @@ class ResourceLoaderContext implements MessageLocalizer { protected $hash; /** @var User|null */ protected $userObj; + /** @var UserIdentity|null|false */ + protected $userIdentity = false; /** @var ResourceLoaderImage|false */ protected $imageObj; @@ -261,6 +265,31 @@ class ResourceLoaderContext implements MessageLocalizer { ->page( PageReferenceValue::localReference( NS_SPECIAL, 'Badtitle/ResourceLoaderContext' ) ); } + /** + * Get the possibly-cached UserIdentity object for the specified username + * + * This will be null on most requests, + * except for load.php requests that have a 'user' parameter set. + * + * @since 1.38 + * @return UserIdentity|null + */ + public function getUserIdentity(): ?UserIdentity { + if ( $this->userIdentity === false ) { + $username = $this->getUser(); + if ( $username === null ) { + // Anonymous user + $this->userIdentity = null; + } else { + // Use provided username if valid + $this->userIdentity = MediaWikiServices::getInstance() + ->getUserFactory() + ->newFromName( $username, UserFactory::RIGOR_VALID ); + } + } + return $this->userIdentity; + } + /** * Get the possibly-cached User object for the specified username * diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php index a51187909d1..393485dbff3 100644 --- a/includes/resourceloader/ResourceLoaderUserModule.php +++ b/includes/resourceloader/ResourceLoaderUserModule.php @@ -37,17 +37,18 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule { * @return array[] */ protected function getPages( ResourceLoaderContext $context ) { - $config = $this->getConfig(); - $user = $context->getUserObj(); - if ( !$user->isRegistered() ) { + $user = $context->getUserIdentity(); + if ( !$user || !$user->isRegistered() ) { return []; } - // Use localised/normalised variant to ensure $excludepage matches - $userPage = $user->getUserPage()->getPrefixedDBkey(); + $config = $this->getConfig(); $pages = []; if ( $config->get( 'AllowUserJs' ) ) { + $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter(); + // Use localised/normalised variant to ensure $excludepage matches + $userPage = $titleFormatter->getPrefixedDBkey( new TitleValue( NS_USER, $user->getName() ) ); $pages["$userPage/common.js"] = [ 'type' => 'script' ]; $pages["$userPage/" . $context->getSkin() . '.js'] = [ 'type' => 'script' ]; } diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php index 06496702324..65dff822b27 100644 --- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php +++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php @@ -53,6 +53,7 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule { $user = $context->getUserObj(); $tokens = [ + // Replacement is tricky - T287542 'patrolToken' => $user->getEditToken( 'patrol' ), 'watchToken' => $user->getEditToken( 'watch' ), 'csrfToken' => $user->getEditToken(), diff --git a/includes/resourceloader/ResourceLoaderUserStylesModule.php b/includes/resourceloader/ResourceLoaderUserStylesModule.php index 3abcd497202..3e4680e6f73 100644 --- a/includes/resourceloader/ResourceLoaderUserStylesModule.php +++ b/includes/resourceloader/ResourceLoaderUserStylesModule.php @@ -38,17 +38,18 @@ class ResourceLoaderUserStylesModule extends ResourceLoaderWikiModule { * @return array[] */ protected function getPages( ResourceLoaderContext $context ) { - $config = $this->getConfig(); - $user = $context->getUserObj(); - if ( !$user->isRegistered() ) { + $user = $context->getUserIdentity(); + if ( !$user || !$user->isRegistered() ) { return []; } - // Use localised/normalised variant to ensure $excludepage matches - $userPage = $user->getUserPage()->getPrefixedDBkey(); + $config = $this->getConfig(); $pages = []; if ( $config->get( 'AllowUserCss' ) ) { + $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter(); + // Use localised/normalised variant to ensure $excludepage matches + $userPage = $titleFormatter->getPrefixedDBkey( new TitleValue( NS_USER, $user->getName() ) ); $pages["$userPage/common.css"] = [ 'type' => 'style' ]; $pages["$userPage/" . $context->getSkin() . '.css'] = [ 'type' => 'style' ]; } diff --git a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php index 0c33e054cde..d99ae4700a7 100644 --- a/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php +++ b/tests/phpunit/includes/resourceloader/DerivativeResourceLoaderContextTest.php @@ -1,5 +1,7 @@ createMock( User::class ); + $userIdentity = $this->createMock( UserIdentity::class ); $parent = $this->createMock( ResourceLoaderContext::class ); $parent ->expects( $this->once() ) ->method( 'getUserObj' ) ->willReturn( $user ); + $parent + ->expects( $this->once() ) + ->method( 'getUserIdentity' ) + ->willReturn( $userIdentity ); $derived = new DerivativeResourceLoaderContext( $parent ); $this->assertSame( $derived->getUserObj(), $user, 'inherit from parent' ); + $this->assertSame( $derived->getUserIdentity(), $userIdentity, 'inherit from parent' ); $derived->setUser( null ); $this->assertNotSame( $derived->getUserObj(), $user, 'different' ); + $this->assertNull( $derived->getUserIdentity(), 'no user identity' ); } public function testChangeDebug() { diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php index dac55ae4459..c3ccce9b453 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderContextTest.php @@ -35,6 +35,7 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase { $this->assertEquals( 'qqx|fallback|0|||||||', $ctx->getHash() ); $this->assertSame( [], $ctx->getReqBase() ); $this->assertInstanceOf( User::class, $ctx->getUserObj() ); + $this->assertNull( $ctx->getUserIdentity() ); } public function testDummy() { @@ -134,12 +135,14 @@ class ResourceLoaderContextTest extends PHPUnit\Framework\TestCase { $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [] ) ); $this->assertSame( null, $ctx->getUser() ); $this->assertFalse( $ctx->getUserObj()->isRegistered() ); + $this->assertNull( $ctx->getUserIdentity() ); $ctx = new ResourceLoaderContext( $this->getResourceLoader(), new FauxRequest( [ 'user' => 'Example' ] ) ); $this->assertSame( 'Example', $ctx->getUser() ); $this->assertEquals( 'Example', $ctx->getUserObj()->getName() ); + $this->assertEquals( 'Example', $ctx->getUserIdentity()->getName() ); } public function testMsg() {