34 use HtmlFormatter\HtmlFormatter;
51 use Wikimedia\IPUtils;
52 use Wikimedia\Parsoid\Core\SectionMetadata;
115 $target, $html =
null, $customAttribs = [], $query = [], $options = []
118 wfWarn( __METHOD__ .
': Requires $target to be a LinkTarget object.', 2 );
119 return "<!-- ERROR -->$html";
123 $options = (array)$options;
126 $linkRenderer = $services->getLinkRendererFactory()
127 ->createFromLegacyOptions( $options );
129 $linkRenderer = $services->getLinkRenderer();
132 if ( $html !==
null ) {
138 if ( in_array(
'known', $options,
true ) ) {
139 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
142 if ( in_array(
'broken', $options,
true ) ) {
143 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
146 if ( in_array(
'noclasses', $options,
true ) ) {
147 return $linkRenderer->makePreloadedLink( $target, $text,
'', $customAttribs, $query );
150 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
167 $target, $html =
null, $customAttribs = [],
168 $query = [], $options = [
'known' ]
170 return self::link( $target, $html, $customAttribs, $query, $options );
190 public static function makeSelfLinkObj( $nt, $html =
'', $query =
'', $trail =
'', $prefix =
'', $hash =
'' ) {
193 'class' =>
'mw-selflink',
196 $attrs[
'href'] =
'#' . $hash;
197 $attrs[
'class'] =
'mw-selflink-fragment';
200 $attrs[
'class'] =
'mw-selflink selflink';
203 if ( !
Hooks::runner()->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
208 $html = htmlspecialchars( $nt->getPrefixedText() );
228 $name = $context->
msg(
'blanknamespace' )->text();
231 getFormattedNsText( $namespace );
233 return $context->
msg(
'invalidtitle-knownnamespace', $namespace, $name,
$title )->text();
236 return $context->
msg(
'invalidtitle-unknownnamespace', $namespace,
$title )->text();
247 private static function fnamePart( $url ) {
248 $basename = strrchr( $url,
'/' );
249 if ( $basename ===
false ) {
252 $basename = substr( $basename, 1 );
269 $alt = self::fnamePart( $url );
274 wfDebug(
"Hook LinkerMakeExternalImage changed the output of external image "
275 .
"with url {$url} and alt text {$alt} to {$img}" );
325 $file, $frameParams = [], $handlerParams = [], $time =
false,
326 $query =
'', $widthOption =
null
333 $file, $frameParams, $handlerParams, $time,
$res,
335 $parser, $query, $widthOption )
340 if (
$file && !
$file->allowInlineDisplay() ) {
341 wfDebug( __METHOD__ .
': ' .
$title->getPrefixedDBkey() .
' does not allow inline display' );
346 $page = $handlerParams[
'page'] ??
false;
347 if ( !isset( $frameParams[
'align'] ) ) {
348 $frameParams[
'align'] =
'';
350 if ( !isset( $frameParams[
'alt'] ) ) {
351 $frameParams[
'alt'] =
'';
353 if ( !isset( $frameParams[
'title'] ) ) {
354 $frameParams[
'title'] =
'';
356 if ( !isset( $frameParams[
'class'] ) ) {
357 $frameParams[
'class'] =
'';
361 $config = $services->getMainConfig();
366 !isset( $handlerParams[
'width'] ) &&
367 !isset( $frameParams[
'manualthumb'] ) &&
368 !isset( $frameParams[
'framed'] )
370 $classes[] =
'mw-default-size';
373 $prefix = $postfix =
'';
375 if ( $enableLegacyMediaDOM ) {
376 if ( $frameParams[
'align'] ==
'center' ) {
377 $prefix =
'<div class="center">';
379 $frameParams[
'align'] =
'none';
383 if (
$file && !isset( $handlerParams[
'width'] ) ) {
384 if ( isset( $handlerParams[
'height'] ) &&
$file->isVectorized() ) {
388 $handlerParams[
'width'] = $svgMaxSize;
390 $handlerParams[
'width'] =
$file->getWidth( $page );
393 if ( isset( $frameParams[
'thumbnail'] )
394 || isset( $frameParams[
'manualthumb'] )
395 || isset( $frameParams[
'framed'] )
396 || isset( $frameParams[
'frameless'] )
397 || !$handlerParams[
'width']
401 if ( $widthOption ===
null || !isset( $thumbLimits[$widthOption] ) ) {
402 $userOptionsLookup = $services->getUserOptionsLookup();
403 $widthOption = $userOptionsLookup->getDefaultOption(
'thumbsize' );
407 if ( isset( $frameParams[
'upright'] ) && $frameParams[
'upright'] == 0 ) {
408 $frameParams[
'upright'] = $thumbUpright;
414 $prefWidth = isset( $frameParams[
'upright'] ) ?
415 round( $thumbLimits[$widthOption] * $frameParams[
'upright'], -1 ) :
416 $thumbLimits[$widthOption];
420 if ( !isset( $handlerParams[
'height'] ) && ( $handlerParams[
'width'] <= 0 ||
421 $prefWidth < $handlerParams[
'width'] ||
$file->isVectorized() ) ) {
422 $handlerParams[
'width'] = $prefWidth;
428 $hasVisibleCaption = isset( $frameParams[
'thumbnail'] ) ||
429 isset( $frameParams[
'manualthumb'] ) ||
430 isset( $frameParams[
'framed'] );
432 if ( $hasVisibleCaption ) {
433 if ( $enableLegacyMediaDOM ) {
438 # Create a thumbnail. Alignment depends on the writing direction of
439 # the page content language (right-aligned for LTR languages,
440 # left-aligned for RTL languages)
441 # If a thumbnail width has not been provided, it is set
442 # to the default user option as specified in Language*.php
443 if ( $frameParams[
'align'] ==
'' ) {
448 $title,
$file, $frameParams, $handlerParams, $time, $query,
453 $rdfaType =
'mw:File';
455 if (
$file && isset( $frameParams[
'frameless'] ) ) {
456 $rdfaType .=
'/Frameless';
457 $srcWidth =
$file->getWidth( $page );
458 # For "frameless" option: do not present an image bigger than the
459 # source (for bitmap-style images). This is the same behavior as the
460 # "thumb" option does it already.
461 if ( $srcWidth && !
$file->mustRender() && $handlerParams[
'width'] > $srcWidth ) {
462 $handlerParams[
'width'] = $srcWidth;
466 if (
$file && isset( $handlerParams[
'width'] ) ) {
467 # Create a resized image, without the additional thumbnail features
468 $thumb =
$file->transform( $handlerParams );
474 $rdfaType =
'mw:Error ' . $rdfaType;
476 if ( $enableLegacyMediaDOM ) {
479 $label = $frameParams[
'title'];
482 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams
487 'alt' => $frameParams[
'alt'],
488 'title' => $frameParams[
'title'],
490 if ( $enableLegacyMediaDOM ) {
492 'valign' => $frameParams[
'valign'] ??
false,
493 'img-class' => $frameParams[
'class'],
495 if ( isset( $frameParams[
'border'] ) ) {
496 $params[
'img-class'] .= ( $params[
'img-class'] !==
'' ?
' ' :
'' ) .
'thumbborder';
500 $s = $thumb->toHtml( $params );
503 if ( $enableLegacyMediaDOM ) {
504 if ( $frameParams[
'align'] !=
'' ) {
507 [
'class' =>
'float' . $frameParams[
'align'] ],
511 return str_replace(
"\n",
' ', $prefix .
$s . $postfix );
517 if ( $frameParams[
'align'] !=
'' ) {
520 $classes[] =
"mw-halign-{$frameParams['align']}";
522 'figcaption', [], $frameParams[
'caption'] ??
''
524 } elseif ( isset( $frameParams[
'valign'] ) ) {
528 $classes[] =
"mw-valign-{$frameParams['valign']}";
531 if ( isset( $frameParams[
'border'] ) ) {
532 $classes[] =
'mw-image-border';
535 if ( isset( $frameParams[
'class'] ) ) {
536 $classes[] = $frameParams[
'class'];
541 'typeof' => $rdfaType,
546 return str_replace(
"\n",
' ',
$s );
559 if ( isset( $frameParams[
'link-url'] ) && $frameParams[
'link-url'] !==
'' ) {
560 $mtoParams[
'custom-url-link'] = $frameParams[
'link-url'];
561 if ( isset( $frameParams[
'link-target'] ) ) {
562 $mtoParams[
'custom-target-link'] = $frameParams[
'link-target'];
565 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams[
'link-url'] );
566 foreach ( $extLinkAttrs as $name => $val ) {
568 $mtoParams[
'parser-extlink-' . $name] = $val;
571 } elseif ( isset( $frameParams[
'link-title'] ) && $frameParams[
'link-title'] !==
'' ) {
574 $linkRenderer->normalizeTarget( $frameParams[
'link-title'] )
576 if ( isset( $frameParams[
'link-title-query'] ) ) {
577 $mtoParams[
'custom-title-link-query'] = $frameParams[
'link-title-query'];
579 } elseif ( !empty( $frameParams[
'no-link'] ) ) {
582 $mtoParams[
'desc-link'] =
true;
583 $mtoParams[
'desc-query'] = $query;
602 $params = [], $framed =
false, $manualthumb =
''
610 if ( $manualthumb ) {
611 $frameParams[
'manualthumb'] = $manualthumb;
612 } elseif ( $framed ) {
613 $frameParams[
'framed'] =
true;
614 } elseif ( !isset( $params[
'width'] ) ) {
615 $classes[] =
'mw-default-size';
618 $title,
$file, $frameParams, $params,
false,
'', $classes
635 $time =
false, $query =
'', array $classes = [], ?
Parser $parser =
null
642 $page = $handlerParams[
'page'] ??
false;
643 if ( !isset( $frameParams[
'align'] ) ) {
644 $frameParams[
'align'] =
'';
645 if ( $enableLegacyMediaDOM ) {
646 $frameParams[
'align'] =
'right';
649 if ( !isset( $frameParams[
'alt'] ) ) {
650 $frameParams[
'alt'] =
'';
652 if ( !isset( $frameParams[
'caption'] ) ) {
653 $frameParams[
'caption'] =
'';
656 if ( empty( $handlerParams[
'width'] ) ) {
658 $handlerParams[
'width'] = isset( $frameParams[
'upright'] ) ? 130 : 180;
663 $manualthumb =
false;
664 $rdfaType =
'mw:File/Thumb';
667 $outerWidth = $handlerParams[
'width'] + 2;
669 if ( isset( $frameParams[
'manualthumb'] ) ) {
670 # Use manually specified thumbnail
672 if ( $manual_title ) {
673 $manual_img = $services->getRepoGroup()
674 ->findFile( $manual_title );
676 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
682 } elseif ( isset( $frameParams[
'framed'] ) ) {
684 $thumb =
$file->getUnscaledThumb( $handlerParams );
686 $rdfaType =
'mw:File/Frame';
688 # Do not present an image bigger than the source, for bitmap-style images
689 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
690 $srcWidth =
$file->getWidth( $page );
691 if ( $srcWidth && !
$file->mustRender() && $handlerParams[
'width'] > $srcWidth ) {
692 $handlerParams[
'width'] = $srcWidth;
694 $thumb =
$file->transform( $handlerParams );
698 $outerWidth = $thumb->getWidth() + 2;
700 $outerWidth = $handlerParams[
'width'] + 2;
705 $linkTitleQuery = [];
708 $linkTitleQuery[
'page'] = $page;
709 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
710 # So we don't need to pass it here in $query. However, the URL for the
711 # zoom icon still needs it, so we make a unique query for it. See T16771
712 # FIXME: What about "lang" and other querystring parameters
717 && !isset( $frameParams[
'link-title'] )
718 && !isset( $frameParams[
'link-url'] )
719 && !isset( $frameParams[
'no-link'] ) ) {
720 $frameParams[
'link-title'] =
$title;
721 $frameParams[
'link-title-query'] = $linkTitleQuery;
724 if ( $frameParams[
'align'] !=
'' ) {
726 $classes[] =
"mw-halign-{$frameParams['align']}";
729 if ( isset( $frameParams[
'class'] ) ) {
730 $classes[] = $frameParams[
'class'];
735 if ( $enableLegacyMediaDOM ) {
736 $s .=
"<div class=\"thumb t{$frameParams['align']}\">"
737 .
"<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
743 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams
746 } elseif ( !$thumb ) {
747 if ( $enableLegacyMediaDOM ) {
748 $s .=
wfMessage(
'thumbnail_error',
'' )->escaped();
751 $title,
'',
'',
'',
'', (
bool)$time, $handlerParams
756 if ( !$noscale && !$manualthumb ) {
760 'alt' => $frameParams[
'alt'],
762 if ( $enableLegacyMediaDOM ) {
764 'img-class' => ( isset( $frameParams[
'class'] ) && $frameParams[
'class'] !==
''
765 ? $frameParams[
'class'] .
' '
766 :
'' ) .
'thumbimage'
770 $s .= $thumb->toHtml( $params );
771 if ( isset( $frameParams[
'framed'] ) ) {
777 'class' =>
'internal',
778 'title' =>
wfMessage(
'thumbnail-more' )->text(),
784 if ( $enableLegacyMediaDOM ) {
785 $s .=
' <div class="thumbcaption">' . $zoomIcon . $frameParams[
'caption'] .
'</div></div></div>';
786 return str_replace(
"\n",
' ',
$s );
790 'figcaption', [], $frameParams[
'caption'] ??
''
793 if ( !$exists || !$thumb ) {
794 $rdfaType =
'mw:Error ' . $rdfaType;
799 'typeof' => $rdfaType,
804 return str_replace(
"\n",
' ',
$s );
817 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
819 $hp15[
'width'] = round( $hp[
'width'] * 1.5 );
821 $hp20[
'width'] = $hp[
'width'] * 2;
822 if ( isset( $hp[
'height'] ) ) {
823 $hp15[
'height'] = round( $hp[
'height'] * 1.5 );
824 $hp20[
'height'] = $hp[
'height'] * 2;
827 $thumb15 =
$file->transform( $hp15 );
828 $thumb20 =
$file->transform( $hp20 );
829 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
830 $thumb->responsiveUrls[
'1.5'] = $thumb15->getUrl();
832 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
833 $thumb->responsiveUrls[
'2'] = $thumb20->getUrl();
852 $title, $label =
'', $query =
'', $unused1 =
'', $unused2 =
'',
853 $time =
false, array $handlerParams = []
856 wfWarn( __METHOD__ .
': Requires $title to be a LinkTarget object.' );
857 return "<!-- ERROR -->" . htmlspecialchars( $label );
862 $mainConfig = $services->getMainConfig();
866 if ( $label ==
'' ) {
867 $label =
$title->getPrefixedText();
871 'class' =>
'mw-broken-media',
873 'data-width' => $handlerParams[
'width'] ??
null,
874 'data-height' => $handlerParams[
'height'] ??
null,
878 $html = htmlspecialchars( $label, ENT_COMPAT );
881 $repoGroup = $services->getRepoGroup();
882 $currentExists = $time
883 && $repoGroup->findFile(
$title ) !==
false;
885 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
890 $repoGroup->getLocalRepo()->checkRedirect(
$title )
896 [
'class' =>
'mw-redirect' ],
898 [
'known',
'noclasses' ]
902 'href' => self::getUploadUrl(
$title, $query ),
904 'title' =>
$title->getPrefixedText()
912 [
'known',
'noclasses' ]
929 if ( $query !=
'' ) {
933 if ( $uploadMissingFileUrl ) {
937 if ( $uploadNavigationUrl ) {
943 return $upload->getLocalURL( $q );
957 $title, [
'time' => $time ]
976 $url =
$file->getUrl();
998 wfDebug(
"Hook LinkerMakeMediaLinkFile changed the output of link "
999 .
"with url {$url} and text {$html} to {$ret}" );
1017 $queryPos = strpos( $name,
'?' );
1018 if ( $queryPos !==
false ) {
1019 $getParams =
wfCgiToArray( substr( $name, $queryPos + 1 ) );
1020 $name = substr( $name, 0, $queryPos );
1025 $slashPos = strpos( $name,
'/' );
1026 if ( $slashPos !==
false ) {
1027 $subpage = substr( $name, $slashPos + 1 );
1028 $name = substr( $name, 0, $slashPos );
1034 $key = strtolower( $name );
1064 $linktype =
'', $attribs = [],
$title =
null
1067 $class =
'external';
1069 $class .=
" $linktype";
1071 if ( isset( $attribs[
'class'] ) && $attribs[
'class'] ) {
1072 $class .=
" {$attribs['class']}";
1074 $attribs[
'class'] = $class;
1077 $text = htmlspecialchars( $text, ENT_COMPAT );
1084 if ( !isset( $attribs[
'rel'] ) || $attribs[
'rel'] ===
'' ) {
1085 $attribs[
'rel'] = $newRel;
1086 } elseif ( $newRel !==
null ) {
1088 $newRels = explode(
' ', $newRel );
1089 $oldRels = explode(
' ', $attribs[
'rel'] );
1090 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1091 $attribs[
'rel'] = implode(
' ', $combined );
1095 $url, $text, $link, $attribs, $linktype );
1097 wfDebug(
"Hook LinkerMakeExternalLink changed the output of link "
1098 .
"with url {$url} and text {$text} to {$link}" );
1101 $attribs[
'href'] = $url;
1120 $altUserName =
false,
1123 if ( $userName ===
'' || $userName ===
false || $userName ===
null ) {
1124 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1125 'that need to be fixed?' );
1126 return wfMessage(
'empty-username' )->parse();
1129 $classes =
'mw-userlink';
1130 if ( $userId == 0 ) {
1134 $classes .=
' mw-extuserlink';
1135 } elseif ( $altUserName ===
false ) {
1136 $altUserName = IPUtils::prettifyIP( $userName );
1138 $classes .=
' mw-anonuserlink';
1144 $classes .=
' mw-tempuserlink';
1150 '<bdi>' . htmlspecialchars( $altUserName !==
false ? $altUserName : $userName ) .
'</bdi>';
1152 if ( isset( $attributes[
'class'] ) ) {
1153 $attributes[
'class'] .=
' ' . $classes;
1155 $attributes[
'class'] = $classes;
1159 ?
self::link( $page, $linkText, $attributes )
1178 $userId, $userText, $redContribsWhenNoEdits =
false, $flags = 0, $edits =
null,
1179 $useParentheses =
true
1181 if ( $userText ===
'' ) {
1182 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1183 'that need to be fixed?' );
1184 return ' ' .
wfMessage(
'empty-username' )->parse();
1188 $talkable = !( $disableAnonTalk && $userId == 0 );
1190 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1204 $attribs[
'class'] =
'mw-usertoollinks-contribs';
1205 if ( $redContribsWhenNoEdits ) {
1206 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1208 $edits = $user->getEditCount();
1210 if ( $edits === 0 ) {
1213 $attribs[
'class'] .=
' mw-usertoollinks-contribs-no-edits';
1221 if ( $blockable && $userCanBlock ) {
1226 if ( $addEmailLink && $user->canSendEmail() ) {
1230 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1236 if ( $useParentheses ) {
1237 return wfMessage(
'word-separator' )->escaped()
1238 .
'<span class="mw-usertoollinks">'
1239 .
wfMessage(
'parentheses' )->rawParams(
$wgLang->pipeList( $items ) )->escaped()
1244 foreach ( $items as $tool ) {
1247 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1248 implode(
' ', $tools ) .
'</span>';
1261 $userId, $userText, $edits =
null, $useParentheses =
true
1273 if ( $userText ===
'' ) {
1274 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1275 'that need to be fixed?' );
1276 return wfMessage(
'empty-username' )->parse();
1280 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-talk' ];
1281 $linkText =
wfMessage(
'talkpagelinktext' )->escaped();
1283 return $userTalkPage
1284 ?
self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1295 if ( $userText ===
'' ) {
1296 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1297 'that need to be fixed?' );
1298 return wfMessage(
'empty-username' )->parse();
1302 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-block' ];
1316 if ( $userText ===
'' ) {
1317 wfLogWarning( __METHOD__ .
' received an empty username. Are there database errors ' .
1318 'that need to be fixed?' );
1319 return wfMessage(
'empty-username' )->parse();
1323 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-mail' ];
1346 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1350 $revUser ? $revUser->getId() : 0,
1351 $revUser ? $revUser->getName() :
''
1354 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1358 return '<span class="' . $class .
'">' . $link .
'</span>';
1370 $class =
'history-deleted';
1372 $class .=
' mw-history-suppressed';
1392 $useParentheses =
true
1401 $userId = $revUser ? $revUser->getId() : 0;
1402 $userText = $revUser ? $revUser->getName() :
'';
1404 if ( $userId || $userText !==
'' ) {
1405 $link = self::userLink(
1409 [
'data-mw-revid' => $revRecord->
getId() ]
1410 ) . self::userToolLinks(
1421 if ( !isset( $link ) ) {
1422 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1426 $class = self::getRevisionDeletedClass( $revRecord );
1427 return ' <span class="' . $class .
' mw-userlink">' . $link .
'</span>';
1443 $formatter =
new HtmlFormatter( $html );
1444 $doc = $formatter->getDoc();
1445 $xpath =
new DOMXPath( $doc );
1446 $nodes = $xpath->query(
'//a[@href]' );
1448 foreach ( $nodes as $node ) {
1449 $node->setAttribute(
1454 return $formatter->getText(
'html' );
1478 $comment,
$title =
null, $local =
false, $wikiId =
null
1481 return $formatter->format( $comment,
$title, $local, $wikiId );
1504 $comment,
$title =
null, $local =
false, $wikiId =
null
1507 return $formatter->formatLinksUnsafe( $comment,
$title, $local, $wikiId );
1519 # :Foobar -- override special treatment of prefix (images, language links)
1520 # /Foobar -- convert to CurrentPage/Foobar
1521 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1522 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1523 # ../Foobar -- convert to CurrentPage/Foobar,
1524 # (from CurrentPage/CurrentSubPage)
1525 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1526 # (from CurrentPage/CurrentSubPage)
1528 $ret = $target; #
default return value is no change
1530 # Some namespaces don't allow subpages,
1531 # so only perform processing if subpages are allowed
1534 hasSubpages( $contextTitle->getNamespace() )
1536 $hash = strpos( $target,
'#' );
1537 if ( $hash !==
false ) {
1538 $suffix = substr( $target, $hash );
1539 $target = substr( $target, 0, $hash );
1544 $target = trim( $target );
1546 getPrefixedText( $contextTitle );
1547 # Look at the first character
1548 if ( $target !=
'' && $target[0] ===
'/' ) {
1549 # / at end means we don't want the slash to be shown
1551 $trailingSlashes = preg_match_all(
'%(/+)$%', $target, $m );
1552 if ( $trailingSlashes ) {
1553 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1555 $noslash = substr( $target, 1 );
1558 $ret = $contextPrefixedText .
'/' . trim( $noslash ) . $suffix;
1559 if ( $text ===
'' ) {
1560 $text = $target . $suffix;
1561 } #
this might be changed
for ugliness reasons
1563 # check for .. subpage backlinks
1565 $nodotdot = $target;
1566 while ( str_starts_with( $nodotdot,
'../' ) ) {
1568 $nodotdot = substr( $nodotdot, 3 );
1570 if ( $dotdotcount > 0 ) {
1571 $exploded = explode(
'/', $contextPrefixedText );
1572 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1573 $ret = implode(
'/', array_slice( $exploded, 0, -$dotdotcount ) );
1574 # / at the end means don't show full path
1575 if ( substr( $nodotdot, -1, 1 ) ===
'/' ) {
1576 $nodotdot = rtrim( $nodotdot,
'/' );
1577 if ( $text ===
'' ) {
1578 $text = $nodotdot . $suffix;
1581 $nodotdot = trim( $nodotdot );
1582 if ( $nodotdot !=
'' ) {
1583 $ret .=
'/' . $nodotdot;
1614 $comment,
$title =
null, $local =
false, $wikiId =
null, $useParentheses =
true
1617 ->formatBlock( $comment,
$title, $local, $wikiId, $useParentheses );
1639 $useParentheses =
true
1643 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1653 $stxt =
wfMessage(
'historyempty' )->escaped();
1655 $stxt =
wfMessage(
'nbytes' )->numParams( $size )->escaped();
1657 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1678 return "</li>\n" . str_repeat(
"</ul>\n</li>\n", $level > 0 ? $level : 0 );
1692 public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex =
false ) {
1693 $classes =
"toclevel-$level";
1698 if ( $sectionIndex !==
false && $sectionIndex !==
'' && !str_starts_with( $sectionIndex,
"T-" ) ) {
1699 $classes .=
" tocsection-$sectionIndex";
1706 [
'href' =>
"#$linkAnchor" ],
1707 Html::element(
'span', [
'class' =>
'tocnumber' ], $tocnumber )
1737 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1739 'type' =>
'checkbox',
1741 'id' =>
'toctogglecheckbox',
1742 'class' =>
'toctogglecheckbox',
1743 'style' =>
'display:none',
1746 'class' =>
'toctitle',
1747 'lang' =>
$lang->getHtmlCode(),
1748 'dir' =>
$lang->getDir(),
1750 .
'<h2 id="mw-toc-heading">' .
$title .
'</h2>'
1751 .
'<span class="toctogglespan">'
1753 'class' =>
'toctogglelabel',
1758 .
"</ul>\n</div>\n";
1774 $maxTocLevel = $options[
'maxtoclevel'] ??
null;
1775 foreach ( $tree as $section ) {
1776 if ( $section instanceof SectionMetadata ) {
1777 $section = $section->toLegacy();
1779 $tocLevel = $section[
'toclevel'];
1780 if ( $maxTocLevel !==
null && $tocLevel < $maxTocLevel ) {
1781 if ( $tocLevel > $lastLevel ) {
1782 $toc .= self::tocIndent();
1783 } elseif ( $tocLevel < $lastLevel ) {
1784 if ( $lastLevel < $maxTocLevel ) {
1785 $toc .= self::tocUnindent(
1786 $lastLevel - $tocLevel );
1788 $toc .= self::tocLineEnd();
1791 $toc .= self::tocLineEnd();
1794 $toc .= self::tocLine( $section[
'linkAnchor'],
1795 $section[
'line'], $section[
'number'],
1796 $tocLevel, $section[
'index'] );
1797 $lastLevel = $tocLevel;
1800 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1801 $toc .= self::tocUnindent( $lastLevel - 1 );
1803 return self::tocList( $toc,
$lang );
1823 $link, $fallbackAnchor =
false
1825 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1827 if ( $fallbackAnchor !==
false && $fallbackAnchor !== $anchor ) {
1828 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1829 $fallback =
"<span id=\"$fallbackAnchor\"></span>";
1831 return "<h$level$attribs"
1832 .
"$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1844 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1846 if ( $trail !==
'' && preg_match( $regex, $trail, $m ) ) {
1847 [ , $inside, $trail ] = $m;
1849 return [ $inside, $trail ];
1889 $editCount = self::getRollbackEditCount( $revRecord );
1890 if ( $editCount ===
false ) {
1894 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1899 $revRecord, $context, $options, $inner ) ) {
1903 if ( !in_array(
'noBrackets', $options,
true ) ) {
1904 $inner = $context->msg(
'brackets' )->rawParams( $inner )->escaped();
1907 if ( MediaWikiServices::getInstance()->getUserOptionsLookup()
1908 ->getBoolOption( $context->getUser(),
'showrollbackconfirmation' )
1910 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1911 $stats->increment(
'rollbackconfirmation.event.load' );
1912 $context->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1915 return '<span class="mw-rollback-link">' . $inner .
'</span>';
1937 if ( func_num_args() > 1 ) {
1938 wfDeprecated( __METHOD__ .
' with $verify parameter',
'1.40' );
1940 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
1941 ->get( MainConfigNames::ShowRollbackEditCount );
1943 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1951 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1952 $res =
$dbr->newSelectQueryBuilder()
1953 ->select( [
'rev_user_text' =>
$revQuery[
'fields'][
'rev_user_text'],
'rev_deleted' ] )
1955 ->where( [
'rev_page' => $revRecord->
getPageId() ] )
1957 ->useIndex( [
'revision' =>
'rev_page_timestamp' ] )
1958 ->orderBy( [
'rev_timestamp',
'rev_id' ], SelectQueryBuilder::SORT_DESC )
1959 ->limit( $showRollbackEditCount + 1 )
1960 ->caller( __METHOD__ )
1963 $revUser = $revRecord->
getUser( RevisionRecord::RAW );
1964 $revUserText = $revUser ? $revUser->getName() :
'';
1968 foreach (
$res as $row ) {
1969 if ( $row->rev_user_text != $revUserText ) {
1970 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1971 || $row->rev_deleted & RevisionRecord::DELETED_USER
1984 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2012 $config = MediaWikiServices::getInstance()->getMainConfig();
2013 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2014 $miserMode = $config->get( MainConfigNames::MiserMode );
2016 $disableRollbackEditCountSpecialPage = [
'Recentchanges',
'Watchlist' ];
2021 $revUser = $revRecord->
getUser();
2022 $revUserText = $revUser ? $revUser->getName() :
'';
2025 'action' =>
'rollback',
2026 'from' => $revUserText,
2027 'token' => $context->getUser()->getEditToken(
'rollback' ),
2031 'data-mw' =>
'interface',
2032 'title' => $context->msg(
'tooltip-rollback' )->text()
2035 $options = [
'known',
'noclasses' ];
2037 if ( $context->getRequest()->getBool(
'bot' ) ) {
2039 $query[
'hidediff'] =
'1';
2040 $query[
'bot'] =
'1';
2044 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2045 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2046 $showRollbackEditCount =
false;
2053 $msg = [
'rollbacklink' ];
2054 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2055 if ( !is_numeric( $editCount ) ) {
2056 $editCount = self::getRollbackEditCount( $revRecord );
2059 if ( $editCount > $showRollbackEditCount ) {
2060 $msg = [
'rollbacklinkcount-morethan',
Message::numParam( $showRollbackEditCount ) ];
2061 } elseif ( $editCount ) {
2066 $html = $context->msg( ...$msg )->parse();
2067 return self::link(
$title, $html, $attrs, $query, $options );
2080 if ( count( $hiddencats ) > 0 ) {
2081 # Construct the HTML
2082 $outText =
'<div class="mw-hiddenCategoriesExplanation">';
2083 $outText .=
wfMessage(
'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2084 $outText .=
"</div><ul>\n";
2086 foreach ( $hiddencats as $titleObj ) {
2087 # If it's hidden, it must exist - no need to check with a LinkBatch
2089 . self::link( $titleObj,
null, [], [],
'known' )
2092 $outText .=
'</ul>';
2100 private static function getContextFromMain() {
2123 public static function titleAttrib( $name, $options =
null, array $msgParams = [], $localizer =
null ) {
2124 if ( !$localizer ) {
2125 $localizer = self::getContextFromMain();
2127 $message = $localizer->msg(
"tooltip-$name", $msgParams );
2128 if ( $message->isDisabled() ) {
2131 $tooltip = $message->text();
2132 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2133 $tooltip = preg_replace(
"/ ?\[alt-.\]$/",
'', $tooltip );
2136 $options = (array)$options;
2138 if ( in_array(
'nonexisting', $options ) ) {
2139 $tooltip = $localizer->msg(
'red-link-title', $tooltip ?:
'' )->text();
2141 if ( in_array(
'withaccess', $options ) ) {
2142 $accesskey = self::accesskey( $name, $localizer );
2143 if ( $accesskey !==
false ) {
2145 if ( $tooltip ===
false || $tooltip ===
'' ) {
2146 $tooltip = $localizer->msg(
'brackets', $accesskey )->text();
2148 $tooltip .= $localizer->msg(
'word-separator' )->text();
2149 $tooltip .= $localizer->msg(
'brackets', $accesskey )->text();
2171 public static function accesskey( $name, $localizer =
null ) {
2172 if ( !isset( self::$accesskeycache[$name] ) ) {
2173 if ( !$localizer ) {
2174 $localizer = self::getContextFromMain();
2176 $msg = $localizer->msg(
"accesskey-$name" );
2177 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2179 return self::$accesskeycache[$name];
2201 $canHide = $performer->
isAllowed(
'deleterevision' );
2202 $canHideHistory = $performer->
isAllowed(
'deletedhistory' );
2203 if ( !$canHide && !( $revRecord->
getVisibility() && $canHideHistory ) ) {
2207 if ( !$revRecord->
userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2208 return self::revDeleteLinkDisabled( $canHide );
2210 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2211 getPrefixedDBkey(
$title );
2212 if ( $revRecord->
getId() ) {
2216 'type' =>
'revision',
2217 'target' => $prefixedDbKey,
2218 'ids' => $revRecord->
getId()
2224 'type' =>
'archive',
2225 'target' => $prefixedDbKey,
2229 return self::revDeleteLink(
2231 $revRecord->
isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2248 public static function revDeleteLink( $query = [], $restricted =
false, $delete =
true ) {
2250 $msgKey = $delete ?
'rev-delundel' :
'rev-showdeleted';
2251 $html =
wfMessage( $msgKey )->escaped();
2252 $tag = $restricted ?
'strong' :
'span';
2253 $link = self::link( $sp, $html, [], $query, [
'known',
'noclasses' ] );
2256 [
'class' =>
'mw-revdelundel-link' ],
2257 wfMessage(
'parentheses' )->rawParams( $link )->escaped()
2273 $msgKey = $delete ?
'rev-delundel' :
'rev-showdeleted';
2274 $html =
wfMessage( $msgKey )->escaped();
2275 $htmlParentheses =
wfMessage(
'parentheses' )->rawParams( $html )->escaped();
2276 return Xml::tags(
'span', [
'class' =>
'mw-revdelundel-link' ], $htmlParentheses );
2290 private static function updateWatchstarTooltipMessage(
2291 string &$tooltip, array &$msgParams, $config, $user, $relevantTitle
2293 if ( !$config || !$user || !$relevantTitle ) {
2294 $mainContext = self::getContextFromMain();
2296 $config = $mainContext->getConfig();
2299 $user = $mainContext->getUser();
2301 if ( !$relevantTitle ) {
2302 $relevantTitle = $mainContext->getSkin()->getRelevantTitle();
2306 $isWatchlistExpiryEnabled = $config->get( MainConfigNames::WatchlistExpiry );
2307 if ( !$isWatchlistExpiryEnabled || !$relevantTitle || !$relevantTitle->canExist() ) {
2311 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2312 $watchedItem = $watchStore->getWatchedItem( $user, $relevantTitle );
2314 $diffInDays = $watchedItem->getExpiryInDays();
2316 if ( $diffInDays ) {
2317 $msgParams = [ $diffInDays ];
2319 $tooltip =
'ca-unwatch-expiring';
2321 $tooltip =
'ca-unwatch-expiring-hours';
2344 array $msgParams = [],
2349 $relevantTitle =
null
2351 $options = (array)$options;
2352 $options[] =
'withaccess';
2353 $tooltipTitle = $name;
2356 if ( !$localizer ) {
2357 $localizer = self::getContextFromMain();
2361 if ( $name ===
'ca-unwatch' ) {
2362 self::updateWatchstarTooltipMessage( $tooltipTitle, $msgParams, $config, $user, $relevantTitle );
2366 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams, $localizer ),
2367 'accesskey' => self::accesskey( $name, $localizer )
2369 if ( $attribs[
'title'] ===
false ) {
2370 unset( $attribs[
'title'] );
2372 if ( $attribs[
'accesskey'] ===
false ) {
2373 unset( $attribs[
'accesskey'] );
2385 public static function tooltip( $name, $options =
null ) {
2386 $tooltip = self::titleAttrib( $name, $options );
2387 if ( $tooltip ===
false ) {
2397 class_alias( Linker::class,
'Linker' );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgTitle
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
An IContextSource implementation which will inherit context from another source but allow individual ...
Class to parse and build external user names.
static getUserLinkTitle( $userName)
Get a target Title to link a username.
static isExternal( $username)
Tells whether the username is external or not.
Implements some public methods and some protected utility functions which are required by multiple ch...
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Marks HTML that shouldn't be escaped.
This class is a collection of static functions that serve two purposes:
static label( $label, $id, array $attribs=[])
Convenience function for generating a label for inputs.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Base class for language-specific code.
A class containing constants representing the names of configuration variables.
const UploadNavigationUrl
Name constant for the UploadNavigationUrl setting, for use with Config::get()
const ThumbUpright
Name constant for the ThumbUpright setting, for use with Config::get()
const EnableUploads
Name constant for the EnableUploads setting, for use with Config::get()
const SVGMaxSize
Name constant for the SVGMaxSize setting, for use with Config::get()
const ResponsiveImages
Name constant for the ResponsiveImages setting, for use with Config::get()
const DisableAnonTalk
Name constant for the DisableAnonTalk setting, for use with Config::get()
const ParserEnableLegacyMediaDOM
Name constant for the ParserEnableLegacyMediaDOM setting, for use with Config::get()
const ThumbLimits
Name constant for the ThumbLimits setting, for use with Config::get()
const UploadMissingFileUrl
Name constant for the UploadMissingFileUrl setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
getTargetLanguage()
Get the target language for the content being parsed.
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Group all the pieces relevant to the context of a request into one instance.
static getMain()
Get the RequestContext object associated with the main request.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a page (or page fragment) title within MediaWiki.
static tryNew( $namespace, $title, $fragment='', $interwiki='')
Constructs a TitleValue, or returns null if the parameters are not valid.
Represents a title within MediaWiki.
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromId( $id)
Static factory method for creation from a given user ID.
Representation of a pair of user and title for watchlist entries.
getExpiry(?int $style=TS_MW)
When the watched item will expire.
Module of static functions for generating XML.
static expandAttributes( $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Interface for configuration instances.
Interface for objects which can provide a MediaWiki context on request.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang