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