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 $stubThreshold = 0;
63 
67  private $titleFormatter;
68 
72  private $linkCache;
73 
77  private $nsInfo;
78 
80  private $hookContainer;
81 
83  private $hookRunner;
84 
89 
98  public function __construct(
104  ) {
105  $this->titleFormatter = $titleFormatter;
106  $this->linkCache = $linkCache;
107  $this->nsInfo = $nsInfo;
108  $this->specialPageFactory = $specialPageFactory;
109  $this->hookContainer = $hookContainer;
110  $this->hookRunner = new HookRunner( $hookContainer );
111  }
112 
116  public function setForceArticlePath( $force ) {
117  $this->forceArticlePath = $force;
118  }
119 
123  public function getForceArticlePath() {
125  }
126 
130  public function setExpandURLs( $expand ) {
131  $this->expandUrls = $expand;
132  }
133 
137  public function getExpandURLs() {
138  return $this->expandUrls;
139  }
140 
144  public function setStubThreshold( $threshold ) {
145  $this->stubThreshold = $threshold;
146  }
147 
151  public function getStubThreshold() {
152  return $this->stubThreshold;
153  }
154 
162  public function makeLink(
163  $target, $text = null, array $extraAttribs = [], array $query = []
164  ) {
165  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
166  if ( $this->castToTitle( $target )->isKnown() ) {
167  return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
168  } else {
169  return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
170  }
171  }
172 
173  private function runBeginHook( $target, &$text, &$extraAttribs, &$query, $isKnown ) {
174  $ret = null;
175  if ( !$this->hookRunner->onHtmlPageLinkRendererBegin(
176  $this, $this->castToTitle( $target ), $text, $extraAttribs, $query, $ret )
177  ) {
178  return $ret;
179  }
180  }
181 
193  public function makePreloadedLink(
194  $target, $text = null, $classes = '', array $extraAttribs = [], array $query = []
195  ) {
196  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
197 
198  // Run begin hook
199  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, true );
200  if ( $ret !== null ) {
201  return $ret;
202  }
203  $target = $this->normalizeTarget( $target );
204  $url = $this->getLinkURL( $target, $query );
205  $attribs = [ 'class' => $classes ];
206  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
207  if ( $prefixedText !== '' ) {
208  $attribs['title'] = $prefixedText;
209  }
210 
211  $attribs = [
212  'href' => $url,
213  ] + $this->mergeAttribs( $attribs, $extraAttribs );
214 
215  if ( $text === null ) {
216  $text = $this->getLinkText( $target );
217  }
218 
219  return $this->buildAElement( $target, $text, $attribs, true );
220  }
221 
229  public function makeKnownLink(
230  $target, $text = null, array $extraAttribs = [], array $query = []
231  ) {
232  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
233  if ( $target instanceof LinkTarget ) {
234  $isExternal = $target->isExternal();
235  } else {
236  // $target instanceof PageReference
237  // treat all PageReferences as local for now
238  $isExternal = false;
239  }
240  $classes = [];
241  if ( $isExternal ) {
242  $classes[] = 'extiw';
243  }
244  $colour = $this->getLinkClasses( $target );
245  if ( $colour !== '' ) {
246  $classes[] = $colour;
247  }
248 
249  return $this->makePreloadedLink(
250  $target,
251  $text,
252  implode( ' ', $classes ),
253  $extraAttribs,
254  $query
255  );
256  }
257 
266  public function makeBrokenLink(
267  $target, $text = null, array $extraAttribs = [], array $query = []
268  ) {
269  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
270  // Run legacy hook
271  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, false );
272  if ( $ret !== null ) {
273  return $ret;
274  }
275 
276  if ( $target instanceof LinkTarget ) {
277  # We don't want to include fragments for broken links, because they
278  # generally make no sense.
279  if ( $target->hasFragment() ) {
280  $target = $target->createFragmentTarget( '' );
281  }
282  }
283  $target = $this->normalizeTarget( $target );
284 
285  if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
286  $query['action'] = 'edit';
287  $query['redlink'] = '1';
288  }
289 
290  $url = $this->getLinkURL( $target, $query );
291  $attribs = [ 'class' => 'new' ];
292  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
293  if ( $prefixedText !== '' ) {
294  // This ends up in parser cache!
295  $attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
296  ->inContentLanguage()
297  ->text();
298  }
299 
300  $attribs = [
301  'href' => $url,
302  ] + $this->mergeAttribs( $attribs, $extraAttribs );
303 
304  if ( $text === null ) {
305  $text = $this->getLinkText( $target );
306  }
307 
308  return $this->buildAElement( $target, $text, $attribs, false );
309  }
310 
320  private function buildAElement( $target, $text, array $attribs, $isKnown ) {
321  $ret = null;
322  if ( !$this->hookRunner->onHtmlPageLinkRendererEnd(
323  $this, $this->castToLinkTarget( $target ), $isKnown, $text, $attribs, $ret )
324  ) {
325  return $ret;
326  }
327 
328  return Html::rawElement( 'a', $attribs, HtmlArmor::getHtml( $text ) );
329  }
330 
335  private function getLinkText( $target ) {
336  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
337  // If the target is just a fragment, with no title, we return the fragment
338  // text. Otherwise, we return the title text itself.
339  if ( $prefixedText === '' && $target instanceof LinkTarget && $target->hasFragment() ) {
340  return $target->getFragment();
341  }
342 
343  return $prefixedText;
344  }
345 
351  private function getLinkURL( $target, $query = [] ) {
352  if ( $this->forceArticlePath ) {
353  $realQuery = $query;
354  $query = [];
355  } else {
356  $realQuery = [];
357  }
358  $url = $this->castToTitle( $target )->getLinkURL( $query, false, $this->expandUrls );
359 
360  if ( $this->forceArticlePath && $realQuery ) {
361  $url = wfAppendQuery( $url, $realQuery );
362  }
363 
364  return $url;
365  }
366 
375  public function normalizeTarget( $target ) {
376  $target = $this->castToLinkTarget( $target );
377  if ( $target->getNamespace() === NS_SPECIAL && !$target->isExternal() ) {
378  list( $name, $subpage ) = $this->specialPageFactory->resolveAlias(
379  $target->getDBkey()
380  );
381  if ( $name ) {
382  return new TitleValue(
383  NS_SPECIAL,
384  $this->specialPageFactory->getLocalNameFor( $name, $subpage ),
385  $target->getFragment()
386  );
387  }
388  }
389 
390  return $target;
391  }
392 
401  private function mergeAttribs( $defaults, $attribs ) {
402  if ( !$attribs ) {
403  return $defaults;
404  }
405  # Merge the custom attribs with the default ones, and iterate
406  # over that, deleting all "false" attributes.
407  $ret = [];
408  $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
409  foreach ( $merged as $key => $val ) {
410  # A false value suppresses the attribute
411  if ( $val !== false ) {
412  $ret[$key] = $val;
413  }
414  }
415  return $ret;
416  }
417 
424  public function getLinkClasses( $target ) {
425  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
426  $target = $this->castToLinkTarget( $target );
427  // Don't call LinkCache if the target is "non-proper"
428  if ( $target->isExternal() || $target->getText() === '' || $target->getNamespace() < 0 ) {
429  return '';
430  }
431  // Make sure the target is in the cache
432  $id = $this->linkCache->addLinkObj( $target );
433  if ( $id == 0 ) {
434  // Doesn't exist
435  return '';
436  }
437 
438  if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) {
439  # Page is a redirect
440  return 'mw-redirect';
441  } elseif (
442  $this->stubThreshold > 0 && $this->nsInfo->isContent( $target->getNamespace() ) &&
443  $this->linkCache->getGoodLinkFieldObj( $target, 'length' ) < $this->stubThreshold
444  ) {
445  # Page is a stub
446  return 'stub';
447  }
448 
449  return '';
450  }
451 
456  private function castToTitle( $target ): Title {
457  if ( $target instanceof LinkTarget ) {
458  return Title::newFromLinkTarget( $target );
459  }
460  // $target instanceof PageReference
461  return Title::castFromPageReference( $target );
462  }
463 
468  private function castToLinkTarget( $target ): LinkTarget {
469  if ( $target instanceof PageReference ) {
470  return Title::castFromPageReference( $target );
471  }
472  // $target instanceof LinkTarget
473  return $target;
474  }
475 }
MediaWiki\Linker\LinkRenderer\$titleFormatter
TitleFormatter $titleFormatter
Definition: LinkRenderer.php:67
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:83
MediaWiki\Linker\LinkRenderer\$stubThreshold
int $stubThreshold
Definition: LinkRenderer.php:62
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
MediaWiki\Linker\LinkRenderer\getLinkURL
getLinkURL( $target, $query=[])
Definition: LinkRenderer.php:351
MediaWiki\Linker\LinkRenderer\mergeAttribs
mergeAttribs( $defaults, $attribs)
Merges two sets of attributes.
Definition: LinkRenderer.php:401
MediaWiki\Linker\LinkRenderer\$linkCache
LinkCache $linkCache
Definition: LinkRenderer.php:72
MediaWiki\Linker\LinkRenderer\$nsInfo
NamespaceInfo $nsInfo
Definition: LinkRenderer.php:77
MediaWiki\Linker\LinkRenderer\__construct
__construct(TitleFormatter $titleFormatter, LinkCache $linkCache, NamespaceInfo $nsInfo, SpecialPageFactory $specialPageFactory, HookContainer $hookContainer)
Definition: LinkRenderer.php:98
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:541
MediaWiki\Linker\LinkRenderer\setStubThreshold
setStubThreshold( $threshold)
Definition: LinkRenderer.php:144
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1182
MediaWiki\Linker\LinkRenderer\normalizeTarget
normalizeTarget( $target)
Normalizes the provided target.
Definition: LinkRenderer.php:375
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:123
MediaWiki\Linker\LinkRenderer\runBeginHook
runBeginHook( $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:173
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:456
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:137
MediaWiki\Linker\LinkRenderer\makeBrokenLink
makeBrokenLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:266
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:468
MediaWiki\Linker\LinkRenderer\setExpandURLs
setExpandURLs( $expand)
Definition: LinkRenderer.php:130
MediaWiki\Linker\LinkRenderer\buildAElement
buildAElement( $target, $text, array $attribs, $isKnown)
Builds the final element.
Definition: LinkRenderer.php:320
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:229
MediaWiki\Linker\LinkRenderer\setForceArticlePath
setForceArticlePath( $force)
Definition: LinkRenderer.php:116
MediaWiki\Linker\LinkRenderer\getLinkClasses
getLinkClasses( $target)
Return the CSS classes of a known link.
Definition: LinkRenderer.php:424
MediaWiki\Linker\LinkRenderer\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: LinkRenderer.php:88
MediaWiki\Linker\LinkRenderer\getLinkText
getLinkText( $target)
Definition: LinkRenderer.php:335
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:327
MediaWiki\Linker\LinkRenderer\$hookContainer
HookContainer $hookContainer
Definition: LinkRenderer.php:80
Title
Represents a title within MediaWiki.
Definition: Title.php:49
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:193
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:212
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:377
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:151
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:554
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Sanitizer
HTML sanitizer for MediaWiki.
Definition: Sanitizer.php:34
MediaWiki\Linker\LinkRenderer\makeLink
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:162
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