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;
31 use Sanitizer;
32 use Title;
33 use TitleFormatter;
34 use TitleValue;
35 use Wikimedia\Assert\Assert;
36 
43 class LinkRenderer {
44 
45  public const CONSTRUCTOR_OPTIONS = [
46  'renderForComment',
47  ];
48 
54  private $forceArticlePath = false;
55 
61  private $expandUrls = false;
62 
68  private $comment = false;
69 
73  private $titleFormatter;
74 
78  private $linkCache;
79 
81  private $hookRunner;
82 
87 
97  public function __construct(
101  HookContainer $hookContainer,
102  ServiceOptions $options
103  ) {
104  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
105  $this->comment = $options->get( 'renderForComment' );
106 
107  $this->titleFormatter = $titleFormatter;
108  $this->linkCache = $linkCache;
109  $this->specialPageFactory = $specialPageFactory;
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 
141  public function isForComment(): bool {
142  return $this->comment;
143  }
144 
149  public function setStubThreshold( $threshold ) {
150  wfDeprecated( __METHOD__, '1.37' );
151  }
152 
157  public function getStubThreshold() {
158  wfDeprecated( __METHOD__, '1.37' );
159  return 0;
160  }
161 
169  public function makeLink(
170  $target, $text = null, array $extraAttribs = [], array $query = []
171  ) {
172  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
173  if ( $this->castToTitle( $target )->isKnown() ) {
174  return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
175  } else {
176  return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
177  }
178  }
179 
180  private function runBeginHook( $target, &$text, &$extraAttribs, &$query, $isKnown ) {
181  $ret = null;
182  if ( !$this->hookRunner->onHtmlPageLinkRendererBegin(
183  $this, $this->castToTitle( $target ), $text, $extraAttribs, $query, $ret )
184  ) {
185  return $ret;
186  }
187  }
188 
200  public function makePreloadedLink(
201  $target, $text = null, $classes = '', array $extraAttribs = [], array $query = []
202  ) {
203  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
204 
205  // Run begin hook
206  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, true );
207  if ( $ret !== null ) {
208  return $ret;
209  }
210  $target = $this->normalizeTarget( $target );
211  $url = $this->getLinkURL( $target, $query );
212  $attribs = [ 'class' => $classes ];
213  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
214  if ( $prefixedText !== '' ) {
215  $attribs['title'] = $prefixedText;
216  }
217 
218  $attribs = [
219  'href' => $url,
220  ] + $this->mergeAttribs( $attribs, $extraAttribs );
221 
222  if ( $text === null ) {
223  $text = $this->getLinkText( $target );
224  }
225 
226  return $this->buildAElement( $target, $text, $attribs, true );
227  }
228 
236  public function makeKnownLink(
237  $target, $text = null, array $extraAttribs = [], array $query = []
238  ) {
239  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
240  if ( $target instanceof LinkTarget ) {
241  $isExternal = $target->isExternal();
242  } else {
243  // $target instanceof PageReference
244  // treat all PageReferences as local for now
245  $isExternal = false;
246  }
247  $classes = [];
248  if ( $isExternal ) {
249  $classes[] = 'extiw';
250  }
251  $colour = $this->getLinkClasses( $target );
252  if ( $colour !== '' ) {
253  $classes[] = $colour;
254  }
255 
256  return $this->makePreloadedLink(
257  $target,
258  $text,
259  implode( ' ', $classes ),
260  $extraAttribs,
261  $query
262  );
263  }
264 
273  public function makeBrokenLink(
274  $target, $text = null, array $extraAttribs = [], array $query = []
275  ) {
276  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
277  // Run legacy hook
278  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, false );
279  if ( $ret !== null ) {
280  return $ret;
281  }
282 
283  if ( $target instanceof LinkTarget ) {
284  # We don't want to include fragments for broken links, because they
285  # generally make no sense.
286  if ( $target->hasFragment() ) {
287  $target = $target->createFragmentTarget( '' );
288  }
289  }
290  $target = $this->normalizeTarget( $target );
291 
292  if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
293  $query['action'] = 'edit';
294  $query['redlink'] = '1';
295  }
296 
297  $url = $this->getLinkURL( $target, $query );
298  $attribs = [ 'class' => 'new' ];
299  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
300  if ( $prefixedText !== '' ) {
301  // This ends up in parser cache!
302  $attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
303  ->inContentLanguage()
304  ->text();
305  }
306 
307  $attribs = [
308  'href' => $url,
309  ] + $this->mergeAttribs( $attribs, $extraAttribs );
310 
311  if ( $text === null ) {
312  $text = $this->getLinkText( $target );
313  }
314 
315  return $this->buildAElement( $target, $text, $attribs, false );
316  }
317 
327  private function buildAElement( $target, $text, array $attribs, $isKnown ) {
328  $ret = null;
329  if ( !$this->hookRunner->onHtmlPageLinkRendererEnd(
330  $this, $this->castToLinkTarget( $target ), $isKnown, $text, $attribs, $ret )
331  ) {
332  return $ret;
333  }
334 
335  return Html::rawElement( 'a', $attribs, HtmlArmor::getHtml( $text ) );
336  }
337 
342  private function getLinkText( $target ) {
343  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
344  // If the target is just a fragment, with no title, we return the fragment
345  // text. Otherwise, we return the title text itself.
346  if ( $prefixedText === '' && $target instanceof LinkTarget && $target->hasFragment() ) {
347  return $target->getFragment();
348  }
349 
350  return $prefixedText;
351  }
352 
358  private function getLinkURL( $target, $query = [] ) {
359  if ( $this->forceArticlePath ) {
360  $realQuery = $query;
361  $query = [];
362  } else {
363  $realQuery = [];
364  }
365  $url = $this->castToTitle( $target )->getLinkURL( $query, false, $this->expandUrls );
366 
367  if ( $this->forceArticlePath && $realQuery ) {
368  $url = wfAppendQuery( $url, $realQuery );
369  }
370 
371  return $url;
372  }
373 
382  public function normalizeTarget( $target ) {
383  $target = $this->castToLinkTarget( $target );
384  if ( $target->getNamespace() === NS_SPECIAL && !$target->isExternal() ) {
385  list( $name, $subpage ) = $this->specialPageFactory->resolveAlias(
386  $target->getDBkey()
387  );
388  if ( $name ) {
389  return new TitleValue(
390  NS_SPECIAL,
391  $this->specialPageFactory->getLocalNameFor( $name, $subpage ),
392  $target->getFragment()
393  );
394  }
395  }
396 
397  return $target;
398  }
399 
408  private function mergeAttribs( $defaults, $attribs ) {
409  if ( !$attribs ) {
410  return $defaults;
411  }
412  # Merge the custom attribs with the default ones, and iterate
413  # over that, deleting all "false" attributes.
414  $ret = [];
415  $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
416  foreach ( $merged as $key => $val ) {
417  # A false value suppresses the attribute
418  if ( $val !== false ) {
419  $ret[$key] = $val;
420  }
421  }
422  return $ret;
423  }
424 
431  public function getLinkClasses( $target ) {
432  Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
433  $target = $this->castToLinkTarget( $target );
434  // Don't call LinkCache if the target is "non-proper"
435  if ( $target->isExternal() || $target->getText() === '' || $target->getNamespace() < 0 ) {
436  return '';
437  }
438  // Make sure the target is in the cache
439  $id = $this->linkCache->addLinkObj( $target );
440  if ( $id == 0 ) {
441  // Doesn't exist
442  return '';
443  }
444 
445  if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) {
446  # Page is a redirect
447  return 'mw-redirect';
448  }
449 
450  return '';
451  }
452 
457  private function castToTitle( $target ): Title {
458  if ( $target instanceof LinkTarget ) {
459  return Title::newFromLinkTarget( $target );
460  }
461  // $target instanceof PageReference
462  return Title::castFromPageReference( $target );
463  }
464 
469  private function castToLinkTarget( $target ): LinkTarget {
470  if ( $target instanceof PageReference ) {
471  return Title::castFromPageReference( $target );
472  }
473  // $target instanceof LinkTarget
474  return $target;
475  }
476 }
MediaWiki\Linker\LinkRenderer\$titleFormatter
TitleFormatter $titleFormatter
Definition: LinkRenderer.php:73
LinkCache
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition: LinkCache.php:41
MediaWiki\Linker\LinkRenderer\$hookRunner
HookRunner $hookRunner
Definition: LinkRenderer.php:81
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
MediaWiki\Linker\LinkRenderer\getLinkURL
getLinkURL( $target, $query=[])
Definition: LinkRenderer.php:358
MediaWiki\Linker\LinkRenderer\mergeAttribs
mergeAttribs( $defaults, $attribs)
Merges two sets of attributes.
Definition: LinkRenderer.php:408
MediaWiki\Linker\LinkRenderer\$linkCache
LinkCache $linkCache
Definition: LinkRenderer.php:78
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:149
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1167
MediaWiki\Linker\LinkRenderer\normalizeTarget
normalizeTarget( $target)
Normalizes the provided target.
Definition: LinkRenderer.php:382
MediaWiki\Linker\LinkRenderer\$expandUrls
string bool int $expandUrls
A PROTO_* constant or false.
Definition: LinkRenderer.php:61
MediaWiki\Linker\LinkRenderer\getForceArticlePath
getForceArticlePath()
Definition: LinkRenderer.php:123
MediaWiki\Linker\LinkRenderer\runBeginHook
runBeginHook( $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:180
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\__construct
__construct(TitleFormatter $titleFormatter, LinkCache $linkCache, SpecialPageFactory $specialPageFactory, HookContainer $hookContainer, ServiceOptions $options)
Definition: LinkRenderer.php:97
MediaWiki\Linker\LinkRenderer\castToTitle
castToTitle( $target)
Definition: LinkRenderer.php:457
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\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: LinkRenderer.php:45
MediaWiki\Linker\LinkRenderer\isForComment
isForComment()
Definition: LinkRenderer.php:141
MediaWiki\Linker\LinkRenderer\makeBrokenLink
makeBrokenLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:273
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
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:54
MediaWiki\Linker\LinkRenderer\castToLinkTarget
castToLinkTarget( $target)
Definition: LinkRenderer.php:469
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:327
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:236
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:431
MediaWiki\Linker\LinkRenderer\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: LinkRenderer.php:86
MediaWiki\Linker\LinkRenderer\getLinkText
getLinkText( $target)
Definition: LinkRenderer.php:342
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:289
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:200
MediaWiki\Linker\LinkRenderer\$comment
bool $comment
Whether links are being rendered for comments.
Definition: LinkRenderer.php:68
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:213
MediaWiki\Config\ServiceOptions\get
get( $key)
Definition: ServiceOptions.php:93
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:339
MediaWiki\Linker\LinkRenderer\getStubThreshold
getStubThreshold()
Definition: LinkRenderer.php:157
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:169
MediaWiki\Linker\LinkTarget\hasFragment
hasFragment()
Whether the link target has a fragment.
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:71
Html
This class is a collection of static functions that serve two purposes:
Definition: Html.php:50
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40