MediaWiki REL1_34
LinkRenderer.php
Go to the documentation of this file.
1<?php
22
23use DummyLinker;
24use Hooks;
25use Html;
26use HtmlArmor;
27use LinkCache;
28use Linker;
31use Sanitizer;
32use Title;
34
42
48 private $forceArticlePath = false;
49
55 private $expandUrls = false;
56
60 private $stubThreshold = 0;
61
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() {
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}
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Hooks class.
Definition Hooks.php:34
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
static getHtml( $input)
Provide a string or HtmlArmor object and get safe HTML back.
Definition HtmlArmor.php:50
This class is a collection of static functions that serve two purposes:
Definition Html.php:49
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition LinkCache.php:34
Some internal bits split of from Skin.php.
Definition Linker.php:35
static normaliseSpecialPage(LinkTarget $target)
Definition Linker.php:207
Class that generates HTML links for pages.
bool $forceArticlePath
Whether to force the pretty article path.
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...
getLegacyOptions( $isKnown)
Get the options in the legacy format.
getLinkURL(LinkTarget $target, array $query=[])
runLegacyBeginHook(LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown)
buildAElement(LinkTarget $target, $text, array $attribs, $isKnown)
Builds the final element.
__construct(TitleFormatter $titleFormatter, LinkCache $linkCache, NamespaceInfo $nsInfo)
runBeginHook(LinkTarget $target, &$text, &$extraAttribs, &$query, $isKnown)
makeBrokenLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
getLinkClasses(LinkTarget $target)
Return the CSS classes of a known link.
mergeAttribs( $defaults, $attribs)
Merges two sets of attributes.
getLinkText(LinkTarget $target)
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
makeLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
normalizeTarget(LinkTarget $target)
Normalizes the provided target.
string bool int $expandUrls
A PROTO_* constant or false.
bool $runLegacyBeginHook
Whether to run the legacy Linker hooks.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:33
Represents a title within MediaWiki.
Definition Title.php:42
const PROTO_HTTPS
Definition Defines.php:209
const NS_SPECIAL
Definition Defines.php:58
const PROTO_HTTP
Definition Defines.php:208
hasFragment()
Whether the link target has a fragment.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
isExternal()
Whether this LinkTarget has an interwiki component.
createFragmentTarget( $fragment)
Creates a new LinkTarget for a different fragment of the same page.
A title formatter service for MediaWiki.
if(! $dbr->tableExists( 'profiling')) $expand