MediaWiki  master
LinkRenderer.php
Go to the documentation of this file.
1 <?php
21 namespace MediaWiki\Linker;
22 
23 use Html;
24 use HtmlArmor;
25 use LinkCache;
30 use NamespaceInfo;
31 use Sanitizer;
32 use Title;
33 use TitleFormatter;
34 use TitleValue;
35 use Wikimedia\Assert\Assert;
36 
43 class LinkRenderer {
44 
50  private $forceArticlePath = false;
51 
57  private $expandUrls = false;
58 
62  private $titleFormatter;
63 
67  private $linkCache;
68 
72  private $nsInfo;
73 
75  private $hookContainer;
76 
78  private $hookRunner;
79 
84 
93  public function __construct(
99  ) {
100  $this->titleFormatter = $titleFormatter;
101  $this->linkCache = $linkCache;
102  $this->nsInfo = $nsInfo;
103  $this->specialPageFactory = $specialPageFactory;
104  $this->hookContainer = $hookContainer;
105  $this->hookRunner = new HookRunner( $hookContainer );
106  }
107 
111  public function setForceArticlePath( $force ) {
112  $this->forceArticlePath = $force;
113  }
114 
118  public function getForceArticlePath() {
120  }
121 
125  public function setExpandURLs( $expand ) {
126  $this->expandUrls = $expand;
127  }
128 
132  public function getExpandURLs() {
133  return $this->expandUrls;
134  }
135 
140  public function setStubThreshold( $threshold ) {
141  wfDeprecated( __METHOD__, '1.37' );
142  }
143 
148  public function getStubThreshold() {
149  wfDeprecated( __METHOD__, '1.37' );
150  return 0;
151  }
152 
160  public function makeLink(
161  $target, $text = null, array $extraAttribs = [], array $query = []
162  ) {
163  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
164  if ( $this->castToTitle( $target )->isKnown() ) {
165  return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
166  } else {
167  return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
168  }
169  }
170 
171  private function runBeginHook( $target, &$text, &$extraAttribs, &$query, $isKnown ) {
172  $ret = null;
173  if ( !$this->hookRunner->onHtmlPageLinkRendererBegin(
174  $this, $this->castToTitle( $target ), $text, $extraAttribs, $query, $ret )
175  ) {
176  return $ret;
177  }
178  }
179 
191  public function makePreloadedLink(
192  $target, $text = null, $classes = '', array $extraAttribs = [], array $query = []
193  ) {
194  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
195 
196  // Run begin hook
197  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, true );
198  if ( $ret !== null ) {
199  return $ret;
200  }
201  $target = $this->normalizeTarget( $target );
202  $url = $this->getLinkURL( $target, $query );
203  $attribs = [ 'class' => $classes ];
204  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
205  if ( $prefixedText !== '' ) {
206  $attribs['title'] = $prefixedText;
207  }
208 
209  $attribs = [
210  'href' => $url,
211  ] + $this->mergeAttribs( $attribs, $extraAttribs );
212 
213  if ( $text === null ) {
214  $text = $this->getLinkText( $target );
215  }
216 
217  return $this->buildAElement( $target, $text, $attribs, true );
218  }
219 
227  public function makeKnownLink(
228  $target, $text = null, array $extraAttribs = [], array $query = []
229  ) {
230  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
231  if ( $target instanceof LinkTarget ) {
232  $isExternal = $target->isExternal();
233  } else {
234  // $target instanceof PageReference
235  // treat all PageReferences as local for now
236  $isExternal = false;
237  }
238  $classes = [];
239  if ( $isExternal ) {
240  $classes[] = 'extiw';
241  }
242  $colour = $this->getLinkClasses( $target );
243  if ( $colour !== '' ) {
244  $classes[] = $colour;
245  }
246 
247  return $this->makePreloadedLink(
248  $target,
249  $text,
250  implode( ' ', $classes ),
251  $extraAttribs,
252  $query
253  );
254  }
255 
264  public function makeBrokenLink(
265  $target, $text = null, array $extraAttribs = [], array $query = []
266  ) {
267  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
268  // Run legacy hook
269  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, false );
270  if ( $ret !== null ) {
271  return $ret;
272  }
273 
274  if ( $target instanceof LinkTarget ) {
275  # We don't want to include fragments for broken links, because they
276  # generally make no sense.
277  if ( $target->hasFragment() ) {
278  $target = $target->createFragmentTarget( '' );
279  }
280  }
281  $target = $this->normalizeTarget( $target );
282 
283  if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
284  $query['action'] = 'edit';
285  $query['redlink'] = '1';
286  }
287 
288  $url = $this->getLinkURL( $target, $query );
289  $attribs = [ 'class' => 'new' ];
290  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
291  if ( $prefixedText !== '' ) {
292  // This ends up in parser cache!
293  $attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
294  ->inContentLanguage()
295  ->text();
296  }
297 
298  $attribs = [
299  'href' => $url,
300  ] + $this->mergeAttribs( $attribs, $extraAttribs );
301 
302  if ( $text === null ) {
303  $text = $this->getLinkText( $target );
304  }
305 
306  return $this->buildAElement( $target, $text, $attribs, false );
307  }
308 
318  private function buildAElement( $target, $text, array $attribs, $isKnown ) {
319  $ret = null;
320  if ( !$this->hookRunner->onHtmlPageLinkRendererEnd(
321  $this, $this->castToLinkTarget( $target ), $isKnown, $text, $attribs, $ret )
322  ) {
323  return $ret;
324  }
325 
326  return Html::rawElement( 'a', $attribs, HtmlArmor::getHtml( $text ) );
327  }
328 
333  private function getLinkText( $target ) {
334  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
335  // If the target is just a fragment, with no title, we return the fragment
336  // text. Otherwise, we return the title text itself.
337  if ( $prefixedText === '' && $target instanceof LinkTarget && $target->hasFragment() ) {
338  return $target->getFragment();
339  }
340 
341  return $prefixedText;
342  }
343 
349  private function getLinkURL( $target, $query = [] ) {
350  if ( $this->forceArticlePath ) {
351  $realQuery = $query;
352  $query = [];
353  } else {
354  $realQuery = [];
355  }
356  $url = $this->castToTitle( $target )->getLinkURL( $query, false, $this->expandUrls );
357 
358  if ( $this->forceArticlePath && $realQuery ) {
359  $url = wfAppendQuery( $url, $realQuery );
360  }
361 
362  return $url;
363  }
364 
373  public function normalizeTarget( $target ) {
374  $target = $this->castToLinkTarget( $target );
375  if ( $target->getNamespace() === NS_SPECIAL && !$target->isExternal() ) {
376  list( $name, $subpage ) = $this->specialPageFactory->resolveAlias(
377  $target->getDBkey()
378  );
379  if ( $name ) {
380  return new TitleValue(
381  NS_SPECIAL,
382  $this->specialPageFactory->getLocalNameFor( $name, $subpage ),
383  $target->getFragment()
384  );
385  }
386  }
387 
388  return $target;
389  }
390 
399  private function mergeAttribs( $defaults, $attribs ) {
400  if ( !$attribs ) {
401  return $defaults;
402  }
403  # Merge the custom attribs with the default ones, and iterate
404  # over that, deleting all "false" attributes.
405  $ret = [];
406  $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
407  foreach ( $merged as $key => $val ) {
408  # A false value suppresses the attribute
409  if ( $val !== false ) {
410  $ret[$key] = $val;
411  }
412  }
413  return $ret;
414  }
415 
422  public function getLinkClasses( $target ) {
423  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
424  $target = $this->castToLinkTarget( $target );
425  // Don't call LinkCache if the target is "non-proper"
426  if ( $target->isExternal() || $target->getText() === '' || $target->getNamespace() < 0 ) {
427  return '';
428  }
429  // Make sure the target is in the cache
430  $id = $this->linkCache->addLinkObj( $target );
431  if ( $id == 0 ) {
432  // Doesn't exist
433  return '';
434  }
435 
436  if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) {
437  # Page is a redirect
438  return 'mw-redirect';
439  }
440 
441  return '';
442  }
443 
448  private function castToTitle( $target ): Title {
449  if ( $target instanceof LinkTarget ) {
450  return Title::newFromLinkTarget( $target );
451  }
452  // $target instanceof PageReference
453  return Title::castFromPageReference( $target );
454  }
455 
460  private function castToLinkTarget( $target ): LinkTarget {
461  if ( $target instanceof PageReference ) {
462  return Title::castFromPageReference( $target );
463  }
464  // $target instanceof LinkTarget
465  return $target;
466  }
467 }
MediaWiki\Linker\LinkRenderer\$titleFormatter
TitleFormatter $titleFormatter
Definition: LinkRenderer.php:62
LinkCache
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition: LinkCache.php:40
MediaWiki\Linker\LinkRenderer\$hookRunner
HookRunner $hookRunner
Definition: LinkRenderer.php:78
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
MediaWiki\Linker\LinkRenderer\getLinkURL
getLinkURL( $target, $query=[])
Definition: LinkRenderer.php:349
MediaWiki\Linker\LinkRenderer\mergeAttribs
mergeAttribs( $defaults, $attribs)
Merges two sets of attributes.
Definition: LinkRenderer.php:399
MediaWiki\Linker\LinkRenderer\$linkCache
LinkCache $linkCache
Definition: LinkRenderer.php:67
MediaWiki\Linker\LinkRenderer\$nsInfo
NamespaceInfo $nsInfo
Definition: LinkRenderer.php:72
MediaWiki\Linker\LinkRenderer\__construct
__construct(TitleFormatter $titleFormatter, LinkCache $linkCache, NamespaceInfo $nsInfo, SpecialPageFactory $specialPageFactory, HookContainer $hookContainer)
Definition: LinkRenderer.php:93
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:43
MediaWiki\SpecialPage\SpecialPageFactory
Factory for handling the special page list and generating SpecialPage objects.
Definition: SpecialPageFactory.php:63
Sanitizer\mergeAttributes
static mergeAttributes( $a, $b)
Merge two sets of HTML attributes.
Definition: Sanitizer.php:542
MediaWiki\Linker\LinkRenderer\setStubThreshold
setStubThreshold( $threshold)
Definition: LinkRenderer.php:140
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1175
MediaWiki\Linker\LinkRenderer\normalizeTarget
normalizeTarget( $target)
Normalizes the provided target.
Definition: LinkRenderer.php:373
MediaWiki\Linker\LinkRenderer\$expandUrls
string bool int $expandUrls
A PROTO_* constant or false.
Definition: LinkRenderer.php:57
MediaWiki\Linker\LinkRenderer\getForceArticlePath
getForceArticlePath()
Definition: LinkRenderer.php:118
MediaWiki\Linker\LinkRenderer\runBeginHook
runBeginHook( $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:171
Page\PageReference
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Definition: PageReference.php:49
MediaWiki\Linker\LinkRenderer\castToTitle
castToTitle( $target)
Definition: LinkRenderer.php:448
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:53
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:422
MediaWiki\Linker\LinkRenderer\getExpandURLs
getExpandURLs()
Definition: LinkRenderer.php:132
MediaWiki\Linker\LinkRenderer\makeBrokenLink
makeBrokenLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:264
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:989
HtmlArmor\getHtml
static getHtml( $input)
Provide a string or HtmlArmor object and get safe HTML back.
Definition: HtmlArmor.php:54
MediaWiki\Linker
MediaWiki\Linker\LinkRenderer\$forceArticlePath
bool $forceArticlePath
Whether to force the pretty article path.
Definition: LinkRenderer.php:50
MediaWiki\Linker\LinkRenderer\castToLinkTarget
castToLinkTarget( $target)
Definition: LinkRenderer.php:460
MediaWiki\Linker\LinkRenderer\setExpandURLs
setExpandURLs( $expand)
Definition: LinkRenderer.php:125
MediaWiki\Linker\LinkRenderer\buildAElement
buildAElement( $target, $text, array $attribs, $isKnown)
Builds the final element.
Definition: LinkRenderer.php:318
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:227
MediaWiki\Linker\LinkRenderer\setForceArticlePath
setForceArticlePath( $force)
Definition: LinkRenderer.php:111
MediaWiki\Linker\LinkRenderer\getLinkClasses
getLinkClasses( $target)
Return the CSS classes of a known link.
Definition: LinkRenderer.php:422
MediaWiki\Linker\LinkRenderer\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: LinkRenderer.php:83
MediaWiki\Linker\LinkRenderer\getLinkText
getLinkText( $target)
Definition: LinkRenderer.php:333
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:294
MediaWiki\Linker\LinkRenderer\$hookContainer
HookContainer $hookContainer
Definition: LinkRenderer.php:75
Title
Represents a title within MediaWiki.
Definition: Title.php:47
MediaWiki\Linker\LinkRenderer\makePreloadedLink
makePreloadedLink( $target, $text=null, $classes='', array $extraAttribs=[], array $query=[])
If you have already looked up the proper CSS classes using LinkRenderer::getLinkClasses() or some oth...
Definition: LinkRenderer.php:191
TitleFormatter
A title formatter service for MediaWiki.
Definition: TitleFormatter.php:35
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
Title\castFromPageReference
static castFromPageReference(?PageReference $pageReference)
Return a Title for a given Reference.
Definition: Title.php:344
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:35
MediaWiki\Linker\LinkRenderer\getStubThreshold
getStubThreshold()
Definition: LinkRenderer.php:148
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:557
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Sanitizer
HTML sanitizer for MediaWiki.
Definition: Sanitizer.php:35
MediaWiki\Linker\LinkRenderer\makeLink
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:160
MediaWiki\Linker\LinkTarget\hasFragment
hasFragment()
Whether the link target has a fragment.
Html
This class is a collection of static functions that serve two purposes:
Definition: Html.php:49
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40