MediaWiki master
UserLinkRenderer.php
Go to the documentation of this file.
1<?php
2declare( strict_types=1 );
3
4namespace MediaWiki\Linker;
5
22use Wikimedia\IPUtils;
24
31
32 private HookRunner $hookRunner;
33 private TempUserConfig $tempUserConfig;
34 private SpecialPageFactory $specialPageFactory;
35 private LinkRenderer $linkRenderer;
36 private TempUserDetailsLookup $tempUserDetailsLookup;
37 private UserIdentityLookup $userIdentityLookup;
38
45 private MapCacheLRU $userLinkCache;
46
47 public function __construct(
48 HookContainer $hookContainer,
49 TempUserConfig $tempUserConfig,
50 SpecialPageFactory $specialPageFactory,
51 LinkRenderer $linkRenderer,
52 TempUserDetailsLookup $tempUserDetailsLookup,
53 UserIdentityLookup $userIdentityLookup
54 ) {
55 $this->hookRunner = new HookRunner( $hookContainer );
56 $this->tempUserConfig = $tempUserConfig;
57 $this->specialPageFactory = $specialPageFactory;
58 $this->linkRenderer = $linkRenderer;
59 $this->tempUserDetailsLookup = $tempUserDetailsLookup;
60 $this->userIdentityLookup = $userIdentityLookup;
61
62 // Set a large enough cache size to accommodate long pagers,
63 // such as Special:RecentChanges with a high limit.
64 $this->userLinkCache = new MapCacheLRU( 1_000 );
65 }
66
78 public function userLink(
79 UserIdentity $targetUser,
80 IContextSource $context,
81 ?string $altUserName = null,
82 array $attributes = []
83 ): string {
84 $outputPage = $context->getOutput();
85 $outputPage->addModuleStyles( [
86 'mediawiki.interface.helpers.styles',
87 'mediawiki.interface.helpers.linker.styles'
88 ] );
89
90 $userName = $targetUser->getName();
91
92 if ( $this->isFromExternalWiki( $targetUser->getWikiId() ) ) {
93 $html = $this->userLinkCache->getWithSetCallback(
94 $this->userLinkCache->makeKey(
95 $targetUser->getWikiId(),
96 $userName,
97 $altUserName ?? '',
98 implode( ' ', $attributes )
99 ),
100 fn () => $this->renderExternalUserLink(
101 $targetUser,
102 $context,
103 $altUserName,
104 $attributes
105 )
106 );
107 } else {
108 $html = $this->userLinkCache->getWithSetCallback(
109 $this->userLinkCache->makeKey(
110 $userName,
111 $altUserName ?? '',
112 implode( ' ', $attributes )
113 ),
114 fn () => $this->renderUserLink(
115 $targetUser,
116 $context,
117 $altUserName,
118 $attributes
119 )
120 );
121 }
122 $prefix = '';
123 $postfix = '';
124 $this->hookRunner->onUserLinkRendererUserLinkPostRender( $targetUser, $context, $html, $prefix, $postfix );
125 return $prefix . $html . $postfix;
126 }
127
138 private function renderExternalUserLink(
139 UserIdentity $targetUser,
140 MessageLocalizer $messageLocalizer,
141 ?string $altUserName = null,
142 array $attributes = []
143 ): string {
144 $userName = $targetUser->getName();
145 $params = $this->getUserLinkParameters( $targetUser, $messageLocalizer );
146 $attributes += $params[ 'extraAttr' ];
147 $classes = $params[ 'classes' ];
148 $postfix = $params[ 'postfix' ];
149
150 $link = $this->linkRenderer->makeExternalLink(
151 WikiMap::getForeignURL(
152 $targetUser->getWikiId(),
153 'User:' . strtr( $userName, ' ', '_' )
154 ),
155 new HtmlArmor(
156 Html::element( 'bdi', [], $altUserName ?? $userName ) . $postfix
157 ),
158 Title::makeTitle( NS_USER, $userName ),
159 '',
160 $attributes + [ 'class' => $classes ]
161 );
162
163 return $link;
164 }
165
177 private function renderUserLink(
178 UserIdentity $targetUser,
179 MessageLocalizer $messageLocalizer,
180 ?string $altUserName = null,
181 array $attributes = []
182 ): string {
183 $userName = $targetUser->getName();
184
185 $params = $this->getUserLinkParameters( $targetUser, $messageLocalizer );
186 $attributes += $params[ 'extraAttr' ];
187 $classes = $params[ 'classes' ];
188 $postfix = $params[ 'postfix' ];
189
190 if ( $this->tempUserConfig->isTempName( $userName ) ) {
191 $pageName = $this->specialPageFactory->getLocalNameFor( 'Contributions', $userName );
192 $page = new TitleValue( NS_SPECIAL, $pageName );
193 } elseif ( !$targetUser->isRegistered() ) {
194 $page = ExternalUserNames::getUserLinkTitle( $userName );
195
196 if ( ExternalUserNames::isExternal( $userName ) ) {
197 $classes[] = 'mw-extuserlink';
198 } elseif ( $altUserName === null ) {
199 $altUserName = IPUtils::prettifyIP( $userName );
200 }
201 $classes[] = 'mw-anonuserlink'; // Separate link class for anons (T45179)
202 } else {
203 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
204 }
205
206 // Wrap the output with <bdi> tags for directionality isolation
207 $linkText =
208 '<bdi>' . htmlspecialchars( $altUserName ?? $userName ) . '</bdi>'
209 . $postfix;
210
211 if ( isset( $attributes['class'] ) ) {
212 $classes[] = $attributes['class'];
213 }
214
215 $attributes['class'] = $classes;
216
217 if ( $page !== null ) {
218 return $this->linkRenderer->makeLink( $page, new HtmlArmor( $linkText ), $attributes );
219 }
220
221 return Html::rawElement( 'span', $attributes, $linkText );
222 }
223
229 private function getUserLinkParameters(
230 UserIdentity $targetUser,
231 MessageLocalizer $messageLocalizer
232 ) {
233 $attributes = [];
234 $classes = [ 'mw-userlink' ];
235 $userName = $targetUser->getName();
236 $isExpired = false;
237 $postfix = '';
238
239 if ( $this->tempUserConfig->isTempName( $userName ) ) {
240 $classes[] = 'mw-tempuserlink';
241 $attributes['data-mw-target'] = $userName;
242
243 if ( $this->isFromExternalWiki( $targetUser->getWikiId() ) ) {
244 // Check if the local wiki has an account with the same name and,
245 // if it does, check if it is expired. We can do this because
246 // temporary accounts expire on all wikis at the same time for a
247 // wiki farm.
248 $localIdentity = $this->userIdentityLookup->getUserIdentityByName(
249 $userName
250 );
251 } else {
252 // For local users, we can directly use $targetUser
253 $localIdentity = $targetUser;
254 }
255
256 if ( $localIdentity instanceof UserIdentity ) {
257 $isExpired = $this->tempUserDetailsLookup->isExpired(
258 $localIdentity
259 );
260 }
261 }
262
263 // Adjust the styling of expired temporary account links (T358469).
264 if ( $isExpired ) {
265 $classes[] = 'mw-tempuserlink-expired';
266
267 $description = $messageLocalizer->msg(
268 'tempuser-expired-link-tooltip'
269 )->text();
270
271 $postfix = Html::element(
272 'span',
273 [
274 'role' => 'presentation',
275 'class' => 'cdx-tooltip mw-tempuserlink-expired--tooltip',
276 ],
277 $description
278 );
279
280 $attributes['aria-description'] = $description;
281
282 // Hide default link title when rendering expired temporary account
283 // links to avoid conflicting with the tooltip.
284 $attributes['title'] = null;
285 }
286
287 return [
288 'classes' => $classes,
289 'extraAttr' => $attributes,
290 'postfix' => $postfix
291 ];
292 }
293
300 protected function isFromExternalWiki( $wikiId ): bool {
301 if ( $wikiId === WikiAwareEntity::LOCAL ) {
302 return false;
303 }
304
305 return !WikiMap::isCurrentWikiDbDomain( $wikiId );
306 }
307}
const NS_USER
Definition Defines.php:67
const NS_SPECIAL
Definition Defines.php:54
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:82
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Class that generates HTML for internal links.
Service class that renders HTML for user-related links.
isFromExternalWiki( $wikiId)
Checks whether a given wiki identifier belongs to an external wiki.
__construct(HookContainer $hookContainer, TempUserConfig $tempUserConfig, SpecialPageFactory $specialPageFactory, LinkRenderer $linkRenderer, TempUserDetailsLookup $tempUserDetailsLookup, UserIdentityLookup $userIdentityLookup)
userLink(UserIdentity $targetUser, IContextSource $context, ?string $altUserName=null, array $attributes=[])
Render a user page link (or user contributions for anonymous and temporary users).
Factory for handling the special page list and generating SpecialPage objects.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:78
Class to parse and build external user names.
Caching lookup service for metadata related to temporary accounts, such as expiration.
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:33
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:32
Store key-value entries in a size-limited in-memory LRU cache.
Interface for objects which can provide a MediaWiki context on request.
Marker interface for entities aware of the wiki they belong to.
getWikiId()
Get the ID of the wiki this page belongs to.
Interface for temporary user creation config and name matching.
Service for looking up UserIdentity.
Interface for objects representing user identity.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
element(SerializerNode $parent, SerializerNode $node, $contents)
array $params
The job parameters.