MediaWiki fundraising/REL1_35
Linker.php
Go to the documentation of this file.
1<?php
25use Wikimedia\IPUtils;
26
36class Linker {
40 public const TOOL_LINKS_NOBLOCK = 1;
41 public const TOOL_LINKS_EMAIL = 2;
42
86 public static function link(
87 $target, $html = null, $customAttribs = [], $query = [], $options = []
88 ) {
89 if ( !$target instanceof LinkTarget ) {
90 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
91 return "<!-- ERROR -->$html";
92 }
93
94 $services = MediaWikiServices::getInstance();
95 $options = (array)$options;
96 if ( $options ) {
97 // Custom options, create new LinkRenderer
98 if ( !isset( $options['stubThreshold'] ) ) {
99 $defaultLinkRenderer = $services->getLinkRenderer();
100 $options['stubThreshold'] = $defaultLinkRenderer->getStubThreshold();
101 }
102 $linkRenderer = $services->getLinkRendererFactory()
103 ->createFromLegacyOptions( $options );
104 } else {
105 $linkRenderer = $services->getLinkRenderer();
106 }
107
108 if ( $html !== null ) {
109 $text = new HtmlArmor( $html );
110 } else {
111 $text = null;
112 }
113
114 if ( in_array( 'known', $options, true ) ) {
115 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
116 }
117
118 if ( in_array( 'broken', $options, true ) ) {
119 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
120 }
121
122 if ( in_array( 'noclasses', $options, true ) ) {
123 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
124 }
125
126 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
127 }
128
142 public static function linkKnown(
143 $target, $html = null, $customAttribs = [],
144 $query = [], $options = [ 'known' ]
145 ) {
146 return self::link( $target, $html, $customAttribs, $query, $options );
147 }
148
164 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
165 $nt = Title::newFromLinkTarget( $nt );
166 $ret = "<a class=\"mw-selflink selflink\">{$prefix}{$html}</a>{$trail}";
167 if ( !Hooks::runner()->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
168 return $ret;
169 }
170
171 if ( $html == '' ) {
172 $html = htmlspecialchars( $nt->getPrefixedText() );
173 }
174 list( $inside, $trail ) = self::splitTrail( $trail );
175 return "<a class=\"mw-selflink selflink\">{$prefix}{$html}{$inside}</a>{$trail}";
176 }
177
188 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
189 // First we check whether the namespace exists or not.
190 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
191 if ( $namespace == NS_MAIN ) {
192 $name = $context->msg( 'blanknamespace' )->text();
193 } else {
194 $name = MediaWikiServices::getInstance()->getContentLanguage()->
195 getFormattedNsText( $namespace );
196 }
197 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
198 }
199
200 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
201 }
202
209 public static function normaliseSpecialPage( LinkTarget $target ) {
210 wfDeprecated( __METHOD__, '1.35' );
211 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
212 return $linkRenderer->normalizeTarget( $target );
213 }
214
223 private static function fnamePart( $url ) {
224 $basename = strrchr( $url, '/' );
225 if ( $basename === false ) {
226 $basename = $url;
227 } else {
228 $basename = substr( $basename, 1 );
229 }
230 return $basename;
231 }
232
243 public static function makeExternalImage( $url, $alt = '' ) {
244 if ( $alt == '' ) {
245 $alt = self::fnamePart( $url );
246 }
247 $img = '';
248 $success = Hooks::runner()->onLinkerMakeExternalImage( $url, $alt, $img );
249 if ( !$success ) {
250 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
251 . "with url {$url} and alt text {$alt} to {$img}" );
252 return $img;
253 }
254 return Html::element( 'img',
255 [
256 'src' => $url,
257 'alt' => $alt
258 ]
259 );
260 }
261
299 public static function makeImageLink( Parser $parser, LinkTarget $title,
300 $file, $frameParams = [], $handlerParams = [], $time = false,
301 $query = "", $widthOption = null
302 ) {
303 $title = Title::newFromLinkTarget( $title );
304 $res = null;
305 $dummy = new DummyLinker;
306 if ( !Hooks::runner()->onImageBeforeProduceHTML( $dummy, $title,
307 $file, $frameParams, $handlerParams, $time, $res,
308 $parser, $query, $widthOption )
309 ) {
310 return $res;
311 }
312
313 if ( $file && !$file->allowInlineDisplay() ) {
314 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display" );
315 return self::link( $title );
316 }
317
318 // Clean up parameters
319 $page = $handlerParams['page'] ?? false;
320 if ( !isset( $frameParams['align'] ) ) {
321 $frameParams['align'] = '';
322 }
323 if ( !isset( $frameParams['alt'] ) ) {
324 $frameParams['alt'] = '';
325 }
326 if ( !isset( $frameParams['title'] ) ) {
327 $frameParams['title'] = '';
328 }
329 if ( !isset( $frameParams['class'] ) ) {
330 $frameParams['class'] = '';
331 }
332
333 $prefix = $postfix = '';
334
335 if ( $frameParams['align'] == 'center' ) {
336 $prefix = '<div class="center">';
337 $postfix = '</div>';
338 $frameParams['align'] = 'none';
339 }
340 if ( $file && !isset( $handlerParams['width'] ) ) {
341 if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
342 // If its a vector image, and user only specifies height
343 // we don't want it to be limited by its "normal" width.
344 global $wgSVGMaxSize;
345 $handlerParams['width'] = $wgSVGMaxSize;
346 } else {
347 $handlerParams['width'] = $file->getWidth( $page );
348 }
349
350 if ( isset( $frameParams['thumbnail'] )
351 || isset( $frameParams['manualthumb'] )
352 || isset( $frameParams['framed'] )
353 || isset( $frameParams['frameless'] )
354 || !$handlerParams['width']
355 ) {
357
358 if ( $widthOption === null || !isset( $wgThumbLimits[$widthOption] ) ) {
359 $widthOption = User::getDefaultOption( 'thumbsize' );
360 }
361
362 // Reduce width for upright images when parameter 'upright' is used
363 if ( isset( $frameParams['upright'] ) && $frameParams['upright'] == 0 ) {
364 $frameParams['upright'] = $wgThumbUpright;
365 }
366
367 // For caching health: If width scaled down due to upright
368 // parameter, round to full __0 pixel to avoid the creation of a
369 // lot of odd thumbs.
370 $prefWidth = isset( $frameParams['upright'] ) ?
371 round( $wgThumbLimits[$widthOption] * $frameParams['upright'], -1 ) :
372 $wgThumbLimits[$widthOption];
373
374 // Use width which is smaller: real image width or user preference width
375 // Unless image is scalable vector.
376 if ( !isset( $handlerParams['height'] ) && ( $handlerParams['width'] <= 0 ||
377 $prefWidth < $handlerParams['width'] || $file->isVectorized() ) ) {
378 $handlerParams['width'] = $prefWidth;
379 }
380 }
381 }
382
383 if ( isset( $frameParams['thumbnail'] ) || isset( $frameParams['manualthumb'] )
384 || isset( $frameParams['framed'] )
385 ) {
386 # Create a thumbnail. Alignment depends on the writing direction of
387 # the page content language (right-aligned for LTR languages,
388 # left-aligned for RTL languages)
389 # If a thumbnail width has not been provided, it is set
390 # to the default user option as specified in Language*.php
391 if ( $frameParams['align'] == '' ) {
392 $frameParams['align'] = $parser->getTargetLanguage()->alignEnd();
393 }
394 return $prefix .
395 self::makeThumbLink2( $title, $file, $frameParams, $handlerParams, $time, $query ) .
396 $postfix;
397 }
398
399 if ( $file && isset( $frameParams['frameless'] ) ) {
400 $srcWidth = $file->getWidth( $page );
401 # For "frameless" option: do not present an image bigger than the
402 # source (for bitmap-style images). This is the same behavior as the
403 # "thumb" option does it already.
404 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
405 $handlerParams['width'] = $srcWidth;
406 }
407 }
408
409 if ( $file && isset( $handlerParams['width'] ) ) {
410 # Create a resized image, without the additional thumbnail features
411 $thumb = $file->transform( $handlerParams );
412 } else {
413 $thumb = false;
414 }
415
416 if ( !$thumb ) {
417 $s = self::makeBrokenImageLinkObj( $title, $frameParams['title'], '', '', '', $time == true );
418 } else {
419 self::processResponsiveImages( $file, $thumb, $handlerParams );
420 $params = [
421 'alt' => $frameParams['alt'],
422 'title' => $frameParams['title'],
423 'valign' => $frameParams['valign'] ?? false,
424 'img-class' => $frameParams['class'] ];
425 if ( isset( $frameParams['border'] ) ) {
426 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
427 }
428 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
429
430 $s = $thumb->toHtml( $params );
431 }
432 if ( $frameParams['align'] != '' ) {
433 $s = Html::rawElement(
434 'div',
435 [ 'class' => 'float' . $frameParams['align'] ],
436 $s
437 );
438 }
439 return str_replace( "\n", ' ', $prefix . $s . $postfix );
440 }
441
450 private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
451 $mtoParams = [];
452 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
453 $mtoParams['custom-url-link'] = $frameParams['link-url'];
454 if ( isset( $frameParams['link-target'] ) ) {
455 $mtoParams['custom-target-link'] = $frameParams['link-target'];
456 }
457 if ( $parser ) {
458 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
459 foreach ( $extLinkAttrs as $name => $val ) {
460 // Currently could include 'rel' and 'target'
461 $mtoParams['parser-extlink-' . $name] = $val;
462 }
463 }
464 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
465 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
466 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
467 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
468 );
469 } elseif ( !empty( $frameParams['no-link'] ) ) {
470 // No link
471 } else {
472 $mtoParams['desc-link'] = true;
473 $mtoParams['desc-query'] = $query;
474 }
475 return $mtoParams;
476 }
477
490 public static function makeThumbLinkObj( LinkTarget $title, $file, $label = '', $alt = '',
491 $align = 'right', $params = [], $framed = false, $manualthumb = ""
492 ) {
493 $frameParams = [
494 'alt' => $alt,
495 'caption' => $label,
496 'align' => $align
497 ];
498 if ( $framed ) {
499 $frameParams['framed'] = true;
500 }
501 if ( $manualthumb ) {
502 $frameParams['manualthumb'] = $manualthumb;
503 }
504 return self::makeThumbLink2( $title, $file, $frameParams, $params );
505 }
506
516 public static function makeThumbLink2( LinkTarget $title, $file, $frameParams = [],
517 $handlerParams = [], $time = false, $query = ""
518 ) {
519 $exists = $file && $file->exists();
520
521 $page = $handlerParams['page'] ?? false;
522 if ( !isset( $frameParams['align'] ) ) {
523 $frameParams['align'] = 'right';
524 }
525 if ( !isset( $frameParams['alt'] ) ) {
526 $frameParams['alt'] = '';
527 }
528 if ( !isset( $frameParams['title'] ) ) {
529 $frameParams['title'] = '';
530 }
531 if ( !isset( $frameParams['caption'] ) ) {
532 $frameParams['caption'] = '';
533 }
534
535 if ( empty( $handlerParams['width'] ) ) {
536 // Reduce width for upright images when parameter 'upright' is used
537 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
538 }
539 $thumb = false;
540 $noscale = false;
541 $manualthumb = false;
542
543 if ( !$exists ) {
544 $outerWidth = $handlerParams['width'] + 2;
545 } else {
546 if ( isset( $frameParams['manualthumb'] ) ) {
547 # Use manually specified thumbnail
548 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
549 if ( $manual_title ) {
550 $manual_img = MediaWikiServices::getInstance()->getRepoGroup()
551 ->findFile( $manual_title );
552 if ( $manual_img ) {
553 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
554 $manualthumb = true;
555 } else {
556 $exists = false;
557 }
558 }
559 } elseif ( isset( $frameParams['framed'] ) ) {
560 // Use image dimensions, don't scale
561 $thumb = $file->getUnscaledThumb( $handlerParams );
562 $noscale = true;
563 } else {
564 # Do not present an image bigger than the source, for bitmap-style images
565 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
566 $srcWidth = $file->getWidth( $page );
567 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
568 $handlerParams['width'] = $srcWidth;
569 }
570 $thumb = $file->transform( $handlerParams );
571 }
572
573 if ( $thumb ) {
574 $outerWidth = $thumb->getWidth() + 2;
575 } else {
576 $outerWidth = $handlerParams['width'] + 2;
577 }
578 }
579
580 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
581 # So we don't need to pass it here in $query. However, the URL for the
582 # zoom icon still needs it, so we make a unique query for it. See T16771
583 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
584 if ( $page ) {
585 $url = wfAppendQuery( $url, [ 'page' => $page ] );
586 }
587 if ( $manualthumb
588 && !isset( $frameParams['link-title'] )
589 && !isset( $frameParams['link-url'] )
590 && !isset( $frameParams['no-link'] ) ) {
591 $frameParams['link-url'] = $url;
592 }
593
594 $s = "<div class=\"thumb t{$frameParams['align']}\">"
595 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
596
597 if ( !$exists ) {
598 $s .= self::makeBrokenImageLinkObj( $title, $frameParams['title'], '', '', '', $time == true );
599 $zoomIcon = '';
600 } elseif ( !$thumb ) {
601 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
602 $zoomIcon = '';
603 } else {
604 if ( !$noscale && !$manualthumb ) {
605 self::processResponsiveImages( $file, $thumb, $handlerParams );
606 }
607 $params = [
608 'alt' => $frameParams['alt'],
609 'title' => $frameParams['title'],
610 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
611 ? $frameParams['class'] . ' '
612 : '' ) . 'thumbimage'
613 ];
614 $params = self::getImageLinkMTOParams( $frameParams, $query ) + $params;
615 $s .= $thumb->toHtml( $params );
616 if ( isset( $frameParams['framed'] ) ) {
617 $zoomIcon = "";
618 } else {
619 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
620 Html::rawElement( 'a', [
621 'href' => $url,
622 'class' => 'internal',
623 'title' => wfMessage( 'thumbnail-more' )->text() ],
624 "" ) );
625 }
626 }
627 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . "</div></div></div>";
628 return str_replace( "\n", ' ', $s );
629 }
630
639 public static function processResponsiveImages( $file, $thumb, $hp ) {
640 global $wgResponsiveImages;
641 if ( $wgResponsiveImages && $thumb && !$thumb->isError() ) {
642 $hp15 = $hp;
643 $hp15['width'] = round( $hp['width'] * 1.5 );
644 $hp20 = $hp;
645 $hp20['width'] = $hp['width'] * 2;
646 if ( isset( $hp['height'] ) ) {
647 $hp15['height'] = round( $hp['height'] * 1.5 );
648 $hp20['height'] = $hp['height'] * 2;
649 }
650
651 $thumb15 = $file->transform( $hp15 );
652 $thumb20 = $file->transform( $hp20 );
653 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
654 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
655 }
656 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
657 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
658 }
659 }
660 }
661
674 public static function makeBrokenImageLinkObj( $title, $label = '',
675 $query = '', $unused1 = '', $unused2 = '', $time = false
676 ) {
677 if ( !$title instanceof LinkTarget ) {
678 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
679 return "<!-- ERROR -->" . htmlspecialchars( $label );
680 }
681
682 $title = Title::castFromLinkTarget( $title );
683
685 if ( $label == '' ) {
686 $label = $title->getPrefixedText();
687 }
688 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
689 $currentExists = $time
690 && $repoGroup->findFile( $title ) !== false;
691
693 && !$currentExists
694 ) {
695 if ( $repoGroup->getLocalRepo()->checkRedirect( $title ) ) {
696 // We already know it's a redirect, so mark it accordingly
697 return self::link(
698 $title,
699 htmlspecialchars( $label ),
700 [ 'class' => 'mw-redirect' ],
701 wfCgiToArray( $query ),
702 [ 'known', 'noclasses' ]
703 );
704 }
705
706 return Html::element( 'a', [
707 'href' => self::getUploadUrl( $title, $query ),
708 'class' => 'new',
709 'title' => $title->getPrefixedText()
710 ], $label );
711 }
712
713 return self::link(
714 $title,
715 htmlspecialchars( $label ),
716 [],
717 wfCgiToArray( $query ),
718 [ 'known', 'noclasses' ]
719 );
720 }
721
730 protected static function getUploadUrl( $destFile, $query = '' ) {
732 $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
733 if ( $query != '' ) {
734 $q .= '&' . $query;
735 }
736
739 }
740
743 }
744
745 $upload = SpecialPage::getTitleFor( 'Upload' );
746
747 return $upload->getLocalURL( $q );
748 }
749
759 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
760 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
761 $title, [ 'time' => $time ]
762 );
763 return self::makeMediaLinkFile( $title, $img, $html );
764 }
765
778 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
779 if ( $file && $file->exists() ) {
780 $url = $file->getUrl();
781 $class = 'internal';
782 } else {
783 $url = self::getUploadUrl( $title );
784 $class = 'new';
785 }
786
787 $alt = $title->getText();
788 if ( $html == '' ) {
789 $html = $alt;
790 }
791
792 $ret = '';
793 $attribs = [
794 'href' => $url,
795 'class' => $class,
796 'title' => $alt
797 ];
798
799 if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
800 Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
801 ) {
802 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
803 . "with url {$url} and text {$html} to {$ret}" );
804 return $ret;
805 }
806
807 return Html::rawElement( 'a', $attribs, $html );
808 }
809
820 public static function specialLink( $name, $key = '' ) {
821 if ( $key == '' ) {
822 $key = strtolower( $name );
823 }
824
825 return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->escaped() );
826 }
827
846 public static function makeExternalLink( $url, $text, $escape = true,
847 $linktype = '', $attribs = [], $title = null
848 ) {
849 global $wgTitle;
850 $class = "external";
851 if ( $linktype ) {
852 $class .= " $linktype";
853 }
854 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
855 $class .= " {$attribs['class']}";
856 }
857 $attribs['class'] = $class;
858
859 if ( $escape ) {
860 $text = htmlspecialchars( $text );
861 }
862
863 if ( !$title ) {
865 }
866 $newRel = Parser::getExternalLinkRel( $url, $title );
867 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
868 $attribs['rel'] = $newRel;
869 } elseif ( $newRel !== null ) {
870 // Merge the rel attributes.
871 $newRels = explode( ' ', $newRel );
872 $oldRels = explode( ' ', $attribs['rel'] );
873 $combined = array_unique( array_merge( $newRels, $oldRels ) );
874 $attribs['rel'] = implode( ' ', $combined );
875 }
876 $link = '';
877 $success = Hooks::runner()->onLinkerMakeExternalLink(
878 $url, $text, $link, $attribs, $linktype );
879 if ( !$success ) {
880 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
881 . "with url {$url} and text {$text} to {$link}" );
882 return $link;
883 }
884 $attribs['href'] = $url;
885 return Html::rawElement( 'a', $attribs, $text );
886 }
887
896 public static function userLink( $userId, $userName, $altUserName = false ) {
897 if ( $userName === '' || $userName === false || $userName === null ) {
898 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
899 'that need to be fixed?' );
900 return wfMessage( 'empty-username' )->parse();
901 }
902
903 $classes = 'mw-userlink';
904 $page = null;
905 if ( $userId == 0 ) {
906 $page = ExternalUserNames::getUserLinkTitle( $userName );
907
908 if ( ExternalUserNames::isExternal( $userName ) ) {
909 $classes .= ' mw-extuserlink';
910 } elseif ( $altUserName === false ) {
911 $altUserName = IPUtils::prettifyIP( $userName );
912 }
913 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
914 } else {
915 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
916 }
917
918 // Wrap the output with <bdi> tags for directionality isolation
919 $linkText =
920 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
921
922 return $page
923 ? self::link( $page, $linkText, [ 'class' => $classes ] )
924 : Html::rawElement( 'span', [ 'class' => $classes ], $linkText );
925 }
926
941 public static function userToolLinks(
942 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
943 $useParentheses = true
944 ) {
945 if ( $userText === '' ) {
946 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
947 'that need to be fixed?' );
948 return ' ' . wfMessage( 'empty-username' )->parse();
949 }
950
952 $talkable = !( $wgDisableAnonTalk && $userId == 0 );
953 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
954 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
955
956 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
957 // No tools for an external user
958 return '';
959 }
960
961 $items = [];
962 if ( $talkable ) {
963 $items[] = self::userTalkLink( $userId, $userText );
964 }
965 if ( $userId ) {
966 // check if the user has an edit
967 $attribs = [];
968 $attribs['class'] = 'mw-usertoollinks-contribs';
969 if ( $redContribsWhenNoEdits ) {
970 if ( intval( $edits ) === 0 && $edits !== 0 ) {
971 $user = User::newFromId( $userId );
972 $edits = $user->getEditCount();
973 }
974 if ( $edits === 0 ) {
975 $attribs['class'] .= ' new';
976 }
977 }
978 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
979
980 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
981 }
982 $user = RequestContext::getMain()->getUser();
983 $userCanBlock = MediaWikiServices::getInstance()->getPermissionManager()
984 ->userHasRight( $user, 'block' );
985 if ( $blockable && $userCanBlock ) {
986 $items[] = self::blockLink( $userId, $userText );
987 }
988
989 if ( $addEmailLink && $user->canSendEmail() ) {
990 $items[] = self::emailLink( $userId, $userText );
991 }
992
993 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
994
995 if ( !$items ) {
996 return '';
997 }
998
999 if ( $useParentheses ) {
1000 return wfMessage( 'word-separator' )->escaped()
1001 . '<span class="mw-usertoollinks">'
1002 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1003 . '</span>';
1004 }
1005
1006 $tools = [];
1007 foreach ( $items as $tool ) {
1008 $tools[] = Html::rawElement( 'span', [], $tool );
1009 }
1010 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1011 implode( ' ', $tools ) . '</span>';
1012 }
1013
1023 public static function userToolLinksRedContribs(
1024 $userId, $userText, $edits = null, $useParentheses = true
1025 ) {
1026 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1027 }
1028
1035 public static function userTalkLink( $userId, $userText ) {
1036 if ( $userText === '' ) {
1037 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1038 'that need to be fixed?' );
1039 return wfMessage( 'empty-username' )->parse();
1040 }
1041
1042 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1043 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1044 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1045
1046 return $userTalkPage
1047 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1048 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1049 }
1050
1057 public static function blockLink( $userId, $userText ) {
1058 if ( $userText === '' ) {
1059 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1060 'that need to be fixed?' );
1061 return wfMessage( 'empty-username' )->parse();
1062 }
1063
1064 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1065 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1066
1067 return self::link( $blockPage,
1068 wfMessage( 'blocklink' )->escaped(),
1069 $moreLinkAttribs
1070 );
1071 }
1072
1078 public static function emailLink( $userId, $userText ) {
1079 if ( $userText === '' ) {
1080 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1081 'that need to be fixed?' );
1082 return wfMessage( 'empty-username' )->parse();
1083 }
1084
1085 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1086 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1087 return self::link( $emailPage,
1088 wfMessage( 'emaillink' )->escaped(),
1089 $moreLinkAttribs
1090 );
1091 }
1092
1101 public static function revUserLink( $rev, $isPublic = false ) {
1102 // TODO inject a user
1103 $user = RequestContext::getMain()->getUser();
1104
1105 if ( $rev instanceof Revision ) {
1106 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1107 $revRecord = $rev->getRevisionRecord();
1108 } else {
1109 $revRecord = $rev;
1110 }
1111
1112 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1113 $link = wfMessage( 'rev-deleted-user' )->escaped();
1114 } elseif ( RevisionRecord::userCanBitfield(
1115 $revRecord->getVisibility(),
1116 RevisionRecord::DELETED_USER,
1117 $user
1118 ) ) {
1119 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $user );
1120 $link = self::userLink(
1121 $revUser ? $revUser->getId() : 0,
1122 $revUser ? $revUser->getName() : ''
1123 );
1124 } else {
1125 $link = wfMessage( 'rev-deleted-user' )->escaped();
1126 }
1127 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1128 return '<span class="history-deleted">' . $link . '</span>';
1129 }
1130 return $link;
1131 }
1132
1142 public static function revUserTools( $rev, $isPublic = false, $useParentheses = true ) {
1143 // TODO inject a user
1144 $user = RequestContext::getMain()->getUser();
1145
1146 if ( $rev instanceof Revision ) {
1147 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1148 $revRecord = $rev->getRevisionRecord();
1149 } else {
1150 $revRecord = $rev;
1151 }
1152
1153 if ( RevisionRecord::userCanBitfield(
1154 $revRecord->getVisibility(),
1155 RevisionRecord::DELETED_USER,
1156 $user
1157 ) &&
1158 ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1159 ) {
1160 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $user );
1161 $userId = $revUser ? $revUser->getId() : 0;
1162 $userText = $revUser ? $revUser->getName() : '';
1163
1164 if ( $userId || $userText !== '' ) {
1165 $link = self::userLink( $userId, $userText )
1166 . self::userToolLinks( $userId, $userText, false, 0, null,
1167 $useParentheses );
1168 }
1169 }
1170
1171 if ( !isset( $link ) ) {
1172 $link = wfMessage( 'rev-deleted-user' )->escaped();
1173 }
1174
1175 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1176 return ' <span class="history-deleted mw-userlink">' . $link . '</span>';
1177 }
1178 return $link;
1179 }
1180
1199 public static function formatComment(
1200 $comment, $title = null, $local = false, $wikiId = null
1201 ) {
1202 # Sanitize text a bit:
1203 $comment = str_replace( "\n", " ", $comment );
1204 # Allow HTML entities (for T15815)
1205 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
1206
1207 # Render autocomments and make links:
1208 $comment = self::formatAutocomments( $comment, $title, $local, $wikiId );
1209 return self::formatLinksInComment( $comment, $title, $local, $wikiId );
1210 }
1211
1229 private static function formatAutocomments(
1230 $comment, $title = null, $local = false, $wikiId = null
1231 ) {
1232 // @todo $append here is something of a hack to preserve the status
1233 // quo. Someone who knows more about bidi and such should decide
1234 // (1) what sane rendering even *is* for an LTR edit summary on an RTL
1235 // wiki, both when autocomments exist and when they don't, and
1236 // (2) what markup will make that actually happen.
1237 $append = '';
1238 $comment = preg_replace_callback(
1239 // To detect the presence of content before or after the
1240 // auto-comment, we use capturing groups inside optional zero-width
1241 // assertions. But older versions of PCRE can't directly make
1242 // zero-width assertions optional, so wrap them in a non-capturing
1243 // group.
1244 '!(?:(?<=(.)))?/\*\s*(.*?)\s*\*/(?:(?=(.)))?!',
1245 function ( $match ) use ( $title, $local, $wikiId, &$append ) {
1246 global $wgLang;
1247
1248 // Ensure all match positions are defined
1249 $match += [ '', '', '', '' ];
1250
1251 $pre = $match[1] !== '';
1252 $auto = $match[2];
1253 $post = $match[3] !== '';
1254 $comment = null;
1255
1256 Hooks::runner()->onFormatAutocomments(
1257 $comment, $pre, $auto, $post, Title::castFromLinkTarget( $title ), $local,
1258 $wikiId );
1259
1260 if ( $comment === null ) {
1261 if ( $title ) {
1262 $section = $auto;
1263 # Remove links that a user may have manually put in the autosummary
1264 # This could be improved by copying as much of Parser::stripSectionName as desired.
1265 $section = str_replace( [
1266 '[[:',
1267 '[[',
1268 ']]'
1269 ], '', $section );
1270
1271 // We don't want any links in the auto text to be linked, but we still
1272 // want to show any [[ ]]
1273 $sectionText = str_replace( '[[', '&#91;[', $auto );
1274
1275 $section = substr( Parser::guessSectionNameFromStrippedText( $section ), 1 );
1276 if ( $section !== '' ) {
1277 if ( $local ) {
1278 $sectionTitle = new TitleValue( NS_MAIN, '', $section );
1279 } else {
1280 $sectionTitle = $title->createFragmentTarget( $section );
1281 }
1283 $sectionTitle,
1284 $wgLang->getArrow() . $wgLang->getDirMark() . $sectionText,
1285 $wikiId,
1286 'noclasses'
1287 );
1288 }
1289 }
1290 if ( $pre ) {
1291 # written summary $presep autocomment (summary /* section */)
1292 $pre = wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
1293 }
1294 if ( $post ) {
1295 # autocomment $postsep written summary (/* section */ summary)
1296 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
1297 }
1298 if ( $auto ) {
1299 $auto = '<span dir="auto"><span class="autocomment">' . $auto . '</span>';
1300 $append .= '</span>';
1301 }
1302 $comment = $pre . $auto;
1303 }
1304 return $comment;
1305 },
1306 $comment
1307 );
1308 return $comment . $append;
1309 }
1310
1330 public static function formatLinksInComment(
1331 $comment, $title = null, $local = false, $wikiId = null
1332 ) {
1333 return preg_replace_callback(
1334 '/
1335 \[\[
1336 \s*+ # ignore leading whitespace, the *+ quantifier disallows backtracking
1337 :? # ignore optional leading colon
1338 ([^[\]|]+) # 1. link target; page names cannot include [, ] or |
1339 (?:\|
1340 # 2. link text
1341 # Stop matching at ]] without relying on backtracking.
1342 ((?:]?[^\]])*+)
1343 )?
1344 \]\]
1345 ([^[]*) # 3. link trail (the text up until the next link)
1346 /x',
1347 function ( $match ) use ( $title, $local, $wikiId ) {
1348 $services = MediaWikiServices::getInstance();
1349
1350 $medians = '(?:';
1351 $medians .= preg_quote(
1352 $services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
1353 $medians .= '|';
1354 $medians .= preg_quote(
1355 $services->getContentLanguage()->getNsText( NS_MEDIA ),
1356 '/'
1357 ) . '):';
1358
1359 $comment = $match[0];
1360
1361 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
1362 if ( strpos( $match[1], '%' ) !== false ) {
1363 $match[1] = strtr(
1364 rawurldecode( $match[1] ),
1365 [ '<' => '&lt;', '>' => '&gt;' ]
1366 );
1367 }
1368
1369 # Handle link renaming [[foo|text]] will show link as "text"
1370 if ( $match[2] != "" ) {
1371 $text = $match[2];
1372 } else {
1373 $text = $match[1];
1374 }
1375 $submatch = [];
1376 $thelink = null;
1377 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
1378 # Media link; trail not supported.
1379 $linkRegexp = '/\[\[(.*?)\]\]/';
1380 $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
1381 if ( $title ) {
1382 $thelink = Linker::makeMediaLinkObj( $title, $text );
1383 }
1384 } else {
1385 # Other kind of link
1386 # Make sure its target is non-empty
1387 if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
1388 $match[1] = substr( $match[1], 1 );
1389 }
1390 if ( $match[1] !== false && $match[1] !== '' ) {
1391 if ( preg_match(
1392 $services->getContentLanguage()->linkTrail(),
1393 $match[3],
1394 $submatch
1395 ) ) {
1396 $trail = $submatch[1];
1397 } else {
1398 $trail = "";
1399 }
1400 $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
1401 list( $inside, $trail ) = Linker::splitTrail( $trail );
1402
1403 $linkText = $text;
1404 $linkTarget = Linker::normalizeSubpageLink( $title, $match[1], $linkText );
1405
1406 try {
1407 $target = $services->getTitleParser()->
1408 parseTitle( $linkTarget );
1409
1410 if ( $target->getText() == '' && !$target->isExternal()
1411 && !$local && $title
1412 ) {
1413 $target = $title->createFragmentTarget( $target->getFragment() );
1414 }
1415
1416 $thelink = Linker::makeCommentLink( $target, $linkText . $inside, $wikiId ) . $trail;
1417 } catch ( MalformedTitleException $e ) {
1418 // Fall through
1419 }
1420 }
1421 }
1422 if ( $thelink ) {
1423 // If the link is still valid, go ahead and replace it in!
1424 $comment = preg_replace(
1425 $linkRegexp,
1426 StringUtils::escapeRegexReplacement( $thelink ),
1427 $comment,
1428 1
1429 );
1430 }
1431
1432 return $comment;
1433 },
1434 $comment
1435 );
1436 }
1437
1451 public static function makeCommentLink(
1452 LinkTarget $linkTarget, $text, $wikiId = null, $options = []
1453 ) {
1454 if ( $wikiId !== null && !$linkTarget->isExternal() ) {
1455 $link = self::makeExternalLink(
1456 WikiMap::getForeignURL(
1457 $wikiId,
1458 $linkTarget->getNamespace() === 0
1459 ? $linkTarget->getDBkey()
1460 : MediaWikiServices::getInstance()->getNamespaceInfo()->
1461 getCanonicalName( $linkTarget->getNamespace() ) .
1462 ':' . $linkTarget->getDBkey(),
1463 $linkTarget->getFragment()
1464 ),
1465 $text,
1466 /* escape = */ false // Already escaped
1467 );
1468 } else {
1469 $link = self::link( $linkTarget, $text, [], [], $options );
1470 }
1471
1472 return $link;
1473 }
1474
1481 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1482 # Valid link forms:
1483 # Foobar -- normal
1484 # :Foobar -- override special treatment of prefix (images, language links)
1485 # /Foobar -- convert to CurrentPage/Foobar
1486 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1487 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1488 # ../Foobar -- convert to CurrentPage/Foobar,
1489 # (from CurrentPage/CurrentSubPage)
1490 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1491 # (from CurrentPage/CurrentSubPage)
1492
1493 $ret = $target; # default return value is no change
1494
1495 # Some namespaces don't allow subpages,
1496 # so only perform processing if subpages are allowed
1497 if (
1498 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1499 hasSubpages( $contextTitle->getNamespace() )
1500 ) {
1501 $hash = strpos( $target, '#' );
1502 if ( $hash !== false ) {
1503 $suffix = substr( $target, $hash );
1504 $target = substr( $target, 0, $hash );
1505 } else {
1506 $suffix = '';
1507 }
1508 # T9425
1509 $target = trim( $target );
1510 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1511 getPrefixedText( $contextTitle );
1512 # Look at the first character
1513 if ( $target != '' && $target[0] === '/' ) {
1514 # / at end means we don't want the slash to be shown
1515 $m = [];
1516 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1517 if ( $trailingSlashes ) {
1518 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1519 } else {
1520 $noslash = substr( $target, 1 );
1521 }
1522
1523 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1524 if ( $text === '' ) {
1525 $text = $target . $suffix;
1526 } # this might be changed for ugliness reasons
1527 } else {
1528 # check for .. subpage backlinks
1529 $dotdotcount = 0;
1530 $nodotdot = $target;
1531 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
1532 ++$dotdotcount;
1533 $nodotdot = substr( $nodotdot, 3 );
1534 }
1535 if ( $dotdotcount > 0 ) {
1536 $exploded = explode( '/', $contextPrefixedText );
1537 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1538 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1539 # / at the end means don't show full path
1540 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1541 $nodotdot = rtrim( $nodotdot, '/' );
1542 if ( $text === '' ) {
1543 $text = $nodotdot . $suffix;
1544 }
1545 }
1546 $nodotdot = trim( $nodotdot );
1547 if ( $nodotdot != '' ) {
1548 $ret .= '/' . $nodotdot;
1549 }
1550 $ret .= $suffix;
1551 }
1552 }
1553 }
1554 }
1555
1556 return $ret;
1557 }
1558
1574 public static function commentBlock(
1575 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1576 ) {
1577 // '*' used to be the comment inserted by the software way back
1578 // in antiquity in case none was provided, here for backwards
1579 // compatibility, acc. to brion -ævar
1580 if ( $comment == '' || $comment == '*' ) {
1581 return '';
1582 }
1583 $formatted = self::formatComment( $comment, $title, $local, $wikiId );
1584 if ( $useParentheses ) {
1585 $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
1586 $classNames = 'comment';
1587 } else {
1588 $classNames = 'comment comment--without-parentheses';
1589 }
1590 return " <span class=\"$classNames\">$formatted</span>";
1591 }
1592
1605 public static function revComment( $rev, $local = false, $isPublic = false,
1606 $useParentheses = true
1607 ) {
1608 // TODO inject a user
1609 $user = RequestContext::getMain()->getUser();
1610
1611 if ( $rev instanceof Revision ) {
1612 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1613 $revRecord = $rev->getRevisionRecord();
1614 } else {
1615 $revRecord = $rev;
1616 }
1617
1618 if ( $revRecord->getComment( RevisionRecord::RAW ) === null ) {
1619 return "";
1620 }
1621 if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) && $isPublic ) {
1622 $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
1623 } elseif ( RevisionRecord::userCanBitfield(
1624 $revRecord->getVisibility(),
1625 RevisionRecord::DELETED_COMMENT,
1626 $user
1627 ) ) {
1628 $comment = $revRecord->getComment( RevisionRecord::FOR_THIS_USER, $user );
1629 $block = self::commentBlock(
1630 $comment ? $comment->text : null,
1631 $revRecord->getPageAsLinkTarget(),
1632 $local,
1633 null,
1634 $useParentheses
1635 );
1636 } else {
1637 $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
1638 }
1639 if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
1640 return " <span class=\"history-deleted comment\">$block</span>";
1641 }
1642 return $block;
1643 }
1644
1650 public static function formatRevisionSize( $size ) {
1651 if ( $size == 0 ) {
1652 $stxt = wfMessage( 'historyempty' )->escaped();
1653 } else {
1654 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1655 }
1656 return "<span class=\"history-size mw-diff-bytes\">$stxt</span>";
1657 }
1658
1665 public static function tocIndent() {
1666 return "\n<ul>\n";
1667 }
1668
1676 public static function tocUnindent( $level ) {
1677 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1678 }
1679
1691 public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1692 $classes = "toclevel-$level";
1693 if ( $sectionIndex !== false ) {
1694 $classes .= " tocsection-$sectionIndex";
1695 }
1696
1697 // <li class="$classes"><a href="#$anchor"><span class="tocnumber">
1698 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1699 return Html::openElement( 'li', [ 'class' => $classes ] )
1700 . Html::rawElement( 'a',
1701 [ 'href' => "#$anchor" ],
1702 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1703 . ' '
1704 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1705 );
1706 }
1707
1715 public static function tocLineEnd() {
1716 return "</li>\n";
1717 }
1718
1727 public static function tocList( $toc, Language $lang = null ) {
1728 $lang = $lang ?? RequestContext::getMain()->getLanguage();
1729
1730 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1731
1732 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1733 . Html::element( 'input', [
1734 'type' => 'checkbox',
1735 'role' => 'button',
1736 'id' => 'toctogglecheckbox',
1737 'class' => 'toctogglecheckbox',
1738 'style' => 'display:none',
1739 ] )
1740 . Html::openElement( 'div', [
1741 'class' => 'toctitle',
1742 'lang' => $lang->getHtmlCode(),
1743 'dir' => $lang->getDir(),
1744 ] )
1745 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1746 . '<span class="toctogglespan">'
1747 . Html::label( '', 'toctogglecheckbox', [
1748 'class' => 'toctogglelabel',
1749 ] )
1750 . '</span>'
1751 . "</div>\n"
1752 . $toc
1753 . "</ul>\n</div>\n";
1754 }
1755
1764 public static function generateTOC( $tree, Language $lang = null ) {
1765 $toc = '';
1766 $lastLevel = 0;
1767 foreach ( $tree as $section ) {
1768 if ( $section['toclevel'] > $lastLevel ) {
1769 $toc .= self::tocIndent();
1770 } elseif ( $section['toclevel'] < $lastLevel ) {
1771 $toc .= self::tocUnindent(
1772 $lastLevel - $section['toclevel'] );
1773 } else {
1774 $toc .= self::tocLineEnd();
1775 }
1776
1777 $toc .= self::tocLine( $section['anchor'],
1778 $section['line'], $section['number'],
1779 $section['toclevel'], $section['index'] );
1780 $lastLevel = $section['toclevel'];
1781 }
1782 $toc .= self::tocLineEnd();
1783 return self::tocList( $toc, $lang );
1784 }
1785
1802 public static function makeHeadline( $level, $attribs, $anchor, $html,
1803 $link, $fallbackAnchor = false
1804 ) {
1805 $anchorEscaped = htmlspecialchars( $anchor );
1806 $fallback = '';
1807 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1808 $fallbackAnchor = htmlspecialchars( $fallbackAnchor );
1809 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1810 }
1811 return "<h$level$attribs"
1812 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1813 . $link
1814 . "</h$level>";
1815 }
1816
1823 public static function splitTrail( $trail ) {
1824 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1825 $inside = '';
1826 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1827 list( , $inside, $trail ) = $m;
1828 }
1829 return [ $inside, $trail ];
1830 }
1831
1861 public static function generateRollback( $rev, IContextSource $context = null,
1862 $options = [ 'verify' ]
1863 ) {
1864 if ( $rev instanceof Revision ) {
1865 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1866 $revRecord = $rev->getRevisionRecord();
1867 } else {
1868 $revRecord = $rev;
1869 }
1870
1871 if ( $context === null ) {
1872 $context = RequestContext::getMain();
1873 }
1874
1875 $editCount = false;
1876 if ( in_array( 'verify', $options, true ) ) {
1877 $editCount = self::getRollbackEditCount( $revRecord, true );
1878 if ( $editCount === false ) {
1879 return '';
1880 }
1881 }
1882
1883 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1884
1885 if ( !in_array( 'noBrackets', $options, true ) ) {
1886 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1887 }
1888
1889 if ( $context->getUser()->getBoolOption( 'showrollbackconfirmation' ) ) {
1890 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1891 $stats->increment( 'rollbackconfirmation.event.load' );
1892 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1893 }
1894
1895 return '<span class="mw-rollback-link">' . $inner . '</span>';
1896 }
1897
1916 public static function getRollbackEditCount( $rev, $verify ) {
1918
1919 if ( $rev instanceof Revision ) {
1920 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1921 $revRecord = $rev->getRevisionRecord();
1922 } else {
1923 $revRecord = $rev;
1924 }
1925
1926 if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
1927 // Nothing has happened, indicate this by returning 'null'
1928 return null;
1929 }
1930
1931 $dbr = wfGetDB( DB_REPLICA );
1932
1933 // Up to the value of $wgShowRollbackEditCount revisions are counted
1934 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1935 $res = $dbr->select(
1936 $revQuery['tables'],
1937 [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ],
1938 [ 'rev_page' => $revRecord->getPageId() ],
1939 __METHOD__,
1940 [
1941 'USE INDEX' => [ 'revision' => 'page_timestamp' ],
1942 'ORDER BY' => 'rev_timestamp DESC',
1943 'LIMIT' => $wgShowRollbackEditCount + 1
1944 ],
1945 $revQuery['joins']
1946 );
1947
1948 $revUser = $revRecord->getUser( RevisionRecord::RAW );
1949 $revUserText = $revUser ? $revUser->getName() : '';
1950
1951 $editCount = 0;
1952 $moreRevs = false;
1953 foreach ( $res as $row ) {
1954 if ( $row->rev_user_text != $revUserText ) {
1955 if ( $verify &&
1956 ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1957 || $row->rev_deleted & RevisionRecord::DELETED_USER
1958 ) ) {
1959 // If the user or the text of the revision we might rollback
1960 // to is deleted in some way we can't rollback. Similar to
1961 // the sanity checks in WikiPage::commitRollback.
1962 return false;
1963 }
1964 $moreRevs = true;
1965 break;
1966 }
1967 $editCount++;
1968 }
1969
1970 if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
1971 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
1972 // and there weren't any other revisions. That means that the current user is the only
1973 // editor, so we can't rollback
1974 return false;
1975 }
1976 return $editCount;
1977 }
1978
1993 public static function buildRollbackLink( $rev, IContextSource $context = null,
1994 $editCount = false
1995 ) {
1997
1998 if ( $rev instanceof Revision ) {
1999 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
2000 $revRecord = $rev->getRevisionRecord();
2001 } else {
2002 $revRecord = $rev;
2003 }
2004
2005 // To config which pages are affected by miser mode
2006 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2007
2008 if ( $context === null ) {
2009 $context = RequestContext::getMain();
2010 }
2011
2012 $title = $revRecord->getPageAsLinkTarget();
2013 $revUser = $revRecord->getUser();
2014 $revUserText = $revUser ? $revUser->getName() : '';
2015
2016 $query = [
2017 'action' => 'rollback',
2018 'from' => $revUserText,
2019 'token' => $context->getUser()->getEditToken( 'rollback' ),
2020 ];
2021
2022 $attrs = [
2023 'data-mw' => 'interface',
2024 'title' => $context->msg( 'tooltip-rollback' )->text()
2025 ];
2026
2027 $options = [ 'known', 'noclasses' ];
2028
2029 if ( $context->getRequest()->getBool( 'bot' ) ) {
2030 // T17999
2031 $query['hidediff'] = '1';
2032 $query['bot'] = '1';
2033 }
2034
2035 $disableRollbackEditCount = false;
2036 if ( $wgMiserMode ) {
2037 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2038 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2039 $disableRollbackEditCount = true;
2040 break;
2041 }
2042 }
2043 }
2044
2045 if ( !$disableRollbackEditCount
2046 && is_int( $wgShowRollbackEditCount )
2048 ) {
2049 if ( !is_numeric( $editCount ) ) {
2050 $editCount = self::getRollbackEditCount( $revRecord, false );
2051 }
2052
2053 if ( $editCount > $wgShowRollbackEditCount ) {
2054 $html = $context->msg( 'rollbacklinkcount-morethan' )
2055 ->numParams( $wgShowRollbackEditCount )->parse();
2056 } else {
2057 $html = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
2058 }
2059
2060 return self::link( $title, $html, $attrs, $query, $options );
2061 }
2062
2063 $html = $context->msg( 'rollbacklink' )->escaped();
2064 return self::link( $title, $html, $attrs, $query, $options );
2065 }
2066
2075 public static function formatHiddenCategories( $hiddencats ) {
2076 $outText = '';
2077 if ( count( $hiddencats ) > 0 ) {
2078 # Construct the HTML
2079 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2080 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2081 $outText .= "</div><ul>\n";
2082
2083 foreach ( $hiddencats as $titleObj ) {
2084 # If it's hidden, it must exist - no need to check with a LinkBatch
2085 $outText .= '<li>'
2086 . self::link( $titleObj, null, [], [], 'known' )
2087 . "</li>\n";
2088 }
2089 $outText .= '</ul>';
2090 }
2091 return $outText;
2092 }
2093
2110 public static function titleAttrib( $name, $options = null, array $msgParams = [] ) {
2111 $message = wfMessage( "tooltip-$name", $msgParams );
2112 if ( !$message->exists() ) {
2113 $tooltip = false;
2114 } else {
2115 $tooltip = $message->text();
2116 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2117 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2118 # Message equal to '-' means suppress it.
2119 if ( $tooltip == '-' ) {
2120 $tooltip = false;
2121 }
2122 }
2123
2124 $options = (array)$options;
2125
2126 if ( in_array( 'nonexisting', $options ) ) {
2127 $tooltip = wfMessage( 'red-link-title', $tooltip ?: '' )->text();
2128 }
2129 if ( in_array( 'withaccess', $options ) ) {
2130 $accesskey = self::accesskey( $name );
2131 if ( $accesskey !== false ) {
2132 // Should be build the same as in jquery.accessKeyLabel.js
2133 if ( $tooltip === false || $tooltip === '' ) {
2134 $tooltip = wfMessage( 'brackets', $accesskey )->text();
2135 } else {
2136 $tooltip .= wfMessage( 'word-separator' )->text();
2137 $tooltip .= wfMessage( 'brackets', $accesskey )->text();
2138 }
2139 }
2140 }
2141
2142 return $tooltip;
2143 }
2144
2145 public static $accesskeycache;
2146
2158 public static function accesskey( $name ) {
2159 if ( isset( self::$accesskeycache[$name] ) ) {
2160 return self::$accesskeycache[$name];
2161 }
2162
2163 $message = wfMessage( "accesskey-$name" );
2164
2165 if ( !$message->exists() ) {
2166 $accesskey = false;
2167 } else {
2168 $accesskey = $message->plain();
2169 if ( $accesskey === '' || $accesskey === '-' ) {
2170 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
2171 # attribute, but this is broken for accesskey: that might be a useful
2172 # value.
2173 $accesskey = false;
2174 }
2175 }
2176
2177 self::$accesskeycache[$name] = $accesskey;
2178 return self::$accesskeycache[$name];
2179 }
2180
2195 public static function getRevDeleteLink( User $user, $rev, LinkTarget $title ) {
2196 if ( $rev instanceof Revision ) {
2197 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
2198 $revRecord = $rev->getRevisionRecord();
2199 } else {
2200 $revRecord = $rev;
2201 }
2202
2203 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
2204 $canHide = $permissionManager->userHasRight( $user, 'deleterevision' );
2205 $canHideHistory = $permissionManager->userHasRight( $user, 'deletedhistory' );
2206 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2207 return '';
2208 }
2209
2210 if ( !RevisionRecord::userCanBitfield(
2211 $revRecord->getVisibility(),
2212 RevisionRecord::DELETED_RESTRICTED,
2213 $user
2214 ) ) {
2215 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2216 }
2217 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2218 getPrefixedDBkey( $title );
2219 if ( $revRecord->getId() ) {
2220 // RevDelete links using revision ID are stable across
2221 // page deletion and undeletion; use when possible.
2222 $query = [
2223 'type' => 'revision',
2224 'target' => $prefixedDbKey,
2225 'ids' => $revRecord->getId()
2226 ];
2227 } else {
2228 // Older deleted entries didn't save a revision ID.
2229 // We have to refer to these by timestamp, ick!
2230 $query = [
2231 'type' => 'archive',
2232 'target' => $prefixedDbKey,
2233 'ids' => $revRecord->getTimestamp()
2234 ];
2235 }
2236 return self::revDeleteLink(
2237 $query,
2238 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2239 $canHide
2240 );
2241 }
2242
2253 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2254 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2255 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2256 $html = wfMessage( $msgKey )->escaped();
2257 $tag = $restricted ? 'strong' : 'span';
2258 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2259 return Xml::tags(
2260 $tag,
2261 [ 'class' => 'mw-revdelundel-link' ],
2262 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2263 );
2264 }
2265
2275 public static function revDeleteLinkDisabled( $delete = true ) {
2276 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2277 $html = wfMessage( $msgKey )->escaped();
2278 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2279 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2280 }
2281
2294 public static function tooltipAndAccesskeyAttribs(
2295 $name,
2296 array $msgParams = [],
2297 $options = null
2298 ) {
2299 $options = (array)$options;
2300 $options[] = 'withaccess';
2301 $tooltipTitle = $name;
2302
2303 // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2304 $skin = RequestContext::getMain()->getSkin();
2305 $isWatchlistExpiryEnabled = $skin->getConfig()->get( 'WatchlistExpiry' );
2306 if ( $name === 'ca-unwatch' && $isWatchlistExpiryEnabled ) {
2307 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2308 $watchedItem = $watchStore->getWatchedItem( $skin->getUser(),
2309 $skin->getRelevantTitle() );
2310 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2311 $diffInDays = $watchedItem->getExpiryInDays();
2312
2313 if ( $diffInDays ) {
2314 $msgParams = [ $diffInDays ];
2315 // Resolves to tooltip-ca-unwatch-expiring message
2316 $tooltipTitle = 'ca-unwatch-expiring';
2317 } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2318 $tooltipTitle = 'ca-unwatch-expiring-hours';
2319 }
2320
2321 }
2322 }
2323
2324 $attribs = [
2325 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams ),
2326 'accesskey' => self::accesskey( $name )
2327 ];
2328 if ( $attribs['title'] === false ) {
2329 unset( $attribs['title'] );
2330 }
2331 if ( $attribs['accesskey'] === false ) {
2332 unset( $attribs['accesskey'] );
2333 }
2334 return $attribs;
2335 }
2336
2344 public static function tooltip( $name, $options = null ) {
2345 $tooltip = self::titleAttrib( $name, $options );
2346 if ( $tooltip === false ) {
2347 return '';
2348 }
2349 return Xml::expandAttributes( [
2350 'title' => $tooltip
2351 ] );
2352 }
2353
2354}
$wgThumbUpright
Adjust width of upright images when parameter 'upright' is used This allows a nicer look for upright ...
$wgThumbLimits
Adjust thumbnails on image pages according to a user setting.
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
$wgSVGMaxSize
Don't scale a SVG larger than this.
$wgShowRollbackEditCount
The $wgShowRollbackEditCount variable is used to show how many edits can be rolled back.
$wgUploadMissingFileUrl
Point the upload link for missing files to an external URL, as with $wgUploadNavigationUrl.
$wgUploadNavigationUrl
Point the upload navigation link to an external URL Useful if you want to use a shared repository by ...
$wgResponsiveImages
Generate and use thumbnails suitable for screens with 1.5 and 2.0 pixel densities.
$wgEnableUploads
Allow users to upload files.
$wgMiserMode
Disable database-intensive features.
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.
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 $function is deprecated.
$fallback
$wgTitle
Definition Setup.php:799
$wgLang
Definition Setup.php:781
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:41
Some internal bits split of from Skin.php.
Definition Linker.php:36
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:1861
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:778
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:86
static fnamePart( $url)
Returns the filename part of an url.
Definition Linker.php:223
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1691
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:896
static $accesskeycache
Definition Linker.php:2145
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition Linker.php:2110
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2158
static formatAutocomments( $comment, $title=null, $local=false, $wikiId=null)
Converts autogenerated comments in edit summaries into section links.
Definition Linker.php:1229
static specialLink( $name, $key='')
Make a link to a special page given its name and, optionally, a message key from the link text.
Definition Linker.php:820
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:142
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:243
static buildRollbackLink( $rev, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:1993
static makeCommentLink(LinkTarget $linkTarget, $text, $wikiId=null, $options=[])
Generates a link to the given LinkTarget.
Definition Linker.php:1451
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:639
static blockLink( $userId, $userText)
Definition Linker.php:1057
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2275
static getRollbackEditCount( $rev, $verify)
This function will return the number of revisions which a rollback would revert and,...
Definition Linker.php:1916
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1481
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition Linker.php:40
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition Linker.php:164
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:730
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1665
static makeImageLink(Parser $parser, LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition Linker.php:299
static revUserLink( $rev, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1101
static revComment( $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition Linker.php:1605
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:188
static emailLink( $userId, $userText)
Definition Linker.php:1078
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2075
static splitTrail( $trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition Linker.php:1823
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="")
Definition Linker.php:516
static generateTOC( $tree, Language $lang=null)
Generate a table of contents from a section tree.
Definition Linker.php:1764
const TOOL_LINKS_EMAIL
Definition Linker.php:41
static formatRevisionSize( $size)
Definition Linker.php:1650
static commentBlock( $comment, $title=null, $local=false, $wikiId=null, $useParentheses=true)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition Linker.php:1574
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2344
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1142
static userTalkLink( $userId, $userText)
Definition Linker.php:1035
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2195
static formatLinksInComment( $comment, $title=null, $local=false, $wikiId=null)
Formats wiki links and media links in text; all other wiki formatting is ignored.
Definition Linker.php:1330
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:759
static makeThumbLinkObj(LinkTarget $title, $file, $label='', $alt='', $align='right', $params=[], $framed=false, $manualthumb="")
Make HTML for a thumbnail including image, border and caption.
Definition Linker.php:490
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:846
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition Linker.php:941
static normaliseSpecialPage(LinkTarget $target)
Definition Linker.php:209
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1802
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1676
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2294
static tocList( $toc, Language $lang=null)
Wraps the TOC in a div with ARIA navigation role and provides the hide/collapse JavaScript.
Definition Linker.php:1727
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:450
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2253
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1715
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
Definition Linker.php:1199
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1023
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false)
Make a "broken" link to an image.
Definition Linker.php:674
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
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.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
Representation of a pair of user and title for watchlist entries.
getExpiry(?int $style=TS_MW)
When the watched item will expire.
const NS_USER
Definition Defines.php:72
const NS_FILE
Definition Defines.php:76
const NS_MAIN
Definition Defines.php:70
const NS_MEDIA
Definition Defines.php:58
const NS_USER_TALK
Definition Defines.php:73
Interface for objects which can provide a MediaWiki context on request.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
isExternal()
Whether this LinkTarget has an interwiki component.
msg( $key,... $params)
This is the method for getting translated interface messages.
const DB_REPLICA
Definition defines.php:25
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!isset( $args[0])) $lang