Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.31% covered (warning)
79.31%
138 / 174
52.38% covered (warning)
52.38%
11 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
LinkRenderer
79.31% covered (warning)
79.31%
138 / 174
52.38% covered (warning)
52.38%
11 / 21
102.42
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 setForceArticlePath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getForceArticlePath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setExpandURLs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExpandURLs
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isForComment
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeLink
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 runBeginHook
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 makePreloadedLink
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
3.00
 makeKnownLink
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
4.03
 makeBrokenLink
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
7
 makeExternalLink
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 makeRedirectHeader
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
 buildAElement
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getLinkText
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 getLinkURL
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 normalizeTarget
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 mergeAttribs
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getLinkClasses
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
6.04
 castToTitle
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 castToLinkTarget
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @author Kunal Mehta <legoktm@debian.org>
20 */
21namespace MediaWiki\Linker;
22
23use HtmlArmor;
24use MediaWiki\Cache\LinkCache;
25use MediaWiki\Config\ServiceOptions;
26use MediaWiki\HookContainer\HookContainer;
27use MediaWiki\HookContainer\HookRunner;
28use MediaWiki\Html\Html;
29use MediaWiki\Language\Language;
30use MediaWiki\Message\Message;
31use MediaWiki\Page\PageReference;
32use MediaWiki\Parser\Parser;
33use MediaWiki\Parser\Sanitizer;
34use MediaWiki\SpecialPage\SpecialPageFactory;
35use MediaWiki\Title\Title;
36use MediaWiki\Title\TitleFormatter;
37use MediaWiki\Title\TitleValue;
38use Wikimedia\Assert\Assert;
39
40/**
41 * Class that generates HTML for internal links.
42 * See the Linker class for other kinds of links.
43 *
44 * @see https://www.mediawiki.org/wiki/Manual:LinkRenderer
45 * @since 1.28
46 */
47class LinkRenderer {
48
49    public const CONSTRUCTOR_OPTIONS = [
50        'renderForComment',
51    ];
52
53    /**
54     * Whether to force the pretty article path
55     *
56     * @var bool
57     */
58    private $forceArticlePath = false;
59
60    /**
61     * A PROTO_* constant or false
62     *
63     * @var string|bool|int
64     */
65    private $expandUrls = false;
66
67    /**
68     * Whether links are being rendered for comments.
69     *
70     * @var bool
71     */
72    private $comment = false;
73
74    /**
75     * @var TitleFormatter
76     */
77    private $titleFormatter;
78
79    /**
80     * @var LinkCache
81     */
82    private $linkCache;
83
84    /** @var HookRunner */
85    private $hookRunner;
86
87    /**
88     * @var SpecialPageFactory
89     */
90    private $specialPageFactory;
91
92    /**
93     * @internal For use by LinkRendererFactory
94     *
95     * @param TitleFormatter $titleFormatter
96     * @param LinkCache $linkCache
97     * @param SpecialPageFactory $specialPageFactory
98     * @param HookContainer $hookContainer
99     * @param ServiceOptions $options
100     */
101    public function __construct(
102        TitleFormatter $titleFormatter,
103        LinkCache $linkCache,
104        SpecialPageFactory $specialPageFactory,
105        HookContainer $hookContainer,
106        ServiceOptions $options
107    ) {
108        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
109        $this->comment = $options->get( 'renderForComment' );
110
111        $this->titleFormatter = $titleFormatter;
112        $this->linkCache = $linkCache;
113        $this->specialPageFactory = $specialPageFactory;
114        $this->hookRunner = new HookRunner( $hookContainer );
115    }
116
117    /**
118     * Whether to force the link to use the article path ($wgArticlePath) even if
119     * a query string is present, resulting in URLs like /wiki/Main_Page?action=foobar.
120     *
121     * @param bool $force
122     */
123    public function setForceArticlePath( $force ) {
124        $this->forceArticlePath = $force;
125    }
126
127    /**
128     * @return bool
129     * @see setForceArticlePath()
130     */
131    public function getForceArticlePath() {
132        return $this->forceArticlePath;
133    }
134
135    /**
136     * Whether/how to expand URLs.
137     *
138     * @param string|bool|int $expand A PROTO_* constant or false for no expansion
139     * @see UrlUtils::expand()
140     */
141    public function setExpandURLs( $expand ) {
142        $this->expandUrls = $expand;
143    }
144
145    /**
146     * @return string|bool|int a PROTO_* constant or false for no expansion
147     * @see setExpandURLs()
148     */
149    public function getExpandURLs() {
150        return $this->expandUrls;
151    }
152
153    /**
154     * True when the links will be rendered in an edit summary or log comment.
155     *
156     * @return bool
157     */
158    public function isForComment(): bool {
159        // This option only exists to power a hack in Wikibase's onHtmlPageLinkRendererEnd hook.
160        return $this->comment;
161    }
162
163    /**
164     * Render a wikilink.
165     * Will call makeKnownLink() or makeBrokenLink() as appropriate.
166     *
167     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
168     * @param-taint $target none
169     * @param string|HtmlArmor|null $text Text that the user can click on to visit the link.
170     * @param-taint $text escapes_html
171     * @param array $extraAttribs Attributes you would like to add to the <a> tag. For example, if
172     * you would like to add title="Text when hovering!", you would set this to [ 'title' => 'Text
173     * when hovering!' ]
174     * @param-taint $extraAttribs none
175     * @param array $query Parameters you would like to add to the URL. For example, if you would
176     * like to add ?redirect=no&debug=1, you would set this to [ 'redirect' => 'no', 'debug' => '1' ]
177     * @param-taint $query none
178     * @return string HTML
179     * @return-taint escaped
180     */
181    public function makeLink(
182        $target, $text = null, array $extraAttribs = [], array $query = []
183    ) {
184        Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
185        if ( $this->castToTitle( $target )->isKnown() ) {
186            return $this->makeKnownLink( $target, $text, $extraAttribs, $query );
187        } else {
188            return $this->makeBrokenLink( $target, $text, $extraAttribs, $query );
189        }
190    }
191
192    private function runBeginHook( $target, &$text, &$extraAttribs, &$query ) {
193        $ret = null;
194        if ( !$this->hookRunner->onHtmlPageLinkRendererBegin(
195            // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
196            $this, $this->castToTitle( $target ), $text, $extraAttribs, $query, $ret )
197        ) {
198            return $ret;
199        }
200    }
201
202    /**
203     * Make a link that's styled as if the target page exists (a "blue link"), with a specified
204     * class attribute.
205     *
206     * Usually you should use makeLink() or makeKnownLink() instead, which will select the CSS
207     * classes automatically. Use this method if the exact styling doesn't matter and you want
208     * to ensure no extra DB lookup happens, e.g. for links generated by the skin.
209     *
210     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
211     * @param-taint $target none
212     * @param string|HtmlArmor|null $text Text that the user can click on to visit the link.
213     * @param-taint $text escapes_html
214     * @param string $classes CSS classes to add
215     * @param-taint $classes none
216     * @param array $extraAttribs Attributes you would like to add to the <a> tag. For example, if
217     * you would like to add title="Text when hovering!", you would set this to [ 'title' => 'Text
218     * when hovering!' ]
219     * @param-taint $extraAttribs none
220     * @param array $query Parameters you would like to add to the URL. For example, if you would
221     * like to add ?redirect=no&debug=1, you would set this to [ 'redirect' => 'no', 'debug' => '1' ]
222     * @param-taint $query none
223     * @return string
224     * @return-taint escaped
225     */
226    public function makePreloadedLink(
227        $target, $text = null, $classes = '', array $extraAttribs = [], array $query = []
228    ) {
229        Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
230
231        // Run begin hook
232        $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query );
233        if ( $ret !== null ) {
234            return $ret;
235        }
236        $target = $this->normalizeTarget( $target );
237        $url = $this->getLinkURL( $target, $query );
238        $attribs = [ 'class' => $classes ];
239        $prefixedText = $this->titleFormatter->getPrefixedText( $target );
240        if ( $prefixedText !== '' ) {
241            $attribs['title'] = $prefixedText;
242        }
243
244        $attribs = [
245            'href' => $url,
246        ] + $this->mergeAttribs( $attribs, $extraAttribs );
247
248        $text ??= $this->getLinkText( $target );
249
250        return $this->buildAElement( $target, $text, $attribs, true );
251    }
252
253    /**
254     * Make a link that's styled as if the target page exists (usually a "blue link", although the
255     * styling might depend on e.g. whether the target is a redirect).
256     *
257     * This will result in a DB lookup if the title wasn't cached yet. If you want to avoid that,
258     * and don't care about matching the exact styling of links within page content, you can use
259     * makePreloadedLink() instead.
260     *
261     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
262     * @param-taint $target none
263     * @param string|HtmlArmor|null $text Text that the user can click on to visit the link.
264     * @param-taint $text escapes_html
265     * @param array $extraAttribs Attributes you would like to add to the <a> tag. For example, if
266     * you would like to add title="Text when hovering!", you would set this to [ 'title' => 'Text
267     * when hovering!' ]
268     * @param-taint $extraAttribs none
269     * @param array $query Parameters you would like to add to the URL. For example, if you would
270     * like to add ?redirect=no&debug=1, you would set this to [ 'redirect' => 'no', 'debug' => '1' ]
271     * @param-taint $query none
272     * @return string HTML
273     * @return-taint escaped
274     */
275    public function makeKnownLink(
276        $target, $text = null, array $extraAttribs = [], array $query = []
277    ) {
278        Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
279        if ( $target instanceof LinkTarget ) {
280            $isExternal = $target->isExternal();
281        } else {
282            // $target instanceof PageReference
283            // treat all PageReferences as local for now
284            $isExternal = false;
285        }
286        $classes = [];
287        if ( $isExternal ) {
288            $classes[] = 'extiw';
289        }
290        $colour = $this->getLinkClasses( $target );
291        if ( $colour !== '' ) {
292            $classes[] = $colour;
293        }
294
295        return $this->makePreloadedLink(
296            $target,
297            $text,
298            implode( ' ', $classes ),
299            $extraAttribs,
300            $query
301        );
302    }
303
304    /**
305     * Make a link that's styled as if the target page doesn't exist (a "red link").
306     *
307     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
308     * @param-taint $target none
309     * @param string|HtmlArmor|null $text Text that the user can click on to visit the link.
310     * @param-taint $text escapes_html
311     * @param array $extraAttribs Attributes you would like to add to the <a> tag. For example, if
312     * you would like to add title="Text when hovering!", you would set this to [ 'title' => 'Text
313     * when hovering!' ]
314     * @param-taint $extraAttribs none
315     * @param array $query Parameters you would like to add to the URL. For example, if you would
316     * like to add ?redirect=no&debug=1, you would set this to [ 'redirect' => 'no', 'debug' => '1' ]
317     * @param-taint $query none
318     * @return string
319     * @return-taint escaped
320     */
321    public function makeBrokenLink(
322        $target, $text = null, array $extraAttribs = [], array $query = []
323    ) {
324        Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
325        // Run legacy hook
326        $ret = $this->runBeginHook( $target, $text, $extraAttribs, $query );
327        if ( $ret !== null ) {
328            return $ret;
329        }
330
331        if ( $target instanceof LinkTarget ) {
332            # We don't want to include fragments for broken links, because they
333            # generally make no sense.
334            if ( $target->hasFragment() ) {
335                $target = $target->createFragmentTarget( '' );
336            }
337        }
338        $target = $this->normalizeTarget( $target );
339
340        if ( !isset( $query['action'] ) && $target->getNamespace() !== NS_SPECIAL ) {
341            $query['action'] = 'edit';
342            $query['redlink'] = '1';
343        }
344
345        $url = $this->getLinkURL( $target, $query );
346        $attribs = [ 'class' => 'new' ];
347        $prefixedText = $this->titleFormatter->getPrefixedText( $target );
348        if ( $prefixedText !== '' ) {
349            // This ends up in parser cache!
350            $attribs['title'] = wfMessage( 'red-link-title', $prefixedText )
351                ->inContentLanguage()
352                ->text();
353        }
354
355        $attribs = [
356            'href' => $url,
357        ] + $this->mergeAttribs( $attribs, $extraAttribs );
358
359        $text ??= $this->getLinkText( $target );
360
361        return $this->buildAElement( $target, $text, $attribs, false );
362    }
363
364    /**
365     * Make an external link
366     *
367     * @since 1.43
368     * @param string $url URL to link to
369     * @param-taint $url escapes_html
370     * @param string|HtmlArmor|Message $text Text of link; will be escaped if
371     *  a string.
372     * @param-taint $text escapes_html
373     * @param LinkTarget $title LinkTarget object used for title specific link attributes
374     * @param-taint $title none
375     * @param string $linktype Type of external link. Gets added to the classes
376     * @param-taint $linktype escapes_html
377     * @param array $attribs Array of extra attributes to <a>
378     * @param-taint $attribs escapes_html
379     * @return string
380     */
381    public function makeExternalLink(
382        string $url, $text, LinkTarget $title, $linktype = '', $attribs = []
383    ) {
384        $class = 'external';
385        if ( $linktype ) {
386            $class .= " $linktype";
387        }
388        if ( isset( $attribs['class'] ) && $attribs['class'] ) {
389            $class .= " {$attribs['class']}";
390        }
391        $attribs['class'] = $class;
392
393        if ( $text instanceof Message ) {
394            $text = $text->escaped();
395        } else {
396            $text = HtmlArmor::getHtml( $text );
397            // Tell phan that $text is non-null after ::getHtml()
398            '@phan-var string $text';
399        }
400
401        $newRel = Parser::getExternalLinkRel( $url, $title );
402        if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
403            $attribs['rel'] = $newRel;
404        } elseif ( $newRel !== null ) {
405            // Merge the rel attributes.
406            $newRels = explode( ' ', $newRel );
407            $oldRels = explode( ' ', $attribs['rel'] );
408            $combined = array_unique( array_merge( $newRels, $oldRels ) );
409            $attribs['rel'] = implode( ' ', $combined );
410        }
411        $link = '';
412        $success = $this->hookRunner->onLinkerMakeExternalLink(
413            $url, $text, $link, $attribs, $linktype );
414        if ( !$success ) {
415            wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
416                . "with url {$url} and text {$text} to {$link}" );
417            return $link;
418        }
419        $attribs['href'] = $url;
420        return Html::rawElement( 'a', $attribs, $text );
421    }
422
423    /**
424     * Return the HTML for the top of a redirect page
425     *
426     * Chances are you should just be using the ParserOutput from
427     * WikitextContent::getParserOutput (which will have this header added
428     * automatically) instead of calling this for redirects.
429     *
430     * If creating your own redirect-alike, please use return value of
431     * this method to set the 'core:redirect-header' extension data field
432     * in your ParserOutput, rather than concatenating HTML directly.
433     * See WikitextContentHandler::fillParserOutput().
434     *
435     * @since 1.41
436     * @param Language $lang
437     * @param Title $target Destination to redirect
438     * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
439     * @return string Containing HTML with redirect link
440     */
441    public function makeRedirectHeader( Language $lang, Title $target, bool $forceKnown = false ) {
442        $html = '<ul class="redirectText">';
443        if ( $forceKnown ) {
444            $link = $this->makeKnownLink(
445                $target,
446                $target->getFullText(),
447                [],
448                // Make sure wiki page redirects are not followed
449                $target->isRedirect() ? [ 'redirect' => 'no' ] : []
450            );
451        } else {
452            $link = $this->makeLink(
453                $target,
454                $target->getFullText(),
455                [],
456                // Make sure wiki page redirects are not followed
457                $target->isRedirect() ? [ 'redirect' => 'no' ] : []
458            );
459        }
460
461        $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
462
463        return Html::rawElement(
464            'div', [ 'class' => 'redirectMsg' ],
465            Html::rawElement( 'p', [], $redirectToText ) .
466            Html::rawElement( 'ul', [ 'class' => 'redirectText' ],
467                Html::rawElement( 'li', [], $link ) )
468        );
469    }
470
471    /**
472     * Builds the final <a> element
473     *
474     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
475     * @param-taint $target none
476     * @param string|HtmlArmor $text
477     * @param-taint $text escapes_html
478     * @param array $attribs
479     * @param-taint $attribs none
480     * @param bool $isKnown
481     * @param-taint $isKnown none
482     * @return null|string
483     * @return-taint escaped
484     */
485    private function buildAElement( $target, $text, array $attribs, $isKnown ) {
486        $ret = null;
487        if ( !$this->hookRunner->onHtmlPageLinkRendererEnd(
488            // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
489            $this, $this->castToLinkTarget( $target ), $isKnown, $text, $attribs, $ret )
490        ) {
491            return $ret;
492        }
493
494        return Html::rawElement( 'a', $attribs, HtmlArmor::getHtml( $text ) );
495    }
496
497    /**
498     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
499     * @return string
500     */
501    private function getLinkText( $target ) {
502        $prefixedText = $this->titleFormatter->getPrefixedText( $target );
503        // If the target is just a fragment, with no title, we return the fragment
504        // text.  Otherwise, we return the title text itself.
505        if ( $prefixedText === '' && $target instanceof LinkTarget && $target->hasFragment() ) {
506            return $target->getFragment();
507        }
508
509        return $prefixedText;
510    }
511
512    /**
513     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
514     * @param array $query Parameters you would like to add to the URL. For example, if you would
515     * like to add ?redirect=no&debug=1, you would set this to [ 'redirect' => 'no', 'debug' => '1' ]
516     * @return string non-escaped text
517     */
518    private function getLinkURL( $target, $query = [] ) {
519        if ( $this->forceArticlePath ) {
520            $realQuery = $query;
521            $query = [];
522        } else {
523            $realQuery = [];
524        }
525        $url = $this->castToTitle( $target )->getLinkURL( $query, false, $this->expandUrls );
526
527        if ( $this->forceArticlePath && $realQuery ) {
528            $url = wfAppendQuery( $url, $realQuery );
529        }
530
531        return $url;
532    }
533
534    /**
535     * Normalizes the provided target
536     *
537     * @internal For use by Linker::getImageLinkMTOParams()
538     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
539     * @return LinkTarget
540     */
541    public function normalizeTarget( $target ) {
542        $target = $this->castToLinkTarget( $target );
543        if ( $target->getNamespace() === NS_SPECIAL && !$target->isExternal() ) {
544            [ $name, $subpage ] = $this->specialPageFactory->resolveAlias(
545                $target->getDBkey()
546            );
547            if ( $name ) {
548                return new TitleValue(
549                    NS_SPECIAL,
550                    $this->specialPageFactory->getLocalNameFor( $name, $subpage ),
551                    $target->getFragment()
552                );
553            }
554        }
555
556        return $target;
557    }
558
559    /**
560     * Merges two sets of attributes
561     *
562     * @param array $defaults
563     * @param array $attribs
564     *
565     * @return array
566     */
567    private function mergeAttribs( $defaults, $attribs ) {
568        if ( !$attribs ) {
569            return $defaults;
570        }
571        # Merge the custom attribs with the default ones, and iterate
572        # over that, deleting all "false" attributes.
573        $ret = [];
574        $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
575        foreach ( $merged as $key => $val ) {
576            # A false value suppresses the attribute
577            if ( $val !== false ) {
578                $ret[$key] = $val;
579            }
580        }
581        return $ret;
582    }
583
584    /**
585     * Returns CSS classes to add to a known link.
586     *
587     * Note that most CSS classes set on wikilinks are actually handled elsewhere (e.g. in
588     * makeKnownLink() or in LinkHolderArray).
589     *
590     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
591     * @return string CSS class
592     */
593    public function getLinkClasses( $target ) {
594        Assert::parameterType( [ LinkTarget::class, PageReference::class ], $target, '$target' );
595        $target = $this->castToLinkTarget( $target );
596        // Don't call LinkCache if the target is "non-proper"
597        if ( $target->isExternal() || $target->getText() === '' || $target->getNamespace() < 0 ) {
598            return '';
599        }
600        // Make sure the target is in the cache
601        $id = $this->linkCache->addLinkObj( $target );
602        if ( $id == 0 ) {
603            // Doesn't exist
604            return '';
605        }
606
607        if ( $this->linkCache->getGoodLinkFieldObj( $target, 'redirect' ) ) {
608            # Page is a redirect
609            return 'mw-redirect';
610        }
611
612        return '';
613    }
614
615    /**
616     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
617     * @return Title
618     */
619    private function castToTitle( $target ): Title {
620        if ( $target instanceof LinkTarget ) {
621            return Title::newFromLinkTarget( $target );
622        }
623        // $target instanceof PageReference
624        return Title::newFromPageReference( $target );
625    }
626
627    /**
628     * @param LinkTarget|PageReference $target Page that will be visited when the user clicks on the link.
629     * @return LinkTarget
630     */
631    private function castToLinkTarget( $target ): LinkTarget {
632        if ( $target instanceof PageReference ) {
633            return Title::newFromPageReference( $target );
634        }
635        // $target instanceof LinkTarget
636        return $target;
637    }
638}