51 use Wikimedia\Assert\Assert;
52 use Wikimedia\IPUtils;
53 use Wikimedia\Parsoid\Core\TOCData;
55 use Wikimedia\RemexHtml\Serializer\SerializerNode;
117 $target, $html =
null, $customAttribs = [], $query = [], $options = []
120 wfWarn( __METHOD__ .
': Requires $target to be a LinkTarget object.', 2 );
121 return "<!-- ERROR -->$html";
125 $options = (array)$options;
128 $linkRenderer = $services->getLinkRendererFactory()
129 ->createFromLegacyOptions( $options );
131 $linkRenderer = $services->getLinkRenderer();
134 if ( $html !==
null ) {
140 if ( in_array(
'known', $options,
true ) ) {
141 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
144 if ( in_array(
'broken', $options,
true ) ) {
145 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
148 if ( in_array(
'noclasses', $options,
true ) ) {
149 return $linkRenderer->makePreloadedLink( $target, $text,
'', $customAttribs, $query );
152 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 =
'' ) {
196 $attrs[
'class'] =
'mw-selflink-fragment';
197 $attrs[
'href'] =
'#' . $hash;
200 $attrs[
'class'] =
'mw-selflink selflink';
204 if ( !$hookRunner->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
209 $html = htmlspecialchars( $nt->getPrefixedText() );
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
334 if ( !$hookRunner->onImageBeforeProduceHTML( $dummy,
$title,
336 $file, $frameParams, $handlerParams, $time,
$res,
338 $parser, $query, $widthOption )
343 if (
$file && !
$file->allowInlineDisplay() ) {
344 wfDebug( __METHOD__ .
': ' .
$title->getPrefixedDBkey() .
' does not allow inline display' );
349 $page = $handlerParams[
'page'] ??
false;
350 if ( !isset( $frameParams[
'align'] ) ) {
351 $frameParams[
'align'] =
'';
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 ( isset( $frameParams[
'frameless'] ) ) {
456 $rdfaType .=
'/Frameless';
458 $srcWidth =
$file->getWidth( $page );
459 # For "frameless" option: do not present an image bigger than the
460 # source (for bitmap-style images). This is the same behavior as the
461 # "thumb" option does it already.
462 if ( $srcWidth && !
$file->mustRender() && $handlerParams[
'width'] > $srcWidth ) {
463 $handlerParams[
'width'] = $srcWidth;
468 if (
$file && isset( $handlerParams[
'width'] ) ) {
469 # Create a resized image, without the additional thumbnail features
470 $thumb =
$file->transform( $handlerParams );
475 if ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) ) {
476 $rdfaType =
'mw:Error ' . $rdfaType;
478 if ( $enableLegacyMediaDOM ) {
479 $label = $frameParams[
'title'];
481 if ( $currentExists && !$thumb ) {
482 $label =
wfMessage(
'thumbnail_error',
'' )->text();
483 } elseif ( $thumb && $thumb->isError() ) {
486 'Unknown MediaTransformOutput: ' . get_class( $thumb )
488 $label = $thumb->toText();
490 $label = $frameParams[
'alt'] ??
'';
494 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams, $currentExists
502 if ( isset( $frameParams[
'alt'] ) ) {
503 $params[
'alt'] = $frameParams[
'alt'];
505 $params[
'title'] = $frameParams[
'title'];
506 if ( $enableLegacyMediaDOM ) {
508 'valign' => $frameParams[
'valign'] ??
false,
509 'img-class' => $frameParams[
'class'],
511 if ( isset( $frameParams[
'border'] ) ) {
512 $params[
'img-class'] .= ( $params[
'img-class'] !==
'' ?
' ' :
'' ) .
'thumbborder';
516 'img-class' =>
'mw-file-element',
520 $s = $thumb->toHtml( $params );
523 if ( $enableLegacyMediaDOM ) {
524 if ( $frameParams[
'align'] !=
'' ) {
527 [
'class' =>
'float' . $frameParams[
'align'] ],
531 return str_replace(
"\n",
' ', $prefix . $s . $postfix );
537 if ( $frameParams[
'align'] !=
'' ) {
540 $classes[] =
"mw-halign-{$frameParams['align']}";
542 'figcaption', [], $frameParams[
'caption'] ??
''
544 } elseif ( isset( $frameParams[
'valign'] ) ) {
548 $classes[] =
"mw-valign-{$frameParams['valign']}";
551 if ( isset( $frameParams[
'border'] ) ) {
552 $classes[] =
'mw-image-border';
555 if ( isset( $frameParams[
'class'] ) ) {
556 $classes[] = $frameParams[
'class'];
561 'typeof' => $rdfaType,
566 return str_replace(
"\n",
' ', $s );
579 if ( isset( $frameParams[
'link-url'] ) && $frameParams[
'link-url'] !==
'' ) {
580 $mtoParams[
'custom-url-link'] = $frameParams[
'link-url'];
581 if ( isset( $frameParams[
'link-target'] ) ) {
582 $mtoParams[
'custom-target-link'] = $frameParams[
'link-target'];
585 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams[
'link-url'] );
586 foreach ( $extLinkAttrs as $name => $val ) {
588 $mtoParams[
'parser-extlink-' . $name] = $val;
591 } elseif ( isset( $frameParams[
'link-title'] ) && $frameParams[
'link-title'] !==
'' ) {
594 $linkRenderer->normalizeTarget( $frameParams[
'link-title'] )
596 if ( isset( $frameParams[
'link-title-query'] ) ) {
597 $mtoParams[
'custom-title-link-query'] = $frameParams[
'link-title-query'];
599 } elseif ( !empty( $frameParams[
'no-link'] ) ) {
602 $mtoParams[
'desc-link'] =
true;
603 $mtoParams[
'desc-query'] = $query;
622 $params = [], $framed =
false, $manualthumb =
''
630 if ( $manualthumb ) {
631 $frameParams[
'manualthumb'] = $manualthumb;
632 } elseif ( $framed ) {
633 $frameParams[
'framed'] =
true;
634 } elseif ( !isset( $params[
'width'] ) ) {
635 $classes[] =
'mw-default-size';
638 $title,
$file, $frameParams, $params,
false,
'', $classes
655 $time =
false, $query =
'', array $classes = [], ?
Parser $parser =
null
662 $page = $handlerParams[
'page'] ??
false;
663 $lang = $handlerParams[
'lang'] ??
false;
665 if ( !isset( $frameParams[
'align'] ) ) {
666 $frameParams[
'align'] =
'';
667 if ( $enableLegacyMediaDOM ) {
668 $frameParams[
'align'] =
'right';
671 if ( !isset( $frameParams[
'caption'] ) ) {
672 $frameParams[
'caption'] =
'';
675 if ( empty( $handlerParams[
'width'] ) ) {
677 $handlerParams[
'width'] = isset( $frameParams[
'upright'] ) ? 130 : 180;
682 $manualthumb =
false;
683 $rdfaType =
'mw:File/Thumb';
687 if ( !isset( $frameParams[
'manualthumb'] ) && isset( $frameParams[
'framed'] ) ) {
688 $rdfaType =
'mw:File/Frame';
690 $outerWidth = $handlerParams[
'width'] + 2;
692 if ( isset( $frameParams[
'manualthumb'] ) ) {
693 # Use manually specified thumbnail
695 if ( $manual_title ) {
696 $manual_img = $services->getRepoGroup()
697 ->findFile( $manual_title );
699 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
703 } elseif ( isset( $frameParams[
'framed'] ) ) {
705 $thumb =
$file->getUnscaledThumb( $handlerParams );
707 $rdfaType =
'mw:File/Frame';
709 # Do not present an image bigger than the source, for bitmap-style images
710 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
711 $srcWidth =
$file->getWidth( $page );
712 if ( $srcWidth && !
$file->mustRender() && $handlerParams[
'width'] > $srcWidth ) {
713 $handlerParams[
'width'] = $srcWidth;
715 $thumb =
$file->transform( $handlerParams );
719 $outerWidth = $thumb->getWidth() + 2;
721 $outerWidth = $handlerParams[
'width'] + 2;
726 $linkTitleQuery = [];
727 if ( $page ||
$lang ) {
729 $linkTitleQuery[
'page'] = $page;
732 $linkTitleQuery[
'lang'] =
$lang;
734 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
735 # So we don't need to pass it here in $query. However, the URL for the
736 # zoom icon still needs it, so we make a unique query for it. See T16771
741 && !isset( $frameParams[
'link-title'] )
742 && !isset( $frameParams[
'link-url'] )
743 && !isset( $frameParams[
'no-link'] ) ) {
744 $frameParams[
'link-title'] =
$title;
745 $frameParams[
'link-title-query'] = $linkTitleQuery;
748 if ( $frameParams[
'align'] !=
'' ) {
750 $classes[] =
"mw-halign-{$frameParams['align']}";
753 if ( isset( $frameParams[
'class'] ) ) {
754 $classes[] = $frameParams[
'class'];
759 if ( $enableLegacyMediaDOM ) {
760 $s .=
"<div class=\"thumb t{$frameParams['align']}\">"
761 .
"<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
765 $rdfaType =
'mw:Error ' . $rdfaType;
767 if ( !$enableLegacyMediaDOM ) {
768 $label = $frameParams[
'alt'] ??
'';
771 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams,
false
774 } elseif ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) ) {
775 $rdfaType =
'mw:Error ' . $rdfaType;
776 if ( $enableLegacyMediaDOM ) {
777 $s .=
wfMessage(
'thumbnail_error',
'' )->escaped();
779 if ( $thumb && $thumb->isError() ) {
782 'Unknown MediaTransformOutput: ' . get_class( $thumb )
784 $label = $thumb->toText();
786 $label =
wfMessage(
'thumbnail_error',
'' )->text();
789 $title, $label,
'',
'',
'', (
bool)$time, $handlerParams,
true
794 if ( !$noscale && !$manualthumb ) {
801 if ( isset( $frameParams[
'alt'] ) ) {
802 $params[
'alt'] = $frameParams[
'alt'];
804 if ( $enableLegacyMediaDOM ) {
806 'img-class' => ( isset( $frameParams[
'class'] ) && $frameParams[
'class'] !==
''
807 ? $frameParams[
'class'] .
' '
808 :
'' ) .
'thumbimage'
812 'img-class' =>
'mw-file-element',
815 if ( $rdfaType ===
'mw:File/Thumb' && $parser ) {
816 $params[
'magnify-resource'] = $url;
817 $parser->getOutput()->addModules( [
'mediawiki.page.media' ] );
821 $s .= $thumb->toHtml( $params );
822 if ( isset( $frameParams[
'framed'] ) ) {
828 'class' =>
'internal',
829 'title' =>
wfMessage(
'thumbnail-more' )->text(),
835 if ( $enableLegacyMediaDOM ) {
836 $s .=
' <div class="thumbcaption">' . $zoomIcon . $frameParams[
'caption'] .
'</div></div></div>';
837 return str_replace(
"\n",
' ', $s );
841 'figcaption', [], $frameParams[
'caption'] ??
''
846 'typeof' => $rdfaType,
851 return str_replace(
"\n",
' ', $s );
864 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
866 $hp15[
'width'] = round( $hp[
'width'] * 1.5 );
868 $hp20[
'width'] = $hp[
'width'] * 2;
869 if ( isset( $hp[
'height'] ) ) {
870 $hp15[
'height'] = round( $hp[
'height'] * 1.5 );
871 $hp20[
'height'] = $hp[
'height'] * 2;
874 $thumb15 =
$file->transform( $hp15 );
875 $thumb20 =
$file->transform( $hp20 );
876 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
877 $thumb->responsiveUrls[
'1.5'] = $thumb15->getUrl();
879 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
880 $thumb->responsiveUrls[
'2'] = $thumb20->getUrl();
900 $title, $label =
'', $query =
'', $unused1 =
'', $unused2 =
'',
901 $time =
false, array $handlerParams = [],
bool $currentExists =
false
904 wfWarn( __METHOD__ .
': Requires $title to be a LinkTarget object.' );
905 return "<!-- ERROR -->" . htmlspecialchars( $label );
910 $mainConfig = $services->getMainConfig();
914 if ( $label ==
'' ) {
915 $label =
$title->getPrefixedText();
919 'class' =>
'mw-file-element mw-broken-media',
921 'data-width' => $handlerParams[
'width'] ??
null,
922 'data-height' => $handlerParams[
'height'] ??
null,
926 $html = htmlspecialchars( $label, ENT_COMPAT );
929 $repoGroup = $services->getRepoGroup();
930 $currentExists = $currentExists ||
931 ( $time && $repoGroup->findFile(
$title ) !== false );
933 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
938 $repoGroup->getLocalRepo()->checkRedirect(
$title )
944 [
'class' =>
'mw-redirect' ],
946 [
'known',
'noclasses' ]
950 'href' => self::getUploadUrl(
$title, $query ),
952 'title' =>
$title->getPrefixedText()
960 [
'known',
'noclasses' ]
977 if ( $query !=
'' ) {
981 if ( $uploadMissingFileUrl ) {
985 if ( $uploadNavigationUrl ) {
991 return $upload->getLocalURL( $q );
1005 $title, [
'time' => $time ]
1024 $url =
$file->getUrl();
1025 $class =
'internal';
1031 $alt =
$title->getText();
1032 if ( $html ==
'' ) {
1046 wfDebug(
"Hook LinkerMakeMediaLinkFile changed the output of link "
1047 .
"with url {$url} and text {$html} to {$ret}" );
1065 $queryPos = strpos( $name,
'?' );
1066 if ( $queryPos !==
false ) {
1067 $getParams =
wfCgiToArray( substr( $name, $queryPos + 1 ) );
1068 $name = substr( $name, 0, $queryPos );
1073 $slashPos = strpos( $name,
'/' );
1074 if ( $slashPos !==
false ) {
1075 $subpage = substr( $name, $slashPos + 1 );
1076 $name = substr( $name, 0, $slashPos );
1082 $key = strtolower( $name );
1112 $linktype =
'', $attribs = [],
$title =
null
1115 $class =
'external';
1117 $class .=
" $linktype";
1119 if ( isset( $attribs[
'class'] ) && $attribs[
'class'] ) {
1120 $class .=
" {$attribs['class']}";
1122 $attribs[
'class'] = $class;
1125 $text = htmlspecialchars( $text, ENT_COMPAT );
1132 if ( !isset( $attribs[
'rel'] ) || $attribs[
'rel'] ===
'' ) {
1133 $attribs[
'rel'] = $newRel;
1134 } elseif ( $newRel !==
null ) {
1136 $newRels = explode(
' ', $newRel );
1137 $oldRels = explode(
' ', $attribs[
'rel'] );
1138 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1139 $attribs[
'rel'] = implode(
' ', $combined );
1143 $url, $text, $link, $attribs, $linktype );
1145 wfDebug(
"Hook LinkerMakeExternalLink changed the output of link "
1146 .
"with url {$url} and text {$text} to {$link}" );
1149 $attribs[
'href'] = $url;
1168 $altUserName =
false,
1171 if ( $userName ===
'' || $userName ===
false || $userName ===
null ) {
1172 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1173 'that need to be fixed?' );
1174 return wfMessage(
'empty-username' )->parse();
1177 $classes =
'mw-userlink';
1179 $classes .=
' mw-tempuserlink';
1181 } elseif ( $userId == 0 ) {
1185 $classes .=
' mw-extuserlink';
1186 } elseif ( $altUserName ===
false ) {
1187 $altUserName = IPUtils::prettifyIP( $userName );
1189 $classes .=
' mw-anonuserlink';
1196 '<bdi>' . htmlspecialchars( $altUserName !==
false ? $altUserName : $userName ) .
'</bdi>';
1198 if ( isset( $attributes[
'class'] ) ) {
1199 $attributes[
'class'] .=
' ' . $classes;
1201 $attributes[
'class'] = $classes;
1205 ?
self::link( $page, $linkText, $attributes )
1224 $userId, $userText, $redContribsWhenNoEdits =
false, $flags = 0, $edits =
null,
1225 $useParentheses =
true
1227 if ( $userText ===
'' ) {
1228 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1229 'that need to be fixed?' );
1230 return ' ' .
wfMessage(
'empty-username' )->parse();
1235 $talkable = !( $disableAnonTalk && $userId == 0 );
1237 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1251 $attribs[
'class'] =
'mw-usertoollinks-contribs';
1252 if ( $redContribsWhenNoEdits ) {
1253 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1255 $edits = $user->getEditCount();
1257 if ( $edits === 0 ) {
1260 $attribs[
'class'] .=
' mw-usertoollinks-contribs-no-edits';
1268 if ( $blockable && $userCanBlock ) {
1273 if ( $addEmailLink && $user->canSendEmail() ) {
1277 (
new HookRunner( $services->getHookContainer() ) )->onUserToolLinksEdit( $userId, $userText, $items );
1283 if ( $useParentheses ) {
1284 return wfMessage(
'word-separator' )->escaped()
1285 .
'<span class="mw-usertoollinks">'
1286 .
wfMessage(
'parentheses' )->rawParams(
$wgLang->pipeList( $items ) )->escaped()
1291 foreach ( $items as $tool ) {
1294 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1295 implode(
' ', $tools ) .
'</span>';
1308 $userId, $userText, $edits =
null, $useParentheses =
true
1320 if ( $userText ===
'' ) {
1321 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1322 'that need to be fixed?' );
1323 return wfMessage(
'empty-username' )->parse();
1327 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-talk' ];
1328 $linkText =
wfMessage(
'talkpagelinktext' )->escaped();
1330 return $userTalkPage
1331 ?
self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1342 if ( $userText ===
'' ) {
1343 wfDebug( __METHOD__ .
' received an empty username. Are there database errors ' .
1344 'that need to be fixed?' );
1345 return wfMessage(
'empty-username' )->parse();
1349 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-block' ];
1363 if ( $userText ===
'' ) {
1364 wfLogWarning( __METHOD__ .
' received an empty username. Are there database errors ' .
1365 'that need to be fixed?' );
1366 return wfMessage(
'empty-username' )->parse();
1370 $moreLinkAttribs = [
'class' =>
'mw-usertoollinks-mail' ];
1392 $revUser = $revRecord->
getUser(
1397 $link =
self::userLink( $revUser->getId(), $revUser->getName() );
1400 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1405 return '<span class="' . $class .
'">' . $link .
'</span>';
1417 $class =
'history-deleted';
1419 $class .=
' mw-history-suppressed';
1439 $useParentheses =
true
1444 $revUser = $revRecord->
getUser(
1449 $link = self::userLink(
1451 $revUser->getName(),
1453 [
'data-mw-revid' => $revRecord->
getId() ]
1454 ) . self::userToolLinks(
1456 $revUser->getName(),
1464 $link =
wfMessage(
'rev-deleted-user' )->escaped();
1468 $class = self::getRevisionDeletedClass( $revRecord );
1469 return ' <span class="' . $class .
' mw-userlink">' . $link .
'</span>';
1487 static function ( SerializerNode $node ):
bool {
1488 return $node->name ===
'a' && isset( $node->attrs[
'href'] );
1490 static function ( SerializerNode $node ): SerializerNode {
1491 $node->attrs[
'href'] =
1519 $comment,
$title =
null, $local =
false, $wikiId =
null
1522 return $formatter->format( $comment,
$title, $local, $wikiId );
1545 $comment,
$title =
null, $local =
false, $wikiId =
null
1548 return $formatter->formatLinksUnsafe( $comment,
$title, $local, $wikiId );
1560 # :Foobar -- override special treatment of prefix (images, language links)
1561 # /Foobar -- convert to CurrentPage/Foobar
1562 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1563 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1564 # ../Foobar -- convert to CurrentPage/Foobar,
1565 # (from CurrentPage/CurrentSubPage)
1566 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1567 # (from CurrentPage/CurrentSubPage)
1569 $ret = $target; #
default return value is no change
1571 # Some namespaces don't allow subpages,
1572 # so only perform processing if subpages are allowed
1575 hasSubpages( $contextTitle->getNamespace() )
1577 $hash = strpos( $target,
'#' );
1578 if ( $hash !==
false ) {
1579 $suffix = substr( $target, $hash );
1580 $target = substr( $target, 0, $hash );
1585 $target = trim( $target );
1587 getPrefixedText( $contextTitle );
1588 # Look at the first character
1589 if ( $target !=
'' && $target[0] ===
'/' ) {
1590 # / at end means we don't want the slash to be shown
1592 $trailingSlashes = preg_match_all(
'%(/+)$%', $target, $m );
1593 if ( $trailingSlashes ) {
1594 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1596 $noslash = substr( $target, 1 );
1599 $ret = $contextPrefixedText .
'/' . trim( $noslash ) . $suffix;
1600 if ( $text ===
'' ) {
1601 $text = $target . $suffix;
1602 } #
this might be changed
for ugliness reasons
1604 # check for .. subpage backlinks
1606 $nodotdot = $target;
1607 while ( str_starts_with( $nodotdot,
'../' ) ) {
1609 $nodotdot = substr( $nodotdot, 3 );
1611 if ( $dotdotcount > 0 ) {
1612 $exploded = explode(
'/', $contextPrefixedText );
1613 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1614 $ret = implode(
'/', array_slice( $exploded, 0, -$dotdotcount ) );
1615 # / at the end means don't show full path
1616 if ( substr( $nodotdot, -1, 1 ) ===
'/' ) {
1617 $nodotdot = rtrim( $nodotdot,
'/' );
1618 if ( $text ===
'' ) {
1619 $text = $nodotdot . $suffix;
1622 $nodotdot = trim( $nodotdot );
1623 if ( $nodotdot !=
'' ) {
1624 $ret .=
'/' . $nodotdot;
1655 $comment,
$title =
null, $local =
false, $wikiId =
null, $useParentheses =
true
1658 ->formatBlock( $comment,
$title, $local, $wikiId, $useParentheses );
1680 $useParentheses =
true
1684 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1694 $stxt =
wfMessage(
'historyempty' )->escaped();
1696 $stxt =
wfMessage(
'nbytes' )->numParams( $size )->escaped();
1698 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1719 return "</li>\n" . str_repeat(
"</ul>\n</li>\n", $level > 0 ? $level : 0 );
1733 public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex =
false ) {
1734 $classes =
"toclevel-$level";
1739 if ( $sectionIndex !==
false && $sectionIndex !==
'' && !str_starts_with( $sectionIndex,
"T-" ) ) {
1740 $classes .=
" tocsection-$sectionIndex";
1747 [
'href' =>
"#$linkAnchor" ],
1748 Html::element(
'span', [
'class' =>
'tocnumber' ], $tocnumber )
1778 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1780 'type' =>
'checkbox',
1782 'id' =>
'toctogglecheckbox',
1783 'class' =>
'toctogglecheckbox',
1784 'style' =>
'display:none',
1787 'class' =>
'toctitle',
1788 'lang' =>
$lang->getHtmlCode(),
1789 'dir' =>
$lang->getDir(),
1791 .
'<h2 id="mw-toc-heading">' .
$title .
'</h2>'
1792 .
'<span class="toctogglespan">'
1794 'class' =>
'toctogglelabel',
1799 .
"</ul>\n</div>\n";
1816 $maxTocLevel = $options[
'maxtoclevel'] ??
null;
1817 if ( $maxTocLevel ===
null ) {
1820 $config = $services->getMainConfig();
1823 foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
1824 $tocLevel = $section->tocLevel;
1825 if ( $tocLevel < $maxTocLevel ) {
1826 if ( $tocLevel > $lastLevel ) {
1827 $toc .= self::tocIndent();
1828 } elseif ( $tocLevel < $lastLevel ) {
1829 if ( $lastLevel < $maxTocLevel ) {
1830 $toc .= self::tocUnindent(
1831 $lastLevel - $tocLevel );
1833 $toc .= self::tocLineEnd();
1836 $toc .= self::tocLineEnd();
1839 $toc .= self::tocLine( $section->linkAnchor,
1840 $section->line, $section->number,
1841 $tocLevel, $section->index );
1842 $lastLevel = $tocLevel;
1845 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1846 $toc .= self::tocUnindent( $lastLevel - 1 );
1848 return self::tocList( $toc,
$lang );
1868 $link, $fallbackAnchor =
false
1870 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1872 if ( $fallbackAnchor !==
false && $fallbackAnchor !== $anchor ) {
1873 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1874 $fallback =
"<span id=\"$fallbackAnchor\"></span>";
1876 return "<h$level$attribs"
1877 .
"$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1889 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1891 if ( $trail !==
'' && preg_match( $regex, $trail, $m ) ) {
1892 [ , $inside, $trail ] = $m;
1894 return [ $inside, $trail ];
1934 $editCount = self::getRollbackEditCount( $revRecord );
1935 if ( $editCount ===
false ) {
1939 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1941 $services = MediaWikiServices::getInstance();
1944 if ( !(
new HookRunner( $services->getHookContainer() ) )->onLinkerGenerateRollbackLink(
1945 $revRecord, $context, $options, $inner ) ) {
1949 if ( !in_array(
'noBrackets', $options,
true ) ) {
1950 $inner = $context->msg(
'brackets' )->rawParams( $inner )->escaped();
1953 if ( $services->getUserOptionsLookup()
1954 ->getBoolOption( $context->getUser(),
'showrollbackconfirmation' )
1956 $stats = $services->getStatsdDataFactory();
1957 $stats->increment(
'rollbackconfirmation.event.load' );
1958 $context->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1961 return '<span class="mw-rollback-link">' . $inner .
'</span>';
1983 if ( func_num_args() > 1 ) {
1984 wfDeprecated( __METHOD__ .
' with $verify parameter',
'1.40' );
1986 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
1987 ->get( MainConfigNames::ShowRollbackEditCount );
1989 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1997 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1998 $res =
$dbr->newSelectQueryBuilder()
1999 ->select( [
'rev_user_text' =>
$revQuery[
'fields'][
'rev_user_text'],
'rev_deleted' ] )
2001 ->where( [
'rev_page' => $revRecord->
getPageId() ] )
2003 ->useIndex( [
'revision' =>
'rev_page_timestamp' ] )
2004 ->orderBy( [
'rev_timestamp',
'rev_id' ], SelectQueryBuilder::SORT_DESC )
2005 ->limit( $showRollbackEditCount + 1 )
2006 ->caller( __METHOD__ )
2009 $revUser = $revRecord->
getUser( RevisionRecord::RAW );
2010 $revUserText = $revUser ? $revUser->getName() :
'';
2014 foreach (
$res as $row ) {
2015 if ( $row->rev_user_text != $revUserText ) {
2016 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2017 || $row->rev_deleted & RevisionRecord::DELETED_USER
2030 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2058 $config = MediaWikiServices::getInstance()->getMainConfig();
2059 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2060 $miserMode = $config->get( MainConfigNames::MiserMode );
2062 $disableRollbackEditCountSpecialPage = [
'Recentchanges',
'Watchlist' ];
2067 $revUser = $revRecord->
getUser();
2068 $revUserText = $revUser ? $revUser->getName() :
'';
2071 'action' =>
'rollback',
2072 'from' => $revUserText,
2073 'token' => $context->getUser()->getEditToken(
'rollback' ),
2077 'data-mw' =>
'interface',
2078 'title' => $context->msg(
'tooltip-rollback' )->text()
2081 $options = [
'known',
'noclasses' ];
2083 if ( $context->getRequest()->getBool(
'bot' ) ) {
2085 $query[
'hidediff'] =
'1';
2086 $query[
'bot'] =
'1';
2090 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2091 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2092 $showRollbackEditCount =
false;
2099 $msg = [
'rollbacklink' ];
2100 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2101 if ( !is_numeric( $editCount ) ) {
2102 $editCount = self::getRollbackEditCount( $revRecord );
2105 if ( $editCount > $showRollbackEditCount ) {
2106 $msg = [
'rollbacklinkcount-morethan',
Message::numParam( $showRollbackEditCount ) ];
2107 } elseif ( $editCount ) {
2112 $html = $context->msg( ...$msg )->parse();
2113 return self::link(
$title, $html, $attrs, $query, $options );
2126 if ( count( $hiddencats ) > 0 ) {
2127 # Construct the HTML
2128 $outText =
'<div class="mw-hiddenCategoriesExplanation">';
2129 $outText .=
wfMessage(
'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2130 $outText .=
"</div><ul>\n";
2132 foreach ( $hiddencats as $titleObj ) {
2133 # If it's hidden, it must exist - no need to check with a LinkBatch
2135 . self::link( $titleObj,
null, [], [],
'known' )
2138 $outText .=
'</ul>';
2146 private static function getContextFromMain() {
2169 public static function titleAttrib( $name, $options =
null, array $msgParams = [], $localizer =
null ) {
2170 if ( !$localizer ) {
2171 $localizer = self::getContextFromMain();
2173 $message = $localizer->msg(
"tooltip-$name", $msgParams );
2174 if ( $message->isDisabled() ) {
2177 $tooltip = $message->text();
2178 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2179 $tooltip = preg_replace(
"/ ?\[alt-.\]$/",
'', $tooltip );
2182 $options = (array)$options;
2184 if ( in_array(
'nonexisting', $options ) ) {
2185 $tooltip = $localizer->msg(
'red-link-title', $tooltip ?:
'' )->text();
2187 if ( in_array(
'withaccess', $options ) ) {
2188 $accesskey = self::accesskey( $name, $localizer );
2189 if ( $accesskey !==
false ) {
2191 if ( $tooltip ===
false || $tooltip ===
'' ) {
2192 $tooltip = $localizer->msg(
'brackets', $accesskey )->text();
2194 $tooltip .= $localizer->msg(
'word-separator' )->text();
2195 $tooltip .= $localizer->msg(
'brackets', $accesskey )->text();
2217 public static function accesskey( $name, $localizer =
null ) {
2218 if ( !isset( self::$accesskeycache[$name] ) ) {
2219 if ( !$localizer ) {
2220 $localizer = self::getContextFromMain();
2222 $msg = $localizer->msg(
"accesskey-$name" );
2223 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2225 return self::$accesskeycache[$name];
2247 $canHide = $performer->
isAllowed(
'deleterevision' );
2248 $canHideHistory = $performer->
isAllowed(
'deletedhistory' );
2249 if ( !$canHide && !( $revRecord->
getVisibility() && $canHideHistory ) ) {
2253 if ( !$revRecord->
userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2254 return self::revDeleteLinkDisabled( $canHide );
2256 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2257 getPrefixedDBkey(
$title );
2258 if ( $revRecord->
getId() ) {
2262 'type' =>
'revision',
2263 'target' => $prefixedDbKey,
2264 'ids' => $revRecord->
getId()
2270 'type' =>
'archive',
2271 'target' => $prefixedDbKey,
2275 return self::revDeleteLink(
2277 $revRecord->
isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2294 public static function revDeleteLink( $query = [], $restricted =
false, $delete =
true ) {
2296 $msgKey = $delete ?
'rev-delundel' :
'rev-showdeleted';
2297 $html =
wfMessage( $msgKey )->escaped();
2298 $tag = $restricted ?
'strong' :
'span';
2299 $link = self::link( $sp, $html, [], $query, [
'known',
'noclasses' ] );
2302 [
'class' =>
'mw-revdelundel-link' ],
2303 wfMessage(
'parentheses' )->rawParams( $link )->escaped()
2319 $msgKey = $delete ?
'rev-delundel' :
'rev-showdeleted';
2320 $html =
wfMessage( $msgKey )->escaped();
2321 $htmlParentheses =
wfMessage(
'parentheses' )->rawParams( $html )->escaped();
2322 return Xml::tags(
'span', [
'class' =>
'mw-revdelundel-link' ], $htmlParentheses );
2336 private static function updateWatchstarTooltipMessage(
2337 string &$tooltip, array &$msgParams, $config, $user, $relevantTitle
2339 if ( !$config || !$user || !$relevantTitle ) {
2340 $mainContext = self::getContextFromMain();
2342 $config = $mainContext->getConfig();
2345 $user = $mainContext->getUser();
2347 if ( !$relevantTitle ) {
2348 $relevantTitle = $mainContext->getSkin()->getRelevantTitle();
2352 $isWatchlistExpiryEnabled = $config->get( MainConfigNames::WatchlistExpiry );
2353 if ( !$isWatchlistExpiryEnabled || !$relevantTitle || !$relevantTitle->canExist() ) {
2357 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2358 $watchedItem = $watchStore->getWatchedItem( $user, $relevantTitle );
2360 $diffInDays = $watchedItem->getExpiryInDays();
2362 if ( $diffInDays ) {
2363 $msgParams = [ $diffInDays ];
2365 $tooltip =
'ca-unwatch-expiring';
2367 $tooltip =
'ca-unwatch-expiring-hours';
2390 array $msgParams = [],
2395 $relevantTitle =
null
2397 $options = (array)$options;
2398 $options[] =
'withaccess';
2399 $tooltipTitle = $name;
2402 if ( !$localizer ) {
2403 $localizer = self::getContextFromMain();
2407 if ( $name ===
'ca-unwatch' ) {
2408 self::updateWatchstarTooltipMessage( $tooltipTitle, $msgParams, $config, $user, $relevantTitle );
2412 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams, $localizer ),
2413 'accesskey' => self::accesskey( $name, $localizer )
2415 if ( $attribs[
'title'] ===
false ) {
2416 unset( $attribs[
'title'] );
2418 if ( $attribs[
'accesskey'] ===
false ) {
2419 unset( $attribs[
'accesskey'] );
2431 public static function tooltip( $name, $options =
null ) {
2432 $tooltip = self::titleAttrib( $name, $options );
2433 if ( $tooltip ===
false ) {
2443 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 using $wgServer (or one of its alternatives).
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 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...
Marks HTML that shouldn't be escaped.
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 MaxTocLevel
Name constant for the MaxTocLevel 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,...
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
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.
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.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang