49use Wikimedia\Assert\Assert;
51use Wikimedia\RemexHtml\Serializer\SerializerNode;
111 $target, $html =
null, $customAttribs = [], $query = [], $options = []
114 wfWarn( __METHOD__ .
': Requires $target to be a LinkTarget object.', 2 );
115 return "<!-- ERROR -->$html";
119 $options = (array)$options;
122 $linkRenderer = $services->getLinkRendererFactory()
123 ->createFromLegacyOptions( $options );
125 $linkRenderer = $services->getLinkRenderer();
128 if ( $html !==
null ) {
134 if ( in_array(
'known', $options,
true ) ) {
135 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
138 if ( in_array(
'broken', $options,
true ) ) {
139 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
142 if ( in_array(
'noclasses', $options,
true ) ) {
143 return $linkRenderer->makePreloadedLink( $target, $text,
'', $customAttribs, $query );
146 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
169 $target, $html =
null, $customAttribs = [],
170 $query = [], $options = [
'known' ]
172 return self::link( $target, $html, $customAttribs, $query, $options );
192 public static function makeSelfLinkObj( $nt, $html =
'', $query =
'', $trail =
'', $prefix =
'', $hash =
'' ) {
193 $nt = Title::newFromLinkTarget( $nt );
196 $attrs[
'class'] =
'mw-selflink-fragment';
197 $attrs[
'href'] =
'#' . $hash;
200 $attrs[
'class'] =
'mw-selflink selflink';
202 $ret = Html::rawElement(
'a', $attrs, $prefix . $html ) . $trail;
204 if ( !$hookRunner->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
209 $html = htmlspecialchars( $nt->getPrefixedText() );
212 return Html::rawElement(
'a', $attrs, $prefix . $html . $inside ) . $trail;
229 $name = $context->
msg(
'blanknamespace' )->text();
232 getFormattedNsText( $namespace );
234 return $context->
msg(
'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
237 return $context->
msg(
'invalidtitle-unknownnamespace', $namespace, $title )->text();
248 private static function fnamePart(
$url ) {
249 $basename = strrchr(
$url,
'/' );
250 if ( $basename ===
false ) {
253 $basename = substr( $basename, 1 );
270 $alt = self::fnamePart(
$url );
274 ->onLinkerMakeExternalImage(
$url, $alt, $img );
276 wfDebug(
"Hook LinkerMakeExternalImage changed the output of external image "
277 .
"with url {$url} and alt text {$alt} to {$img}" );
327 $file, $frameParams = [], $handlerParams = [], $time =
false,
328 $query =
'', $widthOption =
null
330 $title = Title::newFromLinkTarget( $title );
333 if ( !$hookRunner->onImageBeforeProduceHTML(
null, $title,
335 $file, $frameParams, $handlerParams, $time, $res,
337 $parser, $query, $widthOption )
342 if ( $file && !$file->allowInlineDisplay() ) {
343 wfDebug( __METHOD__ .
': ' . $title->getPrefixedDBkey() .
' does not allow inline display' );
348 $page = $handlerParams[
'page'] ??
false;
349 if ( !isset( $frameParams[
'align'] ) ) {
350 $frameParams[
'align'] =
'';
352 if ( !isset( $frameParams[
'title'] ) ) {
353 $frameParams[
'title'] =
'';
355 if ( !isset( $frameParams[
'class'] ) ) {
356 $frameParams[
'class'] =
'';
360 $config = $services->getMainConfig();
365 !isset( $handlerParams[
'width'] ) &&
366 !isset( $frameParams[
'manualthumb'] ) &&
367 !isset( $frameParams[
'framed'] )
369 $classes[] =
'mw-default-size';
372 $prefix = $postfix =
'';
374 if ( $enableLegacyMediaDOM ) {
375 if ( $frameParams[
'align'] ==
'center' ) {
376 $prefix =
'<div class="center">';
378 $frameParams[
'align'] =
'none';
382 if ( $file && !isset( $handlerParams[
'width'] ) ) {
383 if ( isset( $handlerParams[
'height'] ) && $file->isVectorized() ) {
387 $handlerParams[
'width'] = $svgMaxSize;
389 $handlerParams[
'width'] = $file->getWidth( $page );
392 if ( isset( $frameParams[
'thumbnail'] )
393 || isset( $frameParams[
'manualthumb'] )
394 || isset( $frameParams[
'framed'] )
395 || isset( $frameParams[
'frameless'] )
396 || !$handlerParams[
'width']
400 if ( $widthOption ===
null || !isset( $thumbLimits[$widthOption] ) ) {
401 $userOptionsLookup = $services->getUserOptionsLookup();
406 if ( isset( $frameParams[
'upright'] ) && $frameParams[
'upright'] == 0 ) {
407 $frameParams[
'upright'] = $thumbUpright;
413 $prefWidth = isset( $frameParams[
'upright'] ) ?
414 round( $thumbLimits[$widthOption] * $frameParams[
'upright'], -1 ) :
415 $thumbLimits[$widthOption];
419 if ( !isset( $handlerParams[
'height'] ) && ( $handlerParams[
'width'] <= 0 ||
420 $prefWidth < $handlerParams[
'width'] || $file->isVectorized() ) ) {
421 $handlerParams[
'width'] = $prefWidth;
427 $hasVisibleCaption = isset( $frameParams[
'thumbnail'] ) ||
428 isset( $frameParams[
'manualthumb'] ) ||
429 isset( $frameParams[
'framed'] );
431 if ( $hasVisibleCaption ) {
432 if ( $enableLegacyMediaDOM ) {
437 # Create a thumbnail. Alignment depends on the writing direction of
438 # the page content language (right-aligned for LTR languages,
439 # left-aligned for RTL languages)
440 # If a thumbnail width has not been provided, it is set
441 # to the default user option as specified in Language*.php
442 if ( $frameParams[
'align'] ==
'' ) {
447 $title, $file, $frameParams, $handlerParams, $time, $query,
452 $rdfaType =
'mw:File';
454 if ( isset( $frameParams[
'frameless'] ) ) {
455 $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;
467 if ( $file && isset( $handlerParams[
'width'] ) ) {
468 # Create a resized image, without the additional thumbnail features
469 $thumb = $file->transform( $handlerParams );
474 $isBadFile = $file && $thumb &&
477 if ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
478 $rdfaType =
'mw:Error ' . $rdfaType;
479 $currentExists = $file && $file->exists();
480 if ( $enableLegacyMediaDOM ) {
481 $label = $frameParams[
'title'];
483 if ( $currentExists && !$thumb ) {
484 $label =
wfMessage(
'thumbnail_error',
'' )->text();
485 } elseif ( $thumb && $thumb->isError() ) {
488 'Unknown MediaTransformOutput: ' . get_class( $thumb )
490 $label = $thumb->toText();
492 $label = $frameParams[
'alt'] ??
'';
496 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams, $currentExists
504 if ( isset( $frameParams[
'alt'] ) ) {
505 $params[
'alt'] = $frameParams[
'alt'];
507 $params[
'title'] = $frameParams[
'title'];
508 if ( $enableLegacyMediaDOM ) {
510 'valign' => $frameParams[
'valign'] ??
false,
511 'img-class' => $frameParams[
'class'],
513 if ( isset( $frameParams[
'border'] ) ) {
514 $params[
'img-class'] .= ( $params[
'img-class'] !==
'' ?
' ' :
'' ) .
'thumbborder';
518 'img-class' =>
'mw-file-element',
522 $s = $thumb->toHtml( $params );
525 if ( $enableLegacyMediaDOM ) {
526 if ( $frameParams[
'align'] !=
'' ) {
527 $s = Html::rawElement(
529 [
'class' =>
'float' . $frameParams[
'align'] ],
533 return str_replace(
"\n",
' ', $prefix . $s . $postfix );
539 if ( $frameParams[
'align'] !=
'' ) {
542 $classes[] =
"mw-halign-{$frameParams['align']}";
543 $caption = Html::rawElement(
544 'figcaption', [], $frameParams[
'caption'] ??
''
546 } elseif ( isset( $frameParams[
'valign'] ) ) {
550 $classes[] =
"mw-valign-{$frameParams['valign']}";
553 if ( isset( $frameParams[
'border'] ) ) {
554 $classes[] =
'mw-image-border';
557 if ( isset( $frameParams[
'class'] ) ) {
558 $classes[] = $frameParams[
'class'];
563 'typeof' => $rdfaType,
566 $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
568 return str_replace(
"\n",
' ', $s );
581 if ( isset( $frameParams[
'link-url'] ) && $frameParams[
'link-url'] !==
'' ) {
582 $mtoParams[
'custom-url-link'] = $frameParams[
'link-url'];
583 if ( isset( $frameParams[
'link-target'] ) ) {
584 $mtoParams[
'custom-target-link'] = $frameParams[
'link-target'];
587 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams[
'link-url'] );
588 foreach ( $extLinkAttrs as $name => $val ) {
590 $mtoParams[
'parser-extlink-' . $name] = $val;
593 } elseif ( isset( $frameParams[
'link-title'] ) && $frameParams[
'link-title'] !==
'' ) {
595 $mtoParams[
'custom-title-link'] = Title::newFromLinkTarget(
596 $linkRenderer->normalizeTarget( $frameParams[
'link-title'] )
598 if ( isset( $frameParams[
'link-title-query'] ) ) {
599 $mtoParams[
'custom-title-link-query'] = $frameParams[
'link-title-query'];
601 } elseif ( !empty( $frameParams[
'no-link'] ) ) {
604 $mtoParams[
'desc-link'] =
true;
605 $mtoParams[
'desc-query'] = $query;
623 LinkTarget $title, $file, $label =
'', $alt =
'', $align =
null,
624 $params = [], $framed =
false, $manualthumb =
''
632 if ( $manualthumb ) {
633 $frameParams[
'manualthumb'] = $manualthumb;
634 } elseif ( $framed ) {
635 $frameParams[
'framed'] =
true;
636 } elseif ( !isset( $params[
'width'] ) ) {
637 $classes[] =
'mw-default-size';
640 $title, $file, $frameParams, $params,
false,
'', $classes
656 LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
657 $time =
false, $query =
'', array $classes = [], ?
Parser $parser =
null
659 $exists = $file && $file->exists();
664 $page = $handlerParams[
'page'] ??
false;
665 $lang = $handlerParams[
'lang'] ??
false;
667 if ( !isset( $frameParams[
'align'] ) ) {
668 $frameParams[
'align'] =
'';
669 if ( $enableLegacyMediaDOM ) {
670 $frameParams[
'align'] =
'right';
673 if ( !isset( $frameParams[
'caption'] ) ) {
674 $frameParams[
'caption'] =
'';
677 if ( empty( $handlerParams[
'width'] ) ) {
679 $handlerParams[
'width'] = isset( $frameParams[
'upright'] ) ? 130 : 180;
684 $manualthumb =
false;
686 $rdfaType =
'mw:File/Thumb';
690 if ( !isset( $frameParams[
'manualthumb'] ) && isset( $frameParams[
'framed'] ) ) {
691 $rdfaType =
'mw:File/Frame';
693 $outerWidth = $handlerParams[
'width'] + 2;
695 if ( isset( $frameParams[
'manualthumb'] ) ) {
696 # Use manually specified thumbnail
697 $manual_title = Title::makeTitleSafe(
NS_FILE, $frameParams[
'manualthumb'] );
698 if ( $manual_title ) {
699 $manual_img = $services->getRepoGroup()
700 ->findFile( $manual_title );
702 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
707 $srcWidth = $file->getWidth( $page );
708 if ( isset( $frameParams[
'framed'] ) ) {
709 $rdfaType =
'mw:File/Frame';
710 if ( !$file->isVectorized() ) {
716 $handlerParams[
'width'] = $srcWidth;
722 if ( $srcWidth && !$file->mustRender() && $handlerParams[
'width'] > $srcWidth ) {
723 $handlerParams[
'width'] = $srcWidth;
727 ? $file->getUnscaledThumb( $handlerParams )
728 : $file->transform( $handlerParams );
732 $outerWidth = $thumb->getWidth() + 2;
734 $outerWidth = $handlerParams[
'width'] + 2;
738 if ( !$enableLegacyMediaDOM && $parser && $rdfaType ===
'mw:File/Thumb' ) {
739 $parser->getOutput()->addModules( [
'mediawiki.page.media' ] );
742 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
743 $linkTitleQuery = [];
744 if ( $page || $lang ) {
746 $linkTitleQuery[
'page'] = $page;
749 $linkTitleQuery[
'lang'] = $lang;
751 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
752 # So we don't need to pass it here in $query. However, the URL for the
753 # zoom icon still needs it, so we make a unique query for it. See T16771
758 && !isset( $frameParams[
'link-title'] )
759 && !isset( $frameParams[
'link-url'] )
760 && !isset( $frameParams[
'no-link'] ) ) {
761 $frameParams[
'link-title'] = $title;
762 $frameParams[
'link-title-query'] = $linkTitleQuery;
765 if ( $frameParams[
'align'] !=
'' ) {
767 $classes[] =
"mw-halign-{$frameParams['align']}";
770 if ( isset( $frameParams[
'class'] ) ) {
771 $classes[] = $frameParams[
'class'];
776 if ( $enableLegacyMediaDOM ) {
777 $s .=
"<div class=\"thumb t{$frameParams['align']}\">"
778 .
"<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
781 $isBadFile = $exists && $thumb && $parser &&
782 $parser->getBadFileLookup()->isBadFile(
783 $manualthumb ? $manual_title : $title->
getDBkey(),
788 $rdfaType =
'mw:Error ' . $rdfaType;
790 if ( !$enableLegacyMediaDOM ) {
791 $label = $frameParams[
'alt'] ??
'';
794 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams,
false
797 } elseif ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
798 $rdfaType =
'mw:Error ' . $rdfaType;
799 if ( $enableLegacyMediaDOM ) {
801 $s .=
wfMessage(
'thumbnail_error',
'' )->escaped();
804 $title,
'',
'',
'',
'', (
bool)$time, $handlerParams,
true
808 if ( $thumb && $thumb->isError() ) {
811 'Unknown MediaTransformOutput: ' . get_class( $thumb )
813 $label = $thumb->toText();
814 } elseif ( !$thumb ) {
815 $label =
wfMessage(
'thumbnail_error',
'' )->text();
820 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams,
true
825 if ( !$noscale && !$manualthumb ) {
832 if ( isset( $frameParams[
'alt'] ) ) {
833 $params[
'alt'] = $frameParams[
'alt'];
835 if ( $enableLegacyMediaDOM ) {
837 'img-class' => ( isset( $frameParams[
'class'] ) && $frameParams[
'class'] !==
''
838 ? $frameParams[
'class'] .
' '
839 :
'' ) .
'thumbimage'
843 'img-class' =>
'mw-file-element',
846 if ( $rdfaType ===
'mw:File/Thumb' ) {
847 $params[
'magnify-resource'] =
$url;
851 $s .= $thumb->toHtml( $params );
852 if ( isset( $frameParams[
'framed'] ) ) {
855 $zoomIcon = Html::rawElement(
'div', [
'class' =>
'magnify' ],
856 Html::rawElement(
'a', [
858 'class' =>
'internal',
859 'title' =>
wfMessage(
'thumbnail-more' )->text(),
865 if ( $enableLegacyMediaDOM ) {
866 $s .=
' <div class="thumbcaption">' . $zoomIcon . $frameParams[
'caption'] .
'</div></div></div>';
867 return str_replace(
"\n",
' ', $s );
870 $s .= Html::rawElement(
871 'figcaption', [], $frameParams[
'caption'] ??
''
876 'typeof' => $rdfaType,
879 $s = Html::rawElement(
'figure', $attribs, $s );
881 return str_replace(
"\n",
' ', $s );
894 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
896 $hp15[
'width'] = round( $hp[
'width'] * 1.5 );
898 $hp20[
'width'] = $hp[
'width'] * 2;
899 if ( isset( $hp[
'height'] ) ) {
900 $hp15[
'height'] = round( $hp[
'height'] * 1.5 );
901 $hp20[
'height'] = $hp[
'height'] * 2;
904 $thumb15 = $file->transform( $hp15 );
905 $thumb20 = $file->transform( $hp20 );
906 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
907 $thumb->responsiveUrls[
'1.5'] = $thumb15->getUrl();
909 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
910 $thumb->responsiveUrls[
'2'] = $thumb20->getUrl();
930 $title, $label =
'', $query =
'', $unused1 =
'', $unused2 =
'',
931 $time =
false, array $handlerParams = [],
bool $currentExists =
false
934 wfWarn( __METHOD__ .
': Requires $title to be a LinkTarget object.' );
935 return "<!-- ERROR -->" . htmlspecialchars( $label );
938 $title = Title::newFromLinkTarget( $title );
940 $mainConfig = $services->getMainConfig();
944 if ( $label ==
'' ) {
945 $label = $title->getPrefixedText();
949 'class' =>
'mw-file-element mw-broken-media',
951 'data-width' => $handlerParams[
'width'] ??
null,
952 'data-height' => $handlerParams[
'height'] ??
null,
956 $html = htmlspecialchars( $label, ENT_COMPAT );
959 $repoGroup = $services->getRepoGroup();
960 $currentExists = $currentExists ||
961 ( $time && $repoGroup->findFile( $title ) !== false );
963 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
967 $title->inNamespace(
NS_FILE ) &&
968 $repoGroup->getLocalRepo()->checkRedirect( $title )
974 [
'class' =>
'mw-redirect' ],
976 [
'known',
'noclasses' ]
979 return Html::rawElement(
'a', [
980 'href' => self::getUploadUrl( $title, $query ),
982 'title' => $title->getPrefixedText()
990 [
'known',
'noclasses' ]
1006 $q =
'wpDestFile=' . Title::newFromLinkTarget( $destFile )->getPartialURL();
1007 if ( $query !=
'' ) {
1011 if ( $uploadMissingFileUrl ) {
1015 if ( $uploadNavigationUrl ) {
1021 return $upload->getLocalURL( $q );
1035 $title, [
'time' => $time ]
1053 if ( $file && $file->exists() ) {
1054 $url = $file->getUrl();
1055 $class =
'internal';
1062 if ( $html ==
'' ) {
1074 Title::newFromLinkTarget( $title ), $file, $html, $attribs, $ret )
1076 wfDebug(
"Hook LinkerMakeMediaLinkFile changed the output of link "
1077 .
"with url {$url} and text {$html} to {$ret}" );
1081 return Html::rawElement(
'a', $attribs, $html );
1095 $queryPos = strpos( $name,
'?' );
1096 if ( $queryPos !==
false ) {
1097 $getParams =
wfCgiToArray( substr( $name, $queryPos + 1 ) );
1098 $name = substr( $name, 0, $queryPos );
1103 $slashPos = strpos( $name,
'/' );
1104 if ( $slashPos !==
false ) {
1105 $subpage = substr( $name, $slashPos + 1 );
1106 $name = substr( $name, 0, $slashPos );
1112 $key = strtolower( $name );
1144 $linktype =
'', $attribs = [], $title =
null
1149 return $linkRenderer->makeExternalLink(
1151 $escape ? $text :
new HtmlArmor( $text ),
1175 $altUserName =
false,
1178 if ( $userName ===
'' || $userName ===
false || $userName ===
null ) {
1179 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1180 'that need to be fixed?' );
1181 return wfMessage(
'empty-username' )->parse();
1187 RequestContext::getMain(),
1188 $altUserName ===
false ?
null : (string)$altUserName,
1212 $userId, $userText, $redContribsWhenNoEdits =
false, $flags = 0, $edits =
null
1216 $talkable = !( $disableAnonTalk && $userId == 0 );
1218 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1220 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1232 $attribs[
'class'] =
'mw-usertoollinks-contribs';
1233 if ( $redContribsWhenNoEdits ) {
1234 if ( $edits ===
null ) {
1235 $user = UserIdentityValue::newRegistered( $userId, $userText );
1236 $edits = $services->getUserEditTracker()->getUserEditCount( $user );
1238 if ( $edits === 0 ) {
1241 $attribs[
'class'] .=
' mw-usertoollinks-contribs-no-edits';
1248 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed(
'block' );
1249 if ( $blockable && $userCanBlock ) {
1256 ->newEmailUser( RequestContext::getMain()->
getAuthority() )
1263 (
new HookRunner( $services->getHookContainer() ) )->onUserToolLinksEdit( $userId, $userText, $items );
1282 if ( $useParentheses ) {
1283 return wfMessage(
'word-separator' )->escaped()
1284 .
'<span class="mw-usertoollinks">'
1285 .
wfMessage(
'parentheses' )->rawParams(
$wgLang->pipeList( $items ) )->escaped()
1290 foreach ( $items as $tool ) {
1291 $tools[] = Html::rawElement(
'span', [], $tool );
1293 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1294 implode(
' ', $tools ) .
'</span>';
1312 $userId, $userText, $redContribsWhenNoEdits =
false, $flags = 0, $edits =
null,
1313 $useParentheses =
true
1315 if ( $userText ===
'' ) {
1316 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1317 'that need to be fixed?' );
1318 return ' ' .
wfMessage(
'empty-username' )->parse();
1321 $items = self::userToolLinkArray( $userId, $userText, $redContribsWhenNoEdits, $flags, $edits );
1322 return self::renderUserToolLinksArray( $items, $useParentheses );
1335 $userId, $userText, $edits =
null, $useParentheses =
true
1337 return self::userToolLinks( $userId, $userText,
true, 0, $edits, $useParentheses );
1347 if ( $userText ===
'' ) {
1348 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1349 'that need to be fixed?' );
1350 return wfMessage(
'empty-username' )->parse();
1353 $userTalkPage = TitleValue::tryNew(
NS_USER_TALK, strtr( $userText,
' ',
'_' ) );
1354 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-talk' ];
1355 $linkText =
wfMessage(
'talkpagelinktext' )->escaped();
1357 return $userTalkPage
1358 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1359 : Html::rawElement(
'span', $moreLinkAttribs, $linkText );
1369 if ( $userText ===
'' ) {
1370 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1371 'that need to be fixed?' );
1372 return wfMessage(
'empty-username' )->parse();
1375 $blockPage = SpecialPage::getTitleFor(
'Block', $userText );
1376 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-block' ];
1378 return self::link( $blockPage,
1390 if ( $userText ===
'' ) {
1391 wfLogWarning( __METHOD__ .
' received an empty username. Are there database errors ' .
1392 'that need to be fixed?' );
1393 return wfMessage(
'empty-username' )->parse();
1396 $emailPage = SpecialPage::getTitleFor(
'Emailuser', $userText );
1397 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-mail' ];
1398 return self::link( $emailPage,
1417 $authority = RequestContext::getMain()->getAuthority();
1419 $revUser = $revRecord->
getUser(
1420 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1424 $link = self::userLink( $revUser->getId(), $revUser->getName() );
1427 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1430 if ( $revRecord->
isDeleted( RevisionRecord::DELETED_USER ) ) {
1431 $class = self::getRevisionDeletedClass( $revRecord );
1432 return '<span class="' . $class .
'">' . $link .
'</span>';
1444 $class =
'history-deleted';
1445 if ( $revisionRecord->
isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1446 $class .=
' mw-history-suppressed';
1466 $useParentheses =
true
1469 $authority = RequestContext::getMain()->getAuthority();
1471 $revUser = $revRecord->
getUser(
1472 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1476 $link = self::userLink(
1478 $revUser->getName(),
1480 [
'data-mw-revid' => $revRecord->
getId() ]
1481 ) . self::userToolLinks(
1483 $revUser->getName(),
1491 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1494 if ( $revRecord->
isDeleted( RevisionRecord::DELETED_USER ) ) {
1495 $class = self::getRevisionDeletedClass( $revRecord );
1496 return ' <span class="' . $class .
' mw-userlink">' . $link .
'</span>';
1512 return HtmlHelper::modifyElements(
1514 static function ( SerializerNode $node ):
bool {
1515 return $node->name ===
'a' && isset( $node->attrs[
'href'] );
1517 static function ( SerializerNode $node ): SerializerNode {
1518 $urlUtils = MediaWikiServices::getInstance()->getUrlUtils();
1519 $node->attrs[
'href'] =
1520 $urlUtils->expand( $node->attrs[
'href'],
PROTO_RELATIVE ) ??
false;
1535 # :Foobar -- override special treatment of prefix (images, language links)
1536 # /Foobar -- convert to CurrentPage/Foobar
1537 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1538 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1539 # ../Foobar -- convert to CurrentPage/Foobar,
1540 # (from CurrentPage/CurrentSubPage)
1541 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1542 # (from CurrentPage/CurrentSubPage)
1544 $ret = $target; #
default return value is no change
1546 # Some namespaces don't allow subpages,
1547 # so only perform processing if subpages are allowed
1549 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1550 hasSubpages( $contextTitle->getNamespace() )
1552 $hash = strpos( $target,
'#' );
1553 if ( $hash !==
false ) {
1554 $suffix = substr( $target, $hash );
1555 $target = substr( $target, 0, $hash );
1560 $target = trim( $target );
1561 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1562 getPrefixedText( $contextTitle );
1563 # Look at the first character
1564 if ( $target !=
'' && $target[0] ===
'/' ) {
1565 # / at end means we don't want the slash to be shown
1567 $trailingSlashes = preg_match_all(
'%(/+)$%', $target, $m );
1568 if ( $trailingSlashes ) {
1569 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1571 $noslash = substr( $target, 1 );
1574 $ret = $contextPrefixedText .
'/' . trim( $noslash ) . $suffix;
1575 if ( $text ===
'' ) {
1576 $text = $target . $suffix;
1577 } #
this might be changed
for ugliness reasons
1579 # check for .. subpage backlinks
1581 $nodotdot = $target;
1582 while ( str_starts_with( $nodotdot,
'../' ) ) {
1584 $nodotdot = substr( $nodotdot, 3 );
1586 if ( $dotdotcount > 0 ) {
1587 $exploded = explode(
'/', $contextPrefixedText );
1588 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1589 $ret = implode(
'/', array_slice( $exploded, 0, -$dotdotcount ) );
1590 # / at the end means don't show full path
1591 if ( substr( $nodotdot, -1, 1 ) ===
'/' ) {
1592 $nodotdot = rtrim( $nodotdot,
'/' );
1593 if ( $text ===
'' ) {
1594 $text = $nodotdot . $suffix;
1597 $nodotdot = trim( $nodotdot );
1598 if ( $nodotdot !=
'' ) {
1599 $ret .=
'/' . $nodotdot;
1617 $stxt =
wfMessage(
'historyempty' )->escaped();
1619 $stxt =
wfMessage(
'nbytes' )->numParams( $size )->escaped();
1621 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1631 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1633 if ( $trail !==
'' && preg_match( $regex, $trail, $m ) ) {
1634 [ , $inside, $trail ] = $m;
1636 return [ $inside, $trail ];
1674 $context ??= RequestContext::getMain();
1676 $editCount = self::getRollbackEditCount( $revRecord );
1677 if ( $editCount ===
false ) {
1681 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1683 $services = MediaWikiServices::getInstance();
1686 if ( !(
new HookRunner( $services->getHookContainer() ) )->onLinkerGenerateRollbackLink(
1687 $revRecord, $context, $options, $inner ) ) {
1691 if ( !in_array(
'noBrackets', $options,
true ) ) {
1692 $inner = $context->msg(
'brackets' )->rawParams( $inner )->escaped();
1695 if ( $services->getUserOptionsLookup()
1696 ->getBoolOption( $context->getUser(),
'showrollbackconfirmation' )
1698 $services->getStatsFactory()
1699 ->getCounter(
'rollbackconfirmation_event_load_total' )
1700 ->copyToStatsdAt(
'rollbackconfirmation.event.load' )
1702 $context->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1705 return '<span class="mw-rollback-link">' . $inner .
'</span>';
1727 if ( func_num_args() > 1 ) {
1728 wfDeprecated( __METHOD__ .
' with $verify parameter',
'1.40' );
1730 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
1731 ->get( MainConfigNames::ShowRollbackEditCount );
1733 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1738 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
1741 $queryBuilder = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr );
1742 $res = $queryBuilder->where( [
'rev_page' => $revRecord->
getPageId() ] )
1743 ->useIndex( [
'revision' =>
'rev_page_timestamp' ] )
1744 ->orderBy( [
'rev_timestamp',
'rev_id' ], SelectQueryBuilder::SORT_DESC )
1745 ->limit( $showRollbackEditCount + 1 )
1746 ->caller( __METHOD__ )->fetchResultSet();
1748 $revUser = $revRecord->
getUser( RevisionRecord::RAW );
1749 $revUserText = $revUser ? $revUser->getName() :
'';
1753 foreach ( $res as $row ) {
1754 if ( $row->rev_user_text != $revUserText ) {
1755 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1756 || $row->rev_deleted & RevisionRecord::DELETED_USER
1769 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
1797 $config = MediaWikiServices::getInstance()->getMainConfig();
1798 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
1799 $miserMode = $config->get( MainConfigNames::MiserMode );
1801 $disableRollbackEditCountSpecialPage = [
'Recentchanges',
'Watchlist' ];
1803 $context ??= RequestContext::getMain();
1806 $revUser = $revRecord->
getUser();
1807 $revUserText = $revUser ? $revUser->getName() :
'';
1810 'action' =>
'rollback',
1811 'from' => $revUserText,
1812 'token' => $context->getUser()->getEditToken(
'rollback' ),
1816 'data-mw' =>
'interface',
1817 'title' => $context->msg(
'tooltip-rollback' )->text()
1820 $options = [
'known',
'noclasses' ];
1822 if ( $context->getRequest()->getBool(
'bot' ) ) {
1824 $query[
'hidediff'] =
'1';
1825 $query[
'bot'] =
'1';
1829 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
1830 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
1831 $showRollbackEditCount =
false;
1838 $msg = [
'rollbacklink' ];
1839 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
1840 if ( !is_numeric( $editCount ) ) {
1841 $editCount = self::getRollbackEditCount( $revRecord );
1844 if ( $editCount > $showRollbackEditCount ) {
1845 $msg = [
'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
1846 } elseif ( $editCount ) {
1847 $msg = [
'rollbacklinkcount', Message::numParam( $editCount ) ];
1851 $html = $context->msg( ...$msg )->parse();
1852 return self::link( $title, $html, $attrs, $query, $options );
1865 if ( count( $hiddencats ) > 0 ) {
1866 # Construct the HTML
1867 $outText =
'<div class="mw-hiddenCategoriesExplanation">';
1868 $outText .=
wfMessage(
'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
1869 $outText .=
"</div><ul>\n";
1871 foreach ( $hiddencats as $titleObj ) {
1872 # If it's hidden, it must exist - no need to check with a LinkBatch
1874 . self::link( $titleObj,
null, [], [],
'known' )
1877 $outText .=
'</ul>';
1885 private static function getContextFromMain() {
1886 $context = RequestContext::getMain();
1908 public static function titleAttrib( $name, $options =
null, array $msgParams = [], $localizer =
null ) {
1909 if ( !$localizer ) {
1910 $localizer = self::getContextFromMain();
1912 $message = $localizer->msg(
"tooltip-$name", $msgParams );
1915 if ( !$message->exists() && str_starts_with( $name,
'ca-nstab-' ) ) {
1916 $message = $localizer->msg(
'tooltip-ca-nstab' );
1919 if ( $message->isDisabled() ) {
1922 $tooltip = $message->text();
1923 # Compatibility: formerly some tooltips had [alt-.] hardcoded
1924 $tooltip = preg_replace(
"/ ?\[alt-.\]$/",
'', $tooltip );
1927 $options = (array)$options;
1929 if ( in_array(
'nonexisting', $options ) ) {
1930 $tooltip = $localizer->msg(
'red-link-title', $tooltip ?:
'' )->text();
1932 if ( in_array(
'withaccess', $options ) ) {
1933 $accesskey = self::accesskey( $name, $localizer );
1934 if ( $accesskey !==
false ) {
1936 if ( $tooltip ===
false || $tooltip ===
'' ) {
1937 $tooltip = $localizer->msg(
'brackets', $accesskey )->text();
1939 $tooltip .= $localizer->msg(
'word-separator' )->text();
1940 $tooltip .= $localizer->msg(
'brackets', $accesskey )->text();
1963 public static function accesskey( $name, $localizer =
null ) {
1964 if ( !isset( self::$accesskeycache[$name] ) ) {
1965 if ( !$localizer ) {
1966 $localizer = self::getContextFromMain();
1968 $msg = $localizer->msg(
"accesskey-$name" );
1971 if ( !$msg->exists() && str_starts_with( $name,
'ca-nstab-' ) ) {
1972 $msg = $localizer->msg(
'accesskey-ca-nstab' );
1974 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
1976 return self::$accesskeycache[$name];
1998 $canHide = $performer->
isAllowed(
'deleterevision' );
1999 $canHideHistory = $performer->
isAllowed(
'deletedhistory' );
2000 if ( !$canHide && !( $revRecord->
getVisibility() && $canHideHistory ) ) {
2004 if ( !$revRecord->
userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2005 return self::revDeleteLinkDisabled( $canHide );
2007 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2008 getPrefixedDBkey( $title );
2009 if ( $revRecord->
getId() ) {
2013 'type' =>
'revision',
2014 'target' => $prefixedDbKey,
2015 'ids' => $revRecord->
getId()
2021 'type' =>
'archive',
2022 'target' => $prefixedDbKey,
2026 return self::revDeleteLink(
2028 $revRecord->
isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2045 public static function revDeleteLink( $query = [], $restricted =
false, $delete =
true ) {
2046 $sp = SpecialPage::getTitleFor(
'Revisiondelete' );
2047 $msgKey = $delete ?
'rev-delundel' :
'rev-showdeleted';
2048 $html =
wfMessage( $msgKey )->escaped();
2049 $tag = $restricted ?
'strong' :
'span';
2050 $link = self::link( $sp, $html, [], $query, [
'known',
'noclasses' ] );
2053 [
'class' =>
'mw-revdelundel-link' ],
2054 wfMessage(
'parentheses' )->rawParams( $link )->escaped()
2070 $msgKey = $delete ?
'rev-delundel' :
'rev-showdeleted';
2071 $html =
wfMessage( $msgKey )->escaped();
2072 $htmlParentheses =
wfMessage(
'parentheses' )->rawParams( $html )->escaped();
2073 return Xml::tags(
'span', [
'class' =>
'mw-revdelundel-link' ], $htmlParentheses );
2091 array $msgParams = [],
2095 $options = (array)$options;
2096 $options[] =
'withaccess';
2099 if ( !$localizer ) {
2100 $localizer = self::getContextFromMain();
2104 'title' => self::titleAttrib( $name, $options, $msgParams, $localizer ),
2105 'accesskey' => self::accesskey( $name, $localizer )
2107 if ( $attribs[
'title'] ===
false ) {
2108 unset( $attribs[
'title'] );
2110 if ( $attribs[
'accesskey'] ===
false ) {
2111 unset( $attribs[
'accesskey'] );
2123 public static function tooltip( $name, $options =
null ) {
2124 $tooltip = self::titleAttrib( $name, $options );
2125 if ( $tooltip ===
false ) {
2128 return Xml::expandAttributes( [
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.
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') &&MW_ENTRY_POINT !=='cli' $wgLang
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgTitle
Marks HTML that shouldn't be escaped.
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 ...
Group all the pieces relevant to the context of a request into one instance.
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()
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,...
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.