MediaWiki  master
LinkRenderer.php
Go to the documentation of this file.
1 <?php
21 namespace MediaWiki\Linker;
22 
23 use DummyLinker;
24 use Html;
25 use HtmlArmor;
26 use LinkCache;
31 use NamespaceInfo;
32 use Sanitizer;
33 use SpecialPage;
34 use Title;
35 use TitleFormatter;
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 
84  private $runLegacyBeginHook = true;
85 
87  private $hookContainer;
88 
90  private $hookRunner;
91 
96 
105  public function __construct(
111  ) {
112  $this->titleFormatter = $titleFormatter;
113  $this->linkCache = $linkCache;
114  $this->nsInfo = $nsInfo;
115  $this->specialPageFactory = $specialPageFactory;
116  $this->hookContainer = $hookContainer;
117  $this->hookRunner = new HookRunner( $hookContainer );
118  }
119 
123  public function setForceArticlePath( $force ) {
124  $this->forceArticlePath = $force;
125  }
126 
130  public function getForceArticlePath() {
132  }
133 
137  public function setExpandURLs( $expand ) {
138  $this->expandUrls = $expand;
139  }
140 
144  public function getExpandURLs() {
145  return $this->expandUrls;
146  }
147 
151  public function setStubThreshold( $threshold ) {
152  $this->stubThreshold = $threshold;
153  }
154 
158  public function getStubThreshold() {
159  return $this->stubThreshold;
160  }
161 
165  public function setRunLegacyBeginHook( $run ) {
166  $this->runLegacyBeginHook = $run;
167  }
168 
176  public function makeLink(
177  LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
178  ) {
179  $title = Title::newFromLinkTarget( $target );
180  if ( $title->isKnown() ) {
181  return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
182  } else {
183  return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
184  }
185  }
186 
193  private function getLegacyOptions( $isKnown ) {
194  $options = [ 'stubThreshold' => $this->stubThreshold ];
195  if ( $this->forceArticlePath ) {
196  $options[] = 'forcearticlepath';
197  }
198  if ( $this->expandUrls === PROTO_HTTP ) {
199  $options[] = 'http';
200  } elseif ( $this->expandUrls === PROTO_HTTPS ) {
201  $options[] = 'https';
202  }
203 
204  $options[] = $isKnown ? 'known' : 'broken';
205 
206  return $options;
207  }
208 
209  private function runBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown ) {
210  $ret = null;
211  if ( !$this->hookRunner->onHtmlPageLinkRendererBegin(
212  $this, $target, $text, $extraAttribs, $query, $ret )
213  ) {
214  return $ret;
215  }
216 
217  // Now run the legacy hook
218  return $this->runLegacyBeginHook( $target, $text, $extraAttribs, $query, $isKnown );
219  }
220 
221  private function runLegacyBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query,
222  $isKnown
223  ) {
224  if ( !$this->runLegacyBeginHook || !$this->hookContainer->isRegistered( 'LinkBegin' ) ) {
225  // Disabled, or nothing registered
226  return null;
227  }
228 
229  $realOptions = $options = $this->getLegacyOptions( $isKnown );
230  $ret = null;
231  $dummy = new DummyLinker();
232  $title = Title::newFromLinkTarget( $target );
233  if ( $text !== null ) {
234  $realHtml = $html = HtmlArmor::getHtml( $text );
235  } else {
236  $realHtml = $html = null;
237  }
238  if ( !$this->hookRunner->onLinkBegin(
239  $dummy, $title, $html, $extraAttribs, $query, $options, $ret )
240  ) {
241  return $ret;
242  }
243 
244  if ( $html !== null && $html !== $realHtml ) {
245  // &$html was modified, so re-armor it as $text
246  $text = new HtmlArmor( $html );
247  }
248 
249  // Check if they changed any of the options, hopefully not!
250  if ( $options !== $realOptions ) {
251  $factory = MediaWikiServices::getInstance()->getLinkRendererFactory();
252  // They did, so create a separate instance and have that take over the rest
253  $newRenderer = $factory->createFromLegacyOptions( $options );
254  // Don't recurse the hook...
255  $newRenderer->setRunLegacyBeginHook( false );
256  if ( in_array( 'known', $options, true ) ) {
257  return $newRenderer->makeKnownLink( $title, $text, $extraAttribs, $query );
258  } elseif ( in_array( 'broken', $options, true ) ) {
259  return $newRenderer->makeBrokenLink( $title, $text, $extraAttribs, $query );
260  } else {
261  return $newRenderer->makeLink( $title, $text, $extraAttribs, $query );
262  }
263  }
264 
265  return null;
266  }
267 
279  public function makePreloadedLink(
280  LinkTarget $target, $text = null, $classes = '', array $extraAttribs = [], array $query = []
281  ) {
282  // Run begin hook
283  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, true );
284  if ( $ret !== null ) {
285  return $ret;
286  }
287  $target = $this->normalizeTarget( $target );
288  $url = $this->getLinkURL( $target, $query );
289  $attribs = [ 'class' => $classes ];
290  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
291  if ( $prefixedText !== '' ) {
292  $attribs['title'] = $prefixedText;
293  }
294 
295  $attribs = [
296  'href' => $url,
297  ] + $this->mergeAttribs( $attribs, $extraAttribs );
298 
299  if ( $text === null ) {
300  $text = $this->getLinkText( $target );
301  }
302 
303  return $this->buildAElement( $target, $text, $attribs, true );
304  }
305 
313  public function makeKnownLink(
314  LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
315  ) {
316  $classes = [];
317  if ( $target->isExternal() ) {
318  $classes[] = 'extiw';
319  }
320  $colour = $this->getLinkClasses( $target );
321  if ( $colour !== '' ) {
322  $classes[] = $colour;
323  }
324 
325  return $this->makePreloadedLink(
326  $target,
327  $text,
328  implode( ' ', $classes ),
329  $extraAttribs,
330  $query
331  );
332  }
333 
341  public function makeBrokenLink(
342  LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
343  ) {
344  // Run legacy hook
345  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, false );
346  if ( $ret !== null ) {
347  return $ret;
348  }
349 
350  # We don't want to include fragments for broken links, because they
351  # generally make no sense.
352  if ( $target->hasFragment() ) {
353  $target = $target->createFragmentTarget( '' );
354  }
355  $target = $this->normalizeTarget( $target );
356 
357  if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
358  $query['action'] = 'edit';
359  $query['redlink'] = '1';
360  }
361 
362  $url = $this->getLinkURL( $target, $query );
363  $attribs = [ 'class' => 'new' ];
364  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
365  if ( $prefixedText !== '' ) {
366  // This ends up in parser cache!
367  $attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
368  ->inContentLanguage()
369  ->text();
370  }
371 
372  $attribs = [
373  'href' => $url,
374  ] + $this->mergeAttribs( $attribs, $extraAttribs );
375 
376  if ( $text === null ) {
377  $text = $this->getLinkText( $target );
378  }
379 
380  return $this->buildAElement( $target, $text, $attribs, false );
381  }
382 
392  private function buildAElement( LinkTarget $target, $text, array $attribs, $isKnown ) {
393  $ret = null;
394  if ( !$this->hookRunner->onHtmlPageLinkRendererEnd(
395  $this, $target, $isKnown, $text, $attribs, $ret )
396  ) {
397  return $ret;
398  }
399 
400  $html = HtmlArmor::getHtml( $text );
401 
402  // Run legacy hook
403  if ( $this->hookContainer->isRegistered( 'LinkEnd' ) ) {
404  $dummy = new DummyLinker();
405  $title = Title::newFromLinkTarget( $target );
406  $options = $this->getLegacyOptions( $isKnown );
407  if ( !$this->hookRunner->onLinkEnd(
408  $dummy, $title, $options, $html, $attribs, $ret )
409  ) {
410  return $ret;
411  }
412  }
413 
414  return Html::rawElement( 'a', $attribs, $html );
415  }
416 
421  private function getLinkText( LinkTarget $target ) {
422  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
423  // If the target is just a fragment, with no title, we return the fragment
424  // text. Otherwise, we return the title text itself.
425  if ( $prefixedText === '' && $target->hasFragment() ) {
426  return $target->getFragment();
427  }
428 
429  return $prefixedText;
430  }
431 
432  private function getLinkURL( LinkTarget $target, array $query = [] ) {
433  // TODO: Use a LinkTargetResolver service instead of Title
434  $title = Title::newFromLinkTarget( $target );
435  if ( $this->forceArticlePath ) {
436  $realQuery = $query;
437  $query = [];
438  } else {
439  $realQuery = [];
440  }
441  $url = $title->getLinkURL( $query, false, $this->expandUrls );
442 
443  if ( $this->forceArticlePath && $realQuery ) {
444  $url = wfAppendQuery( $url, $realQuery );
445  }
446 
447  return $url;
448  }
449 
458  public function normalizeTarget( LinkTarget $target ) {
459  if ( $target->getNamespace() == NS_SPECIAL && !$target->isExternal() ) {
460  list( $name, $subpage ) = $this->specialPageFactory->resolveAlias(
461  $target->getDBkey()
462  );
463  if ( $name ) {
464  return SpecialPage::getTitleValueFor( $name, $subpage, $target->getFragment() );
465  }
466  }
467 
468  return $target;
469  }
470 
479  private function mergeAttribs( $defaults, $attribs ) {
480  if ( !$attribs ) {
481  return $defaults;
482  }
483  # Merge the custom attribs with the default ones, and iterate
484  # over that, deleting all "false" attributes.
485  $ret = [];
486  $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
487  foreach ( $merged as $key => $val ) {
488  # A false value suppresses the attribute
489  if ( $val !== false ) {
490  $ret[$key] = $val;
491  }
492  }
493  return $ret;
494  }
495 
502  public function getLinkClasses( LinkTarget $target ) {
503  // Make sure the target is in the cache
504  $id = $this->linkCache->addLinkObj( $target );
505  if ( $id == 0 ) {
506  // Doesn't exist
507  return '';
508  }
509 
510  if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) {
511  # Page is a redirect
512  return 'mw-redirect';
513  } elseif (
514  $this->stubThreshold > 0 && $this->nsInfo->isContent( $target->getNamespace() ) &&
515  $this->linkCache->getGoodLinkFieldObj( $target, 'length' ) < $this->stubThreshold
516  ) {
517  # Page is a stub
518  return 'stub';
519  }
520 
521  return '';
522  }
523 }
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:34
MediaWiki\Linker\LinkRenderer\$hookRunner
HookRunner $hookRunner
Definition: LinkRenderer.php:90
MediaWiki\Linker\LinkRenderer\$stubThreshold
int $stubThreshold
Definition: LinkRenderer.php:62
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
MediaWiki\Linker\LinkRenderer\getLinkText
getLinkText(LinkTarget $target)
Definition: LinkRenderer.php:421
MediaWiki\Linker\LinkRenderer\mergeAttribs
mergeAttribs( $defaults, $attribs)
Merges two sets of attributes.
Definition: LinkRenderer.php:479
MediaWiki\Linker\LinkRenderer\normalizeTarget
normalizeTarget(LinkTarget $target)
Normalizes the provided target.
Definition: LinkRenderer.php:458
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:146
MediaWiki\Linker\LinkRenderer\$linkCache
LinkCache $linkCache
Definition: LinkRenderer.php:72
MediaWiki\Linker\LinkRenderer\getLegacyOptions
getLegacyOptions( $isKnown)
Get the options in the legacy format.
Definition: LinkRenderer.php:193
MediaWiki\Linker\LinkRenderer\setRunLegacyBeginHook
setRunLegacyBeginHook( $run)
Definition: LinkRenderer.php:165
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:105
MediaWiki\Linker\LinkTarget\createFragmentTarget
createFragmentTarget( $fragment)
Creates a new LinkTarget for a different fragment of the same page.
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:65
MediaWiki\Linker\LinkRenderer\buildAElement
buildAElement(LinkTarget $target, $text, array $attribs, $isKnown)
Builds the final element.
Definition: LinkRenderer.php:392
Sanitizer\mergeAttributes
static mergeAttributes( $a, $b)
Merge two sets of HTML attributes.
Definition: Sanitizer.php:794
MediaWiki\Linker\LinkRenderer\setStubThreshold
setStubThreshold( $threshold)
Definition: LinkRenderer.php:151
MediaWiki\Linker\LinkRenderer\getLinkClasses
getLinkClasses(LinkTarget $target)
Return the CSS classes of a known link.
Definition: LinkRenderer.php:502
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1198
MediaWiki\Linker\LinkRenderer\$expandUrls
string bool int $expandUrls
A PROTO_* constant or false.
Definition: LinkRenderer.php:57
MediaWiki\Linker\LinkTarget\isExternal
isExternal()
Whether this LinkTarget has an interwiki component.
MediaWiki\Linker\LinkRenderer\getForceArticlePath
getForceArticlePath()
Definition: LinkRenderer.php:130
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:439
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:177
MediaWiki\Linker\LinkRenderer\getExpandURLs
getExpandURLs()
Definition: LinkRenderer.php:144
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:58
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
SpecialPage\getTitleValueFor
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
Definition: SpecialPage.php:105
HtmlArmor\getHtml
static getHtml( $input)
Provide a string or HtmlArmor object and get safe HTML back.
Definition: HtmlArmor.php:50
MediaWiki\Linker
MediaWiki\Linker\LinkRenderer\$forceArticlePath
bool $forceArticlePath
Whether to force the pretty article path.
Definition: LinkRenderer.php:50
$title
$title
Definition: testCompression.php:38
MediaWiki\Linker\LinkRenderer\setExpandURLs
setExpandURLs( $expand)
Definition: LinkRenderer.php:137
MediaWiki\Linker\LinkRenderer\makePreloadedLink
makePreloadedLink(LinkTarget $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:279
PROTO_HTTPS
const PROTO_HTTPS
Definition: Defines.php:209
MediaWiki\Linker\LinkRenderer\setForceArticlePath
setForceArticlePath( $force)
Definition: LinkRenderer.php:123
MediaWiki\Linker\LinkRenderer\runBeginHook
runBeginHook(LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:209
MediaWiki\Linker\LinkRenderer\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: LinkRenderer.php:95
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:39
MediaWiki\Linker\LinkRenderer\$runLegacyBeginHook
bool $runLegacyBeginHook
Whether to run the legacy Linker hooks.
Definition: LinkRenderer.php:84
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
MediaWiki\Linker\LinkTarget\getFragment
getFragment()
Get the link fragment (i.e.
PROTO_HTTP
const PROTO_HTTP
Definition: Defines.php:208
MediaWiki\Linker\LinkRenderer\runLegacyBeginHook
runLegacyBeginHook(LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:221
DummyLinker
Definition: DummyLinker.php:8
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:284
MediaWiki\Linker\LinkRenderer\$hookContainer
HookContainer $hookContainer
Definition: LinkRenderer.php:87
Title
Represents a title within MediaWiki.
Definition: Title.php:42
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:313
TitleFormatter
A title formatter service for MediaWiki.
Definition: TitleFormatter.php:34
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:44
MediaWiki\Linker\LinkRenderer\getLinkURL
getLinkURL(LinkTarget $target, array $query=[])
Definition: LinkRenderer.php:432
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:158
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:23
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Sanitizer
HTML sanitizer for MediaWiki.
Definition: Sanitizer.php:33
MediaWiki\Linker\LinkRenderer\makeLink
makeLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:176
MediaWiki\Linker\LinkTarget\hasFragment
hasFragment()
Whether the link target has a fragment.
MediaWiki\Linker\LinkRenderer\makeBrokenLink
makeBrokenLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:341
Html
This class is a collection of static functions that serve two purposes:
Definition: Html.php:49