MediaWiki  master
LinkRenderer.php
Go to the documentation of this file.
1 <?php
21 namespace MediaWiki\Linker;
22 
23 use DummyLinker;
24 use Hooks;
25 use Html;
26 use HtmlArmor;
27 use LinkCache;
28 use Linker;
30 use NamespaceInfo;
31 use Sanitizer;
32 use Title;
33 use TitleFormatter;
34 
41 class LinkRenderer {
42 
48  private $forceArticlePath = false;
49 
55  private $expandUrls = false;
56 
60  private $stubThreshold = 0;
61 
65  private $titleFormatter;
66 
70  private $linkCache;
71 
75  private $nsInfo;
76 
82  private $runLegacyBeginHook = true;
83 
89  public function __construct(
91  ) {
92  $this->titleFormatter = $titleFormatter;
93  $this->linkCache = $linkCache;
94  $this->nsInfo = $nsInfo;
95  }
96 
100  public function setForceArticlePath( $force ) {
101  $this->forceArticlePath = $force;
102  }
103 
107  public function getForceArticlePath() {
109  }
110 
114  public function setExpandURLs( $expand ) {
115  $this->expandUrls = $expand;
116  }
117 
121  public function getExpandURLs() {
122  return $this->expandUrls;
123  }
124 
128  public function setStubThreshold( $threshold ) {
129  $this->stubThreshold = $threshold;
130  }
131 
135  public function getStubThreshold() {
136  return $this->stubThreshold;
137  }
138 
142  public function setRunLegacyBeginHook( $run ) {
143  $this->runLegacyBeginHook = $run;
144  }
145 
153  public function makeLink(
154  LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
155  ) {
156  $title = Title::newFromLinkTarget( $target );
157  if ( $title->isKnown() ) {
158  return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
159  } else {
160  return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
161  }
162  }
163 
170  private function getLegacyOptions( $isKnown ) {
171  $options = [ 'stubThreshold' => $this->stubThreshold ];
172  if ( $this->forceArticlePath ) {
173  $options[] = 'forcearticlepath';
174  }
175  if ( $this->expandUrls === PROTO_HTTP ) {
176  $options[] = 'http';
177  } elseif ( $this->expandUrls === PROTO_HTTPS ) {
178  $options[] = 'https';
179  }
180 
181  $options[] = $isKnown ? 'known' : 'broken';
182 
183  return $options;
184  }
185 
186  private function runBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown ) {
187  $ret = null;
188  if ( !Hooks::run( 'HtmlPageLinkRendererBegin',
189  [ $this, $target, &$text, &$extraAttribs, &$query, &$ret ] )
190  ) {
191  return $ret;
192  }
193 
194  // Now run the legacy hook
195  return $this->runLegacyBeginHook( $target, $text, $extraAttribs, $query, $isKnown );
196  }
197 
198  private function runLegacyBeginHook( LinkTarget $target, &$text, &$extraAttribs, &$query,
199  $isKnown
200  ) {
201  if ( !$this->runLegacyBeginHook || !Hooks::isRegistered( 'LinkBegin' ) ) {
202  // Disabled, or nothing registered
203  return null;
204  }
205 
206  $realOptions = $options = $this->getLegacyOptions( $isKnown );
207  $ret = null;
208  $dummy = new DummyLinker();
209  $title = Title::newFromLinkTarget( $target );
210  if ( $text !== null ) {
211  $realHtml = $html = HtmlArmor::getHtml( $text );
212  } else {
213  $realHtml = $html = null;
214  }
215  if ( !Hooks::run( 'LinkBegin',
216  [ $dummy, $title, &$html, &$extraAttribs, &$query, &$options, &$ret ], '1.28' )
217  ) {
218  return $ret;
219  }
220 
221  if ( $html !== null && $html !== $realHtml ) {
222  // &$html was modified, so re-armor it as $text
223  $text = new HtmlArmor( $html );
224  }
225 
226  // Check if they changed any of the options, hopefully not!
227  if ( $options !== $realOptions ) {
228  $factory = MediaWikiServices::getInstance()->getLinkRendererFactory();
229  // They did, so create a separate instance and have that take over the rest
230  $newRenderer = $factory->createFromLegacyOptions( $options );
231  // Don't recurse the hook...
232  $newRenderer->setRunLegacyBeginHook( false );
233  if ( in_array( 'known', $options, true ) ) {
234  return $newRenderer->makeKnownLink( $title, $text, $extraAttribs, $query );
235  } elseif ( in_array( 'broken', $options, true ) ) {
236  return $newRenderer->makeBrokenLink( $title, $text, $extraAttribs, $query );
237  } else {
238  return $newRenderer->makeLink( $title, $text, $extraAttribs, $query );
239  }
240  }
241 
242  return null;
243  }
244 
256  public function makePreloadedLink(
257  LinkTarget $target, $text = null, $classes = '', array $extraAttribs = [], array $query = []
258  ) {
259  // Run begin hook
260  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, true );
261  if ( $ret !== null ) {
262  return $ret;
263  }
264  $target = $this->normalizeTarget( $target );
265  $url = $this->getLinkURL( $target, $query );
266  $attribs = [ 'class' => $classes ];
267  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
268  if ( $prefixedText !== '' ) {
269  $attribs['title'] = $prefixedText;
270  }
271 
272  $attribs = [
273  'href' => $url,
274  ] + $this->mergeAttribs( $attribs, $extraAttribs );
275 
276  if ( $text === null ) {
277  $text = $this->getLinkText( $target );
278  }
279 
280  return $this->buildAElement( $target, $text, $attribs, true );
281  }
282 
290  public function makeKnownLink(
291  LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
292  ) {
293  $classes = [];
294  if ( $target->isExternal() ) {
295  $classes[] = 'extiw';
296  }
297  $colour = $this->getLinkClasses( $target );
298  if ( $colour !== '' ) {
299  $classes[] = $colour;
300  }
301 
302  return $this->makePreloadedLink(
303  $target,
304  $text,
305  implode( ' ', $classes ),
306  $extraAttribs,
307  $query
308  );
309  }
310 
318  public function makeBrokenLink(
319  LinkTarget $target, $text = null, array $extraAttribs = [], array $query = []
320  ) {
321  // Run legacy hook
322  $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query, false );
323  if ( $ret !== null ) {
324  return $ret;
325  }
326 
327  # We don't want to include fragments for broken links, because they
328  # generally make no sense.
329  if ( $target->hasFragment() ) {
330  $target = $target->createFragmentTarget( '' );
331  }
332  $target = $this->normalizeTarget( $target );
333 
334  if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
335  $query['action'] = 'edit';
336  $query['redlink'] = '1';
337  }
338 
339  $url = $this->getLinkURL( $target, $query );
340  $attribs = [ 'class' => 'new' ];
341  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
342  if ( $prefixedText !== '' ) {
343  // This ends up in parser cache!
344  $attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
345  ->inContentLanguage()
346  ->text();
347  }
348 
349  $attribs = [
350  'href' => $url,
351  ] + $this->mergeAttribs( $attribs, $extraAttribs );
352 
353  if ( $text === null ) {
354  $text = $this->getLinkText( $target );
355  }
356 
357  return $this->buildAElement( $target, $text, $attribs, false );
358  }
359 
369  private function buildAElement( LinkTarget $target, $text, array $attribs, $isKnown ) {
370  $ret = null;
371  if ( !Hooks::run( 'HtmlPageLinkRendererEnd',
372  [ $this, $target, $isKnown, &$text, &$attribs, &$ret ] )
373  ) {
374  return $ret;
375  }
376 
377  $html = HtmlArmor::getHtml( $text );
378 
379  // Run legacy hook
380  if ( Hooks::isRegistered( 'LinkEnd' ) ) {
381  $dummy = new DummyLinker();
382  $title = Title::newFromLinkTarget( $target );
383  $options = $this->getLegacyOptions( $isKnown );
384  if ( !Hooks::run( 'LinkEnd',
385  [ $dummy, $title, $options, &$html, &$attribs, &$ret ], '1.28' )
386  ) {
387  return $ret;
388  }
389  }
390 
391  return Html::rawElement( 'a', $attribs, $html );
392  }
393 
398  private function getLinkText( LinkTarget $target ) {
399  $prefixedText = $this->titleFormatter->getPrefixedText( $target );
400  // If the target is just a fragment, with no title, we return the fragment
401  // text. Otherwise, we return the title text itself.
402  if ( $prefixedText === '' && $target->hasFragment() ) {
403  return $target->getFragment();
404  }
405 
406  return $prefixedText;
407  }
408 
409  private function getLinkURL( LinkTarget $target, array $query = [] ) {
410  // TODO: Use a LinkTargetResolver service instead of Title
411  $title = Title::newFromLinkTarget( $target );
412  if ( $this->forceArticlePath ) {
413  $realQuery = $query;
414  $query = [];
415  } else {
416  $realQuery = [];
417  }
418  $url = $title->getLinkURL( $query, false, $this->expandUrls );
419 
420  if ( $this->forceArticlePath && $realQuery ) {
421  $url = wfAppendQuery( $url, $realQuery );
422  }
423 
424  return $url;
425  }
426 
434  private function normalizeTarget( LinkTarget $target ) {
435  return Linker::normaliseSpecialPage( $target );
436  }
437 
446  private function mergeAttribs( $defaults, $attribs ) {
447  if ( !$attribs ) {
448  return $defaults;
449  }
450  # Merge the custom attribs with the default ones, and iterate
451  # over that, deleting all "false" attributes.
452  $ret = [];
453  $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
454  foreach ( $merged as $key => $val ) {
455  # A false value suppresses the attribute
456  if ( $val !== false ) {
457  $ret[$key] = $val;
458  }
459  }
460  return $ret;
461  }
462 
469  public function getLinkClasses( LinkTarget $target ) {
470  // Make sure the target is in the cache
471  $id = $this->linkCache->addLinkObj( $target );
472  if ( $id == 0 ) {
473  // Doesn't exist
474  return '';
475  }
476 
477  if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) {
478  # Page is a redirect
479  return 'mw-redirect';
480  } elseif (
481  $this->stubThreshold > 0 && $this->nsInfo->isContent( $target->getNamespace() ) &&
482  $this->linkCache->getGoodLinkFieldObj( $target, 'length' ) < $this->stubThreshold
483  ) {
484  # Page is a stub
485  return 'stub';
486  }
487 
488  return '';
489  }
490 }
MediaWiki\Linker\LinkRenderer\$titleFormatter
TitleFormatter $titleFormatter
Definition: LinkRenderer.php:65
LinkCache
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition: LinkCache.php:34
MediaWiki\Linker\LinkRenderer\$stubThreshold
int $stubThreshold
Definition: LinkRenderer.php:60
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
Linker
Some internal bits split of from Skin.php.
Definition: Linker.php:36
MediaWiki\Linker\LinkRenderer\getLinkText
getLinkText(LinkTarget $target)
Definition: LinkRenderer.php:398
MediaWiki\Linker\LinkRenderer\mergeAttribs
mergeAttribs( $defaults, $attribs)
Merges two sets of attributes.
Definition: LinkRenderer.php:446
MediaWiki\Linker\LinkRenderer\normalizeTarget
normalizeTarget(LinkTarget $target)
Normalizes the provided target.
Definition: LinkRenderer.php:434
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
MediaWiki\Linker\LinkRenderer\$linkCache
LinkCache $linkCache
Definition: LinkRenderer.php:70
MediaWiki\Linker\LinkRenderer\getLegacyOptions
getLegacyOptions( $isKnown)
Get the options in the legacy format.
Definition: LinkRenderer.php:170
MediaWiki\Linker\LinkRenderer\setRunLegacyBeginHook
setRunLegacyBeginHook( $run)
Definition: LinkRenderer.php:142
MediaWiki\Linker\LinkRenderer\$nsInfo
NamespaceInfo $nsInfo
Definition: LinkRenderer.php:75
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:41
MediaWiki\Linker\LinkRenderer\buildAElement
buildAElement(LinkTarget $target, $text, array $attribs, $isKnown)
Builds the final element.
Definition: LinkRenderer.php:369
MediaWiki\Linker\LinkRenderer\setStubThreshold
setStubThreshold( $threshold)
Definition: LinkRenderer.php:128
MediaWiki\Linker\LinkRenderer\getLinkClasses
getLinkClasses(LinkTarget $target)
Return the CSS classes of a known link.
Definition: LinkRenderer.php:469
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1263
MediaWiki\Linker\LinkRenderer\$expandUrls
string bool int $expandUrls
A PROTO_* constant or false.
Definition: LinkRenderer.php:55
MediaWiki\Linker\LinkTarget\isExternal
isExternal()
Whether this LinkTarget has an interwiki component.
MediaWiki\Linker\LinkRenderer\getForceArticlePath
getForceArticlePath()
Definition: LinkRenderer.php:107
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:161
MediaWiki\Linker\LinkRenderer\getExpandURLs
getExpandURLs()
Definition: LinkRenderer.php:121
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:49
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
HtmlArmor\getHtml
static getHtml( $input)
Provide a string or HtmlArmor object and get safe HTML back.
Definition: HtmlArmor.php:50
MediaWiki\Linker
Definition: LinkRenderer.php:21
MediaWiki\Linker\LinkRenderer\$forceArticlePath
bool $forceArticlePath
Whether to force the pretty article path.
Definition: LinkRenderer.php:48
$title
$title
Definition: testCompression.php:36
MediaWiki\Linker\LinkRenderer\setExpandURLs
setExpandURLs( $expand)
Definition: LinkRenderer.php:114
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:256
PROTO_HTTPS
const PROTO_HTTPS
Definition: Defines.php:200
MediaWiki\Linker\LinkRenderer\setForceArticlePath
setForceArticlePath( $force)
Definition: LinkRenderer.php:100
Linker\normaliseSpecialPage
static normaliseSpecialPage(LinkTarget $target)
Definition: Linker.php:208
MediaWiki\Linker\LinkRenderer\runBeginHook
runBeginHook(LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:186
MediaWiki\Linker\LinkRenderer\$runLegacyBeginHook
bool $runLegacyBeginHook
Whether to run the legacy Linker hooks.
Definition: LinkRenderer.php:82
MediaWiki\Linker\LinkTarget\getFragment
getFragment()
Get the link fragment (i.e.
PROTO_HTTP
const PROTO_HTTP
Definition: Defines.php:199
MediaWiki\Linker\LinkRenderer\runLegacyBeginHook
runLegacyBeginHook(LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown)
Definition: LinkRenderer.php:198
DummyLinker
Definition: DummyLinker.php:6
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:269
Hooks\isRegistered
static isRegistered( $name)
Returns true if a hook has a function registered to it.
Definition: Hooks.php:80
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:290
TitleFormatter
A title formatter service for MediaWiki.
Definition: TitleFormatter.php:34
$run
$run
Definition: checkDupeMessages.php:28
MediaWiki\Linker\LinkRenderer\getLinkURL
getLinkURL(LinkTarget $target, array $query=[])
Definition: LinkRenderer.php:409
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:33
MediaWiki\Linker\LinkRenderer\getStubThreshold
getStubThreshold()
Definition: LinkRenderer.php:135
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
MediaWiki\Linker\LinkRenderer\makeLink
makeLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:153
MediaWiki\Linker\LinkRenderer\__construct
__construct(TitleFormatter $titleFormatter, LinkCache $linkCache, NamespaceInfo $nsInfo)
Definition: LinkRenderer.php:89
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:318
Hooks
Hooks class.
Definition: Hooks.php:34